From adaa36dbec1ce9aaafd61873b9d3d898a8c240cf Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Thu, 11 Apr 2024 15:14:52 +0200 Subject: Bring all interesting fixes from the C++ implementation back into the C implementation --- src/cancel.h | 3 - src/compat.c | 20 ++- src/compat.h | 1 + src/deep.c | 13 +- src/keeper.c | 83 ++++++--- src/keeper.h | 9 +- src/lanes.c | 115 +++++++------ src/lanes.h | 4 +- src/lanes.lua | 449 +++++++++++++++++++++++++------------------------ src/lanes_private.h | 10 +- src/linda.c | 115 +++++++------ src/macros_and_utils.h | 7 +- src/platform.h | 1 + src/state.c | 2 +- src/tools.c | 50 ++++-- src/tools.h | 16 +- src/uniquekey.h | 4 +- 17 files changed, 505 insertions(+), 397 deletions(-) (limited to 'src') diff --git a/src/cancel.h b/src/cancel.h index c7c5433..b25d9f9 100644 --- a/src/cancel.h +++ b/src/cancel.h @@ -43,9 +43,6 @@ typedef enum // crc64/we of string "CANCEL_ERROR" generated at http://www.nitrxgen.net/hashgen/ static DECLARE_CONST_UNIQUE_KEY(CANCEL_ERROR, 0xe97d41626cc97577); // 'cancel_error' sentinel -// crc64/we of string "CANCEL_TEST_KEY" generated at http://www.nitrxgen.net/hashgen/ -static DECLARE_CONST_UNIQUE_KEY(CANCEL_TEST_KEY, 0xe66f5960c57d133a); // used as registry key - cancel_result thread_cancel( lua_State* L, Lane* s, CancelOp op_, double secs_, bool_t force_, double waitkill_timeout_); static inline int cancel_error( lua_State* L) diff --git a/src/compat.c b/src/compat.c index 19159a9..bc39d4c 100644 --- a/src/compat.c +++ b/src/compat.c @@ -1,6 +1,6 @@ /* * ############################################################################################### - * ######################################### Lua 5.1/5.2 ######################################### + * ####################################### Lua 5.1/5.2/5.3 ####################################### * ############################################################################################### */ #include "compat.h" @@ -9,7 +9,11 @@ /* ** Copied from Lua 5.2 loadlib.c */ +// ################################################################################################ +// ################################################################################################ #if LUA_VERSION_NUM == 501 +// ################################################################################################ +// ################################################################################################ static int luaL_getsubtable (lua_State *L, int idx, const char *fname) { lua_getfield(L, idx, fname); @@ -26,6 +30,8 @@ static int luaL_getsubtable (lua_State *L, int idx, const char *fname) } } +// ################################################################################################ + void luaL_requiref (lua_State *L, const char *modname, lua_CFunction openf, int glb) { lua_pushcfunction(L, openf); @@ -43,7 +49,11 @@ void luaL_requiref (lua_State *L, const char *modname, lua_CFunction openf, int } #endif // LUA_VERSION_NUM +// ################################################################################################ +// ################################################################################################ #if LUA_VERSION_NUM < 504 +// ################################################################################################ +// ################################################################################################ void* lua_newuserdatauv( lua_State* L, size_t sz, int nuvalue) { @@ -51,8 +61,12 @@ void* lua_newuserdatauv( lua_State* L, size_t sz, int nuvalue) return lua_newuserdata( L, sz); } +// ################################################################################################ + +// push on stack uservalue #n of full userdata at idx int lua_getiuservalue( lua_State* L, int idx, int n) { + // full userdata can have only 1 uservalue before 5.4 if( n > 1) { lua_pushnil( L); @@ -76,6 +90,10 @@ int lua_getiuservalue( lua_State* L, int idx, int n) return lua_type( L, -1); } +// ################################################################################################ + +// Pops a value from the stack and sets it as the new n-th user value associated to the full userdata at the given index. +// Returns 0 if the userdata does not have that value. int lua_setiuservalue( lua_State* L, int idx, int n) { if( n > 1 diff --git a/src/compat.h b/src/compat.h index e44f827..fbcbee1 100644 --- a/src/compat.h +++ b/src/compat.h @@ -90,6 +90,7 @@ int lua_setiuservalue( lua_State* L, int idx, int n); #define luaG_registerlibfuncs( L, _funcs) luaL_setfuncs( L, _funcs, 0) #define lua504_dump lua_dump #define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) +#define LUA_ERRGCMM 666 // doesn't exist in Lua 5.4, we don't care about the actual value #endif // LUA_VERSION_NUM == 504 diff --git a/src/deep.c b/src/deep.c index 9496477..a1f078a 100644 --- a/src/deep.c +++ b/src/deep.c @@ -236,7 +236,7 @@ char const* push_deep_proxy( Universe* U, lua_State* L, DeepPrelude* prelude, in *proxy = prelude; // Get/create metatable for 'idfunc' (in this state) - lua_pushlightuserdata( L, (void*)(ptrdiff_t)(prelude->idfunc)); // DPC proxy idfunc + lua_pushlightuserdata( L, (void*)(uintptr_t)(prelude->idfunc)); // DPC proxy idfunc get_deep_lookup( L); // DPC proxy metatable? if( lua_isnil( L, -1)) // // No metatable yet. @@ -278,7 +278,7 @@ char const* push_deep_proxy( Universe* U, lua_State* L, DeepPrelude* prelude, in // Memorize for later rounds lua_pushvalue( L, -1); // DPC proxy metatable metatable - lua_pushlightuserdata( L, (void*)(ptrdiff_t)(prelude->idfunc)); // DPC proxy metatable metatable idfunc + lua_pushlightuserdata( L, (void*)(uintptr_t)(prelude->idfunc)); // DPC proxy metatable metatable idfunc set_deep_lookup( L); // DPC proxy metatable // 2 - cause the target state to require the module that exported the idfunc @@ -473,15 +473,18 @@ bool_t copydeep( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, ui lua_pop( L, 1); // ... u [uv]* STACK_MID( L, nuv); - errmsg = push_deep_proxy( U, L2, *(DeepPrelude**) lua_touserdata( L, i), nuv, mode_); // u + errmsg = push_deep_proxy( U, L2, *(DeepPrelude**) lua_touserdata( L, i), nuv, mode_); // u // transfer all uservalues of the source in the destination { int const clone_i = lua_gettop( L2); while( nuv) { - inter_copy_one( U, L2, L2_cache_i, L, lua_absindex( L, -1), VT_NORMAL, mode_, upName_); // u uv - lua_pop( L, 1); // ... u [uv]* + if(!inter_copy_one( U, L2, L2_cache_i, L, lua_absindex( L, -1), VT_NORMAL, mode_, upName_)) // u uv + { + return luaL_error(L, "Cannot copy upvalue type '%s'", luaL_typename(L, -1)); + } + lua_pop( L, 1); // ... u [uv]* // this pops the value from the stack lua_setiuservalue( L2, clone_i, nuv); // u -- nuv; diff --git a/src/keeper.c b/src/keeper.c index 8aa734a..a1505b7 100644 --- a/src/keeper.c +++ b/src/keeper.c @@ -122,7 +122,7 @@ static void fifo_push( lua_State* L, keeper_fifo* fifo_, lua_Integer count_) static void fifo_peek( lua_State* L, keeper_fifo* fifo_, lua_Integer count_) { lua_Integer i; - STACK_GROW( L, count_); + STACK_GROW( L, (int) count_); for( i = 0; i < count_; ++ i) { lua_rawgeti( L, 1, (int)( fifo_->first + i)); @@ -136,7 +136,7 @@ static void fifo_pop( lua_State* L, keeper_fifo* fifo_, lua_Integer count_) int const fifo_idx = lua_gettop( L); // ... fifo int i; // each iteration pushes a value on the stack! - STACK_GROW( L, count_ + 2); + STACK_GROW( L, (int) count_ + 2); // skip first item, we will push it last for( i = 1; i < count_; ++ i) { @@ -169,7 +169,7 @@ static void fifo_pop( lua_State* L, keeper_fifo* fifo_, lua_Integer count_) static DECLARE_CONST_UNIQUE_KEY( FIFOS_KEY, 0xdce50bbc351cd465); static void push_table( lua_State* L, int idx_) { - STACK_GROW( L, 4); + STACK_GROW( L, 5); STACK_CHECK( L, 0); idx_ = lua_absindex( L, idx_); REGISTRY_GET( L, FIFOS_KEY); // ud fifos @@ -189,7 +189,7 @@ static void push_table( lua_State* L, int idx_) STACK_END( L, 1); } -int keeper_push_linda_storage( Universe* U, lua_State* L, void* ptr_, ptrdiff_t magic_) +int keeper_push_linda_storage( Universe* U, lua_State* L, void* ptr_, uintptr_t magic_) { Keeper* const K = which_keeper( U->keepers, magic_); lua_State* const KL = K ? K->L : NULL; @@ -557,7 +557,7 @@ int keepercall_count( lua_State* L) { lua_pop( L, 1); // out fifos keys } - } + } // all keys are exhausted // out fifos lua_pop( L, 1); // out } ASSERT_L( lua_gettop( L) == 1); @@ -633,6 +633,7 @@ void init_keepers( Universe* U, lua_State* L) { int i; int nb_keepers; + int keepers_gc_threshold; STACK_CHECK( L, 0); // L K lua_getfield( L, 1, "nb_keepers"); // nb_keepers @@ -642,6 +643,12 @@ void init_keepers( Universe* U, lua_State* L) { (void) luaL_error( L, "Bad number of keepers (%d)", nb_keepers); } + STACK_MID(L, 0); + + lua_getfield(L, 1, "keepers_gc_threshold"); // keepers_gc_threshold + keepers_gc_threshold = (int) lua_tointeger(L, -1); + lua_pop(L, 1); // + STACK_MID(L, 0); // Keepers contains an array of 1 s_Keeper, adjust for the actual number of keeper states { @@ -656,6 +663,7 @@ void init_keepers( Universe* U, lua_State* L) return; } memset( U->keepers, 0, bytes); + U->keepers->gc_threshold = keepers_gc_threshold; U->keepers->nb_keepers = nb_keepers; } for( i = 0; i < nb_keepers; ++ i) // keepersUD @@ -669,10 +677,12 @@ void init_keepers( Universe* U, lua_State* L) } U->keepers->keeper_array[i].L = K; - // we can trigger a GC from inside keeper_call(), where a keeper is acquired - // from there, GC can collect a linda, which would acquire the keeper again, and deadlock the thread. - // therefore, we need a recursive mutex. - MUTEX_RECURSIVE_INIT( &U->keepers->keeper_array[i].keeper_cs); + MUTEX_INIT( &U->keepers->keeper_array[i].keeper_cs); + + if (U->keepers->gc_threshold >= 0) + { + lua_gc(K, LUA_GCSTOP, 0); + } STACK_CHECK( K, 0); @@ -693,7 +703,7 @@ void init_keepers( Universe* U, lua_State* L) if( !lua_isnil( L, -1)) { // when copying with mode eLM_ToKeeper, error message is pushed at the top of the stack, not raised immediately - if( luaG_inter_copy_package( U, L, K, -1, eLM_ToKeeper)) + if( luaG_inter_copy_package( U, L, K, -1, eLM_ToKeeper) != eICR_Success) { // if something went wrong, the error message is at the top of the stack lua_remove( L, -2); // error_msg @@ -721,22 +731,22 @@ void init_keepers( Universe* U, lua_State* L) } // should be called only when inside a keeper_acquire/keeper_release pair (see linda_protected_call) -Keeper* which_keeper(Keepers* keepers_, ptrdiff_t magic_) +Keeper* which_keeper(Keepers* keepers_, uintptr_t magic_) { int const nbKeepers = keepers_->nb_keepers; - unsigned int i = (unsigned int)((magic_ >> KEEPER_MAGIC_SHIFT) % nbKeepers); - return &keepers_->keeper_array[i]; + if (nbKeepers) + { + unsigned int i = (unsigned int)((magic_ >> KEEPER_MAGIC_SHIFT) % nbKeepers); + return &keepers_->keeper_array[i]; + } + return NULL; } -Keeper* keeper_acquire( Keepers* keepers_, ptrdiff_t magic_) +Keeper* keeper_acquire( Keepers* keepers_, uintptr_t magic_) { int const nbKeepers = keepers_->nb_keepers; // can be 0 if this happens during main state shutdown (lanes is being GC'ed -> no keepers) - if( nbKeepers == 0) - { - return NULL; - } - else + if( nbKeepers) { /* * Any hashing will do that maps pointers to 0..GNbKeepers-1 @@ -752,12 +762,13 @@ Keeper* keeper_acquire( Keepers* keepers_, ptrdiff_t magic_) //++ K->count; return K; } + return NULL; } -void keeper_release( Keeper* K) +void keeper_release( Keeper* K_) { //-- K->count; - if( K) MUTEX_UNLOCK( &K->keeper_cs); + if( K_) MUTEX_UNLOCK( &K_->keeper_cs); } void keeper_toggle_nil_sentinels( lua_State* L, int val_i_, LookupMode const mode_) @@ -805,7 +816,7 @@ int keeper_call( Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, vo lua_pushlightuserdata( K, linda); - if( (args == 0) || luaG_inter_copy( U, L, K, args, eLM_ToKeeper) == 0) // L->K + if( (args == 0) || luaG_inter_copy( U, L, K, args, eLM_ToKeeper) == eICR_Success) // L->K { lua_call( K, 1 + args, LUA_MULTRET); @@ -814,12 +825,38 @@ int keeper_call( Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, vo // this may interrupt a lane, causing the destruction of the underlying OS thread // after this, another lane making use of this keeper can get an error code from the mutex-locking function // when attempting to grab the mutex again (WINVER <= 0x400 does this, but locks just fine, I don't know about pthread) - if( (retvals > 0) && luaG_inter_move( U, K, L, retvals, eLM_FromKeeper) != 0) // K->L + if( (retvals > 0) && luaG_inter_move( U, K, L, retvals, eLM_FromKeeper) != eICR_Success) // K->L { retvals = -1; } } // whatever happens, restore the stack to where it was at the origin lua_settop( K, Ktos); + + + // don't do this for this particular function, as it is only called during Linda destruction, and we don't want to raise an error, ever + if (func_ != KEEPER_API(clear)) + { + // since keeper state GC is stopped, let's run a step once in a while if required + int const gc_threshold = U->keepers->gc_threshold; + if (gc_threshold == 0) + { + lua_gc(K, LUA_GCSTEP, 0); + } + else if (gc_threshold > 0) + { + int const gc_usage = lua_gc(K, LUA_GCCOUNT, 0); + if (gc_usage >= gc_threshold) + { + lua_gc(K, LUA_GCCOLLECT, 0); + int const gc_usage_after = lua_gc(K, LUA_GCCOUNT, 0); + if (gc_usage_after > gc_threshold) + { + luaL_error(L, "Keeper GC threshold is too low, need at least %d", gc_usage_after); + } + } + } + } + return retvals; } diff --git a/src/keeper.h b/src/keeper.h index d30aa36..7c55809 100644 --- a/src/keeper.h +++ b/src/keeper.h @@ -21,6 +21,7 @@ typedef struct s_Keeper Keeper; struct s_Keepers { + int gc_threshold; int nb_keepers; Keeper keeper_array[1]; }; @@ -29,12 +30,12 @@ typedef struct s_Keepers Keepers; void init_keepers( Universe* U, lua_State* L); void close_keepers( Universe* U); -Keeper* which_keeper( Keepers* keepers_, ptrdiff_t magic_); -Keeper* keeper_acquire( Keepers* keepers_, ptrdiff_t magic_); +Keeper* which_keeper( Keepers* keepers_, uintptr_t magic_); +Keeper* keeper_acquire( Keepers* keepers_, uintptr_t magic_); #define KEEPER_MAGIC_SHIFT 3 -void keeper_release( Keeper* K); +void keeper_release( Keeper* K_); void keeper_toggle_nil_sentinels( lua_State* L, int val_i_, LookupMode const mode_); -int keeper_push_linda_storage( Universe* U, lua_State* L, void* ptr_, ptrdiff_t magic_); +int keeper_push_linda_storage( Universe* U, lua_State* L, void* ptr_, uintptr_t magic_); // crc64/we of string "NIL_SENTINEL" generated at http://www.nitrxgen.net/hashgen/ static DECLARE_CONST_UNIQUE_KEY( NIL_SENTINEL, 0x7eaafa003a1d11a1); diff --git a/src/lanes.c b/src/lanes.c index 332a1b8..ca2b53a 100644 --- a/src/lanes.c +++ b/src/lanes.c @@ -442,7 +442,7 @@ static bool_t selfdestruct_remove( Lane* s) /* * Process end; cancel any still free-running threads */ -static int selfdestruct_gc( lua_State* L) +static int universe_gc( lua_State* L) { Universe* U = (Universe*) lua_touserdata( L, 1); @@ -456,7 +456,7 @@ static int selfdestruct_gc( lua_State* L) while( s != SELFDESTRUCT_END) { // attempt a regular unforced hard cancel with a small timeout - bool_t cancelled = THREAD_ISNULL( s->thread) || thread_cancel( L, s, CO_Hard, 0.0001, FALSE, 0.0); + bool_t cancelled = THREAD_ISNULL( s->thread) || (thread_cancel( L, s, CO_Hard, 0.0001, FALSE, 0.0) != CR_Timeout); // if we failed, and we know the thread is waiting on a linda if( cancelled == FALSE && s->status == WAITING && s->waiting_on != NULL) { @@ -609,7 +609,7 @@ static int selfdestruct_gc( lua_State* L) // LUAG_FUNC( set_singlethreaded) { - uint_t cores = luaG_optunsigned( L, 1, 1); + lua_Integer cores = luaG_optunsigned( L, 1, 1); (void) cores; // prevent "unused" warning #ifdef PLATFORM_OSX @@ -653,24 +653,16 @@ static DECLARE_CONST_UNIQUE_KEY( EXTENDED_STACKTRACE_REGKEY, 0x2357c69a7c92c936) LUAG_FUNC( set_error_reporting) { - bool_t equal; - luaL_checktype( L, 1, LUA_TSTRING); - lua_pushliteral( L, "extended"); - equal = lua_rawequal( L, -1, 1); - lua_pop( L, 1); - if( equal) + luaL_checktype(L, 1, LUA_TSTRING); + char const* mode = lua_tostring(L, 1); + bool_t const extended = (strcmp(mode, "extended") == 0); + bool_t const basic = (strcmp(mode, "basic") == 0); + if (!extended && !basic) { - goto done; + return luaL_error(L, "unsupported error reporting model %s", mode); } - lua_pushliteral( L, "basic"); - equal = !lua_rawequal( L, -1, 1); - lua_pop( L, 1); - if( equal) - { - return luaL_error( L, "unsupported error reporting model"); - } -done: - REGISTRY_SET( L, EXTENDED_STACKTRACE_REGKEY, lua_pushboolean( L, equal)); + + REGISTRY_SET( L, EXTENDED_STACKTRACE_REGKEY, lua_pushboolean( L, extended ? 1 : 0)); return 0; } @@ -788,7 +780,8 @@ static void push_stack_trace( lua_State* L, int rc_, int stk_base_) LUAG_FUNC( set_debug_threadname) { - DECLARE_CONST_UNIQUE_KEY( hidden_regkey, LG_set_debug_threadname); + // fnv164 of string "debug_threadname" generated at https://www.pelock.com/products/hash-calculator + static DECLARE_CONST_UNIQUE_KEY( hidden_regkey, 0x79C0669AAAE04440); // C s_lane structure is a light userdata upvalue Lane* s = lua_touserdata( L, lua_upvalueindex( 1)); luaL_checktype( L, -1, LUA_TSTRING); // "name" @@ -1049,10 +1042,10 @@ LUAG_FUNC( lane_new) char const* libs_str = lua_tostring( L, 2); bool_t const have_priority = !lua_isnoneornil( L, 3); int const priority = have_priority ? (int) lua_tointeger( L, 3) : THREAD_PRIO_DEFAULT; - uint_t const globals_idx = lua_isnoneornil( L, 4) ? 0 : 4; - uint_t const package_idx = lua_isnoneornil( L, 5) ? 0 : 5; - uint_t const required_idx = lua_isnoneornil( L, 6) ? 0 : 6; - uint_t const gc_cb_idx = lua_isnoneornil( L, 7) ? 0 : 7; + int const globals_idx = lua_isnoneornil( L, 4) ? 0 : 4; + int const package_idx = lua_isnoneornil( L, 5) ? 0 : 5; + int const required_idx = lua_isnoneornil( L, 6) ? 0 : 6; + int const gc_cb_idx = lua_isnoneornil( L, 7) ? 0 : 7; #define FIXED_ARGS 7 int const nargs = lua_gettop(L) - FIXED_ARGS; @@ -1090,7 +1083,8 @@ LUAG_FUNC( lane_new) if( package_idx != 0) { // when copying with mode eLM_LaneBody, should raise an error in case of problem, not leave it one the stack - (void) luaG_inter_copy_package( U, L, L2, package_idx, eLM_LaneBody); + InterCopyResult const ret = luaG_inter_copy_package( U, L, L2, package_idx, eLM_LaneBody); + ASSERT_L(ret == eICR_Success); // either all went well, or we should not even get here } // modules to require in the target lane *before* the function is transfered! @@ -1179,25 +1173,32 @@ LUAG_FUNC( lane_new) STACK_MID( L2, 0); // Lane main function - if( lua_type( L, 1) == LUA_TFUNCTION) { - int res; - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane body\n" INDENT_END)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); - lua_pushvalue( L, 1); // func libs priority globals package required gc_cb [... args ...] func - res = luaG_inter_move( U, L, L2, 1, eLM_LaneBody); // func libs priority globals package required gc_cb [... args ...] // func - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); - if( res != 0) + int const func_type = lua_type(L, 1); + if (func_type == LUA_TFUNCTION) { - return luaL_error( L, "tried to copy unsupported types"); + InterCopyResult res; + DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: transfer lane body\n" INDENT_END)); + DEBUGSPEW_CODE(++U->debugspew_indent_depth); + lua_pushvalue(L, 1); // func libs priority globals package required gc_cb [... args ...] func + res = luaG_inter_move(U, L, L2, 1, eLM_LaneBody); // func libs priority globals package required gc_cb [... args ...] // func + DEBUGSPEW_CODE(--U->debugspew_indent_depth); + if (res != eICR_Success) + { + return luaL_error(L, "tried to copy unsupported types"); + } } - } - else if( lua_type( L, 1) == LUA_TSTRING) - { - // compile the string - if( luaL_loadstring( L2, lua_tostring( L, 1)) != 0) // func + else if (func_type == LUA_TSTRING) { - return luaL_error( L, "error when parsing lane function code"); + // compile the string + if (luaL_loadstring(L2, lua_tostring(L, 1)) != 0) // func + { + return luaL_error(L, "error when parsing lane function code"); + } + } + else + { + luaL_error(L, "Expected function, got %s", lua_typename(L, func_type)); // doesn't return } } STACK_MID( L, 0); @@ -1207,12 +1208,12 @@ LUAG_FUNC( lane_new) // revive arguments if( nargs > 0) { - int res; + InterCopyResult res; DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane arguments\n" INDENT_END)); DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); res = luaG_inter_move( U, L, L2, nargs, eLM_LaneBody); // func libs priority globals package required gc_cb // func [... args ...] DEBUGSPEW_CODE( -- U->debugspew_indent_depth); - if( res != 0) + if( res != eICR_Success) { return luaL_error( L, "tried to copy unsupported types"); } @@ -1277,7 +1278,7 @@ LUAG_FUNC( lane_new) lua_setiuservalue( L, -2, 1); // func libs priority globals package required gc_cb lane // Store 's' in the lane's registry, for 'cancel_test()' (we do cancel tests at pending send/receive). - REGISTRY_SET( L2, CANCEL_TEST_KEY, lua_pushlightuserdata( L2, s)); // func [... args ...] + REGISTRY_SET( L2, LANE_POINTER_REGKEY, lua_pushlightuserdata( L2, s)); // func [... args ...] STACK_END( L, 1); STACK_END( L2, 1 + nargs); @@ -1457,8 +1458,8 @@ LUAG_FUNC( thread_join) { case DONE: { - uint_t n = lua_gettop( L2); // whole L2 stack - if( (n > 0) && (luaG_inter_move( U, L2, L, n, eLM_LaneBody) != 0)) + int n = lua_gettop( L2); // whole L2 stack + if( (n > 0) && (luaG_inter_move( U, L2, L, n, eLM_LaneBody) != eICR_Success)) { return luaL_error( L, "tried to copy unsupported types"); } @@ -1472,7 +1473,7 @@ LUAG_FUNC( thread_join) STACK_GROW( L, 3); lua_pushnil( L); // even when ERROR_FULL_STACK, if the error is not LUA_ERRRUN, the handler wasn't called, and we only have 1 error message on the stack ... - if( luaG_inter_move( U, L2, L, n, eLM_LaneBody) != 0) // nil "err" [trace] + if( luaG_inter_move( U, L2, L, n, eLM_LaneBody) != eICR_Success) // nil "err" [trace] { return luaL_error( L, "tried to copy unsupported types: %s", lua_tostring( L, -n)); } @@ -1874,7 +1875,7 @@ LUAG_FUNC( configure) DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); lua_newtable( L); // settings universe mt lua_getfield( L, 1, "shutdown_timeout"); // settings universe mt shutdown_timeout - lua_pushcclosure( L, selfdestruct_gc, 1); // settings universe mt selfdestruct_gc + lua_pushcclosure( L, universe_gc, 1); // settings universe mt universe_gc lua_setfield( L, -2, "__gc"); // settings universe mt lua_setmetatable( L, -2); // settings universe lua_pop( L, 1); // settings @@ -2051,16 +2052,20 @@ static void EnableCrashingOnCrashes( void) const DWORD EXCEPTION_SWALLOWING = 0x1; HMODULE kernel32 = LoadLibraryA("kernel32.dll"); - tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32, "GetProcessUserModeExceptionPolicy"); - tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32, "SetProcessUserModeExceptionPolicy"); - if( pGetPolicy && pSetPolicy) + if (kernel32) { - DWORD dwFlags; - if( pGetPolicy( &dwFlags)) + tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32, "GetProcessUserModeExceptionPolicy"); + tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32, "SetProcessUserModeExceptionPolicy"); + if( pGetPolicy && pSetPolicy) { - // Turn off the filter - pSetPolicy( dwFlags & ~EXCEPTION_SWALLOWING); + DWORD dwFlags; + if( pGetPolicy( &dwFlags)) + { + // Turn off the filter + pSetPolicy( dwFlags & ~EXCEPTION_SWALLOWING); + } } + FreeLibrary(kernel32); } //typedef void (* SignalHandlerPointer)( int); /*SignalHandlerPointer previousHandler =*/ signal( SIGABRT, signal_handler); @@ -2072,7 +2077,7 @@ static void EnableCrashingOnCrashes( void) while( !s_ecoc_go_ahead) { Sleep(1); } // changes threads } } -#endif // PLATFORM_WIN32 +#endif // PLATFORM_WIN32 && !defined NDEBUG int LANES_API luaopen_lanes_core( lua_State* L) { diff --git a/src/lanes.h b/src/lanes.h index 7e1a2e5..62b9ea9 100644 --- a/src/lanes.h +++ b/src/lanes.h @@ -11,8 +11,8 @@ #endif // (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) #define LANES_VERSION_MAJOR 3 -#define LANES_VERSION_MINOR 16 -#define LANES_VERSION_PATCH 3 +#define LANES_VERSION_MINOR 17 +#define LANES_VERSION_PATCH 0 #define LANES_MIN_VERSION_REQUIRED(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR>MAJOR) || (LANES_VERSION_MAJOR==MAJOR && (LANES_VERSION_MINOR>MINOR || (LANES_VERSION_MINOR==MINOR && LANES_VERSION_PATCH>=PATCH)))) #define LANES_VERSION_LESS_THAN(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR 0 return type( val_) == "number" and val_ > 0 end, + keepers_gc_threshold = function( val_) + -- keepers_gc_threshold should be a number + return type( val_) == "number" + end, with_timers = boolean_param_checker, allocator = function( val_) -- can be nil, "protected", or a function @@ -363,261 +368,263 @@ lanes.configure = function( settings_) if settings.with_timers ~= false then - -- - -- On first 'require "lanes"', a timer lane is spawned that will maintain - -- timer tables and sleep in between the timer events. All interaction with - -- the timer lane happens via a 'timer_gateway' Linda, which is common to - -- all that 'require "lanes"'. - -- - -- Linda protocol to timer lane: - -- - -- TGW_KEY: linda_h, key, [wakeup_at_secs], [repeat_secs] - -- - local TGW_KEY= "(timer control)" -- the key does not matter, a 'weird' key may help debugging - local TGW_QUERY, TGW_REPLY = "(timer query)", "(timer reply)" - local first_time_key= "first time" - - local first_time = timer_gateway:get( first_time_key) == nil - timer_gateway:set( first_time_key, true) + -- + -- On first 'require "lanes"', a timer lane is spawned that will maintain + -- timer tables and sleep in between the timer events. All interaction with + -- the timer lane happens via a 'timer_gateway' Linda, which is common to + -- all that 'require "lanes"'. + -- + -- Linda protocol to timer lane: + -- + -- TGW_KEY: linda_h, key, [wakeup_at_secs], [repeat_secs] + -- + local TGW_KEY= "(timer control)" -- the key does not matter, a 'weird' key may help debugging + local TGW_QUERY, TGW_REPLY = "(timer query)", "(timer reply)" + local first_time_key= "first time" - -- - -- Timer lane; initialize only on the first 'require "lanes"' instance (which naturally - -- has 'table' always declared) - -- - if first_time then + local first_time = timer_gateway:get( first_time_key) == nil + timer_gateway:set( first_time_key, true) local now_secs = core.now_secs - assert( type( now_secs) == "function") - ----- - -- Snore loop (run as a lane on the background) - -- - -- High priority, to get trustworthy timings. + local wakeup_conv = core.wakeup_conv + -- - -- We let the timer lane be a "free running" thread; no handle to it - -- remains. + -- Timer lane; initialize only on the first 'require "lanes"' instance (which naturally + -- has 'table' always declared) -- - local timer_body = function() - set_debug_threadname( "LanesTimer") - -- - -- { [deep_linda_lightuserdata]= { [deep_linda_lightuserdata]=linda_h, - -- [key]= { wakeup_secs [,period_secs] } [, ...] }, - -- } - -- - -- Collection of all running timers, indexed with linda's & key. + if first_time then + + assert( type( now_secs) == "function") + ----- + -- Snore loop (run as a lane on the background) -- - -- Note that we need to use the deep lightuserdata identifiers, instead - -- of 'linda_h' themselves as table indices. Otherwise, we'd get multiple - -- entries for the same timer. + -- High priority, to get trustworthy timings. -- - -- The 'hidden' reference to Linda proxy is used in 'check_timers()' but - -- also important to keep the Linda alive, even if all outside world threw - -- away pointers to it (which would ruin uniqueness of the deep pointer). - -- Now we're safe. + -- We let the timer lane be a "free running" thread; no handle to it + -- remains. -- - local collection = {} - local table_insert = assert( table.insert) - - local get_timers = function() - local r = {} - for deep, t in pairs( collection) do - -- WR( tostring( deep)) - local l = t[deep] - for key, timer_data in pairs( t) do - if key ~= deep then - table_insert( r, {l, key, timer_data}) + local timer_body = function() + set_debug_threadname( "LanesTimer") + -- + -- { [deep_linda_lightuserdata]= { [deep_linda_lightuserdata]=linda_h, + -- [key]= { wakeup_secs [,period_secs] } [, ...] }, + -- } + -- + -- Collection of all running timers, indexed with linda's & key. + -- + -- Note that we need to use the deep lightuserdata identifiers, instead + -- of 'linda_h' themselves as table indices. Otherwise, we'd get multiple + -- entries for the same timer. + -- + -- The 'hidden' reference to Linda proxy is used in 'check_timers()' but + -- also important to keep the Linda alive, even if all outside world threw + -- away pointers to it (which would ruin uniqueness of the deep pointer). + -- Now we're safe. + -- + local collection = {} + local table_insert = assert( table.insert) + + local get_timers = function() + local r = {} + for deep, t in pairs( collection) do + -- WR( tostring( deep)) + local l = t[deep] + for key, timer_data in pairs( t) do + if key ~= deep then + table_insert( r, {l, key, timer_data}) + end end end - end - return r - end -- get_timers() - - -- - -- set_timer( linda_h, key [,wakeup_at_secs [,period_secs]] ) - -- - local set_timer = function( linda, key, wakeup_at, period) - assert( wakeup_at == nil or wakeup_at > 0.0) - assert( period == nil or period > 0.0) - - local linda_deep = linda:deep() - assert( linda_deep) + return r + end -- get_timers() - -- Find or make a lookup for this timer -- - local t1 = collection[linda_deep] - if not t1 then - t1 = { [linda_deep] = linda} -- proxy to use the Linda - collection[linda_deep] = t1 - end + -- set_timer( linda_h, key [,wakeup_at_secs [,period_secs]] ) + -- + local set_timer = function( linda, key, wakeup_at, period) + assert( wakeup_at == nil or wakeup_at > 0.0) + assert( period == nil or period > 0.0) - if wakeup_at == nil then - -- Clear the timer - -- - t1[key]= nil + local linda_deep = linda:deep() + assert( linda_deep) - -- Remove empty tables from collection; speeds timer checks and - -- lets our 'safety reference' proxy be gc:ed as well. + -- Find or make a lookup for this timer -- - local empty = true - for k, _ in pairs( t1) do - if k ~= linda_deep then - empty = false - break - end - end - if empty then - collection[linda_deep] = nil + local t1 = collection[linda_deep] + if not t1 then + t1 = { [linda_deep] = linda} -- proxy to use the Linda + collection[linda_deep] = t1 end - -- Note: any unread timer value is left at 'linda[key]' intensionally; - -- clearing a timer just stops it. - else - -- New timer or changing the timings - -- - local t2 = t1[key] - if not t2 then - t2= {} - t1[key]= t2 - end + if wakeup_at == nil then + -- Clear the timer + -- + t1[key]= nil - t2[1] = wakeup_at - t2[2] = period -- can be 'nil' - end - end -- set_timer() + -- Remove empty tables from collection; speeds timer checks and + -- lets our 'safety reference' proxy be gc:ed as well. + -- + local empty = true + for k, _ in pairs( t1) do + if k ~= linda_deep then + empty = false + break + end + end + if empty then + collection[linda_deep] = nil + end - ----- - -- [next_wakeup_at]= check_timers() - -- Check timers, and wake up the ones expired (if any) - -- Returns the closest upcoming (remaining) wakeup time (or 'nil' if none). - local check_timers = function() - local now = now_secs() - local next_wakeup - - for linda_deep,t1 in pairs(collection) do - for key,t2 in pairs(t1) do + -- Note: any unread timer value is left at 'linda[key]' intensionally; + -- clearing a timer just stops it. + else + -- New timer or changing the timings -- - if key==linda_deep then - -- no 'continue' in Lua :/ - else - -- 't2': { wakeup_at_secs [,period_secs] } + local t2 = t1[key] + if not t2 then + t2= {} + t1[key]= t2 + end + + t2[1] = wakeup_at + t2[2] = period -- can be 'nil' + end + end -- set_timer() + + ----- + -- [next_wakeup_at]= check_timers() + -- Check timers, and wake up the ones expired (if any) + -- Returns the closest upcoming (remaining) wakeup time (or 'nil' if none). + local check_timers = function() + local now = now_secs() + local next_wakeup + + for linda_deep,t1 in pairs(collection) do + for key,t2 in pairs(t1) do -- - local wakeup_at= t2[1] - local period= t2[2] -- may be 'nil' - - if wakeup_at <= now then - local linda= t1[linda_deep] - assert(linda) - - linda:set( key, now ) - - -- 'pairs()' allows the values to be modified (and even - -- removed) as far as keys are not touched - - if not period then - -- one-time timer; gone - -- - t1[key]= nil - wakeup_at= nil -- no 'continue' in Lua :/ - else - -- repeating timer; find next wakeup (may jump multiple repeats) - -- - repeat - wakeup_at= wakeup_at+period - until wakeup_at > now - - t2[1]= wakeup_at + if key==linda_deep then + -- no 'continue' in Lua :/ + else + -- 't2': { wakeup_at_secs [,period_secs] } + -- + local wakeup_at= t2[1] + local period= t2[2] -- may be 'nil' + + if wakeup_at <= now then + local linda= t1[linda_deep] + assert(linda) + + linda:set( key, now ) + + -- 'pairs()' allows the values to be modified (and even + -- removed) as far as keys are not touched + + if not period then + -- one-time timer; gone + -- + t1[key]= nil + wakeup_at= nil -- no 'continue' in Lua :/ + else + -- repeating timer; find next wakeup (may jump multiple repeats) + -- + repeat + wakeup_at= wakeup_at+period + until wakeup_at > now + + t2[1]= wakeup_at + end end - end - if wakeup_at and ((not next_wakeup) or (wakeup_at < next_wakeup)) then - next_wakeup= wakeup_at + if wakeup_at and ((not next_wakeup) or (wakeup_at < next_wakeup)) then + next_wakeup= wakeup_at + end end + end -- t2 loop + end -- t1 loop + + return next_wakeup -- may be 'nil' + end -- check_timers() + + local timer_gateway_batched = timer_gateway.batched + set_finalizer( function( err, stk) + if err and type( err) ~= "userdata" then + WR( "LanesTimer error: "..tostring(err)) + --elseif type( err) == "userdata" then + -- WR( "LanesTimer after cancel" ) + --else + -- WR("LanesTimer finalized") + end + end) + while true do + local next_wakeup = check_timers() + + -- Sleep until next timer to wake up, or a set/clear command + -- + local secs + if next_wakeup then + secs = next_wakeup - now_secs() + if secs < 0 then secs = 0 end + end + local key, what = timer_gateway:receive( secs, TGW_KEY, TGW_QUERY) + + if key == TGW_KEY then + assert( getmetatable( what) == "Linda") -- 'what' should be a linda on which the client sets a timer + local _, key, wakeup_at, period = timer_gateway:receive( 0, timer_gateway_batched, TGW_KEY, 3) + assert( key) + set_timer( what, key, wakeup_at, period and period > 0 and period or nil) + elseif key == TGW_QUERY then + if what == "get_timers" then + timer_gateway:send( TGW_REPLY, get_timers()) + else + timer_gateway:send( TGW_REPLY, "unknown query " .. what) end - end -- t2 loop - end -- t1 loop - - return next_wakeup -- may be 'nil' - end -- check_timers() - - local timer_gateway_batched = timer_gateway.batched - set_finalizer( function( err, stk) - if err and type( err) ~= "userdata" then - WR( "LanesTimer error: "..tostring(err)) - --elseif type( err) == "userdata" then - -- WR( "LanesTimer after cancel" ) - --else - -- WR("LanesTimer finalized") + --elseif secs == nil then -- got no value while block-waiting? + -- WR( "timer lane: no linda, aborted?") + end end - end) - while true do - local next_wakeup = check_timers() + end -- timer_body() + timer_lane = gen( "*", { package= {}, priority = max_prio}, timer_body)() -- "*" instead of "io,package" for LuaJIT compatibility... + end -- first_time - -- Sleep until next timer to wake up, or a set/clear command + ----- + -- = timer( linda_h, key_val, date_tbl|first_secs [,period_secs] ) + -- + -- PUBLIC LANES API + timer = function( linda, key, a, period ) + if getmetatable( linda) ~= "Linda" then + error "expecting a Linda" + end + if a == 0.0 then + -- Caller expects to get current time stamp in Linda, on return + -- (like the timer had expired instantly); it would be good to set this + -- as late as possible (to give most current time) but also we want it + -- to precede any possible timers that might start striking. -- - local secs - if next_wakeup then - secs = next_wakeup - now_secs() - if secs < 0 then secs = 0 end - end - local key, what = timer_gateway:receive( secs, TGW_KEY, TGW_QUERY) - - if key == TGW_KEY then - assert( getmetatable( what) == "Linda") -- 'what' should be a linda on which the client sets a timer - local _, key, wakeup_at, period = timer_gateway:receive( 0, timer_gateway_batched, TGW_KEY, 3) - assert( key) - set_timer( what, key, wakeup_at, period and period > 0 and period or nil) - elseif key == TGW_QUERY then - if what == "get_timers" then - timer_gateway:send( TGW_REPLY, get_timers()) - else - timer_gateway:send( TGW_REPLY, "unknown query " .. what) - end - --elseif secs == nil then -- got no value while block-waiting? - -- WR( "timer lane: no linda, aborted?") + linda:set( key, now_secs()) + + if not period or period==0.0 then + timer_gateway:send( TGW_KEY, linda, key, nil, nil ) -- clear the timer + return -- nothing more to do end + a= period end - end -- timer_body() - timer_lane = gen( "*", { package= {}, priority = max_prio}, timer_body)() -- "*" instead of "io,package" for LuaJIT compatibility... - end -- first_time - ----- - -- = timer( linda_h, key_val, date_tbl|first_secs [,period_secs] ) - -- - -- PUBLIC LANES API - timer = function( linda, key, a, period ) - if getmetatable( linda) ~= "Linda" then - error "expecting a Linda" - end - if a == 0.0 then - -- Caller expects to get current time stamp in Linda, on return - -- (like the timer had expired instantly); it would be good to set this - -- as late as possible (to give most current time) but also we want it - -- to precede any possible timers that might start striking. + local wakeup_at= type(a)=="table" and wakeup_conv(a) -- given point of time + or (a and now_secs()+a or nil) + -- queue to timer -- - linda:set( key, core.now_secs()) + timer_gateway:send( TGW_KEY, linda, key, wakeup_at, period ) + end -- timer() - if not period or period==0.0 then - timer_gateway:send( TGW_KEY, linda, key, nil, nil ) -- clear the timer - return -- nothing more to do - end - a= period - end - - local wakeup_at= type(a)=="table" and core.wakeup_conv(a) -- given point of time - or (a and core.now_secs()+a or nil) - -- queue to timer + ----- + -- {[{linda, slot, when, period}[,...]]} = timers() -- - timer_gateway:send( TGW_KEY, linda, key, wakeup_at, period ) - end - - ----- - -- {[{linda, slot, when, period}[,...]]} = timers() - -- - -- PUBLIC LANES API - timers = function() - timer_gateway:send( TGW_QUERY, "get_timers") - local _, r = timer_gateway:receive( TGW_REPLY) - return r - end + -- PUBLIC LANES API + timers = function() + timer_gateway:send( TGW_QUERY, "get_timers") + local _, r = timer_gateway:receive( TGW_REPLY) + return r + end -- timers() end -- settings.with_timers diff --git a/src/lanes_private.h b/src/lanes_private.h index 6717fe0..8143216 100644 --- a/src/lanes_private.h +++ b/src/lanes_private.h @@ -72,18 +72,24 @@ struct s_Lane }; typedef struct s_Lane Lane; +// xxh64 of string "LANE_POINTER_REGKEY" generated at https://www.pelock.com/products/hash-calculator +static DECLARE_CONST_UNIQUE_KEY( LANE_POINTER_REGKEY, 0xB3022205633743BC); // used as registry key + // To allow free-running threads (longer lifespan than the handle's) // 'Lane' are malloc/free'd and the handle only carries a pointer. // This is not deep userdata since the handle's not portable among lanes. // -#define lua_toLane( L, i) (*((Lane**) luaL_checkudata( L, i, "Lane"))) +inline Lane* lua_toLane(lua_State* L, int i_) +{ + return *(Lane**)(luaL_checkudata(L, i_, "Lane")); +} static inline Lane* get_lane_from_registry( lua_State* L) { Lane* s; STACK_GROW( L, 1); STACK_CHECK( L, 0); - REGISTRY_GET( L, CANCEL_TEST_KEY); + REGISTRY_GET( L, LANE_POINTER_REGKEY); s = lua_touserdata( L, -1); // lightuserdata (true 's_lane' pointer) / nil lua_pop( L, 1); STACK_END( L, 0); diff --git a/src/linda.c b/src/linda.c index 8b59790..2128520 100644 --- a/src/linda.c +++ b/src/linda.c @@ -52,11 +52,11 @@ struct s_Linda SIGNAL_T read_happened; SIGNAL_T write_happened; Universe* U; // the universe this linda belongs to - ptrdiff_t group; // a group to control keeper allocation between lindas + uintptr_t group; // a group to control keeper allocation between lindas enum e_cancel_request simulate_cancel; char name[1]; }; -#define LINDA_KEEPER_HASHSEED( linda) (linda->group ? linda->group : (ptrdiff_t)linda) +#define LINDA_KEEPER_HASHSEED( linda) (linda->group ? linda->group : (uintptr_t)linda) static void* linda_id( lua_State*, DeepOp); @@ -125,7 +125,7 @@ LUAG_FUNC( linda_send) enum e_cancel_request cancel = CANCEL_NONE; int pushed; time_d timeout = -1.0; - uint_t key_i = 2; // index of first key, if timeout not there + int key_i = 2; // index of first key, if timeout not there bool_t as_nil_sentinel; // if not NULL, send() will silently send a single nil if nothing is provided if( lua_type( L, 2) == LUA_TNUMBER) // we don't want to use lua_isnumber() because of autocoercion @@ -151,7 +151,7 @@ LUAG_FUNC( linda_send) STACK_GROW( L, 1); // make sure there is something to send - if( (uint_t)lua_gettop( L) == key_i) + if( lua_gettop( L) == key_i) { if( as_nil_sentinel) { @@ -270,16 +270,17 @@ LUAG_FUNC( linda_send) * returns the actual consumed values, or nil if there weren't enough values to consume * */ -#define BATCH_SENTINEL "270e6c9d-280f-4983-8fee-a7ecdda01475" + // xxh64 of string "BATCH_SENTINEL" generated at https://www.pelock.com/products/hash-calculator +DECLARE_CONST_UNIQUE_KEY(BATCH_SENTINEL, 0x2DDFEE0968C62AA7); LUAG_FUNC( linda_receive) { struct s_Linda* linda = lua_toLinda( L, 1); int pushed, expected_pushed_min, expected_pushed_max; enum e_cancel_request cancel = CANCEL_NONE; - keeper_api_t keeper_receive; + keeper_api_t selected_keeper_receive; time_d timeout = -1.0; - uint_t key_i = 2; + int key_i = 2; if( lua_type( L, 2) == LUA_TNUMBER) // we don't want to use lua_isnumber() because of autocoercion { @@ -294,7 +295,7 @@ LUAG_FUNC( linda_receive) // are we in batched mode? { int is_batched; - lua_pushliteral( L, BATCH_SENTINEL); + push_unique_key( L, BATCH_SENTINEL); is_batched = lua501_equal( L, key_i, -1); lua_pop( L, 1); if( is_batched) @@ -304,7 +305,7 @@ LUAG_FUNC( linda_receive) // make sure the keys are of a valid type check_key_types( L, key_i, key_i); // receive multiple values from a single slot - keeper_receive = KEEPER_API( receive_batched); + selected_keeper_receive = KEEPER_API( receive_batched); // we expect a user-defined amount of return value expected_pushed_min = (int)luaL_checkinteger( L, key_i + 1); expected_pushed_max = (int)luaL_optinteger( L, key_i + 2, expected_pushed_min); @@ -321,7 +322,7 @@ LUAG_FUNC( linda_receive) // make sure the keys are of a valid type check_key_types( L, key_i, lua_gettop( L)); // receive a single value, checking multiple slots - keeper_receive = KEEPER_API( receive); + selected_keeper_receive = KEEPER_API( receive); // we expect a single (value, key) pair of returned values expected_pushed_min = expected_pushed_max = 2; } @@ -347,7 +348,7 @@ LUAG_FUNC( linda_receive) } // all arguments of receive() but the first are passed to the keeper's receive function - pushed = keeper_call( linda->U, K->L, keeper_receive, L, linda, key_i); + pushed = keeper_call( linda->U, K->L, selected_keeper_receive, L, linda, key_i); if( pushed < 0) { break; @@ -511,29 +512,27 @@ LUAG_FUNC( linda_get) // make sure the key is of a valid type (throws an error if not the case) check_key_types( L, 2, 2); - { - Keeper* K = which_keeper( linda->U->keepers, LINDA_KEEPER_HASHSEED( linda)); - if( linda->simulate_cancel == CANCEL_NONE) - { - pushed = keeper_call( linda->U, K->L, KEEPER_API( get), L, linda, 2); - if( pushed > 0) - { - keeper_toggle_nil_sentinels( L, lua_gettop( L) - pushed, eLM_FromKeeper); - } - } - else // linda is cancelled - { - // do nothing and return lanes.cancel_error - push_unique_key( L, CANCEL_ERROR); - pushed = 1; - } - // an error can be raised if we attempt to read an unregistered function - if( pushed < 0) + if( linda->simulate_cancel == CANCEL_NONE) + { + Keeper* const K = which_keeper(linda->U->keepers, LINDA_KEEPER_HASHSEED(linda)); + pushed = keeper_call( linda->U, K->L, KEEPER_API( get), L, linda, 2); + if( pushed > 0) { - return luaL_error( L, "tried to copy unsupported types"); + keeper_toggle_nil_sentinels( L, lua_gettop( L) - pushed, eLM_FromKeeper); } } + else // linda is cancelled + { + // do nothing and return lanes.cancel_error + push_unique_key( L, CANCEL_ERROR); + pushed = 1; + } + // an error can be raised if we attempt to read an unregistered function + if( pushed < 0) + { + return luaL_error( L, "tried to copy unsupported types"); + } return pushed; } @@ -557,26 +556,23 @@ LUAG_FUNC( linda_limit) // make sure the key is of a valid type check_key_types( L, 2, 2); + if( linda->simulate_cancel == CANCEL_NONE) { - Keeper* K = which_keeper( linda->U->keepers, LINDA_KEEPER_HASHSEED( linda)); - - if( linda->simulate_cancel == CANCEL_NONE) - { - pushed = keeper_call( linda->U, K->L, KEEPER_API( limit), L, linda, 2); - ASSERT_L( pushed == 0 || pushed == 1); // no error, optional boolean value saying if we should wake blocked writer threads - if( pushed == 1) - { - ASSERT_L( lua_type( L, -1) == LUA_TBOOLEAN && lua_toboolean( L, -1) == 1); - SIGNAL_ALL( &linda->read_happened); // To be done from within the 'K' locking area - } - } - else // linda is cancelled + Keeper* const K = which_keeper(linda->U->keepers, LINDA_KEEPER_HASHSEED(linda)); + pushed = keeper_call( linda->U, K->L, KEEPER_API( limit), L, linda, 2); + ASSERT_L( pushed == 0 || pushed == 1); // no error, optional boolean value saying if we should wake blocked writer threads + if( pushed == 1) { - // do nothing and return lanes.cancel_error - push_unique_key( L, CANCEL_ERROR); - pushed = 1; + ASSERT_L( lua_type( L, -1) == LUA_TBOOLEAN && lua_toboolean( L, -1) == 1); + SIGNAL_ALL( &linda->read_happened); // To be done from within the 'K' locking area } } + else // linda is cancelled + { + // do nothing and return lanes.cancel_error + push_unique_key( L, CANCEL_ERROR); + pushed = 1; + } // propagate pushed boolean if any return pushed; } @@ -762,6 +758,7 @@ static void* linda_id( lua_State* L, DeepOp op_) { case eDO_new: { + Universe* const U = universe_get(L); struct s_Linda* s; size_t name_len = 0; char const* linda_name = NULL; @@ -795,7 +792,6 @@ static void* linda_id( lua_State* L, DeepOp op_) * just don't use L's allocF because we don't know which state will get the honor of GCing the linda */ { - Universe* const U = universe_get(L); AllocatorDefinition* const allocD = &U->internal_allocator; s = (struct s_Linda*) allocD->allocF(allocD->allocUD, NULL, 0, sizeof(struct s_Linda) + name_len); // terminating 0 is already included } @@ -804,7 +800,7 @@ static void* linda_id( lua_State* L, DeepOp op_) s->prelude.magic.value = DEEP_VERSION.value; SIGNAL_INIT( &s->read_happened); SIGNAL_INIT( &s->write_happened); - s->U = universe_get( L); + s->U = U; s->simulate_cancel = CANCEL_NONE; s->group = linda_group << KEEPER_MAGIC_SHIFT; s->name[0] = 0; @@ -815,25 +811,32 @@ static void* linda_id( lua_State* L, DeepOp op_) case eDO_delete: { - Keeper* K; + Keeper* myK; struct s_Linda* linda = lua_touserdata( L, 1); ASSERT_L( linda); // Clean associated structures in the keeper state. - K = keeper_acquire( linda->U->keepers, LINDA_KEEPER_HASHSEED( linda)); - if( K && K->L) // can be NULL if this happens during main state shutdown (lanes is GC'ed -> no keepers -> no need to cleanup) + myK = which_keeper( linda->U->keepers, LINDA_KEEPER_HASHSEED( linda)); + if (myK) { + // if collected from my own keeper, we can't acquire/release it + // because we are already inside a protected area, and trying to do so would deadlock! + bool_t const need_acquire_release = (myK->L != L); + // Clean associated structures in the keeper state. + Keeper* const K = need_acquire_release ? keeper_acquire(linda->U->keepers, LINDA_KEEPER_HASHSEED(linda)) : myK; // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex... - keeper_call( linda->U, K->L, KEEPER_API( clear), L, linda, 0); + keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0); + if(need_acquire_release) + { + keeper_release(K); + } } - keeper_release( K); // There aren't any lanes waiting on these lindas, since all proxies have been gc'ed. Right? SIGNAL_FREE( &linda->read_happened); SIGNAL_FREE( &linda->write_happened); { - Universe* const U = universe_get(L); - AllocatorDefinition* const allocD = &U->internal_allocator; + AllocatorDefinition* const allocD = &linda->U->internal_allocator; (void) allocD->allocF(allocD->allocUD, linda, sizeof(struct s_Linda) + strlen(linda->name), 0); } return NULL; @@ -901,7 +904,7 @@ static void* linda_id( lua_State* L, DeepOp op_) lua_setfield( L, -2, "dump"); // some constants - lua_pushliteral( L, BATCH_SENTINEL); + push_unique_key( L, BATCH_SENTINEL); lua_setfield( L, -2, "batched"); push_unique_key( L, NIL_SENTINEL); diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h index 05a46b5..e184476 100644 --- a/src/macros_and_utils.h +++ b/src/macros_and_utils.h @@ -6,6 +6,7 @@ #include "lua.h" #include "lualib.h" +#include "lauxlib.h" // M$ compiler doesn't support 'inline' keyword in C files... #if defined( _MSC_VER) @@ -81,7 +82,11 @@ extern char const* debugspew_indent; #define ASSERT_L(c) _ASSERT_L(L,c) -#define STACK_GROW( L, n) do { if (!lua_checkstack(L,(int)(n))) luaL_error( L, "Cannot grow stack!" ); } while( 0) +inline void STACK_GROW(lua_State * L, int n_) +{ + if (!lua_checkstack(L, n_)) + luaL_error(L, "Cannot grow stack!"); +} // non-string keyed registry access #define REGISTRY_SET( L, key_, value_) \ diff --git a/src/platform.h b/src/platform.h index da5264e..2f71c07 100644 --- a/src/platform.h +++ b/src/platform.h @@ -7,6 +7,7 @@ #define PLATFORM_XBOX #elif (defined _WIN32) #define PLATFORM_WIN32 + #define NOMINMAX #elif (defined __linux__) #define PLATFORM_LINUX #elif (defined __APPLE__) && (defined __MACH__) diff --git a/src/state.c b/src/state.c index 21ca397..32e5b47 100644 --- a/src/state.c +++ b/src/state.c @@ -205,7 +205,7 @@ static void copy_one_time_settings( Universe* U, lua_State* L, lua_State* L2) REGISTRY_GET( L, CONFIG_REGKEY); // config // copy settings from from source to destination registry - if( luaG_inter_move( U, L, L2, 1, eLM_LaneBody) < 0) // // config + if( luaG_inter_move( U, L, L2, 1, eLM_LaneBody) != eICR_Success) // // config { (void) luaL_error( L, "failed to copy settings when loading lanes.core"); } diff --git a/src/tools.c b/src/tools.c index 80e0f71..c43d8a2 100644 --- a/src/tools.c +++ b/src/tools.c @@ -242,8 +242,14 @@ void initialize_allocator_function( Universe* U, lua_State* L) U->internal_allocator.allocF = libc_lua_Alloc; U->internal_allocator.allocUD = NULL; } + else if (U->provide_allocator == luaG_provide_protected_allocator) + { + // user wants mutex protection on the state's allocator. Use protection for our own allocations too, just in case. + U->internal_allocator.allocF = lua_getallocf(L, &U->internal_allocator.allocUD); + } else { + // no protection required, just use whatever we have as-is. U->internal_allocator = U->protected_allocator.definition; } } @@ -844,8 +850,8 @@ static bool_t lookup_table( lua_State* L2, lua_State* L, uint_t i, LookupMode mo */ static bool_t push_cached_table( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i) { - bool_t not_found_in_cache; // L2 - DECLARE_CONST_UNIQUE_KEY( p, lua_topointer( L, i)); + bool_t not_found_in_cache; // L2 + void const* p = lua_topointer( L, i); ASSERT_L( L2_cache_i != 0); STACK_GROW( L2, 3); @@ -854,17 +860,17 @@ static bool_t push_cached_table( lua_State* L2, uint_t L2_cache_i, lua_State* L, // We don't need to use the from state ('L') in ID since the life span // is only for the duration of a copy (both states are locked). // push a light userdata uniquely representing the table - push_unique_key( L2, p); // ... p + lua_pushlightuserdata( L2, (void*) p); // ... p //fprintf( stderr, "<< ID: %s >>\n", lua_tostring( L2, -1)); - lua_rawget( L2, L2_cache_i); // ... {cached|nil} + lua_rawget( L2, L2_cache_i); // ... {cached|nil} not_found_in_cache = lua_isnil( L2, -1); if( not_found_in_cache) { lua_pop( L2, 1); // ... lua_newtable( L2); // ... {} - push_unique_key( L2, p); // ... {} p + lua_pushlightuserdata( L2, (void*) p); // ... {} p lua_pushvalue( L2, -2); // ... {} p {} lua_rawset( L2, L2_cache_i); // ... {} } @@ -1446,7 +1452,7 @@ static void inter_copy_keyvaluepair( Universe* U, lua_State* L2, uint_t L2_cache uint_t key_i = val_i - 1; // Only basic key types are copied over; others ignored - if( inter_copy_one( U, L2, 0 /*key*/, L, key_i, VT_KEY, mode_, upName_)) + if( inter_copy_one( U, L2, L2_cache_i, L, key_i, VT_KEY, mode_, upName_)) { char* valPath = (char*) upName_; if( U->verboseErrors) @@ -1596,7 +1602,10 @@ static bool_t copyclone( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_Stat // assign uservalues while( uvi > 0) { - inter_copy_one( U, L2, L2_cache_i, L, lua_absindex( L, -1), VT_NORMAL, mode_, upName_); // ... u uv + if(!inter_copy_one( U, L2, L2_cache_i, L, lua_absindex( L, -1), VT_NORMAL, mode_, upName_)) // ... u uv + { + (void) luaL_error(L, "Cannot copy upvalue type '%s'", luaL_typename(L, -1)); + } lua_pop( L, 1); // ... mt __lanesclone [uv]* // this pops the value from the stack lua_setiuservalue( L2, -2, uvi); // ... u @@ -1730,7 +1739,10 @@ static bool_t inter_copy_function( Universe* U, lua_State* L2, uint_t L2_cache_i // transfer and assign uservalues while( uvi > 0) { - inter_copy_one( U, L2, L2_cache_i, L, lua_absindex( L, -1), vt, mode_, upName_); // ... mt u uv + if(!inter_copy_one( U, L2, L2_cache_i, L, lua_absindex( L, -1), vt, mode_, upName_)) // ... mt u uv + { + (void) luaL_error(L, "Cannot copy upvalue type '%s'", luaL_typename(L, -1)); + } lua_pop( L, 1); // ... u [uv]* // this pops the value from the stack lua_setiuservalue( L2, -2, uvi); // ... mt u @@ -1957,7 +1969,7 @@ bool_t inter_copy_one( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* * * Note: Parameters are in this order ('L' = from first) to be same as 'lua_xmove'. */ -int luaG_inter_copy( Universe* U, lua_State* L, lua_State* L2, uint_t n, LookupMode mode_) +InterCopyResult luaG_inter_copy( Universe* U, lua_State* L, lua_State* L2, uint_t n, LookupMode mode_) { uint_t top_L = lua_gettop( L); // ... {}n uint_t top_L2 = lua_gettop( L2); // ... @@ -1974,7 +1986,7 @@ int luaG_inter_copy( Universe* U, lua_State* L, lua_State* L2, uint_t n, LookupM // requesting to copy more than is available? DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "nothing to copy()\n" INDENT_END)); DEBUGSPEW_CODE( -- U->debugspew_indent_depth); - return -1; + return eICR_NotEnoughValues; } STACK_CHECK( L2, 0); @@ -2010,24 +2022,24 @@ int luaG_inter_copy( Universe* U, lua_State* L, lua_State* L2, uint_t n, LookupM // Remove the cache table. Persistent caching would cause i.e. multiple // messages passed in the same table to use the same table also in receiving end. lua_remove( L2, top_L2 + 1); - return 0; + return eICR_Success; } // error -> pop everything from the target state stack lua_settop( L2, top_L2); STACK_END( L2, 0); - return -2; + return eICR_Error; } -int luaG_inter_move( Universe* U, lua_State* L, lua_State* L2, uint_t n, LookupMode mode_) +InterCopyResult luaG_inter_move( Universe* U, lua_State* L, lua_State* L2, uint_t n, LookupMode mode_) { - int ret = luaG_inter_copy( U, L, L2, n, mode_); + InterCopyResult ret = luaG_inter_copy( U, L, L2, n, mode_); lua_pop( L, (int) n); return ret; } -int luaG_inter_copy_package( Universe* U, lua_State* L, lua_State* L2, int package_idx_, LookupMode mode_) +InterCopyResult luaG_inter_copy_package( Universe* U, lua_State* L, lua_State* L2, int package_idx_, LookupMode mode_) { DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "luaG_inter_copy_package()\n" INDENT_END)); DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); @@ -2040,7 +2052,11 @@ int luaG_inter_copy_package( Universe* U, lua_State* L, lua_State* L2, int packa lua_pushfstring( L, "expected package as table, got %s", luaL_typename( L, package_idx_)); STACK_MID( L, 1); // raise the error when copying from lane to lane, else just leave it on the stack to be raised later - return ( mode_ == eLM_LaneBody) ? lua_error( L) : 1; + if (mode_ == eLM_LaneBody) + { + lua_error(L); // doesn't return + } + return eICR_Error; } lua_getglobal( L2, "package"); if( !lua_isnil( L2, -1)) // package library not loaded: do nothing @@ -2076,5 +2092,5 @@ int luaG_inter_copy_package( Universe* U, lua_State* L, lua_State* L2, int packa STACK_END( L2, 0); STACK_END( L, 0); DEBUGSPEW_CODE( -- U->debugspew_indent_depth); - return 0; + return eICR_Success; } diff --git a/src/tools.h b/src/tools.h index a0893e4..6c08734 100644 --- a/src/tools.h +++ b/src/tools.h @@ -31,14 +31,22 @@ enum e_vt VT_KEY, VT_METATABLE }; + +enum eInterCopyResult +{ + eICR_Success, + eICR_NotEnoughValues, + eICR_Error +}; +typedef enum eInterCopyResult InterCopyResult; + bool_t inter_copy_one( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt vt, LookupMode mode_, char const* upName_); // ################################################################################################ -int luaG_inter_copy_package( Universe* U, lua_State* L, lua_State* L2, int package_idx_, LookupMode mode_); - -int luaG_inter_copy( Universe* U, lua_State* L, lua_State* L2, uint_t n, LookupMode mode_); -int luaG_inter_move( Universe* U, lua_State* L, lua_State* L2, uint_t n, LookupMode mode_); +InterCopyResult luaG_inter_copy_package( Universe* U, lua_State* L, lua_State* L2, int package_idx_, LookupMode mode_); +InterCopyResult luaG_inter_copy( Universe* U, lua_State* L, lua_State* L2, uint_t n, LookupMode mode_); +InterCopyResult luaG_inter_move( Universe* U, lua_State* L, lua_State* L2, uint_t n, LookupMode mode_); int luaG_nameof( lua_State* L); diff --git a/src/uniquekey.h b/src/uniquekey.h index 015fbf2..7162753 100644 --- a/src/uniquekey.h +++ b/src/uniquekey.h @@ -11,9 +11,9 @@ struct s_UniqueKey typedef struct s_UniqueKey UniqueKey; #if LUAJIT_FLAVOR() == 64 // building against LuaJIT headers for 64 bits, light userdata is restricted to 47 significant bits, because LuaJIT uses the other bits for internal optimizations -#define MAKE_UNIQUE_KEY( p_) ((void*)((ptrdiff_t)(p_) & 0x7fffffffffffull)) +#define MAKE_UNIQUE_KEY( p_) ((void*)((uintptr_t)(p_) & 0x7fffffffffffull)) #else // LUAJIT_FLAVOR() -#define MAKE_UNIQUE_KEY( p_) ((void*)(ptrdiff_t)(p_)) +#define MAKE_UNIQUE_KEY( p_) ((void*)(uintptr_t)(p_)) #endif // LUAJIT_FLAVOR() #define DECLARE_UNIQUE_KEY( name_) UniqueKey name_ -- cgit v1.2.3-55-g6feb