From 27cc07721b3c3c2d0b9c8678c8a3ddc7fe96cd97 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 22 Apr 2016 22:52:33 +0000 Subject: py: Add basic _thread module, with ability to start a new thread. --- py/modthread.c | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 py/modthread.c (limited to 'py/modthread.c') diff --git a/py/modthread.c b/py/modthread.c new file mode 100644 index 0000000000..5104ef1275 --- /dev/null +++ b/py/modthread.c @@ -0,0 +1,148 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "py/runtime.h" +#include "py/stackctrl.h" + +#if MICROPY_PY_THREAD + +#include "py/mpthread.h" + +#if 0 // print debugging info +#define DEBUG_PRINT (1) +#define DEBUG_printf DEBUG_printf +#else // don't print debugging info +#define DEBUG_PRINT (0) +#define DEBUG_printf(...) (void)0 +#endif + +/****************************************************************/ +// _thread module + +STATIC mp_obj_t mod_thread_get_ident(void) { + return mp_obj_new_int_from_uint((uintptr_t)mp_thread_get_state()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_get_ident_obj, mod_thread_get_ident); + +typedef struct _thread_entry_args_t { + mp_obj_t fun; + size_t n_args; + size_t n_kw; + const mp_obj_t *args; +} thread_entry_args_t; + +STATIC void *thread_entry(void *args_in) { + thread_entry_args_t *args = (thread_entry_args_t*)args_in; + + mp_state_thread_t ts; + mp_thread_set_state(&ts); + + mp_stack_set_top(&ts + 1); // need to include ts in root-pointer scan + mp_stack_set_limit(16 * 1024); // fixed stack limit for now + + // TODO set more thread-specific state here: + // mp_pending_exception? (root pointer) + // cur_exception (root pointer) + // dict_locals? (root pointer) uPy doesn't make a new locals dict for functions, just for classes, so it's different to CPy + + DEBUG_printf("[thread] start ts=%p args=%p stack=%p\n", &ts, &args, MP_STATE_THREAD(stack_top)); + + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_call_function_n_kw(args->fun, args->n_args, args->n_kw, args->args); + nlr_pop(); + } else { + // uncaught exception + // check for SystemExit + if (mp_obj_is_subclass_fast(mp_obj_get_type((mp_obj_t)nlr.ret_val), &mp_type_SystemExit)) { + // swallow exception silently + } else { + // print exception out + mp_printf(&mp_plat_print, "Unhandled exception in thread started by "); + mp_obj_print_helper(&mp_plat_print, args->fun, PRINT_REPR); + mp_printf(&mp_plat_print, "\n"); + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + } + } + + DEBUG_printf("[thread] finish ts=%p\n", &ts); + + return NULL; +} + +STATIC mp_obj_t mod_thread_start_new_thread(size_t n_args, const mp_obj_t *args) { + mp_uint_t pos_args_len; + mp_obj_t *pos_args_items; + mp_obj_get_array(args[1], &pos_args_len, &pos_args_items); + thread_entry_args_t *th_args = m_new_obj(thread_entry_args_t); + th_args->fun = args[0]; + if (n_args == 2) { + // just position arguments + th_args->n_args = pos_args_len; + th_args->n_kw = 0; + th_args->args = pos_args_items; + } else { + // positional and keyword arguments + if (mp_obj_get_type(args[2]) != &mp_type_dict) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "expecting a dict for keyword args")); + } + mp_map_t *map = &((mp_obj_dict_t*)MP_OBJ_TO_PTR(args[2]))->map; + th_args->n_args = pos_args_len; + th_args->n_kw = map->used; + mp_obj_t *all_args = m_new(mp_obj_t, th_args->n_args + 2 * th_args->n_kw); + memcpy(all_args, pos_args_items, pos_args_len * sizeof(mp_obj_t)); + for (size_t i = 0, n = pos_args_len; i < map->alloc; ++i) { + if (MP_MAP_SLOT_IS_FILLED(map, i)) { + all_args[n++] = map->table[i].key; + all_args[n++] = map->table[i].value; + } + } + th_args->args = all_args; + } + // TODO implement setting thread stack size + mp_thread_create(thread_entry, th_args); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_thread_start_new_thread_obj, 2, 3, mod_thread_start_new_thread); + +STATIC const mp_rom_map_elem_t mp_module_thread_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__thread) }, + { MP_ROM_QSTR(MP_QSTR_get_ident), MP_ROM_PTR(&mod_thread_get_ident_obj) }, + { MP_ROM_QSTR(MP_QSTR_start_new_thread), MP_ROM_PTR(&mod_thread_start_new_thread_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_thread_globals, mp_module_thread_globals_table); + +const mp_obj_module_t mp_module_thread = { + .base = { &mp_type_module }, + .name = MP_QSTR__thread, + .globals = (mp_obj_dict_t*)&mp_module_thread_globals, +}; + +#endif // MICROPY_PY_THREAD -- cgit v1.2.3 From 3eb7a268091ef68248d58ddb3ad11465f1cb2199 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 23 Apr 2016 12:24:44 +0000 Subject: py/modthread: Properly cast concrete exception pointer to an object. --- py/modthread.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'py/modthread.c') diff --git a/py/modthread.c b/py/modthread.c index 5104ef1275..5d01fe601c 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -80,14 +80,15 @@ STATIC void *thread_entry(void *args_in) { } else { // uncaught exception // check for SystemExit - if (mp_obj_is_subclass_fast(mp_obj_get_type((mp_obj_t)nlr.ret_val), &mp_type_SystemExit)) { + mp_obj_base_t *exc = (mp_obj_base_t*)nlr.ret_val; + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { // swallow exception silently } else { // print exception out mp_printf(&mp_plat_print, "Unhandled exception in thread started by "); mp_obj_print_helper(&mp_plat_print, args->fun, PRINT_REPR); mp_printf(&mp_plat_print, "\n"); - mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(exc)); } } -- cgit v1.2.3 From 707f98f207d85b1de9814e90254d52a78ac19739 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 25 Apr 2016 09:02:47 +0000 Subject: py/modthread: Add stack_size() function. --- py/modthread.c | 17 +++++++++++++++-- py/mpthread.h | 2 +- unix/mpthreadport.c | 29 +++++++++++++++++++++++++++-- 3 files changed, 43 insertions(+), 5 deletions(-) (limited to 'py/modthread.c') diff --git a/py/modthread.c b/py/modthread.c index 5d01fe601c..8135a5e88c 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -45,11 +45,24 @@ /****************************************************************/ // _thread module +STATIC size_t thread_stack_size = 0; + STATIC mp_obj_t mod_thread_get_ident(void) { return mp_obj_new_int_from_uint((uintptr_t)mp_thread_get_state()); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_get_ident_obj, mod_thread_get_ident); +STATIC mp_obj_t mod_thread_stack_size(size_t n_args, const mp_obj_t *args) { + mp_obj_t ret = mp_obj_new_int_from_uint(thread_stack_size); + if (n_args == 0) { + thread_stack_size = 0; + } else { + thread_stack_size = mp_obj_get_int(args[0]); + } + return ret; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_thread_stack_size_obj, 0, 1, mod_thread_stack_size); + typedef struct _thread_entry_args_t { mp_obj_t fun; size_t n_args; @@ -126,8 +139,7 @@ STATIC mp_obj_t mod_thread_start_new_thread(size_t n_args, const mp_obj_t *args) } th_args->args = all_args; } - // TODO implement setting thread stack size - mp_thread_create(thread_entry, th_args); + mp_thread_create(thread_entry, th_args, thread_stack_size); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_thread_start_new_thread_obj, 2, 3, mod_thread_start_new_thread); @@ -135,6 +147,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_thread_start_new_thread_obj, 2, 3 STATIC const mp_rom_map_elem_t mp_module_thread_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__thread) }, { MP_ROM_QSTR(MP_QSTR_get_ident), MP_ROM_PTR(&mod_thread_get_ident_obj) }, + { MP_ROM_QSTR(MP_QSTR_stack_size), MP_ROM_PTR(&mod_thread_stack_size_obj) }, { MP_ROM_QSTR(MP_QSTR_start_new_thread), MP_ROM_PTR(&mod_thread_start_new_thread_obj) }, }; diff --git a/py/mpthread.h b/py/mpthread.h index 6c6de79886..bbf27da50d 100644 --- a/py/mpthread.h +++ b/py/mpthread.h @@ -38,7 +38,7 @@ mp_state_thread_t *mp_thread_get_state(void); void mp_thread_set_state(void *state); -void mp_thread_create(void *(*entry)(void*), void *arg); +void mp_thread_create(void *(*entry)(void*), void *arg, size_t stack_size); #endif // MICROPY_PY_THREAD diff --git a/unix/mpthreadport.c b/unix/mpthreadport.c index af75be1aa1..73e08dcb86 100644 --- a/unix/mpthreadport.c +++ b/unix/mpthreadport.c @@ -46,9 +46,34 @@ void mp_thread_set_state(void *state) { pthread_setspecific(tls_key, state); } -void mp_thread_create(void *(*entry)(void*), void *arg) { +void mp_thread_create(void *(*entry)(void*), void *arg, size_t stack_size) { + // default stack size is 8k machine-words + if (stack_size == 0) { + stack_size = 8192 * BYTES_PER_WORD; + } + + // set thread attributes + pthread_attr_t attr; + int ret = pthread_attr_init(&attr); + if (ret != 0) { + goto er; + } + ret = pthread_attr_setstacksize(&attr, stack_size); + if (ret != 0) { + goto er; + } + + // create thread pthread_t id; - pthread_create(&id, NULL, entry, arg); + ret = pthread_create(&id, &attr, entry, arg); + if (ret != 0) { + goto er; + } + + return; + +er: + nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(ret))); } #endif // MICROPY_PY_THREAD -- cgit v1.2.3 From 2dacd604c51d27f08076e76abd91de43bc2481f3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 25 Apr 2016 09:15:21 +0000 Subject: py/modthread: Add exit() function. Simply raises the SystemExit exception. --- py/modthread.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'py/modthread.c') diff --git a/py/modthread.c b/py/modthread.c index 8135a5e88c..2420cfc214 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -144,11 +144,17 @@ STATIC mp_obj_t mod_thread_start_new_thread(size_t n_args, const mp_obj_t *args) } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_thread_start_new_thread_obj, 2, 3, mod_thread_start_new_thread); +STATIC mp_obj_t mod_thread_exit(void) { + nlr_raise(mp_obj_new_exception(&mp_type_SystemExit)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_exit_obj, mod_thread_exit); + STATIC const mp_rom_map_elem_t mp_module_thread_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__thread) }, { MP_ROM_QSTR(MP_QSTR_get_ident), MP_ROM_PTR(&mod_thread_get_ident_obj) }, { MP_ROM_QSTR(MP_QSTR_stack_size), MP_ROM_PTR(&mod_thread_stack_size_obj) }, { MP_ROM_QSTR(MP_QSTR_start_new_thread), MP_ROM_PTR(&mod_thread_start_new_thread_obj) }, + { MP_ROM_QSTR(MP_QSTR_exit), MP_ROM_PTR(&mod_thread_exit_obj) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_thread_globals, mp_module_thread_globals_table); -- cgit v1.2.3 From 801d1b3803e4c8070a8bea6ee4f563a799d010b7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 25 Apr 2016 11:21:48 +0000 Subject: py/modthread: Implement lock object, for creating a mutex. --- py/modthread.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++ py/mpthread.h | 3 +++ unix/mpthreadport.c | 27 +++++++++++++++++++ 3 files changed, 104 insertions(+) (limited to 'py/modthread.c') diff --git a/py/modthread.c b/py/modthread.c index 2420cfc214..01316f9624 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -42,6 +42,73 @@ #define DEBUG_printf(...) (void)0 #endif +/****************************************************************/ +// Lock object + +STATIC const mp_obj_type_t mp_type_thread_lock; + +typedef struct _mp_obj_thread_lock_t { + mp_obj_base_t base; + mp_thread_mutex_t mutex; + bool locked; +} mp_obj_thread_lock_t; + +STATIC mp_obj_thread_lock_t *mp_obj_new_thread_lock(void) { + mp_obj_thread_lock_t *self = m_new_obj(mp_obj_thread_lock_t); + self->base.type = &mp_type_thread_lock; + mp_thread_mutex_init(&self->mutex); + self->locked = false; + return self; +} + +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; + if (n_args > 1) { + wait = mp_obj_get_int(args[1]); + // TODO support timeout arg + } + int ret = mp_thread_mutex_lock(&self->mutex, wait); + if (ret == 0) { + return mp_const_false; + } else if (ret == 1) { + self->locked = true; + return mp_const_true; + } else { + nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(-ret))); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(thread_lock_acquire_obj, 1, 3, thread_lock_acquire); + +STATIC mp_obj_t thread_lock_release(mp_obj_t self_in) { + mp_obj_thread_lock_t *self = MP_OBJ_TO_PTR(self_in); + // TODO check if already unlocked + self->locked = false; + mp_thread_mutex_unlock(&self->mutex); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(thread_lock_release_obj, thread_lock_release); + +STATIC mp_obj_t thread_lock_locked(mp_obj_t self_in) { + mp_obj_thread_lock_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(self->locked); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(thread_lock_locked_obj, thread_lock_locked); + +STATIC const mp_rom_map_elem_t thread_lock_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_acquire), MP_ROM_PTR(&thread_lock_acquire_obj) }, + { MP_ROM_QSTR(MP_QSTR_release), MP_ROM_PTR(&thread_lock_release_obj) }, + { MP_ROM_QSTR(MP_QSTR_locked), MP_ROM_PTR(&thread_lock_locked_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(thread_lock_locals_dict, thread_lock_locals_dict_table); + +STATIC const mp_obj_type_t mp_type_thread_lock = { + { &mp_type_type }, + .name = MP_QSTR_lock, + .locals_dict = (mp_obj_dict_t*)&thread_lock_locals_dict, +}; + /****************************************************************/ // _thread module @@ -149,12 +216,19 @@ STATIC mp_obj_t mod_thread_exit(void) { } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_exit_obj, mod_thread_exit); +STATIC mp_obj_t mod_thread_allocate_lock(void) { + return MP_OBJ_FROM_PTR(mp_obj_new_thread_lock()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_allocate_lock_obj, mod_thread_allocate_lock); + STATIC const mp_rom_map_elem_t mp_module_thread_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__thread) }, + { MP_ROM_QSTR(MP_QSTR_LockType), MP_ROM_PTR(&mp_type_thread_lock) }, { MP_ROM_QSTR(MP_QSTR_get_ident), MP_ROM_PTR(&mod_thread_get_ident_obj) }, { MP_ROM_QSTR(MP_QSTR_stack_size), MP_ROM_PTR(&mod_thread_stack_size_obj) }, { MP_ROM_QSTR(MP_QSTR_start_new_thread), MP_ROM_PTR(&mod_thread_start_new_thread_obj) }, { MP_ROM_QSTR(MP_QSTR_exit), MP_ROM_PTR(&mod_thread_exit_obj) }, + { MP_ROM_QSTR(MP_QSTR_allocate_lock), MP_ROM_PTR(&mod_thread_allocate_lock_obj) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_thread_globals, mp_module_thread_globals_table); diff --git a/py/mpthread.h b/py/mpthread.h index bbf27da50d..498380a5e1 100644 --- a/py/mpthread.h +++ b/py/mpthread.h @@ -39,6 +39,9 @@ mp_state_thread_t *mp_thread_get_state(void); void mp_thread_set_state(void *state); void mp_thread_create(void *(*entry)(void*), void *arg, size_t stack_size); +void mp_thread_mutex_init(mp_thread_mutex_t *mutex); +int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait); +void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex); #endif // MICROPY_PY_THREAD diff --git a/unix/mpthreadport.c b/unix/mpthreadport.c index 73e08dcb86..3a7e3ad4f5 100644 --- a/unix/mpthreadport.c +++ b/unix/mpthreadport.c @@ -76,4 +76,31 @@ er: nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(ret))); } +void mp_thread_mutex_init(mp_thread_mutex_t *mutex) { + pthread_mutex_init(mutex, NULL); +} + +int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { + int ret; + if (wait) { + ret = pthread_mutex_lock(mutex); + if (ret == 0) { + return 1; + } + } else { + ret = pthread_mutex_trylock(mutex); + if (ret == 0) { + return 1; + } else if (ret == EBUSY) { + return 0; + } + } + return -ret; +} + +void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { + pthread_mutex_unlock(mutex); + // TODO check return value +} + #endif // MICROPY_PY_THREAD -- cgit v1.2.3 From 34fc006f5e75f064f3aad9ccc98963dc2fa01e25 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 25 Apr 2016 11:33:53 +0000 Subject: py/modthread: Add with-context capabilities to lock object. --- py/modthread.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'py/modthread.c') diff --git a/py/modthread.c b/py/modthread.c index 01316f9624..fd5932059e 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -95,10 +95,17 @@ STATIC mp_obj_t thread_lock_locked(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(thread_lock_locked_obj, thread_lock_locked); +STATIC mp_obj_t thread_lock___exit__(size_t n_args, const mp_obj_t *args) { + return thread_lock_release(args[0]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(thread_lock___exit___obj, 4, 4, thread_lock___exit__); + STATIC const mp_rom_map_elem_t thread_lock_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_acquire), MP_ROM_PTR(&thread_lock_acquire_obj) }, { MP_ROM_QSTR(MP_QSTR_release), MP_ROM_PTR(&thread_lock_release_obj) }, { MP_ROM_QSTR(MP_QSTR_locked), MP_ROM_PTR(&thread_lock_locked_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&thread_lock_acquire_obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&thread_lock___exit___obj) }, }; STATIC MP_DEFINE_CONST_DICT(thread_lock_locals_dict, thread_lock_locals_dict_table); -- cgit v1.2.3 From 7f4658a7ee15dc9d6b539905f5e3a9b62f02cd70 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 25 Apr 2016 20:58:22 +0000 Subject: py/modthread: Satisfy unused-args warning. --- py/modthread.c | 1 + 1 file changed, 1 insertion(+) (limited to 'py/modthread.c') diff --git a/py/modthread.c b/py/modthread.c index fd5932059e..a30be688e4 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -96,6 +96,7 @@ STATIC mp_obj_t thread_lock_locked(mp_obj_t self_in) { STATIC MP_DEFINE_CONST_FUN_OBJ_1(thread_lock_locked_obj, thread_lock_locked); STATIC mp_obj_t thread_lock___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; // unused return thread_lock_release(args[0]); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(thread_lock___exit___obj, 4, 4, thread_lock___exit__); -- cgit v1.2.3 From 722cff5fd0debb250af827fd556dc0cab94e07d7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 4 May 2016 09:51:01 +0000 Subject: py/modthread: Be more careful with root pointers when creating a thread. --- py/modthread.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) (limited to 'py/modthread.c') diff --git a/py/modthread.c b/py/modthread.c index a30be688e4..4e654dfe89 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -142,7 +142,7 @@ typedef struct _thread_entry_args_t { mp_obj_t fun; size_t n_args; size_t n_kw; - const mp_obj_t *args; + mp_obj_t args[]; } thread_entry_args_t; STATIC void *thread_entry(void *args_in) { @@ -186,35 +186,49 @@ STATIC void *thread_entry(void *args_in) { } STATIC mp_obj_t mod_thread_start_new_thread(size_t n_args, const mp_obj_t *args) { + // This structure holds the Python function and arguments for thread entry. + // We copy all arguments into this structure to keep ownership of them. + // We must be very careful about root pointers because this pointer may + // disappear from our address space before the thread is created. + thread_entry_args_t *th_args; + + // get positional arguments mp_uint_t pos_args_len; mp_obj_t *pos_args_items; mp_obj_get_array(args[1], &pos_args_len, &pos_args_items); - thread_entry_args_t *th_args = m_new_obj(thread_entry_args_t); - th_args->fun = args[0]; + + // check for keyword arguments if (n_args == 2) { // just position arguments - th_args->n_args = pos_args_len; + th_args = m_new_obj_var(thread_entry_args_t, mp_obj_t, pos_args_len); th_args->n_kw = 0; - th_args->args = pos_args_items; } else { // positional and keyword arguments if (mp_obj_get_type(args[2]) != &mp_type_dict) { nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "expecting a dict for keyword args")); } mp_map_t *map = &((mp_obj_dict_t*)MP_OBJ_TO_PTR(args[2]))->map; - th_args->n_args = pos_args_len; + th_args = m_new_obj_var(thread_entry_args_t, mp_obj_t, pos_args_len + 2 * map->used); th_args->n_kw = map->used; - mp_obj_t *all_args = m_new(mp_obj_t, th_args->n_args + 2 * th_args->n_kw); - memcpy(all_args, pos_args_items, pos_args_len * sizeof(mp_obj_t)); + // copy across the keyword arguments for (size_t i = 0, n = pos_args_len; i < map->alloc; ++i) { if (MP_MAP_SLOT_IS_FILLED(map, i)) { - all_args[n++] = map->table[i].key; - all_args[n++] = map->table[i].value; + th_args->args[n++] = map->table[i].key; + th_args->args[n++] = map->table[i].value; } } - th_args->args = all_args; } + + // copy agross the positional arguments + th_args->n_args = pos_args_len; + memcpy(th_args->args, pos_args_items, pos_args_len * sizeof(mp_obj_t)); + + // set the function for thread entry + th_args->fun = args[0]; + + // spawn the thread! mp_thread_create(thread_entry, th_args, thread_stack_size); + return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_thread_start_new_thread_obj, 2, 3, mod_thread_start_new_thread); -- cgit v1.2.3 From 9172c0cb252faa47f56e61f67ceb213012631de2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 4 May 2016 09:52:19 +0000 Subject: py/modthread: Call mp_thread_start/mp_thread_finish around threads. So the underlying thread implementation can do any necessary bookkeeping. --- py/modthread.c | 6 ++++++ py/mpthread.h | 2 ++ 2 files changed, 8 insertions(+) (limited to 'py/modthread.c') diff --git a/py/modthread.c b/py/modthread.c index 4e654dfe89..930ca45bbb 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -154,6 +154,9 @@ STATIC void *thread_entry(void *args_in) { mp_stack_set_top(&ts + 1); // need to include ts in root-pointer scan mp_stack_set_limit(16 * 1024); // fixed stack limit for now + // signal that we are set up and running + mp_thread_start(); + // TODO set more thread-specific state here: // mp_pending_exception? (root pointer) // cur_exception (root pointer) @@ -182,6 +185,9 @@ STATIC void *thread_entry(void *args_in) { DEBUG_printf("[thread] finish ts=%p\n", &ts); + // signal that we are finished + mp_thread_finish(); + return NULL; } diff --git a/py/mpthread.h b/py/mpthread.h index a92f2d0797..d0164ea29e 100644 --- a/py/mpthread.h +++ b/py/mpthread.h @@ -41,6 +41,8 @@ struct _mp_state_thread_t; struct _mp_state_thread_t *mp_thread_get_state(void); void mp_thread_set_state(void *state); 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); int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait); void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex); -- cgit v1.2.3 From 4cec63a9dbed86c4332b9e7fcd56d092046193e4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 26 May 2016 10:42:53 +0000 Subject: py: Implement a simple global interpreter lock. This makes the VM/runtime thread safe, at the cost of not being able to run code in parallel. --- py/modthread.c | 6 ++++++ py/mpconfig.h | 6 ++++++ py/mpstate.h | 5 +++++ py/mpthread.h | 8 ++++++++ py/runtime.c | 6 ++++++ py/vm.c | 4 ++++ 6 files changed, 35 insertions(+) (limited to 'py/modthread.c') diff --git a/py/modthread.c b/py/modthread.c index 930ca45bbb..7efad78c9a 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -146,6 +146,8 @@ typedef struct _thread_entry_args_t { } thread_entry_args_t; STATIC void *thread_entry(void *args_in) { + // Execution begins here for a new thread. We do not have the GIL. + thread_entry_args_t *args = (thread_entry_args_t*)args_in; mp_state_thread_t ts; @@ -154,6 +156,8 @@ STATIC void *thread_entry(void *args_in) { mp_stack_set_top(&ts + 1); // need to include ts in root-pointer scan mp_stack_set_limit(16 * 1024); // fixed stack limit for now + MP_THREAD_GIL_ENTER(); + // signal that we are set up and running mp_thread_start(); @@ -188,6 +192,8 @@ STATIC void *thread_entry(void *args_in) { // signal that we are finished mp_thread_finish(); + MP_THREAD_GIL_EXIT(); + return NULL; } diff --git a/py/mpconfig.h b/py/mpconfig.h index 998d1b6924..b79ddac196 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -829,6 +829,12 @@ typedef double mp_float_t; #define MICROPY_PY_THREAD (0) #endif +// Whether to make the VM/runtime thread-safe using a global lock +// If not enabled then thread safety must be provided at the Python level +#ifndef MICROPY_PY_THREAD_GIL +#define MICROPY_PY_THREAD_GIL (MICROPY_PY_THREAD) +#endif + // Extended modules #ifndef MICROPY_PY_UCTYPES diff --git a/py/mpstate.h b/py/mpstate.h index 3ee243dffa..b79fd7c5c7 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -175,6 +175,11 @@ typedef struct _mp_state_vm_t { #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF && MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE == 0 mp_int_t mp_emergency_exception_buf_size; #endif + + #if MICROPY_PY_THREAD_GIL + // This is a global mutex used to make the VM/runtime thread-safe. + mp_thread_mutex_t gil_mutex; + #endif } mp_state_vm_t; // This structure holds state that is specific to a given thread. diff --git a/py/mpthread.h b/py/mpthread.h index d0164ea29e..747de60fef 100644 --- a/py/mpthread.h +++ b/py/mpthread.h @@ -38,6 +38,14 @@ struct _mp_state_thread_t; +#if MICROPY_PY_THREAD_GIL +#define MP_THREAD_GIL_ENTER() mp_thread_mutex_lock(&MP_STATE_VM(gil_mutex), 1) +#define MP_THREAD_GIL_EXIT() mp_thread_mutex_unlock(&MP_STATE_VM(gil_mutex)) +#else +#define MP_THREAD_GIL_ENTER() +#define MP_THREAD_GIL_EXIT() +#endif + struct _mp_state_thread_t *mp_thread_get_state(void); void mp_thread_set_state(void *state); void mp_thread_create(void *(*entry)(void*), void *arg, size_t stack_size); diff --git a/py/runtime.c b/py/runtime.c index 7f28abbf4f..f88c92be63 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -91,6 +91,12 @@ void mp_init(void) { // start with no extensions to builtins MP_STATE_VM(mp_module_builtins_override_dict) = NULL; #endif + + #if MICROPY_PY_THREAD_GIL + mp_thread_mutex_init(&MP_STATE_VM(gil_mutex)); + #endif + + MP_THREAD_GIL_ENTER(); } void mp_deinit(void) { diff --git a/py/vm.c b/py/vm.c index bd5bae115e..65801401d1 100644 --- a/py/vm.c +++ b/py/vm.c @@ -1263,6 +1263,10 @@ pending_exception_check: RAISE(obj); } + // TODO make GIL release more efficient + MP_THREAD_GIL_EXIT(); + MP_THREAD_GIL_ENTER(); + } // for loop } else { -- cgit v1.2.3 From c567afc5fa8e79d65b77cdaf570748055ba87cc2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 26 May 2016 11:24:52 +0000 Subject: py/modthread: Make Lock objects work when GIL is enabled. --- py/modthread.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'py/modthread.c') diff --git a/py/modthread.c b/py/modthread.c index 7efad78c9a..e5d040671b 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -44,19 +44,24 @@ /****************************************************************/ // Lock object +// Note: with the GIL enabled we can easily synthesise a lock object STATIC const mp_obj_type_t mp_type_thread_lock; typedef struct _mp_obj_thread_lock_t { mp_obj_base_t base; + #if !MICROPY_PY_THREAD_GIL mp_thread_mutex_t mutex; - bool locked; + #endif + volatile bool locked; } mp_obj_thread_lock_t; STATIC mp_obj_thread_lock_t *mp_obj_new_thread_lock(void) { mp_obj_thread_lock_t *self = m_new_obj(mp_obj_thread_lock_t); self->base.type = &mp_type_thread_lock; + #if !MICROPY_PY_THREAD_GIL mp_thread_mutex_init(&self->mutex); + #endif self->locked = false; return self; } @@ -68,6 +73,19 @@ STATIC mp_obj_t thread_lock_acquire(size_t n_args, const mp_obj_t *args) { wait = mp_obj_get_int(args[1]); // TODO support timeout arg } + #if MICROPY_PY_THREAD_GIL + if (self->locked) { + if (!wait) { + return mp_const_false; + } + do { + MP_THREAD_GIL_EXIT(); + MP_THREAD_GIL_ENTER(); + } while (self->locked); + } + self->locked = true; + return mp_const_true; + #else int ret = mp_thread_mutex_lock(&self->mutex, wait); if (ret == 0) { return mp_const_false; @@ -77,6 +95,7 @@ STATIC mp_obj_t thread_lock_acquire(size_t n_args, const mp_obj_t *args) { } else { nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(-ret))); } + #endif } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(thread_lock_acquire_obj, 1, 3, thread_lock_acquire); @@ -84,7 +103,9 @@ STATIC mp_obj_t thread_lock_release(mp_obj_t self_in) { mp_obj_thread_lock_t *self = MP_OBJ_TO_PTR(self_in); // TODO check if already unlocked self->locked = false; + #if !MICROPY_PY_THREAD_GIL mp_thread_mutex_unlock(&self->mutex); + #endif return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(thread_lock_release_obj, thread_lock_release); -- cgit v1.2.3 From df95f52583e0f5e3f2a74f7461bb00e2f24b3079 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 30 May 2016 16:56:51 +0100 Subject: py/modthread: Allow to properly set the stack limit of a thread. We rely on the port setting and adjusting the stack size so there is enough room to recover from hitting the stack limit. --- cc3200/mpthreadport.c | 17 +++++++++++------ py/modthread.c | 8 ++++++-- py/mpthread.h | 2 +- unix/mpthreadport.c | 15 ++++++++++----- 4 files changed, 28 insertions(+), 14 deletions(-) (limited to 'py/modthread.c') diff --git a/cc3200/mpthreadport.c b/cc3200/mpthreadport.c index 7cc44d73dc..a9fb3f0d47 100644 --- a/cc3200/mpthreadport.c +++ b/cc3200/mpthreadport.c @@ -107,32 +107,37 @@ STATIC void freertos_entry(void *arg) { } } -void mp_thread_create(void *(*entry)(void*), void *arg, size_t stack_size) { +void mp_thread_create(void *(*entry)(void*), void *arg, size_t *stack_size) { // store thread entry function into a global variable so we can access it ext_thread_entry = entry; - if (stack_size == 0) { - stack_size = 2048; // default stack size + if (*stack_size == 0) { + *stack_size = 4096; // default stack size + } else if (*stack_size < 2048) { + *stack_size = 2048; // minimum stack size } mp_thread_mutex_lock(&thread_mutex, 1); // create thread - StackType_t *stack = m_new(StackType_t, stack_size / sizeof(StackType_t)); + StackType_t *stack = m_new(StackType_t, *stack_size / sizeof(StackType_t)); StaticTask_t *task_buf = m_new(StaticTask_t, 1); - TaskHandle_t id = xTaskCreateStatic(freertos_entry, "Thread", stack_size / sizeof(void*), arg, 2, stack, task_buf); + TaskHandle_t id = xTaskCreateStatic(freertos_entry, "Thread", *stack_size / sizeof(void*), arg, 2, stack, task_buf); if (id == NULL) { mp_thread_mutex_unlock(&thread_mutex); nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, "can't create thread")); } + // adjust stack_size to provide room to recover from hitting the limit + *stack_size -= 512; + // add thread to linked list of all threads thread_t *th = m_new_obj(thread_t); th->id = id; th->ready = 0; th->arg = arg; th->stack = stack; - th->stack_len = stack_size / sizeof(StackType_t); + th->stack_len = *stack_size / sizeof(StackType_t); th->next = thread; thread = th; diff --git a/py/modthread.c b/py/modthread.c index e5d040671b..6c8340c928 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -160,6 +160,7 @@ STATIC mp_obj_t mod_thread_stack_size(size_t n_args, const mp_obj_t *args) { STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_thread_stack_size_obj, 0, 1, mod_thread_stack_size); typedef struct _thread_entry_args_t { + size_t stack_size; mp_obj_t fun; size_t n_args; size_t n_kw; @@ -175,7 +176,7 @@ STATIC void *thread_entry(void *args_in) { mp_thread_set_state(&ts); mp_stack_set_top(&ts + 1); // need to include ts in root-pointer scan - mp_stack_set_limit(16 * 1024); // fixed stack limit for now + mp_stack_set_limit(args->stack_size); MP_THREAD_GIL_ENTER(); @@ -256,11 +257,14 @@ STATIC mp_obj_t mod_thread_start_new_thread(size_t n_args, const mp_obj_t *args) th_args->n_args = pos_args_len; memcpy(th_args->args, pos_args_items, pos_args_len * sizeof(mp_obj_t)); + // set the stack size to use + th_args->stack_size = thread_stack_size; + // set the function for thread entry th_args->fun = args[0]; // spawn the thread! - mp_thread_create(thread_entry, th_args, thread_stack_size); + mp_thread_create(thread_entry, th_args, &th_args->stack_size); return mp_const_none; } diff --git a/py/mpthread.h b/py/mpthread.h index 75e8984ba6..7f8d4dec7c 100644 --- a/py/mpthread.h +++ b/py/mpthread.h @@ -40,7 +40,7 @@ struct _mp_state_thread_t; struct _mp_state_thread_t *mp_thread_get_state(void); void mp_thread_set_state(void *state); -void mp_thread_create(void *(*entry)(void*), void *arg, size_t stack_size); +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); diff --git a/unix/mpthreadport.c b/unix/mpthreadport.c index 336db3aa92..e5cfe7a669 100644 --- a/unix/mpthreadport.c +++ b/unix/mpthreadport.c @@ -133,10 +133,12 @@ void mp_thread_start(void) { pthread_mutex_unlock(&thread_mutex); } -void mp_thread_create(void *(*entry)(void*), void *arg, size_t stack_size) { - // default stack size is 8k machine-words - if (stack_size == 0) { - stack_size = 8192 * BYTES_PER_WORD; +void mp_thread_create(void *(*entry)(void*), void *arg, size_t *stack_size) { + // default stack size is 8k machine-words, minimum is 2k + if (*stack_size == 0) { + *stack_size = 8192 * BYTES_PER_WORD; + } else if (*stack_size < 2048 * BYTES_PER_WORD) { + *stack_size = 2048 * BYTES_PER_WORD; } // set thread attributes @@ -145,7 +147,7 @@ void mp_thread_create(void *(*entry)(void*), void *arg, size_t stack_size) { if (ret != 0) { goto er; } - ret = pthread_attr_setstacksize(&attr, stack_size); + ret = pthread_attr_setstacksize(&attr, *stack_size); if (ret != 0) { goto er; } @@ -160,6 +162,9 @@ void mp_thread_create(void *(*entry)(void*), void *arg, size_t stack_size) { goto er; } + // adjust stack_size to provide room to recover from hitting the limit + *stack_size -= 1024 * BYTES_PER_WORD; + // add thread to linked list of all threads thread_t *th = malloc(sizeof(thread_t)); th->id = id; -- cgit v1.2.3