From 44540b9335f3bbd2f6fda3e13329b28ec76b6d7a Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Fri, 11 Oct 2013 20:08:32 +0200 Subject: version 3.7.0 * fix lanes.threads() not being available in a lane where lanes.configure() settings didn't contain track_lanes although the initial configure() call did. * require "lanes".configure() sequence is only necessary at the first require "lanes". * fix a crash at application shutdown where in some situations we could deinitialize the protected allocator mutex while a lane was still using it. * fix timers broken by change 69 --- CHANGES | 15 ++- docs/index.html | 14 +-- src/keeper.c | 46 ++++++--- src/keeper.h | 2 +- src/lanes.c | 251 ++++++++++++++++++++++++++-------------------- src/lanes.lua | 72 ++++++------- src/threading.c | 5 +- src/threading.h | 2 +- src/tools.c | 96 ++++++++++++------ src/tools.h | 12 ++- tests/fibonacci.lua | 5 +- tests/irayo_recursive.lua | 6 +- tests/recursive.lua | 6 +- 13 files changed, 319 insertions(+), 213 deletions(-) diff --git a/CHANGES b/CHANGES index 5a42777..8a4e9e2 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,19 @@ CHANGES: -CHANGE 72: BGe 3-Ost-13 +CHANGE 76: BGe 10-Oct-13 + * version 3.7.0 + * fix lanes.threads() not being available in a lane where lanes.configure() settings didn't contain track_lanes although the initial configure() call did. + +CHANGE 75: BGe 7-Oct-13 + * require "lanes".configure() sequence is only necessary at the first require "lanes". + +CHANGE 74: BGe 7-Oct-13 + * fix a crash at application shutdown where in some situations we could deinitialize the protected allocator mutex while a lane was still using it. + +CHANGE 73: BGe 4-Oct-13 + * fix timers broken by change 69 + +CHANGE 72: BGe 3-Oct-13 * bugfix: no longer create a global named "lanes.core" inside lanes having "*" as library list CHANGE 71: BGe 30-Sept-13 diff --git a/docs/index.html b/docs/index.html index eda1758..20a5c3f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -70,7 +70,7 @@

- This document was revised on 27-Sept-13, and applies to version 3.6.6. + This document was revised on 11-Oct-13, and applies to version 3.7.0.

@@ -248,7 +248,10 @@ After lanes is required, it is necessary to call lanes.configure(), which is the only function exposed by the module at this point. Calling configure() will perform one-time initializations and make the rest of the API available.

- At the same time, configure() itself will be replaced by another function that raises an error if called again with differing arguments. + At the same time, configure() itself will be replaced by another function that raises an error if called again with differing arguments, if any. +

+

+ IMPORTANT NOTE: Starting with version 3.7.0, only the first occurence of require "lanes" must be followed by a call to .configure(). From this point, a simple require "lanes" will do wherever you need to require lanes again.

@@ -326,7 +329,8 @@ nil/false/anything - Any non-nil|false value instructs Lanes keeps track of all lanes, so that lanes.threads() can list them. + Any non-nil|false value instructs Lanes keeps track of all lanes, so that lanes.threads() can list them. If false, lanes.threads() will raise an error when called. + Default is false. @@ -364,9 +368,7 @@

- NEW (version 3.5.0) -
- Once Lanes is configured, one should register with Lanes the modules exporting functions that will be transferred either during lane generation or through lindas. + (Since v3.5.0) Once Lanes is configured, one should register with Lanes the modules exporting functions that will be transferred either during lane generation or through lindas.
Use lanes.require() for this purpose. This will call the original require(), then add the result to the lookup databases.

diff --git a/src/keeper.c b/src/keeper.c index bd232cd..c9b2039 100644 --- a/src/keeper.c +++ b/src/keeper.c @@ -70,7 +70,7 @@ typedef struct static keeper_fifo* prepare_fifo_access( lua_State* L, int idx) { keeper_fifo* fifo = (keeper_fifo*) lua_touserdata( L, idx); - if( fifo) + if( fifo != NULL) { idx = lua_absindex( L, idx); STACK_GROW( L, 1); @@ -286,15 +286,15 @@ int keepercall_receive( lua_State* L) { int top = lua_gettop( L); int i; - keeper_fifo* fifo; push_table( L, 1); // ud keys fifos lua_replace( L, 1); // fifos keys for( i = 2; i <= top; ++ i) { + keeper_fifo* fifo; lua_pushvalue( L, i); // fifos keys key[i] lua_rawget( L, 1); // fifos keys fifo fifo = prepare_fifo_access( L, -1); // fifos keys fifo - if( fifo && fifo->count > 0) + if( fifo != NULL && fifo->count > 0) { fifo_pop( L, fifo, 1); // fifos keys val if( !lua_isnil( L, -1)) @@ -332,7 +332,7 @@ int keepercall_receive_batched( lua_State* L) lua_rawget( L, 2); // key fifos fifo lua_remove( L, 2); // key fifo fifo = prepare_fifo_access( L, 2); // key fifo - if( fifo && fifo->count >= min_count) + if( fifo != NULL && fifo->count >= min_count) { fifo_pop( L, fifo, __min( max_count, fifo->count)); // key ... } @@ -360,7 +360,7 @@ int keepercall_limit( lua_State* L) lua_pushvalue( L, -1); // fifos key key lua_rawget( L, -3); // fifos key fifo fifo = (keeper_fifo*) lua_touserdata( L, -1); - if( !fifo) + if( fifo == NULL) { lua_pop( L, 1); // fifos key fifo_new( L); // fifos key fifo @@ -437,7 +437,7 @@ int keepercall_get( lua_State* L) lua_replace( L, 1); // fifos key lua_rawget( L, 1); // fifos fifo fifo = prepare_fifo_access( L, -1); // fifos fifo - if( fifo && fifo->count > 0) + if( fifo != NULL && fifo->count > 0) { lua_remove( L, 1); // fifo // read one value off the fifo @@ -497,7 +497,7 @@ int keepercall_count( lua_State* L) lua_rawget( L, 2); // out fifos keys fifo fifo = prepare_fifo_access( L, -1); // out fifos keys fifo lua_pop( L, 1); // out fifos keys - if( fifo) + if( fifo != NULL) { lua_pushinteger( L, fifo->count); // out fifos keys count lua_rawset( L, 1); // out fifos keys @@ -540,14 +540,17 @@ void close_keepers( void) for( i = 0; i < GNbKeepers; ++ i) { lua_State* L = GKeepers[i].L; - GKeepers[i].L = 0; + GKeepers[i].L = NULL; lua_close( L); } for( i = 0; i < GNbKeepers; ++ i) { MUTEX_FREE( &GKeepers[i].lock_); } - if( GKeepers) free( GKeepers); + if( GKeepers != NULL) + { + free( GKeepers); + } GKeepers = NULL; GNbKeepers = 0; } @@ -560,20 +563,29 @@ void close_keepers( void) * Note: Any problems would be design flaws; the created Lua state is left * unclosed, because it does not really matter. In production code, this * function never fails. +* settings table is at position 1 on the stack */ -char const* init_keepers( lua_State* L, int _on_state_create, int const _nbKeepers) +char const* init_keepers( lua_State* L) { - int i; - assert( _nbKeepers >= 1); - GNbKeepers = _nbKeepers; - GKeepers = malloc( _nbKeepers * sizeof( struct s_Keeper)); - for( i = 0; i < _nbKeepers; ++ i) + int i, on_state_create; + STACK_CHECK( L); + lua_getfield( L, 1, "nb_keepers"); + GNbKeepers = lua_tointeger( L, -1); + lua_pop( L, 1); + STACK_MID( L, 0); + assert( GNbKeepers >= 1); + + lua_getfield( L, 1, "on_state_create"); + on_state_create = lua_isnil( L, -1) ? -1 : lua_absindex( L, -1); + + GKeepers = malloc( GNbKeepers * sizeof( struct s_Keeper)); + for( i = 0; i < GNbKeepers; ++ i) { lua_State* K; DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "### init_keepers %d BEGIN\n" INDENT_END, i)); DEBUGSPEW_CODE( ++ debugspew_indent_depth); // we don't need any libs in the keeper states - K = luaG_newstate( L, _on_state_create, NULL); + K = luaG_newstate( L, on_state_create, NULL); STACK_CHECK( K); @@ -596,6 +608,8 @@ char const* init_keepers( lua_State* L, int _on_state_create, int const _nbKeepe GKeepers[i].L = K; //GKeepers[i].count = 0; } + lua_pop( L, 1); + STACK_END( L, 0); #if HAVE_KEEPER_ATEXIT_DESINIT atexit( atexit_close_keepers); #endif // HAVE_KEEPER_ATEXIT_DESINIT diff --git a/src/keeper.h b/src/keeper.h index 420eca1..2ef443d 100644 --- a/src/keeper.h +++ b/src/keeper.h @@ -13,7 +13,7 @@ struct s_Keeper // problem: maybe on some platforms (linux) atexit() is called after DLL/so are unloaded... #define HAVE_KEEPER_ATEXIT_DESINIT 0 -char const* init_keepers( lua_State* L, int _on_state_create, int const _nbKeepers); +char const* init_keepers( lua_State* L); #if !HAVE_KEEPER_ATEXIT_DESINIT void close_keepers( void); #endif // HAVE_KEEPER_ATEXIT_DESINIT diff --git a/src/lanes.c b/src/lanes.c index 5ccc05a..3a3cdf0 100644 --- a/src/lanes.c +++ b/src/lanes.c @@ -52,7 +52,7 @@ * ... */ -char const* VERSION = "3.6.6"; +char const* VERSION = "3.7.0"; /* =============================================================================== @@ -1932,7 +1932,7 @@ LUAG_FUNC( thread_new) uint_t required = lua_isnoneornil( L, 8) ? 0 : 8; #define FIXED_ARGS 8 - uint_t args= lua_gettop(L) - FIXED_ARGS; + uint_t args = lua_gettop(L) - FIXED_ARGS; if( prio < THREAD_PRIO_MIN || prio > THREAD_PRIO_MAX) { @@ -2002,8 +2002,19 @@ LUAG_FUNC( thread_new) } else { + // if is it "lanes" or "lanes.core", make sure we have copied the initial settings over + // which might not be the case if the libs list didn't include lanes.core or "*" + if( strncmp( name, "lanes.core", len) == 0) // this works both both "lanes" and "lanes.core" because of len + { + luaG_copy_one_time_settings( L, L2, name); + } lua_pushlstring( L2, name, len); // require() name - lua_pcall( L2, 1, 1, 0); // ret + if( lua_pcall( L2, 1, 1, 0) != LUA_OK) // ret/errcode + { + // propagate error to main state if any + luaG_inter_move( L2, L, 1, eLM_LaneBody); // + return lua_error( L); + } STACK_MID( L2, 1); // after requiring the module, register the functions it exported in our name<->function database populate_func_lookup_table( L2, -1, name); @@ -2110,7 +2121,7 @@ LUAG_FUNC( thread_new) ASSERT_L( s); //memset( s, 0, sizeof(struct s_lane) ); - s->L= L2; + s->L = L2; s->status= PENDING; s->waiting_on = NULL; s->debug_name = NULL; @@ -2128,8 +2139,8 @@ LUAG_FUNC( thread_new) // Set metatable for the userdata // - lua_pushvalue( L, lua_upvalueindex(1) ); - lua_setmetatable( L, -2 ); + lua_pushvalue( L, lua_upvalueindex( 1)); + lua_setmetatable( L, -2); STACK_MID( L, 1); // Clear environment for the userdata @@ -2140,11 +2151,11 @@ LUAG_FUNC( thread_new) // Place 's' in registry, for 'cancel_test()' (even if 'cs'==0 we still // do cancel tests at pending send/receive). // - lua_pushlightuserdata( L2, CANCEL_TEST_KEY ); - lua_pushlightuserdata( L2, s ); - lua_rawset( L2, LUA_REGISTRYINDEX ); + lua_pushlightuserdata( L2, CANCEL_TEST_KEY); + lua_pushlightuserdata( L2, s); + lua_rawset( L2, LUA_REGISTRYINDEX); - if (cs) + if( cs) { lua_sethook( L2, cancel_hook, LUA_MASKCOUNT, cs ); } @@ -2632,9 +2643,6 @@ LUAG_FUNC( wakeup_conv ) static const struct luaL_Reg lanes_functions [] = { {"linda", LG_linda}, {"now_secs", LG_now_secs}, -#if HAVE_LANE_TRACKING - {"threads", LG_threads}, -#endif // HAVE_LANE_TRACKING {"wakeup_conv", LG_wakeup_conv}, {"nameof", luaG_nameof}, {"set_singlethreaded", LG_set_singlethreaded}, @@ -2644,10 +2652,18 @@ static const struct luaL_Reg lanes_functions [] = { /* ** One-time initializations + * settings table it at position 1 on the stack */ -static void init_once_LOCKED( lua_State* L, int const _on_state_create, int const nbKeepers, lua_Number _shutdown_timeout, bool_t _track_lanes, bool_t verbose_errors) +static void init_once_LOCKED( lua_State* L) { - GVerboseErrors = verbose_errors; + STACK_CHECK( L); + + lua_getfield( L, 1, "verbose_errors"); + GVerboseErrors = lua_toboolean( L, -1); + lua_pop( L, 1); + + STACK_MID( L, 0); + #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) now_secs(); // initialize 'now_secs()' internal offset #endif @@ -2657,26 +2673,27 @@ static void init_once_LOCKED( lua_State* L, int const _on_state_create, int cons #endif #if HAVE_LANE_TRACKING - tracking_first = _track_lanes ? TRACKING_END : NULL; + MUTEX_INIT( &tracking_cs); + lua_getfield( L, 1, "track_lanes"); + tracking_first = lua_toboolean( L, -1) ? TRACKING_END : NULL; + lua_pop( L, 1); + STACK_MID( L, 0); #endif // HAVE_LANE_TRACKING // Locks for 'tools.c' inc/dec counters // - MUTEX_INIT( &deep_lock ); - MUTEX_INIT( &mtid_lock ); + MUTEX_INIT( &deep_lock); + MUTEX_INIT( &mtid_lock); // Serialize calls to 'require' from now on, also in the primary state // - MUTEX_RECURSIVE_INIT( &require_cs ); + MUTEX_RECURSIVE_INIT( &require_cs); serialize_require( L); // Linked chains handling // - MUTEX_INIT( &selfdestruct_cs ); -#if HAVE_LANE_TRACKING - MUTEX_INIT( &tracking_cs); -#endif // HAVE_LANE_TRACKING + MUTEX_INIT( &selfdestruct_cs); //--- // Linux needs SCHED_RR to change thread priorities, and that is only @@ -2702,7 +2719,7 @@ static void init_once_LOCKED( lua_State* L, int const _on_state_create, int cons #endif // LINUX_SCHED_RR #endif // PLATFORM_LINUX { - char const* err = init_keepers( L, _on_state_create, nbKeepers); + char const* err = init_keepers( L); if (err) { (void) luaL_error( L, "Unable to initialize: %s", err ); @@ -2713,40 +2730,41 @@ static void init_once_LOCKED( lua_State* L, int const _on_state_create, int cons // ASSERT_L( timer_deep == NULL); - STACK_CHECK( L); - { - // proxy_ud= deep_userdata( idfunc ) - // - lua_pushliteral( L, "lanes-timer"); // push a name for debug purposes - luaG_deep_userdata( L, linda_id); - STACK_MID( L, 2); - lua_remove( L, -2); // remove the name as we no longer need it - - ASSERT_L( lua_isuserdata(L,-1) ); + // proxy_ud= deep_userdata( idfunc ) + // + lua_pushliteral( L, "lanes-timer"); // push a name for debug purposes + luaG_deep_userdata( L, linda_id); + STACK_MID( L, 2); + lua_remove( L, -2); // remove the name as we no longer need it - // Proxy userdata contents is only a 'DEEP_PRELUDE*' pointer - // - timer_deep = * (DEEP_PRELUDE**) lua_touserdata( L, -1); - ASSERT_L( timer_deep && (timer_deep->refcount == 1) && timer_deep->deep); + ASSERT_L( lua_isuserdata(L,-1)); - // The host Lua state must always have a reference to this Linda object in order for the timer_deep pointer to be valid. - // So store a reference that we will never actually use. - // at the same time, use this object as a 'desinit' marker: - // when the main lua State is closed, this object will be GC'ed - { - lua_newuserdata( L, 1); - lua_newtable( L); - lua_pushnumber( L, _shutdown_timeout); - lua_pushcclosure( L, selfdestruct_gc, 1); - lua_setfield( L, -2, "__gc"); - lua_pushliteral( L, "AtExit"); - lua_setfield( L, -2, "__metatable"); - lua_setmetatable( L, -2); - } - lua_insert( L, -2); // Swap key with the Linda object - lua_rawset( L, LUA_REGISTRYINDEX); + // Proxy userdata contents is only a 'DEEP_PRELUDE*' pointer + // + timer_deep = * (DEEP_PRELUDE**) lua_touserdata( L, -1); + ASSERT_L( timer_deep && (timer_deep->refcount == 1) && timer_deep->deep); + // The host Lua state must always have a reference to this Linda object in order for the timer_deep pointer to be valid. + // So store a reference that we will never actually use. + // at the same time, use this object as a 'desinit' marker: + // when the main lua State is closed, this object will be GC'ed + { + lua_newuserdata( L, 1); + lua_newtable( L); + lua_getfield( L, 1, "shutdown_timeout"); + lua_pushcclosure( L, selfdestruct_gc, 1); + lua_setfield( L, -2, "__gc"); + lua_pushliteral( L, "AtExit"); + lua_setfield( L, -2, "__metatable"); + lua_setmetatable( L, -2); } + lua_insert( L, -2); // Swap key with the Linda object + lua_rawset( L, LUA_REGISTRYINDEX); + + // we'll need this everytime we transfer some C function from/to this state + lua_newtable( L); + lua_setfield( L, LUA_REGISTRYINDEX, LOOKUP_REGKEY); + STACK_END( L, 0); } @@ -2754,23 +2772,19 @@ static volatile long s_initCount = 0; // upvalue 1: module name // upvalue 2: module table +// param 1: settings table LUAG_FUNC( configure) { char const* name = luaL_checkstring( L, lua_upvalueindex( 1)); - // all parameter checks are done lua-side - int const nbKeepers = (int)lua_tointeger( L, 1); - int const on_state_create = lua_isfunction( L, 2) ? 2 : 0; - lua_Number shutdown_timeout = lua_tonumber( L, 3); - bool_t track_lanes = lua_toboolean( L, 4); - bool_t protect_allocator = lua_toboolean( L, 5); - bool_t verbose_errors = lua_toboolean( L, 6); + _ASSERT_L( L, lua_type( L, 1) == LUA_TTABLE); + STACK_CHECK( L); DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "%p: lanes.configure() BEGIN\n" INDENT_END, L)); DEBUGSPEW_CODE( ++ debugspew_indent_depth); - STACK_CHECK( L); // not in init_once_LOCKED because we can have several hosted "master" Lua states where Lanes is require()d. - if( protect_allocator) + lua_getfield( L, 1, "protect_allocator"); // settings protect_allocator + if( lua_toboolean( L, -1)) { void* ud; lua_Alloc allocf = lua_getallocf( L, &ud); @@ -2783,6 +2797,7 @@ LUAG_FUNC( configure) lua_setallocf( L, protected_lua_Alloc, s); } } + lua_pop( L, 1); // settings STACK_MID( L, 0); /* @@ -2797,7 +2812,7 @@ LUAG_FUNC( configure) static volatile int /*bool*/ go_ahead; // = 0 if( InterlockedCompareExchange( &s_initCount, 1, 0) == 0) { - init_once_LOCKED( L, on_state_create, nbKeepers, shutdown_timeout, track_lanes, verbose_errors); + init_once_LOCKED( L); go_ahead = 1; // let others pass } else @@ -2815,7 +2830,7 @@ LUAG_FUNC( configure) // if( s_initCount == 0) { - init_once_LOCKED( L, on_state_create, nbKeepers, shutdown_timeout, track_lanes, verbose_errors); + init_once_LOCKED( L); s_initCount = 1; } } @@ -2824,56 +2839,64 @@ LUAG_FUNC( configure) #endif // THREADAPI == THREADAPI_PTHREAD // Retrieve main module interface table - lua_pushvalue( L, lua_upvalueindex( 2)); // ... M + lua_pushvalue( L, lua_upvalueindex( 2)); // settings M // remove configure() (this function) from the module interface - lua_pushnil( L); // ... M nil - lua_setfield( L, -2, "configure"); // ... M + lua_pushnil( L); // settings M nil + lua_setfield( L, -2, "configure"); // settings M // add functions to the module's table luaG_registerlibfuncs( L, lanes_functions); +#if HAVE_LANE_TRACKING + // register core.threads() only if settings say it should be available + if( tracking_first != NULL) + { + lua_pushcfunction( L, LG_threads); // settings M LG_threads() + lua_setfield( L, -2, "threads"); + } +#endif // HAVE_LANE_TRACKING STACK_MID( L, 1); ASSERT_L( timer_deep != NULL); // initialized by init_once_LOCKED - luaG_push_proxy( L, linda_id, (DEEP_PRELUDE*) timer_deep); // ... M timer_deep - lua_setfield( L, -2, "timer_gateway"); // ... M + luaG_push_proxy( L, linda_id, (DEEP_PRELUDE*) timer_deep); // settings M timer_deep + lua_setfield( L, -2, "timer_gateway"); // settings M STACK_MID( L, 1); // prepare the metatable for threads // contains keys: { __gc, __index, cached_error, cached_tostring, cancel, join } // - lua_newtable( L); // ... M mt - lua_pushcfunction( L, LG_thread_gc); // ... M mt LG_thread_gc - lua_setfield( L, -2, "__gc"); // ... M mt - lua_pushcfunction( L, LG_thread_index); // ... M mt LG_thread_index - lua_setfield( L, -2, "__index"); // ... M mt - lua_getglobal( L, "error"); // ... M mt error + lua_newtable( L); // settings M mt + lua_pushcfunction( L, LG_thread_gc); // settings M mt LG_thread_gc + lua_setfield( L, -2, "__gc"); // settings M mt + lua_pushcfunction( L, LG_thread_index); // settings M mt LG_thread_index + lua_setfield( L, -2, "__index"); // settings M mt + lua_getglobal( L, "error"); // settings M mt error ASSERT_L( lua_isfunction( L, -1)); - lua_setfield( L, -2, "cached_error"); // ... M mt - lua_getglobal( L, "tostring"); // ... M mt tostring + lua_setfield( L, -2, "cached_error"); // settings M mt + lua_getglobal( L, "tostring"); // settings M mt tostring ASSERT_L( lua_isfunction( L, -1)); - lua_setfield( L, -2, "cached_tostring"); // ... M mt - lua_pushcfunction( L, LG_thread_join); // ... M mt LG_thread_join - lua_setfield( L, -2, "join"); // ... M mt - lua_pushcfunction( L, LG_thread_cancel); // ... M mt LG_thread_cancel - lua_setfield( L, -2, "cancel"); // ... M mt - lua_pushliteral( L, "Lane"); // ... M mt "Lane" - lua_setfield( L, -2, "__metatable"); // ... M mt + lua_setfield( L, -2, "cached_tostring"); // settings M mt + lua_pushcfunction( L, LG_thread_join); // settings M mt LG_thread_join + lua_setfield( L, -2, "join"); // settings M mt + lua_pushcfunction( L, LG_thread_cancel); // settings M mt LG_thread_cancel + lua_setfield( L, -2, "cancel"); // settings M mt + lua_pushliteral( L, "Lane"); // settings M mt "Lane" + lua_setfield( L, -2, "__metatable"); // settings M mt - lua_pushcclosure( L, LG_thread_new, 1); // ... M LG_thread_new - lua_setfield(L, -2, "thread_new"); // ... M + lua_pushcclosure( L, LG_thread_new, 1); // settings M LG_thread_new + lua_setfield(L, -2, "thread_new"); // settings M // we can't register 'lanes.require' normally because we want to create an upvalued closure - lua_getglobal( L, "require"); // ... M require - lua_pushcclosure( L, LG_require, 1); // ... M lanes.require - lua_setfield( L, -2, "require"); // ... M + lua_getglobal( L, "require"); // settings M require + lua_pushcclosure( L, LG_require, 1); // settings M lanes.require + lua_setfield( L, -2, "require"); // settings M - lua_pushstring(L, VERSION); // ... M VERSION - lua_setfield(L, -2, "version"); // ... M + lua_pushstring(L, VERSION); // settings M VERSION + lua_setfield(L, -2, "version"); // settings M - lua_pushinteger(L, THREAD_PRIO_MAX); // ... M THREAD_PRIO_MAX - lua_setfield(L, -2, "max_prio"); // ... M + lua_pushinteger(L, THREAD_PRIO_MAX); // settings M THREAD_PRIO_MAX + lua_setfield(L, -2, "max_prio"); // settings M - lua_pushlightuserdata( L, CANCEL_ERROR); // ... M CANCEL_ERROR - lua_setfield(L, -2, "cancel_error"); // ... M + lua_pushlightuserdata( L, CANCEL_ERROR); // settings M CANCEL_ERROR + lua_setfield(L, -2, "cancel_error"); // settings M // register all native functions found in that module in the transferable functions database // we process it before _G because we don't want to find the module when scanning _G (this would generate longer names) @@ -2882,14 +2905,18 @@ LUAG_FUNC( configure) // record all existing C/JIT-fast functions // Lua 5.2 no longer has LUA_GLOBALSINDEX: we must push globals table on the stack - lua_pushglobaltable( L); // ... M _G + lua_pushglobaltable( L); // settings M _G populate_func_lookup_table( L, -1, NULL); - lua_pop( L, 2); // ... + lua_pop( L, 1); // settings M + // set _R[CONFIG_REGKEY] = settings + lua_pushvalue( L, -2); // settings M settings + lua_setfield( L, LUA_REGISTRYINDEX, CONFIG_REGKEY); // settings M + lua_pop( L, 1); // settings STACK_END( L, 0); DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "%p: lanes.configure() END\n" INDENT_END, L)); DEBUGSPEW_CODE( -- debugspew_indent_depth); - // Return nothing - return 0; + // Return the settings table + return 1; } // helper to have correct callstacks when crashing a Win32 running on 64 bits Windows @@ -2920,16 +2947,28 @@ int LANES_API luaopen_lanes_core( lua_State* L) { EnableCrashingOnCrashes(); - STACK_GROW( L, 3); + STACK_GROW( L, 4); STACK_CHECK( L); // Create main module interface table // we only have 1 closure, which must be called to configure Lanes - lua_newtable( L); // M - lua_pushvalue( L, 1); // M "lanes.core" - lua_pushvalue( L, -2); // M "lanes.core" M - lua_pushcclosure( L, LG_configure, 2); // M LG_configure() - lua_setfield( L, -2, "configure"); // M + lua_newtable( L); // M + lua_pushvalue( L, 1); // M "lanes.core" + lua_pushvalue( L, -2); // M "lanes.core" M + lua_pushcclosure( L, LG_configure, 2); // M LG_configure() + lua_getfield( L, LUA_REGISTRYINDEX, CONFIG_REGKEY); // M LG_configure() settings + if( !lua_isnil( L, -1)) // this is not the first require "lanes.core": call configure() immediately + { + lua_pushvalue( L, -1); // M LG_configure() settings settings + lua_setfield( L, -4, "settings"); // M LG_configure() settings + lua_call( L, 1, 0); // M + } + else + { + // will do nothing on first invocation, as we haven't stored settings in the registry yet + lua_setfield( L, -3, "settings"); // M LG_configure() + lua_setfield( L, -2, "configure"); // M + } STACK_END( L, 1); return 1; diff --git a/src/lanes.lua b/src/lanes.lua index 01ca181..0e27ea6 100644 --- a/src/lanes.lua +++ b/src/lanes.lua @@ -35,13 +35,15 @@ THE SOFTWARE. =============================================================================== ]]-- +local core = require "lanes.core" -- Lua 5.1: module() creates a global variable -- Lua 5.2: module() is gone -- almost everything module() does is done by require() anyway -- -> simply create a table, populate it, return it, and be done local lanes = {} -lanes.configure = function( _params) +-- this function is available in the public interface until it is called, after which it disappears +lanes.configure = function( settings_) -- This check is for sublanes requiring Lanes -- @@ -68,7 +70,7 @@ lanes.configure = function( _params) on_state_create = nil, shutdown_timeout = 0.25, with_timers = true, - track_lanes = nil, + track_lanes = false, verbose_errors = false, -- LuaJIT provides a thread-unsafe allocator by default, so we need to protect it when used in parallel lanes protect_allocator = (jit and jit.version) and true or false @@ -117,35 +119,32 @@ lanes.configure = function( _params) end } - local params_checker = function( _params) - if not _params then + local params_checker = function( settings_) + if not settings_ then return default_params end - if type( _params) ~= "table" then - error( "Bad parameter #1 to lanes.configure(), should be a table") + -- make a copy of the table to leave the provided one unchanged, *and* to help ensure it won't change behind our back + local settings = {} + if type( settings_) ~= "table" then + error "Bad parameter #1 to lanes.configure(), should be a table" end -- any setting not present in the provided parameters takes the default value - for key, value in pairs( default_params) do - local my_param = _params[key] + for key, checker in pairs( param_checkers) do + local my_param = settings_[key] local param if my_param ~= nil then param = my_param else param = default_params[key] end - if not param_checkers[key]( param) then + if not checker( param) then error( "Bad " .. key .. ": " .. tostring( param), 2) end - _params[key] = param + settings[key] = param end - return _params + return settings end - - _params = params_checker( _params) - - local core = require "lanes.core" - assert( type( core)=="table") - core.configure( _params.nb_keepers, _params.on_state_create, _params.shutdown_timeout, _params.track_lanes, _params.protect_allocator, _params.verbose_errors) + local settings = core.configure and core.configure( params_checker( settings_)) or core.settings local thread_new = assert( core.thread_new) local set_singlethreaded = assert( core.set_singlethreaded) local max_prio = assert( core.max_prio) @@ -316,8 +315,8 @@ local function gen( ... ) -- Lane generator -- return function(...) - return thread_new( func, libs, _params.on_state_create, cs, prio, g_tbl, package_tbl, required, ...) -- args - end + return thread_new( func, libs, settings.on_state_create, cs, prio, g_tbl, package_tbl, required, ...) -- args + end end ---=== Lindas ===--- @@ -338,7 +337,7 @@ local timer = function() error "timers are not active" end local timer_lane = nil local timers = timer -if _params.with_timers ~= false then +if settings.with_timers ~= false then local timer_gateway = assert( core.timer_gateway) -- @@ -364,6 +363,8 @@ timer_gateway:set(first_time_key,true) -- if first_time then + local now_secs = core.now_secs + assert( type( now_secs) == "function") ----- -- Snore loop (run as a lane on the background) -- @@ -373,10 +374,7 @@ if first_time then -- remains. -- local timer_body = function() - -- require lanes.core inside the timer body to prevent pulling now_secs() through an uvpvalue - local core = require "lanes.core" - core.configure( _params.nb_keepers, _params.on_state_create, _params.shutdown_timeout, _params.track_lanes, _params.protect_allocator, _params.verbose_errors) - + set_debug_threadname( "LanesTimer") -- -- { [deep_linda_lightuserdata]= { [deep_linda_lightuserdata]=linda_h, -- [key]= { wakeup_secs [,period_secs] } [, ...] }, @@ -463,8 +461,6 @@ if first_time then end end -- set_timer() - local now_secs = core.now_secs - assert( type( now_secs) == "function") ----- -- [next_wakeup_at]= check_timers() -- Check timers, and wake up the ones expired (if any) @@ -520,7 +516,6 @@ if first_time then end -- check_timers() local timer_gateway_batched = timer_gateway.batched - set_debug_threadname( "LanesTimer") set_finalizer( function( err, stk) if err and type( err) ~= "userdata" then WR( "LanesTimer error: "..tostring(err)) @@ -601,7 +596,7 @@ timers = function() return r end -end -- _params.with_timers +end -- settings.with_timers ---=== Lock & atomic generators ===--- @@ -673,26 +668,23 @@ end lanes.linda = core.linda lanes.cancel_error = core.cancel_error lanes.nameof = core.nameof - lanes.threads = (_params.track_lanes and core.threads) and core.threads or function() error "lane tracking is not available" end + lanes.threads = core.threads or function() error "lane tracking is not available" end -- core.threads isn't registered if settings.track_lanes is false lanes.timer = timer lanes.timer_lane = timer_lane lanes.timers = timers lanes.genlock = genlock lanes.now_secs = core.now_secs lanes.genatomic = genatomic - -- from now on, calling configure does nothing but checking that we don't call it with parameters that changed compared to the first invocation - lanes.configure = function( _params2) - _params2 = params_checker( _params2 or _params) - for key, value2 in pairs( _params2) do - local value = _params[key] - if value2 ~= value then - error( "mismatched configuration: " .. key .. " is " .. tostring( value2) .. " instead of " .. tostring( value)) - end - end - return lanes - end + lanes.configure = nil -- no need to call configure() ever again return lanes end -- lanes.configure +-- no need to force calling configure() excepted the first time +if core.settings then + return lanes.configure() +else + return lanes +end + --the end return lanes diff --git a/src/threading.c b/src/threading.c index 1e4fc92..42c0cde 100644 --- a/src/threading.c +++ b/src/threading.c @@ -84,8 +84,9 @@ THE SOFTWARE. * error in _this_ code. */ #if defined( PLATFORM_XBOX) || defined( PLATFORM_WIN32) || defined( PLATFORM_POCKETPC) -static void FAIL( const char *funcname, int rc ) { - fprintf( stderr, "%s() failed! (%d)\n", funcname, rc ); +static void FAIL( char const* funcname, int rc) +{ + fprintf( stderr, "%s() failed! (%d)\n", funcname, rc ); #ifdef _MSC_VER __debugbreak(); // give a chance to the debugger! #endif // _MSC_VER diff --git a/src/threading.h b/src/threading.h index 7d94f26..d2320aa 100644 --- a/src/threading.h +++ b/src/threading.h @@ -97,7 +97,7 @@ enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED }; #define SIGNAL_T CONDITION_VARIABLE #define MUTEX_T CRITICAL_SECTION #define MUTEX_INIT( ref) InitializeCriticalSection( ref) - #define MUTEX_FREE( ref) DeleteCriticalSection( ref) + #define MUTEX_FREE( ref) do{ DeleteCriticalSection( ref); *ref = INVALID_HANDLE_VALUE;} while(0) #define MUTEX_LOCK( ref) EnterCriticalSection( ref) #define MUTEX_UNLOCK( ref) LeaveCriticalSection( ref) diff --git a/src/tools.c b/src/tools.c index b02177b..95104d6 100644 --- a/src/tools.c +++ b/src/tools.c @@ -44,9 +44,31 @@ THE SOFTWARE. #include #include +/* + * ############################################################################################### + * ########################################### ASSERT ############################################ + * ############################################################################################### + */ +void ASSERT_IMPL( lua_State* L, bool_t cond_, char const* file_, int const line_, char const* text_) +{ + if ( !cond_) + { + (void) luaL_error( L, "ASSERT failed: %s:%d '%s'", file_, line_, text_); + } +} + // for verbose errors bool_t GVerboseErrors = FALSE; +char const* const CONFIG_REGKEY = "ee932492-a654-4506-9da8-f16540bdb5d4"; +char const* const LOOKUP_REGKEY = "ddea37aa-50c7-4d3f-8e0b-fb7a9d62bac5"; + +/* + * ############################################################################################### + * ######################################### Lua 5.1/5.2 ######################################### + * ############################################################################################### + */ + /* ** Copied from Lua 5.2 loadlib.c */ @@ -137,6 +159,19 @@ void luaG_dump( lua_State* L ) { fprintf( stderr, "\n" ); } +// just like lua_xmove, args are (from, to) +void luaG_copy_one_time_settings( lua_State* L, lua_State* L2, char const* name_) +{ + STACK_GROW( L, 1); + // copy settings from from source to destination registry + lua_getfield( L, LUA_REGISTRYINDEX, CONFIG_REGKEY); + if( luaG_inter_move( L, L2, 1, eLM_LaneBody) < 0) // error? + { + (void) luaL_error( L, "failed to copy settings when loading %s", name_); + } + lua_setfield( L2, LUA_REGISTRYINDEX, CONFIG_REGKEY); +} + /*---=== luaG_newstate ===---*/ @@ -171,24 +206,31 @@ static const luaL_Reg libs[] = { NULL, NULL } }; -static void open1lib( lua_State* L, char const* name, size_t len) +static void open1lib( lua_State* L, char const* name_, size_t len_, lua_State* from_) { int i; for( i = 0; libs[i].name; ++ i) { - if( strncmp( name, libs[i].name, len) == 0) + if( strncmp( name_, libs[i].name, len_) == 0) { lua_CFunction libfunc = libs[i].func; - if( libfunc) + name_ = libs[i].name; // note that the provided name_ doesn't necessarily ends with '\0', hence len_ + if( libfunc != NULL) { - bool_t createGlobal = (libfunc != require_lanes_core) ? TRUE : FALSE; // don't want to create a global for "lanes.core" - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "opening %.*s library\n" INDENT_END, len, name)); + bool_t const isLanesCore = (libfunc == require_lanes_core) ? TRUE : FALSE; // don't want to create a global for "lanes.core" + DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "opening %.*s library\n" INDENT_END, len_, name_)); STACK_CHECK( L); + if( isLanesCore == TRUE) + { + // copy settings from from source to destination registry + luaG_copy_one_time_settings( from_, L, name_); + } // open the library as if through require(), and create a global as well if necessary (the library table is left on the stack) - luaL_requiref( L, libs[i].name, libfunc, createGlobal); - if( createGlobal == FALSE) + luaL_requiref( L, name_, libfunc, !isLanesCore); + // lanes.core doesn't declare a global, so scan it here and now + if( isLanesCore == TRUE) { - populate_func_lookup_table( L, -1, name); + populate_func_lookup_table( L, -1, name_); } lua_pop( L, 1); STACK_END( L, 0); @@ -473,14 +515,8 @@ void populate_func_lookup_table( lua_State* L, int _i, char const* name_) DEBUGSPEW_CODE( ++ debugspew_indent_depth); STACK_GROW( L, 3); STACK_CHECK( L); - lua_getfield( L, LUA_REGISTRYINDEX, LOOKUP_KEY); // {}? - if( lua_isnil( L, -1)) // nil - { - lua_pop( L, 1); // - lua_newtable( L); // {} - lua_pushvalue( L, -1); // {} {} - lua_setfield( L, LUA_REGISTRYINDEX, LOOKUP_KEY); // {} - } + lua_getfield( L, LUA_REGISTRYINDEX, LOOKUP_REGKEY); // {} + ASSERT_L( lua_istable( L, -1)); if( lua_type( L, in_base) == LUA_TFUNCTION) // for example when a module is a simple function { name_ = name_ ? name_ : "NULL"; @@ -538,17 +574,18 @@ lua_State* luaG_newstate( lua_State* _from, int const _on_state_create, char con lua_Alloc allocF = lua_getallocf( _from, &allocUD); lua_State* L = lua_newstate( allocF, allocUD); - if( !L) + if( L == NULL) { - luaL_error( _from, "'lua_newstate()' failed; out of memory"); + (void) luaL_error( _from, "'lua_newstate()' failed; out of memory"); } // neither libs (not even 'base') nor special init func: we are done if( libs == NULL && _on_state_create <= 0) { + DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "luaG_newstate(NULL)\n" INDENT_END)); return L; } - // if we are here, no keeper state is involved (because libs == NULL when we init keepers) + // from this point, we are not creating a keeper state (because libs == NULL when we init keepers) DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "luaG_newstate()\n" INDENT_END)); DEBUGSPEW_CODE( ++ debugspew_indent_depth); @@ -556,12 +593,15 @@ lua_State* luaG_newstate( lua_State* _from, int const _on_state_create, char con STACK_GROW( L, 2); STACK_CHECK( L); // 'lua.c' stops GC during initialization so perhaps its a good idea. :) - // but do it after _on_state_create in case it does a lot of stuff... lua_gc( L, LUA_GCSTOP, 0); + // we'll need this everytime we transfer some C function from/to this state + lua_newtable( L); + lua_setfield( L, LUA_REGISTRYINDEX, LOOKUP_REGKEY); + // Anything causes 'base' to be taken in // - if( libs) + if( libs != NULL) { // special "*" case (mainly to help with LuaJIT compatibility) // as we are called from luaopen_lanes_core() already, and that would deadlock @@ -570,7 +610,7 @@ lua_State* luaG_newstate( lua_State* _from, int const _on_state_create, char con DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "opening ALL standard libraries\n" INDENT_END)); luaL_openlibs( L); // don't forget lanes.core for regular lane states - open1lib( L, "lanes.core", 10); + open1lib( L, "lanes.core", 10, _from); libs = NULL; // done with libs } else @@ -604,7 +644,7 @@ lua_State* luaG_newstate( lua_State* _from, int const _on_state_create, char con while( isalnum( p[len]) || p[len] == '.') ++ len; // open library - open1lib( L, p, len); + open1lib( L, p, len, _from); } serialize_require( L); } @@ -612,7 +652,7 @@ lua_State* luaG_newstate( lua_State* _from, int const _on_state_create, char con lua_gc( L, LUA_GCRESTART, 0); STACK_CHECK( L); - // call this after the base libraries are loaded! + // call this after the base libraries are loaded and GC is restarted if( _on_state_create > 0) { DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "calling on_state_create()\n" INDENT_END)); @@ -633,7 +673,7 @@ lua_State* luaG_newstate( lua_State* _from, int const _on_state_create, char con // capture error and forward it to main state if( lua_pcall( L, 0, 0, 0) != LUA_OK) { - luaL_error( _from, "on_state_create failed: \"%s\"", lua_isstring( L, -1) ? lua_tostring( L, -1) : lua_typename( L, lua_type( L, -1))); + (void) luaL_error( _from, "on_state_create failed: \"%s\"", lua_isstring( L, -1) ? lua_tostring( L, -1) : lua_typename( L, lua_type( L, -1))); } STACK_MID( L, 0); } @@ -1486,7 +1526,7 @@ static void lookup_native_func( lua_State* L2, lua_State* L, uint_t i, enum eLoo else { // fetch the name from the source state's lookup table - lua_getfield( L, LUA_REGISTRYINDEX, LOOKUP_KEY); // ... f ... {} + lua_getfield( L, LUA_REGISTRYINDEX, LOOKUP_REGKEY); // ... f ... {} ASSERT_L( lua_istable( L, -1)); lua_pushvalue( L, i); // ... f ... {} f lua_rawget( L, -2); // ... f ... {} "f.q.n" @@ -1533,7 +1573,7 @@ static void lookup_native_func( lua_State* L2, lua_State* L, uint_t i, enum eLoo } else { - lua_getfield( L2, LUA_REGISTRYINDEX, LOOKUP_KEY); // {} + lua_getfield( L2, LUA_REGISTRYINDEX, LOOKUP_REGKEY); // {} ASSERT_L( lua_istable( L2, -1)); lua_pushlstring( L2, fqn, len); // {} "f.q.n" lua_rawget( L2, -2); // {} f @@ -1923,7 +1963,7 @@ static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, u */ if( inter_copy_one_( L2, L2_cache_i, L, val_i, VT_NORMAL, mode_, valPath)) { - ASSERT_L( lua_istable(L2,-3)); + ASSERT_L( lua_istable( L2, -3)); lua_rawset( L2, -3); // add to table (pops key & val) } else diff --git a/src/tools.h b/src/tools.h index 1113131..6d3b3c7 100644 --- a/src/tools.h +++ b/src/tools.h @@ -56,7 +56,8 @@ extern int debugspew_indent_depth; #define STACK_END(L,c) /*nothing*/ #define STACK_DUMP(L) /*nothing*/ #else - #define _ASSERT_L(lua,c) do { if (!(c)) luaL_error( lua, "ASSERT failed: %s:%d '%s'", __FILE__, __LINE__, #c ); } while( 0) + void ASSERT_IMPL( lua_State* L, bool_t cond_, char const* file_, int const line_, char const* text_); + #define _ASSERT_L(lua,c) ASSERT_IMPL( lua, (c) != 0, __FILE__, __LINE__, #c) // #define STACK_CHECK(L) { int const _oldtop_##L = lua_gettop( L) #define STACK_MID(L,change) \ @@ -75,7 +76,7 @@ extern int debugspew_indent_depth; #define STACK_GROW(L,n) do { if (!lua_checkstack(L,n)) luaL_error( L, "Cannot grow stack!" ); } while( 0) -#define LUAG_FUNC( func_name ) static int LG_##func_name( lua_State *L ) +#define LUAG_FUNC( func_name ) static int LG_##func_name( lua_State* L) #define luaG_optunsigned(L,i,d) ((uint_t) luaL_optinteger(L,i,d)) #define luaG_tounsigned(L,i) ((uint_t) lua_tointeger(L,i)) @@ -83,6 +84,7 @@ extern int debugspew_indent_depth; void luaG_dump( lua_State* L ); lua_State* luaG_newstate( lua_State* _from, int const _on_state_create, char const* libs); +void luaG_copy_one_time_settings( lua_State* L, lua_State* L2, char const* name_); typedef struct { volatile int refcount; @@ -117,5 +119,7 @@ extern MUTEX_T require_cs; // for verbose errors extern bool_t GVerboseErrors; -#endif - // TOOLS_H +char const* const CONFIG_REGKEY; +char const* const LOOKUP_REGKEY; + +#endif // TOOLS_H diff --git a/tests/fibonacci.lua b/tests/fibonacci.lua index 5cdb4be..6dba1dd 100644 --- a/tests/fibonacci.lua +++ b/tests/fibonacci.lua @@ -12,8 +12,7 @@ -- Need to say it's 'local' so it can be an upvalue -- -local lanes = require "lanes" -lanes.configure{ nb_keepers =1, with_timers = false} +local lanes = require "lanes".configure{ nb_keepers =1, with_timers = false} local function WR(str) io.stderr:write( str.."\n" ) @@ -33,7 +32,7 @@ end -- local function fib( n ) set_debug_threadname( "fib(" .. n .. ")") - local lanes = require"lanes".configure() + local lanes = require"lanes" -- local sum local floor= assert(math.floor) diff --git a/tests/irayo_recursive.lua b/tests/irayo_recursive.lua index f6fe87e..2f8b8a8 100644 --- a/tests/irayo_recursive.lua +++ b/tests/irayo_recursive.lua @@ -1,3 +1,4 @@ +local lanes = require "lanes".configure{ nb_keepers = 1, with_timers = false} -- -- Bugs filed by irayo Jul-2008 -- @@ -8,10 +9,9 @@ local function recurse() print("level "..i); if i > 10 then return "finished" end - local lanes = require "lanes" - lanes.configure{ nb_keepers = 1, with_timers = false} + --local lanes = require "lanes" - local lane = lanes.gen( "*", { globals = { ["i"]= i + 1 } }, recurse ) () + local lane = lanes.gen( "base,string,lanes.core", { globals = { ["i"]= i + 1 } }, recurse ) () return lane[1] end diff --git a/tests/recursive.lua b/tests/recursive.lua index 571fc1e..82a43b9 100644 --- a/tests/recursive.lua +++ b/tests/recursive.lua @@ -12,8 +12,10 @@ local function func( depth ) end local lanes = require "lanes" - -- lanes.configure() is gone after we call it... - lanes.configure() + -- lanes.configure() is available only at the first require() + if lanes.configure then + lanes = lanes.configure() + end local lane= lanes.gen("*", func)( depth+1 ) return lane[1] end -- cgit v1.2.3-55-g6feb