diff --git a/ports/cc3200/mpconfigport.h b/ports/cc3200/mpconfigport.h index 93fc291c1a4c2..c822010ad7de6 100644 --- a/ports/cc3200/mpconfigport.h +++ b/ports/cc3200/mpconfigport.h @@ -109,6 +109,7 @@ #define MICROPY_PY_UERRNO_ERRORCODE (0) #define MICROPY_PY_THREAD (1) #define MICROPY_PY_THREAD_GIL (1) +#define MICROPY_PY_THREAD_LOCK_TIMEOUT (1) #define MICROPY_PY_UBINASCII (1) #define MICROPY_PY_UCTYPES (0) #define MICROPY_PY_UZLIB (0) diff --git a/ports/cc3200/mpthreadport.c b/ports/cc3200/mpthreadport.c index 4b6f27d578066..ff1d8e84a457b 100644 --- a/ports/cc3200/mpthreadport.c +++ b/ports/cc3200/mpthreadport.c @@ -169,6 +169,16 @@ void mp_thread_mutex_init(mp_thread_mutex_t *mutex) { // To allow hard interrupts to work with threading we only take/give the semaphore // if we are not within an interrupt context and interrupts are enabled. +#ifdef MICROPY_PY_THREAD_LOCK_TIMEOUT +int mp_thread_mutex_lock_timeout(mp_thread_mutex_t *mutex, int timeout_us) { + if ((HAL_NVIC_INT_CTRL_REG & HAL_VECTACTIVE_MASK) == 0 && query_irq() == IRQ_STATE_ENABLED) { + int ret = xSemaphoreTake(mutex->handle, timeout_us < 0 ? portMAX_DELAY : timeout_us / portTICK_PERIOD_MS / 1000); + return ret == pdTRUE; + } else { + return 1; + } +} +#else int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { if ((HAL_NVIC_INT_CTRL_REG & HAL_VECTACTIVE_MASK) == 0 && query_irq() == IRQ_STATE_ENABLED) { int ret = xSemaphoreTake(mutex->handle, wait ? portMAX_DELAY : 0); @@ -177,6 +187,7 @@ int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { return 1; } } +#endif void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { if ((HAL_NVIC_INT_CTRL_REG & HAL_VECTACTIVE_MASK) == 0 && query_irq() == IRQ_STATE_ENABLED) { diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index d8822980b7de8..f6157e6346f90 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -66,6 +66,7 @@ #define MICROPY_PY_THREAD (1) #define MICROPY_PY_THREAD_GIL (1) #define MICROPY_PY_THREAD_GIL_VM_DIVISOR (32) +#define MICROPY_PY_THREAD_LOCK_TIMEOUT (1) // extended modules #ifndef MICROPY_PY_BLUETOOTH diff --git a/ports/esp32/mpthreadport.c b/ports/esp32/mpthreadport.c index e6c7e9bc80eb2..8968c43daab20 100644 --- a/ports/esp32/mpthreadport.c +++ b/ports/esp32/mpthreadport.c @@ -203,9 +203,15 @@ void mp_thread_mutex_init(mp_thread_mutex_t *mutex) { xSemaphoreGive(mutex->handle); } +#ifdef MICROPY_PY_THREAD_LOCK_TIMEOUT +int mp_thread_mutex_lock_timeout(mp_thread_mutex_t *mutex, int timeout_us) { + return (pdTRUE == xSemaphoreTake(mutex->handle, timeout_us < 0 ? portMAX_DELAY : timeout_us / portTICK_PERIOD_MS / 1000)); +} +#else int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { return pdTRUE == xSemaphoreTake(mutex->handle, wait ? portMAX_DELAY : 0); } +#endif void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { xSemaphoreGive(mutex->handle); diff --git a/ports/stm32/mpthreadport.h b/ports/stm32/mpthreadport.h index e2b39979fb4c8..651467aeef10d 100644 --- a/ports/stm32/mpthreadport.h +++ b/ports/stm32/mpthreadport.h @@ -44,9 +44,16 @@ static inline void mp_thread_mutex_init(mp_thread_mutex_t *m) { pyb_mutex_init(m); } +#ifdef MICROPY_PY_THREAD_LOCK_TIMEOUT +static inline int mp_thread_mutex_lock_timeout(mp_thread_mutex_t *mutex, int timeout_us) { + // TODO timeout_us + return pyb_mutex_lock(m, wait); +} +#else static inline int mp_thread_mutex_lock(mp_thread_mutex_t *m, int wait) { return pyb_mutex_lock(m, wait); } +#endif static inline void mp_thread_mutex_unlock(mp_thread_mutex_t *m) { pyb_mutex_unlock(m); diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index a0b9192bfcf27..be1bb40d4438e 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -221,10 +221,12 @@ static inline unsigned long mp_urandom_seed_init(void) { #include #endif -// If threading is enabled, configure the atomic section. #if MICROPY_PY_THREAD +// If threading is enabled, configure the atomic section. #define MICROPY_BEGIN_ATOMIC_SECTION() (mp_thread_unix_begin_atomic_section(), 0xffffffff) #define MICROPY_END_ATOMIC_SECTION(x) (void)x; mp_thread_unix_end_atomic_section() +// Enable lock timeout support +#define MICROPY_PY_THREAD_LOCK_TIMEOUT (1) #endif // In lieu of a WFI(), slow down polling from being a tight loop. diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index 6a267e723635c..10567b107b27d 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -39,6 +39,11 @@ #include #include +#ifdef MICROPY_PY_THREAD_LOCK_TIMEOUT +#include +#include +#endif + #include "shared/runtime/gchelper.h" // Some platforms don't have SIGRTMIN but if we do have it, use it to avoid @@ -293,6 +298,41 @@ void mp_thread_mutex_init(mp_thread_mutex_t *mutex) { pthread_mutex_init(mutex, NULL); } +#ifdef MICROPY_PY_THREAD_LOCK_TIMEOUT +int mp_thread_mutex_lock_timeout(mp_thread_mutex_t *mutex, int timeout_us) { + int ret; + if (timeout_us < 0) { + ret = pthread_mutex_lock(mutex); + if (ret == 0) { + return 1; + } + } else if (timeout_us == 0) { + ret = pthread_mutex_trylock(mutex); + if (ret == 0) { + return 1; + } else if (ret == EBUSY) { + return 0; + } + } else /* if (timeout_us > 0) */ { + struct timeval _timeval; + struct timezone _timezone; + gettimeofday(&_timeval, &_timezone); + uint32_t _timeout_sec = timeout_us / 1000000; + uint32_t _timeout_nano = 1000 * (timeout_us % 1000000 + _timeval.tv_usec); + struct timespec _timespec = { + .tv_sec = _timeout_sec + _timeval.tv_sec + (_timeout_nano / 1000000000), + .tv_nsec = _timeout_nano % 1000000000 + }; + ret = pthread_mutex_timedlock(mutex, &_timespec); + if (ret == 0) { + return 1; + } else if (ret == ETIMEDOUT) { + return 0; + } + } + return -ret; +} +#else int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { int ret; if (wait) { @@ -310,6 +350,7 @@ int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { } return -ret; } +#endif void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { pthread_mutex_unlock(mutex); diff --git a/py/modthread.c b/py/modthread.c index 51d63e4703720..402a0e85a903d 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -63,13 +63,38 @@ STATIC mp_obj_thread_lock_t *mp_obj_new_thread_lock(void) { STATIC mp_obj_t thread_lock_acquire(size_t n_args, const mp_obj_t *args) { mp_obj_thread_lock_t *self = MP_OBJ_TO_PTR(args[0]); bool wait = true; + int timeout_us = -1; if (n_args > 1) { wait = mp_obj_get_int(args[1]); - // TODO support timeout arg + if (n_args > 2) { + // Timeout is a float as in CPython + // For boards that do not have builtin float one can pass the timeout in us +#if MICROPY_PY_BUILTINS_FLOAT + // timeout is a float in CPython + timeout_us = (int) (1000000 * mp_obj_get_float(args[2])); +#else + // timeout is an integer us if no floats + timeout_us = mp_obj_get_int(args[2]); +#endif + if (!wait && timeout_us >= 0) { + mp_raise_ValueError("can't specify a timeout for a non-blocking call"); + } + }; } +#ifdef MICROPY_PY_THREAD_LOCK_TIMEOUT MP_THREAD_GIL_EXIT(); - int ret = mp_thread_mutex_lock(&self->mutex, wait); + int ret = mp_thread_mutex_lock_timeout(&self->mutex, wait ? timeout_us : 0); MP_THREAD_GIL_ENTER(); +#else + int ret = 0; + if (timeout_us < 0) { + MP_THREAD_GIL_EXIT(); + ret = mp_thread_mutex_lock(&self->mutex, wait); + MP_THREAD_GIL_ENTER(); + } else if (timeout_us >= 0) { + mp_raise_ValueError("timeout not supported"); + } +#endif if (ret == 0) { return mp_const_false; } else if (ret == 1) { diff --git a/py/mpthread.h b/py/mpthread.h index e611ef4c1197e..035d02327239e 100644 --- a/py/mpthread.h +++ b/py/mpthread.h @@ -44,7 +44,12 @@ void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size); void mp_thread_start(void); void mp_thread_finish(void); void mp_thread_mutex_init(mp_thread_mutex_t *mutex); +#ifdef MICROPY_PY_THREAD_LOCK_TIMEOUT +int mp_thread_mutex_lock_timeout(mp_thread_mutex_t *mutex, int timeout_us); +#define mp_thread_mutex_lock(mutex, wait) mp_thread_mutex_lock_timeout(mutex, wait ? -1 : 0 ) +#else int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait); +#endif void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex); #endif // MICROPY_PY_THREAD diff --git a/tests/thread/thread_locktimed1.py b/tests/thread/thread_locktimed1.py new file mode 100644 index 0000000000000..1e0ec3011f4dc --- /dev/null +++ b/tests/thread/thread_locktimed1.py @@ -0,0 +1,40 @@ +# test timed _thread lock object using a single thread +# +# MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd + +import _thread + +# create lock +lock = _thread.allocate_lock() + +print(type(lock) == _thread.LockType) + +# should be unlocked +print(lock.locked()) + +# try acquire +print(lock.acquire()) +print(lock.locked()) + +# this fail with timeout +print(lock.acquire(1, 0.1)) +print(lock.locked()) + +# this will fail with negative timeout +print(lock.acquire(0, -1)) +print(lock.locked()) + +# these will raise value errors +try: + print(lock.acquire(0, 1)) +except ValueError: + print("can't specify a timeout for a non-blocking call") + +try: + print(lock.acquire(0, 0)) +except ValueError: + print("can't specify a timeout for a non-blocking call") + +print(lock.locked()) +lock.release() +print(lock.locked()) diff --git a/tests/thread/thread_locktimed2.py b/tests/thread/thread_locktimed2.py new file mode 100644 index 0000000000000..baf19080eb8ac --- /dev/null +++ b/tests/thread/thread_locktimed2.py @@ -0,0 +1,35 @@ +# test timed _thread lock object using multiple threads +# +# MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd +try: + import utime as time +except ImportError: + import time +import _thread + +# create lock +lock = _thread.allocate_lock() + +def thread_entry(): + lock.acquire() + time.sleep(1) + print('have it') + lock.release() + +# spawn the threads +for i in range(4): + _thread.start_new_thread(thread_entry, ()) + +# wait for threads to start +time.sleep(.1) + +# will timeout and roughly measure time +start = time.time() +lock.acquire(1, 2.5) +stop = time.time() +print((stop - start) > 2) +print((stop - start) < 3) + +# wait for threads to finish +lock.acquire() +print('done') \ No newline at end of file