Skip to content

Timed lock aquisition #5599

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

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ports/cc3200/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,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 (0)
#define MICROPY_PY_UCTYPES (0)
#define MICROPY_PY_UZLIB (0)
Expand Down
11 changes: 11 additions & 0 deletions ports/cc3200/mpthreadport.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions ports/esp32/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,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
#define MICROPY_PY_UCTYPES (1)
Expand Down
6 changes: 6 additions & 0 deletions ports/esp32/mpthreadport.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,15 @@ void mp_thread_mutex_init(mp_thread_mutex_t *mutex) {
mutex->handle = xSemaphoreCreateMutexStatic(&mutex->buffer);
}

#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);
Expand Down
2 changes: 2 additions & 0 deletions ports/unix/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@
#define MICROPY_FATFS_MAX_SS (4096)
#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */

#define MICROPY_PY_THREAD_LOCK_TIMEOUT (1)

// Define to MICROPY_ERROR_REPORTING_DETAILED to get function, etc.
// names in exception messages (may require more RAM).
#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED)
Expand Down
41 changes: 41 additions & 0 deletions ports/unix/mpthreadport.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
#include <sched.h>
#include <semaphore.h>

#ifdef MICROPY_PY_THREAD_LOCK_TIMEOUT
#include <math.h>
#include <sys/time.h>
#endif

// this structure forms a linked list, one node per active thread
typedef struct _thread_t {
pthread_t id; // system id of thread
Expand Down Expand Up @@ -253,6 +258,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) {
Expand All @@ -270,6 +310,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);
Expand Down
29 changes: 27 additions & 2 deletions py/modthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,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");

This comment was marked as resolved.

}
#endif
if (ret == 0) {
return mp_const_false;
} else if (ret == 1) {
Expand Down
5 changes: 5 additions & 0 deletions py/mpthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions tests/thread/thread_locktimed1.py
Original file line number Diff line number Diff line change
@@ -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())
35 changes: 35 additions & 0 deletions tests/thread/thread_locktimed2.py
Original file line number Diff line number Diff line change
@@ -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')