From 5d88c4a2b80060290467d84033082f3af0c333b2 Mon Sep 17 00:00:00 2001 From: ame Date: Tue, 4 Nov 2025 01:47:09 -0600 Subject: thread testcancel and usleep --- docs/thread/async.md | 21 +++++++++--- docs/thread/usleep.md | 5 +++ library/lullaby/thread.lua | 27 ++++++++++++++-- readme.md | 2 -- src/thread.c | 80 ++++++++++++++++++++++++++++++++++------------ src/thread.h | 6 ++-- 6 files changed, 109 insertions(+), 32 deletions(-) create mode 100644 docs/thread/usleep.md diff --git a/docs/thread/async.md b/docs/thread/async.md index 0f04e12..98497ba 100644 --- a/docs/thread/async.md +++ b/docs/thread/async.md @@ -8,6 +8,21 @@ the argument function is executed by the thread until it has been completed. the the reason for the res function it because lua_call (in the c api) requires a number or return values before you run the function +the res function also provides some child methods for thread managment + +### res:testclose + +res:testclose() + +closes the thread if it is being requested to close from async:close + +### res:autoclose + +res:autoclose() + +calls res:testclose() every lua line, using a debug hook + +--- ### async:await @@ -25,12 +40,10 @@ kills the thread, this may close it in an unsafe way, and should be avoided async:close() -kills the thread in a more safe manor, should be preferred in most cases. however it is best to let the thread exit itself - -some systems may not support this (pthread_cancel) and so this function will call async:kill() in cases where it cant. android is one of the main ones +waits for the thread to internally call res:testclose or exit ### async:clean async:clean() -calls the __gc metamethod, will call async:close() if it is still running +calls the __gc metamethod, will call async:kill() if it is still running diff --git a/docs/thread/usleep.md b/docs/thread/usleep.md new file mode 100644 index 0000000..75208a9 --- /dev/null +++ b/docs/thread/usleep.md @@ -0,0 +1,5 @@ +## usleep + +thread.usleep(μN) + +puts the thread to sleep for N microseconds (duh) diff --git a/library/lullaby/thread.lua b/library/lullaby/thread.lua index 7a1888c..41444ff 100644 --- a/library/lullaby/thread.lua +++ b/library/lullaby/thread.lua @@ -16,7 +16,7 @@ function async.await(T) end ---@return nil function async.clean(T) end ----stops the thread, may be unavaliable on some systems (android) and will call async:kill instead +---waits for the thread to either call res:testcancel() or for it to exit ---@param T async-table ---@return nil function async.close(T) end @@ -31,8 +31,26 @@ function async.kill(T) end ---@type lightuserdata async._ = nil +---@class async-res-table +---@overload fun(a: any): nil +local asyncres = setmetatable({}, { + __call = function() end +}) + +---checks if the thread is being requested to close with thread:close() +---@param T async-res-table +---@return nil +function asyncres.testclose(T) end + +---calls res:testclose every line +---@param T async-res-table +---@return nil +function asyncres.autoclose(T) end +---@meta + ---@async ----@param fun fun(res: fun(...)): nil function to call, parameter will set a return value for the thread +---@nodiscard +---@param fun fun(res: async-res-table): nil function to call, parameter will set a return value for the thread, also contains methods for thread managment ---@return async-table function thread.async(fun) end @@ -56,6 +74,7 @@ function buffer.set(T, value) end ---@return nil function buffer.mod(T, fun) end +---@nodiscard ---puts a value into a atomic thread-safe buffer ---@param value any ---@return buffer-table @@ -86,4 +105,8 @@ function mutex.free(T) end ---@return mutex-table function thread.mutex() end +---puts the thread to sleep for N microseconds +---@param N integer +function thread.usleep(N) end + return thread diff --git a/readme.md b/readme.md index 958e298..c2bb81b 100644 --- a/readme.md +++ b/readme.md @@ -58,8 +58,6 @@ for working on the code base, i recommend using bear to generate compile_command * better tests * rewrite docs - - * net mostly complete * portability (memmem) diff --git a/src/thread.c b/src/thread.c index 2a2de39..8dcfe2a 100644 --- a/src/thread.c +++ b/src/thread.c @@ -15,9 +15,9 @@ struct thread_info { str* function; lua_State* L; - int return_count, done; + int return_count, done, request_close; pthread_t tid; - pthread_mutex_t* lock, *ready_lock; + pthread_mutex_t* lock, *ready_lock, *close_lock; pthread_cond_t* cond; }; @@ -107,6 +107,37 @@ int l_res(lua_State* L){ return 1; } +int _res_testclose(lua_State* L){ + lua_pushstring(L, "_"); + lua_gettable(L, 1); + struct thread_info* info = lua_touserdata(L, -1); + + pthread_mutex_lock(&*info->close_lock); + pthread_cond_signal(&*info->cond); + + if(info->request_close){ + info->done = 1; + + pthread_mutex_unlock(&*info->lock); + pthread_mutex_unlock(&*info->close_lock); + + pthread_exit(NULL); + } + + pthread_mutex_unlock(&*info->close_lock); + + return 0; +} + +void _res_testclose_debug(lua_State* L, lua_Debug* d){ + _res_testclose(L); +} + +int _res_autoclose(lua_State* L){ + lua_sethook(L, _res_testclose_debug, LUA_HOOKLINE, 1); + return 0; +} + void _thread_exit_signal(int i){ pthread_exit(NULL); } @@ -116,10 +147,6 @@ void* handle_thread(void* _args){ lua_State* L = args->L; pthread_mutex_lock(&*args->lock); -#ifdef SUPPORTS_PTHREAD_CANCEL - pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); -#endif - signal(SIGUSR1, _thread_exit_signal); pthread_detach(args->tid); @@ -130,6 +157,8 @@ void* handle_thread(void* _args){ lua_newtable(L); int res_idx = lua_gettop(L); + luaI_tsetcf(L, res_idx, "testclose", _res_testclose); + luaI_tsetcf(L, res_idx, "autoclose", _res_autoclose); luaI_tsetlud(L, res_idx, "_", args); lua_newtable(L); @@ -149,6 +178,10 @@ void* handle_thread(void* _args){ args->done = 1; pthread_mutex_unlock(&*args->lock); + pthread_mutex_lock(&*args->close_lock); + pthread_cond_signal(&*args->cond); + pthread_mutex_unlock(&*args->close_lock); + return NULL; } @@ -159,7 +192,6 @@ int _thread_await(lua_State* L){ if(info->L == NULL) luaI_error(L, -1, "thread was already closed") if(info->tid == 0) luaI_error(L, -2, "thread was killed early") - //maybe error here if tid is zero pthread_mutex_lock(&*info->lock); env_table(info->L, 0); @@ -202,11 +234,7 @@ int _thread_clean(lua_State* L){ luaI_tsetnil(L, 1, "_"); if(info->tid != 0 && !info->done){ -#ifdef SUPPORTS_PTHREAD_CANCEL - pthread_cancel(info->tid); -#else pthread_kill(info->tid, SIGUSR1); -#endif } //lua_gc(info->L, LUA_GCRESTART); @@ -218,6 +246,11 @@ int _thread_clean(lua_State* L){ pthread_mutex_destroy(&*info->lock); free(info->lock); + pthread_mutex_destroy(&*info->close_lock); + free(info->close_lock); + + pthread_cond_destroy(&*info->cond); + free(info->cond); free(info); } @@ -236,19 +269,22 @@ int _thread_kill(lua_State* L){ } int _thread_close(lua_State* L){ -#ifdef SUPPORTS_PTHREAD_CANCEL - lua_pushstring(L, "_"); lua_gettable(L, 1); struct thread_info* info = lua_touserdata(L, -1); - if(info->tid != 0) pthread_cancel(info->tid); + if(info->tid == 0) return 0; + + pthread_mutex_lock(&*info->close_lock); + + info->request_close = 1; + + pthread_cond_wait(&*info->cond, &*info->close_lock); + pthread_mutex_unlock(&*info->close_lock); + info->tid = 0; return 0; -#else - return _thread_kill(L); -#endif } int l_async(lua_State* oL){ @@ -264,6 +300,9 @@ int l_async(lua_State* oL){ pthread_mutex_init(&*args->lock, NULL); args->ready_lock = malloc(sizeof * args->ready_lock); pthread_mutex_init(&*args->ready_lock, NULL); + args->close_lock = malloc(sizeof * args->close_lock); + pthread_mutex_init(&*args->close_lock, NULL); + args->return_count = 0; args->cond = malloc(sizeof * args->cond); pthread_cond_init(&*args->cond, NULL); @@ -279,8 +318,6 @@ int l_async(lua_State* oL){ pthread_cond_wait(&*args->cond, &*args->ready_lock); pthread_mutex_unlock(&*args->ready_lock); - pthread_cond_destroy(&*args->cond); - free(args->cond); pthread_mutex_destroy(&*args->ready_lock); free(args->ready_lock); @@ -508,8 +545,11 @@ int l_buffer(lua_State* L){ return 1; } -void _lua_getfenv(lua_State* L){ +int l_usleep(lua_State* L){ + uint64_t n = lua_tonumber(L, 1); + usleep(n); + return 0; } int l_testcopy(lua_State* L){ diff --git a/src/thread.h b/src/thread.h index f0435eb..8061971 100644 --- a/src/thread.h +++ b/src/thread.h @@ -1,14 +1,11 @@ #include "lua.h" #include "config.h" -#ifndef __ANDROID__ -#define SUPPORTS_PTHREAD_CANCEL -#endif - int l_async(lua_State*); int l_buffer(lua_State*); int l_testcopy(lua_State*); int l_mutex(lua_State*); +int l_usleep(lua_State*); void lib_thread_clean(); @@ -19,6 +16,7 @@ static const luaL_Reg thread_function_list [] = { {"buffer",l_buffer}, {"testcopy",l_testcopy}, {"mutex", l_mutex}, + {"usleep", l_usleep}, {NULL,NULL} }; -- cgit v1.2.3