Skip to content

zephyr: Make time.sleep() and friends thread safe. #17866

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

danicampora
Copy link
Member

@danicampora danicampora commented Aug 8, 2025

Currently the delay function is using a single static k_poll_event object which means that multiple threads sleeping at similar times will try to use the same event variable leading to a eventual crash.

I created a new mp_hal_delay_ms() function that is heavily inspired in the one from the ESP32 port.

@danicampora danicampora changed the title ports/zephyr: Make time.sleep() and friends thread safe. zephyr: Make time.sleep() and friends thread safe. Aug 8, 2025
@danicampora danicampora force-pushed the threading_fix_pr branch 2 times, most recently from c85b6b4 to 97f7406 Compare August 8, 2025 08:22
Currently the delay function is using a single static k_poll_event
object which means that multiple threads sleeping at similar times
will try to use the same event variable leading to a eventual crash.

Signed-off-by: Daniel Campora <danicampora@gmail.com>
@dpgeorge
Copy link
Member

Thanks for the PR. I can see why this is needed for the case of more than one thread.

But, I did spend quite a bit of effort implementing the existing sleep code, see 916c3fd and d120859

The idea is that it should be possible to tell the zephyr OS to sleep for the maximum possible time, so it could maybe use a low-power sleep. And then the semaphore can wake up the thread early if needed, eg when the user sends ctrl-C, or a I2CTarget IRQ fires and needs to run a Python callback. That's handled by mp_hal_signal_event().

IMO it would be good to keep all those capabilities (sleep for a long time, wake early via a semaphore). The question is how to do that when there are multiple threads using the sleep mechanism. My simple proposal for that would be to have 2 sleep functions: the existing sleep implementation would be used for the main thread, and the implementation in this PR would be used for all other threads. You can select which one via something like this:

void mp_hal_delay_ms(mp_uint_t delay_ms) {
    if (mp_thread_get_state() == &mp_state_ctx.thread) {
        // main thread
        mp_hal_wait_sem(NULL, delay_ms);
    } else {
        // other threads
        mp_hal_delay_ms_polling(delay_ms);
    }
}

(Or maybe you can think of a better way to detect the main thread?)

@dpgeorge
Copy link
Member

Actually, maybe the sleep on non-main threads could be a simple k_msleep(delay_ms)? They don't need to wake to process events, that can be handled by the main thread (that would also mean ctrl-C only goes to the main thread, but that actually matches CPython semantics).

@danicampora
Copy link
Member Author

@dpgeorge thanks for explaining the reasons of having the original implementation! I will play a bit with your suggestions and update this PR asap.

@danicampora
Copy link
Member Author

@dpgeorge I tried both of the suggestions you made, but they don't produce the desired behaviour.

First problem is that k_msleep(delay_ms) cannot be used directly as otherwise the GIL is never released between sleeps. So this approach:

void mp_hal_delay_ms(mp_uint_t delay_ms) {
    if (mp_thread_get_state() == &mp_state_ctx.thread) {
        // main thread
        mp_hal_wait_sem(NULL, delay_ms);
    } else {
        // other threads
        mp_hal_delay_ms_polling(delay_ms);
    }
}

Seems to work better except that the sleep for the main function does not work at all. Need do continue debugging...

This is my simple test:

import time
import _thread

def thread_entry(num):
    for i in range(4):
        print('Running thread num', num + 1)
        time.sleep_ms(500 * (num + 1))

for i in range(4):
    _thread.start_new_thread(thread_entry, (i,))

time.sleep(8)
print("done")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants