From 7f7b29063d2f19a8bc2b229ae9b0ec82ce447cab Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Tue, 13 Aug 2013 08:12:05 +0200 Subject: version 3.6.3 * lane:cancel() only causes cancel_test() to return true but won't interrupt execution of the lane during linda operations * more explicit errors when trying to transfer unknown source functions (with new configure option verbose_errors) * default options wrap allocator around a mutex when run by LuaJIT --- CHANGES | 10 +++ docs/index.html | 35 ++++++++--- lanes-3.6.2-1.rockspec | 66 ------------------- lanes-3.6.3-1.rockspec | 66 +++++++++++++++++++ src/lanes.c | 156 +++++++++++++++++++++++++++------------------ src/lanes.lua | 11 +++- src/tools.c | 168 +++++++++++++++++++++++++++++++++++-------------- src/tools.h | 3 + tests/fifo.lua | 7 ++- tests/linda_perf.lua | 5 ++ tests/require.lua | 7 ++- 11 files changed, 345 insertions(+), 189 deletions(-) delete mode 100644 lanes-3.6.2-1.rockspec create mode 100644 lanes-3.6.3-1.rockspec diff --git a/CHANGES b/CHANGES index 538acba..134b64f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,15 @@ CHANGES: +CHANGE 67: BGe 2-Aug-13 + * version 3.6.3 + * lane:cancel() only causes cancel_test() to return true but won't interrupt execution of the lane during linda operations + +CHANGE 66: BGe 31-Jul-13 + * more explicit errors when trying to transfer unknown source functions (with new configure option verbose_errors) + +CHANGE 65: BGe 23-Jul-13 + * default options wrap allocator around a mutex when run by LuaJIT + CHANGE 64: BGe 20-Jul-13 * WIN32 builds against pre-Vista versions no longer use PulseEvent to fix occasional hangs when a wake event is missed diff --git a/docs/index.html b/docs/index.html index 617403a..0e32504 100644 --- a/docs/index.html +++ b/docs/index.html @@ -70,7 +70,7 @@

- This document was revised on 20-May-13, and applies to version 3.6.2. + This document was revised on 02-Aug-13, and applies to version 3.6.3.

@@ -292,6 +292,19 @@ + + + .verbose_errors + + + nil/false/true + + + (Since v3.6.3) If equal to true, Lanes will collect more information when transfering stuff across Lua states to help identify errors (with a cost). + Default is false. + + + .protect_allocator @@ -539,7 +552,7 @@ integer >= 1/true - By default, lanes are only cancellable when they enter a pending :receive() or :send() call. With this option, one can set cancellation check to occur every N Lua statements. The value true uses a default value (100). + By default, lanes are only cancellable when they enter a pending :receive() or :send() call. With this option, one can set cancellation check to occur every N Lua statements. The value true uses a default value (100). It is also possible to manually test for cancel requests with cancel_test(). @@ -705,7 +718,7 @@ - received cancellation and finished itself. + received cancellation and finished itself. @@ -833,8 +846,10 @@

- cancel()sends a cancellation request to the lane. If timeout_secs is non-zero, waits for the request to be processed, or a timeout to occur. - Returns true if the lane was already done (in "done", "error" or "cancelled" status) or if the cancellation was fruitful within timeout period. + cancel()sends a cancellation request to the lane.
+ If timeout_secs is positive (aka "hard cancel"), waits for the request to be processed, or a timeout to occur.
+ If timeout_secs is negative (aka "soft cancel"), starting with version 3.6.3, will only cause cancel_test() to return true, so that the lane can cleanup manually. You can't provide a second argument in that case.
+ Returns true if soft cancelling, or the lane was already done (in "done", "error" or "cancelled" status), or the cancellation was fruitful within timeout period.

@@ -887,7 +902,7 @@ -- no special error: true error print( " error: "..tostring(err)) elseif type( err) == "userdata" then - -- lane cancellation is performed by throwing a special userdata as error + -- lane cancellation is performed by throwing a special userdata as error print( "after cancel") else -- no error: we just got finalized @@ -977,11 +992,15 @@

- send returns true if the sending succeeded, and false if the queue limit was met, and the queue did not empty enough during the given timeout, or the operation was cancelled. + Hard cancellation will cause pending linda operations to abort execution of the lane through a cancellation error. This means that you have to install a finalizer in your lane if you want to run some code in that situation. +

+ +

+ send returns true if the sending succeeded, and false if the queue limit was met, and the queue did not empty enough during the given timeout.

- Equally, receive returns a key and the value extracted from it, or nothing for timeout or cancellation. Note that nils can be sent and received; the key value will tell it apart from a timeout. + Equally, receive returns a key and the value extracted from it, or nothing for timeout. Note that nils can be sent and received; the key value will tell it apart from a timeout.
Version 3.4.0 introduces an API change in the returned values: receive returns the key followed by the value(s), in that order, and not the other way around.

diff --git a/lanes-3.6.2-1.rockspec b/lanes-3.6.2-1.rockspec deleted file mode 100644 index 8679c08..0000000 --- a/lanes-3.6.2-1.rockspec +++ /dev/null @@ -1,66 +0,0 @@ --- --- Lanes rockspec --- --- Ref: --- --- - -package = "Lanes" - -version = "3.6.2-1" - -source= { - url= "git://github.com/LuaLanes/lanes.git", - branch= "v3.6.2" -} - -description = { - summary= "Multithreading support for Lua", - detailed= [[ - Lua Lanes is a portable, message passing multithreading library - providing the possibility to run multiple Lua states in parallel. - ]], - license= "MIT/X11", - homepage="https://github.com/LuaLanes/lanes", - maintainer="Benoit Germain " -} - --- Q: What is the difference of "windows" and "win32"? Seems there is none; --- so should we list either one or both? --- -supported_platforms= { "win32", - "macosx", - "linux", - "freebsd", -- TBD: not tested - "msys", -- TBD: not supported by LuaRocks 1.0 (or is it?) -} - -dependencies= { - "lua >= 5.1", -- builds with either 5.1 and 5.2 -} - -build = { - type = "builtin", - platforms = - { - linux = - { - modules = - { - ["lanes.core"] = - { - libraries = "pthread" - }, - } - } - }, - modules = - { - ["lanes.core"] = - { - sources = { "src/lanes.c", "src/keeper.c", "src/tools.c", "src/threading.c"}, - incdirs = { "src"}, - }, - lanes = "src/lanes.lua" - } -} diff --git a/lanes-3.6.3-1.rockspec b/lanes-3.6.3-1.rockspec new file mode 100644 index 0000000..18bae0a --- /dev/null +++ b/lanes-3.6.3-1.rockspec @@ -0,0 +1,66 @@ +-- +-- Lanes rockspec +-- +-- Ref: +-- +-- + +package = "Lanes" + +version = "3.6.3-1" + +source= { + url= "git://github.com/LuaLanes/lanes.git", + branch= "v3.6.3" +} + +description = { + summary= "Multithreading support for Lua", + detailed= [[ + Lua Lanes is a portable, message passing multithreading library + providing the possibility to run multiple Lua states in parallel. + ]], + license= "MIT/X11", + homepage="https://github.com/LuaLanes/lanes", + maintainer="Benoit Germain " +} + +-- Q: What is the difference of "windows" and "win32"? Seems there is none; +-- so should we list either one or both? +-- +supported_platforms= { "win32", + "macosx", + "linux", + "freebsd", -- TBD: not tested + "msys", -- TBD: not supported by LuaRocks 1.0 (or is it?) +} + +dependencies= { + "lua >= 5.1", -- builds with either 5.1 and 5.2 +} + +build = { + type = "builtin", + platforms = + { + linux = + { + modules = + { + ["lanes.core"] = + { + libraries = "pthread" + }, + } + } + }, + modules = + { + ["lanes.core"] = + { + sources = { "src/lanes.c", "src/keeper.c", "src/tools.c", "src/threading.c"}, + incdirs = { "src"}, + }, + lanes = "src/lanes.lua" + } +} diff --git a/src/lanes.c b/src/lanes.c index 08bdb9a..e36a073 100644 --- a/src/lanes.c +++ b/src/lanes.c @@ -52,7 +52,7 @@ * ... */ -char const* VERSION = "3.6.2"; +char const* VERSION = "3.6.3"; /* =============================================================================== @@ -115,10 +115,21 @@ THE SOFTWARE. */ #define ERROR_FULL_STACK +/* + * Lane cancellation request modes + */ +enum e_cancel_request +{ + CANCEL_NONE, // no pending cancel request + CANCEL_SOFT, // user wants the lane to cancel itself manually on cancel_test() + CANCEL_HARD // user wants the lane to be interrupted (meaning code won't return from those functions) from inside linda:send/receive calls +}; + // NOTE: values to be changed by either thread, during execution, without // locking, are marked "volatile" // -struct s_lane { +struct s_lane +{ THREAD_T thread; // // M: sub-thread OS thread @@ -140,7 +151,7 @@ struct s_lane { // // When status is WAITING, points on the linda's signal the thread waits on, else NULL - volatile bool_t cancel_request; + volatile enum e_cancel_request cancel_request; // // M: sets to FALSE, flags TRUE for cancel request // S: reads to see if cancel is requested @@ -178,7 +189,7 @@ struct s_lane { // For tracking only }; -static bool_t cancel_test( lua_State*L ); +static enum e_cancel_request cancel_test( lua_State* L); static void cancel_error( lua_State*L ); #define CANCEL_TEST_KEY ((void*)cancel_test) // used as registry key @@ -372,7 +383,7 @@ LUAG_FUNC( linda_send) { struct s_Linda *linda = lua_toLinda( L, 1); bool_t ret; - bool_t cancel = FALSE; + 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 @@ -434,7 +445,7 @@ LUAG_FUNC( linda_send) /* limit faced; push until timeout */ cancel = cancel_test( L); // testing here causes no delays - if (cancel) + if( cancel != CANCEL_NONE) // if user wants to cancel, the call returns without sending anything { break; } @@ -486,7 +497,8 @@ LUAG_FUNC( linda_send) return luaL_error( L, "tried to copy unsupported types"); } - if( cancel) + // raise an error interrupting execution only in case of hard cancel + if( cancel == CANCEL_HARD) cancel_error( L); lua_pushboolean( L, ret); @@ -510,7 +522,7 @@ LUAG_FUNC( linda_receive) { struct s_Linda *linda = lua_toLinda( L, 1); int pushed, expected_pushed_min, expected_pushed_max; - bool_t cancel = FALSE; + enum e_cancel_request cancel = CANCEL_NONE; keeper_api_t keeper_receive; time_d timeout = -1.0; @@ -592,16 +604,16 @@ LUAG_FUNC( linda_receive) /* nothing received; wait until timeout */ cancel = cancel_test( L); // testing here causes no delays - if( cancel) + if( cancel != CANCEL_NONE) // if user wants to cancel, the call returns without providing anything { break; } // change status of lane to "waiting" { - struct s_lane *s; + struct s_lane* s; enum e_status prev_status = ERROR_ST; // prevent 'might be used uninitialized' warnings - STACK_GROW(L,1); + STACK_GROW( L, 1); STACK_CHECK( L); lua_pushlightuserdata( L, CANCEL_TEST_KEY); @@ -643,7 +655,8 @@ LUAG_FUNC( linda_receive) return luaL_error( L, "tried to copy unsupported types"); } - if( cancel) + // raise an error interrupting execution only in case of hard cancel + if( cancel == CANCEL_HARD) cancel_error( L); return pushed; @@ -1176,31 +1189,41 @@ static cancel_result thread_cancel( struct s_lane *s, double secs, bool_t force) } else if( s->status < DONE) { - s->cancel_request = TRUE; // it's now signaled to stop // signal the linda the wake up the thread so that it can react to the cancel query // let us hope we never land here with a pointer on a linda that has been destroyed... + if( secs < 0.0) + { + s->cancel_request = CANCEL_SOFT; // it's now signaled to stop + // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own + // say we succeeded though + result = CR_Cancelled; + } + else { - SIGNAL_T *waiting_on = s->waiting_on; - if( s->status == WAITING && waiting_on != NULL) + s->cancel_request = CANCEL_HARD; // it's now signaled to stop { - SIGNAL_ALL( waiting_on); + SIGNAL_T *waiting_on = s->waiting_on; + if( s->status == WAITING && waiting_on != NULL) + { + SIGNAL_ALL( waiting_on); + } } - } - result = THREAD_WAIT( &s->thread, secs, &s->done_signal, &s->done_lock, &s->status) ? CR_Cancelled : CR_Timeout; + result = THREAD_WAIT( &s->thread, secs, &s->done_signal, &s->done_lock, &s->status) ? CR_Cancelled : CR_Timeout; - if( (result == CR_Timeout) && force) - { - // Killing is asynchronous; we _will_ wait for it to be done at - // GC, to make sure the data structure can be released (alternative - // would be use of "cancellation cleanup handlers" that at least - // PThread seems to have). - // - THREAD_KILL( &s->thread); - s->mstatus = KILLED; // mark 'gc' to wait for it - // note that s->status value must remain to whatever it was at the time of the kill - // because we need to know if we can lua_close() the Lua State or not. - result = CR_Killed; + if( (result == CR_Timeout) && force) + { + // Killing is asynchronous; we _will_ wait for it to be done at + // GC, to make sure the data structure can be released (alternative + // would be use of "cancellation cleanup handlers" that at least + // PThread seems to have). + // + THREAD_KILL( &s->thread); + s->mstatus = KILLED; // mark 'gc' to wait for it + // note that s->status value must remain to whatever it was at the time of the kill + // because we need to know if we can lua_close() the Lua State or not. + result = CR_Killed; + } } } else @@ -1307,7 +1330,7 @@ static int selfdestruct_gc( lua_State* L) struct s_lane* s = selfdestruct_first; while( s != SELFDESTRUCT_END ) { - // attempt a regular unforced cancel with a small timeout + // attempt a regular unforced hard cancel with a small timeout bool_t cancelled = THREAD_ISNULL( s->thread) || thread_cancel( s, 0.0001, FALSE); // if we failed, and we know the thread is waiting on a linda if( cancelled == FALSE && s->status == WAITING && s->waiting_on != NULL) @@ -1353,7 +1376,7 @@ static int selfdestruct_gc( lua_State* L) struct s_lane* s = selfdestruct_first; while( s != SELFDESTRUCT_END) { - if( s->cancel_request) + if( s->cancel_request == CANCEL_HARD) ++ n; s = s->selfdestruct_next; } @@ -1444,21 +1467,22 @@ static int selfdestruct_gc( lua_State* L) * Returns TRUE if any locks are to be exited, and 'cancel_error()' called, * to make execution of the lane end. */ -static bool_t cancel_test( lua_State*L ) { - struct s_lane *s; +static enum e_cancel_request cancel_test( lua_State* L) +{ + struct s_lane* s; - STACK_GROW(L,1); + STACK_GROW( L, 1); - STACK_CHECK( L); - lua_pushlightuserdata( L, CANCEL_TEST_KEY ); - lua_rawget( L, LUA_REGISTRYINDEX ); - s= lua_touserdata( L, -1 ); // lightuserdata (true 's_lane' pointer) / nil - lua_pop(L,1); - STACK_END( L, 0); + STACK_CHECK( L); + lua_pushlightuserdata( L, CANCEL_TEST_KEY); + lua_rawget( L, LUA_REGISTRYINDEX); + s = lua_touserdata( L, -1); // lightuserdata (true 's_lane' pointer) / nil + lua_pop( L, 1); + STACK_END( L, 0); - // 's' is NULL for the original main state (no-one can cancel that) - // - return s && s->cancel_request; + // 's' is NULL for the original main state (no-one can cancel that) + // + return s ? s->cancel_request : CANCEL_NONE; } static void cancel_error( lua_State*L ) { @@ -1467,22 +1491,24 @@ static void cancel_error( lua_State*L ) { lua_error(L); // no return } -static void cancel_hook( lua_State*L, lua_Debug *ar ) { - (void)ar; - if (cancel_test(L)) cancel_error(L); +static void cancel_hook( lua_State*L, lua_Debug *ar ) +{ + (void)ar; + if( cancel_test( L) != CANCEL_NONE) + cancel_error( L); } //--- -// bool= cancel_test() +// bool = cancel_test() // // Available inside the global namespace of lanes // returns a boolean saying if a cancel request is pending // LUAG_FUNC( cancel_test) { - bool_t test = cancel_test( L); - lua_pushboolean( L, test); + enum e_cancel_request test = cancel_test( L); + lua_pushboolean( L, test != CANCEL_NONE); return 1; } @@ -1724,7 +1750,7 @@ static THREAD_RETURN_T THREAD_CALLCONV lane_main( void *vs) // Tie "cancel_test()" to the state // lua_pushcfunction( L, LG_cancel_test); - lua_setglobal( L, "cancel_test" ); + lua_setglobal( L, "cancel_test"); #ifdef ERROR_FULL_STACK // Tie "set_error_reporting()" to the state @@ -2065,7 +2091,7 @@ LUAG_FUNC( thread_new) s->status= PENDING; s->waiting_on = NULL; s->debug_name = NULL; - s->cancel_request= FALSE; + s->cancel_request = CANCEL_NONE; #if THREADWAIT_METHOD == THREADWAIT_CONDVAR MUTEX_INIT( &s->done_lock); @@ -2183,6 +2209,10 @@ LUAG_FUNC( thread_cancel) if( lua_isnumber( L, 2)) { secs = lua_tonumber( L, 2); + if( secs < 0.0 && lua_gettop( L) > 2) + { + return luaL_error( L, "can't force a soft cancel"); + } ++ force_i; } else if( lua_isnil( L, 2)) @@ -2611,8 +2641,9 @@ void register_core_libfuncs_for_keeper( lua_State* L) /* ** One-time initializations */ -static void init_once_LOCKED( lua_State* L, int const _on_state_create, int const nbKeepers, lua_Number _shutdown_timeout, bool_t _track_lanes) +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) { + GVerboseErrors = verbose_errors; #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) now_secs(); // initialize 'now_secs()' internal offset #endif @@ -2724,10 +2755,12 @@ 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); + // all these can be nil when lanes.core is required internally! (but are only processed at first init anyway) 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); DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "%p: lanes.configure() BEGIN\n" INDENT_END, L)); DEBUGSPEW_CODE( ++ debugspew_indent_depth); @@ -2738,11 +2771,14 @@ LUAG_FUNC( configure) { void* ud; lua_Alloc allocf = lua_getallocf( L, &ud); - struct ProtectedAllocator_s* s = (struct ProtectedAllocator_s*) allocf( ud, NULL, 0, sizeof( struct ProtectedAllocator_s)); - s->allocf = allocf; - s->ud = ud; - MUTEX_INIT( &s->lock); - lua_setallocf( L, protected_lua_Alloc, s); + if( allocf != protected_lua_Alloc) // just in case + { + struct ProtectedAllocator_s* s = (struct ProtectedAllocator_s*) allocf( ud, NULL, 0, sizeof( struct ProtectedAllocator_s)); + s->allocf = allocf; + s->ud = ud; + MUTEX_INIT( &s->lock); + lua_setallocf( L, protected_lua_Alloc, s); + } } // Create main module interface table @@ -2818,7 +2854,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); + init_once_LOCKED( L, on_state_create, nbKeepers, shutdown_timeout, track_lanes, verbose_errors); go_ahead = 1; // let others pass } else @@ -2836,7 +2872,7 @@ LUAG_FUNC( configure) // if( s_initCount == 0) { - init_once_LOCKED( L, on_state_create, nbKeepers, shutdown_timeout, track_lanes); + init_once_LOCKED( L, on_state_create, nbKeepers, shutdown_timeout, track_lanes, verbose_errors); s_initCount = 1; } } diff --git a/src/lanes.lua b/src/lanes.lua index ac3da26..175002a 100644 --- a/src/lanes.lua +++ b/src/lanes.lua @@ -69,6 +69,7 @@ lanes.configure = function( _params) shutdown_timeout = 0.25, with_timers = true, track_lanes = nil, + 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 } @@ -105,6 +106,14 @@ lanes.configure = function( _params) track_lanes = function( _val) -- track_lanes may be nil or boolean return _val and type( _val) == "boolean" or true + end, + verbose_errors = function( _val) + -- verbose_errors may be nil or boolean + if _val then + return type( _val) == "boolean" + else + return true -- _val is either false or nil + end end } @@ -138,7 +147,7 @@ lanes.configure = function( _params) assert( type( core)=="table") -- configure() is available only the first time lanes.core is required process-wide, and we *must* call it to have the other functions in the interface - if core.configure then core.configure( _params.nb_keepers, _params.on_state_create, _params.shutdown_timeout, _params.track_lanes, _params.protect_allocator) end + if core.configure then core.configure( _params.nb_keepers, _params.on_state_create, _params.shutdown_timeout, _params.track_lanes, _params.protect_allocator, _params.verbose_errors) end local thread_new = assert( core.thread_new) diff --git a/src/tools.c b/src/tools.c index d2dfdf5..a3cc6b7 100644 --- a/src/tools.c +++ b/src/tools.c @@ -42,6 +42,10 @@ THE SOFTWARE. #include #include #include +#include + +// for verbose errors +bool_t GVerboseErrors = FALSE; /* ** Copied from Lua 5.2 loadlib.c @@ -1285,9 +1289,9 @@ static bool_t push_cached_table( lua_State *L2, uint_t L2_cache_i, lua_State *L, * * Always pushes a function to 'L2'. */ -static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i); +static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, char const* upName_); -static void push_cached_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i) +static void push_cached_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, char const* upName_) { void* const aspointer = (void*)lua_topointer( L, i); // TBD: Merge this and same code for tables @@ -1319,7 +1323,7 @@ static void push_cached_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, ui // via upvalues // // pushes a copy of the func, stores a reference in the cache - inter_copy_func( L2, L2_cache_i, L, i); // ... {cache} ... function + inter_copy_func( L2, L2_cache_i, L, i, upName_); // ... {cache} ... function } else // found function in the cache { @@ -1363,16 +1367,19 @@ static int discover_object_name_recur( lua_State* L, int shortest_, int depth_) lua_pushinteger( L, 1); // o "r" {c} {fqn} ... {?} {?} 1 lua_rawset( L, cache); // o "r" {c} {fqn} ... {?} // scan table contents + STACK_CHECK( L); lua_pushnil( L); // o "r" {c} {fqn} ... {?} nil while( lua_next( L, -2)) // o "r" {c} {fqn} ... {?} k v { - //char const *const key = lua_tostring( L, -2); // only for debugging (BEWARE, IT MAY CHANGE THE VALUE IF IT IS CONVERTIBLE, AND WRECK THE LOOP ITERATION PROCESS!) + //char const *const strKey = (lua_type( L, -2) == LUA_TSTRING) ? lua_tostring( L, -2) : NULL; // only for debugging + //lua_Number const numKey = (lua_type( L, -2) == LUA_TNUMBER) ? lua_tonumber( L, -2) : -6666; // only for debugging // append key name to fqn stack ++ depth_; lua_pushvalue( L, -2); // o "r" {c} {fqn} ... {?} k v k lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k v if( lua_rawequal( L, -1, what)) // is it what we are looking for? { + STACK_MID( L, 2); // update shortest name if( depth_ < shortest_) { @@ -1382,32 +1389,74 @@ static int discover_object_name_recur( lua_State* L, int shortest_, int depth_) } // no need to search further at this level lua_pop( L, 2); // o "r" {c} {fqn} ... {?} + STACK_MID( L, 0); break; } - else if( lua_istable( L, -1)) + else if( lua_istable( L, -1)) // o "r" {c} {fqn} ... {?} k {} { + STACK_MID( L, 2); shortest_ = discover_object_name_recur( L, shortest_, depth_); // search in the table's metatable too - if( lua_getmetatable( L, -1)) + if( lua_getmetatable( L, -1)) // o "r" {c} {fqn} ... {?} k {} {mt} { if( lua_istable( L, -1)) { + ++ depth_; + lua_pushliteral( L, "__metatable"); // o "r" {c} {fqn} ... {?} k {} {mt} "__metatable" + lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k {} {mt} shortest_ = discover_object_name_recur( L, shortest_, depth_); + lua_pushnil( L); // o "r" {c} {fqn} ... {?} k {} {mt} nil + lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k {} {mt} + -- depth_; } - lua_pop( L, 1); + lua_pop( L, 1); // o "r" {c} {fqn} ... {?} k {} + STACK_MID( L, 2); } } - else if( lua_isuserdata( L, -1)) + else if( lua_isthread( L, -1)) // o "r" {c} {fqn} ... {?} k T { + // search in the object's uservalue if it is a table + lua_getuservalue( L, -1); // o "r" {c} {fqn} ... {?} k T {u} + if( lua_istable( L, -1)) + { + shortest_ = discover_object_name_recur( L, shortest_, depth_); + } + lua_pop( L, 1); // o "r" {c} {fqn} ... {?} k T + STACK_MID( L, 2); + } + else if( lua_isuserdata( L, -1)) // o "r" {c} {fqn} ... {?} k U + { + STACK_MID( L, 2); // search in the object's metatable (some modules are built that way) - if( lua_getmetatable( L, -1)) + if( lua_getmetatable( L, -1)) // o "r" {c} {fqn} ... {?} k U {mt} { if( lua_istable( L, -1)) { + ++ depth_; + lua_pushliteral( L, "__metatable"); // o "r" {c} {fqn} ... {?} k U {mt} "__metatable" + lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k U {mt} shortest_ = discover_object_name_recur( L, shortest_, depth_); + lua_pushnil( L); // o "r" {c} {fqn} ... {?} k U {mt} nil + lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k U {mt} + -- depth_; } - lua_pop( L, 1); + lua_pop( L, 1); // o "r" {c} {fqn} ... {?} k U + STACK_MID( L, 2); + } + // search in the object's uservalue if it is a table + lua_getuservalue( L, -1); // o "r" {c} {fqn} ... {?} k U {u} + if( lua_istable( L, -1)) + { + ++ depth_; + lua_pushliteral( L, "uservalue"); // o "r" {c} {fqn} ... {?} k v {u} "uservalue" + lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k v {u} + shortest_ = discover_object_name_recur( L, shortest_, depth_); + lua_pushnil( L); // o "r" {c} {fqn} ... {?} k v {u} nil + lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k v {u} + -- depth_; } + lua_pop( L, 1); // o "r" {c} {fqn} ... {?} k U + STACK_MID( L, 2); } // make ready for next iteration lua_pop( L, 1); // o "r" {c} {fqn} ... {?} k @@ -1416,6 +1465,7 @@ static int discover_object_name_recur( lua_State* L, int shortest_, int depth_) lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k -- depth_; } // o "r" {c} {fqn} ... {?} + STACK_END( L, 0); // remove the visited table from the cache, in case a shorter path to the searched object exists lua_pushvalue( L, -1); // o "r" {c} {fqn} ... {?} {?} lua_pushnil( L); // o "r" {c} {fqn} ... {?} {?} nil @@ -1442,6 +1492,7 @@ int luaG_nameof( lua_State* L) lua_insert( L, -2); // "type" o return 2; } + STACK_GROW( L, 4); // this slot will contain the shortest name we found when we are done lua_pushnil( L); // o nil @@ -1461,7 +1512,7 @@ int luaG_nameof( lua_State* L) /* * Push a looked-up native/LuaJIT function. */ -static void lookup_native_func( lua_State* L2, lua_State* L, uint_t i) +static void lookup_native_func( lua_State* L2, lua_State* L, uint_t i, char const* upName_) { char const* fqn; // L // L2 size_t len; @@ -1478,14 +1529,28 @@ static void lookup_native_func( lua_State* L2, lua_State* L, uint_t i) lua_pop( L, 2); // ... f ... if( !fqn) { - char const* from; + char const *from, *typewhat, *what, *gotchaA, *gotchaB; // try to discover the name of the function we want to send - lua_pushcfunction( L, luaG_nameof); // ... f ...luaG_nameof - lua_pushvalue( L, i); // ... f ... luaG_nameof f - lua_call( L, 1, 2); // ... f ... "type" "name" - lua_getglobal( L, "decoda_name"); // ... f ... "type" "name" decoda_name + lua_getglobal( L, "decoda_name"); // ... f ... decoda_name from = lua_tostring( L, -1); - (void) luaL_error( L, "%s '%s' not found in %s origin transfer database.", lua_tostring( L, -3), lua_tostring( L, -2), from ? from : "main"); + lua_pushcfunction( L, luaG_nameof); // ... f ... decoda_name luaG_nameof + lua_pushvalue( L, i); // ... f ... decoda_name luaG_nameof f + lua_call( L, 1, 2); // ... f ... decoda_name "type" "name"|nil + typewhat = (lua_type( L, -2) == LUA_TSTRING) ? lua_tostring( L, -2) : luaL_typename( L, -2); + // second return value can be nil if the function was not found + // probable reason: the function was removed from the source Lua state before Lanes was required. + if( lua_isnil( L, -1)) + { + gotchaA = " referenced by"; + gotchaB = "\n(did you remove it from the source Lua state before requiring Lanes?)"; + what = upName_; + } + else + { + gotchaB = ""; + what = (lua_type( L, -1) == LUA_TSTRING) ? lua_tostring( L, -1) : luaL_typename( L, -1); + } + (void) luaL_error( L, "%s%s '%s' not found in %s origin transfer database.%s", typewhat, gotchaA, what, from ? from : "main", gotchaB); return; } STACK_END( L, 0); @@ -1516,9 +1581,9 @@ static void lookup_native_func( lua_State* L2, lua_State* L, uint_t i) enum e_vt { VT_NORMAL, VT_KEY, VT_METATABLE }; -static bool_t inter_copy_one_( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i, enum e_vt value_type ); +static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt value_type, char const* upName_); -static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i) +static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, char const* upName_) { FuncSubType funcSubType; /*lua_CFunction cfunc =*/ luaG_tocfunction( L, i, &funcSubType); // NULL for LuaJIT-fast && bytecode functions @@ -1612,14 +1677,14 @@ static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uin * instead, the function shall have LUA_RIDX_GLOBALS taken in the destination state! */ { - DEBUGSPEW_CODE( char const* upname); + char const* upname; #if LUA_VERSION_NUM == 502 // With Lua 5.2, each Lua function gets its environment as one of its upvalues (named LUA_ENV, aka "_ENV" by default) // Generally this is LUA_RIDX_GLOBALS, which we don't want to copy from the source to the destination state... // -> if we encounter an upvalue equal to the global table in the source, bind it to the destination's global table lua_pushglobaltable( L); // ... _G #endif // LUA_VERSION_NUM - for( n = 0; (DEBUGSPEW_CODE( upname =) lua_getupvalue( L, i, 1 + n)) != NULL; ++ n) + for( n = 0; (upname = lua_getupvalue( L, i, 1 + n)) != NULL; ++ n) { // ... _G up[n] DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "UPNAME[%d]: %s\n" INDENT_END, n, upname)); #if LUA_VERSION_NUM == 502 @@ -1630,8 +1695,10 @@ static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uin else #endif // LUA_VERSION_NUM { - if( !inter_copy_one_( L2, L2_cache_i, L, lua_gettop( L), VT_NORMAL)) // ... {cache} ... function + if( !inter_copy_one_( L2, L2_cache_i, L, lua_gettop( L), VT_NORMAL, upname)) // ... {cache} ... function + { luaL_error( L, "Cannot copy upvalue type '%s'", luaL_typename( L, -1)); + } } lua_pop( L, 1); // ... _G } @@ -1664,7 +1731,7 @@ static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uin { lua_pop( L2, 1); // ... {cache} ... // No need to transfer upvalues for C/JIT functions since they weren't actually copied, only looked up - lookup_native_func( L2, L, i); // ... {cache} ... function + lookup_native_func( L2, L, i, upName_); // ... {cache} ... function } STACK_END( L, 0); } @@ -1679,7 +1746,7 @@ static void inter_copy_func( lua_State* L2, uint_t L2_cache_i, lua_State* L, uin * * Returns TRUE if value was pushed, FALSE if its type is non-supported. */ -static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt vt) +static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, enum e_vt vt, char const* upName_) { bool_t ret = TRUE; @@ -1762,25 +1829,9 @@ static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, u break; } { - /* - * Passing C functions is risky; if they refer to LUA_ENVIRONINDEX - * and/or LUA_REGISTRYINDEX they might work unintended (not work) - * at the target. - * - * On the other hand, NOT copying them causes many self tests not - * to work (timer, hangtest, ...) - * - * The trouble is, we cannot KNOW if the function at hand is safe - * or not. We cannot study it's behaviour. We could trust the user, - * but they might not even know they're sending lua_CFunction over - * (as upvalues etc.). - */ -#if 0 - if( lua_iscfunction( L, i)) - luaL_error( L, "Copying lua_CFunction between Lua states is risky, and currently disabled." ); -#endif + DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "FUNCTION\n" INDENT_END)); STACK_CHECK( L2); - push_cached_func( L2, L2_cache_i, L, i); + push_cached_func( L2, L2_cache_i, L, i, upName_); STACK_END( L2, 1); } break; @@ -1822,13 +1873,28 @@ static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, u /* Only basic key types are copied over; others ignored */ - if( inter_copy_one_( L2, 0 /*key*/, L, key_i, VT_KEY)) + if( inter_copy_one_( L2, 0 /*key*/, L, key_i, VT_KEY, upName_)) { + char* valPath = (char*) upName_; + if( GVerboseErrors) + { + // for debug purposes, let's try to build a useful name + if( lua_type( L, key_i) == LUA_TSTRING) + { + valPath = (char*) alloca( strlen( upName_) + strlen( lua_tostring( L, key_i)) + 2); + sprintf( valPath, "%s.%s", upName_, lua_tostring( L, key_i)); + } + else if( lua_type( L, key_i) == LUA_TNUMBER) + { + valPath = (char*) alloca( strlen( upName_) + 32 + 3); + sprintf( valPath, "%s[" LUA_NUMBER_FMT "]", upName_, lua_tonumber( L, key_i)); + } + } /* * Contents of metatables are copied with cache checking; * important to detect loops. */ - if( inter_copy_one_( L2, L2_cache_i, L, val_i, VT_NORMAL)) + if( inter_copy_one_( L2, L2_cache_i, L, val_i, VT_NORMAL, valPath)) { ASSERT_L( lua_istable(L2,-3)); lua_rawset( L2, -3); // add to table (pops key & val) @@ -1870,7 +1936,7 @@ static bool_t inter_copy_one_( lua_State* L2, uint_t L2_cache_i, lua_State* L, u lua_pop( L2, 1); STACK_MID( L2, 2); ASSERT_L( lua_istable(L,-1)); - if( inter_copy_one_( L2, L2_cache_i /*for function cacheing*/, L, lua_gettop(L) /*[-1]*/, VT_METATABLE)) + if( inter_copy_one_( L2, L2_cache_i /*for function cacheing*/, L, lua_gettop(L) /*[-1]*/, VT_METATABLE, upName_)) { // // L2 ([-3]: copied table) @@ -1942,7 +2008,9 @@ int luaG_inter_copy( lua_State* L, lua_State* L2, uint_t n) { uint_t top_L = lua_gettop( L); uint_t top_L2 = lua_gettop( L2); - uint_t i; + uint_t i, j; + char tmpBuf[16]; + char* pBuf = GVerboseErrors ? tmpBuf : "?"; bool_t copyok = TRUE; if( n > top_L) @@ -1960,9 +2028,13 @@ int luaG_inter_copy( lua_State* L, lua_State* L2, uint_t n) */ lua_newtable( L2); - for( i = top_L - n + 1; i <= top_L; ++ i) + for( i = top_L - n + 1, j = 1; i <= top_L; ++ i, ++ j) { - copyok = inter_copy_one_( L2, top_L2 + 1, L, i, VT_NORMAL); + if( GVerboseErrors) + { + sprintf( tmpBuf, "arg_%d", j); + } + copyok = inter_copy_one_( L2, top_L2 + 1, L, i, VT_NORMAL, pBuf); if( !copyok) { break; diff --git a/src/tools.h b/src/tools.h index 2fe7259..93ed92c 100644 --- a/src/tools.h +++ b/src/tools.h @@ -107,5 +107,8 @@ void populate_func_lookup_table( lua_State* L, int _i, char const* _name); void serialize_require( lua_State *L); extern MUTEX_T require_cs; +// for verbose errors +extern bool_t GVerboseErrors; + #endif // TOOLS_H diff --git a/tests/fifo.lua b/tests/fifo.lua index b68d8a4..47db4c9 100644 --- a/tests/fifo.lua +++ b/tests/fifo.lua @@ -4,10 +4,9 @@ -- Sample program for Lua Lanes -- -local lanes = require "lanes" -lanes.configure() +local lanes = require "lanes".configure{shutdown_timeout=3,with_timers=true} -local linda= lanes.linda() +local linda= lanes.linda( "atom") local atomic_inc= lanes.genatomic( linda, "FIFO_n" ) assert( atomic_inc()==1 ) @@ -46,3 +45,5 @@ print( B:receive( 2.0 ) ) -- Note: A and B can be passed between threads, or used as upvalues -- by multiple threads (other parts will be copied but the 'linda' -- handle is shared userdata and will thus point to the single place) +lanes.timer_lane:cancel() +lanes.timer_lane:join() \ No newline at end of file diff --git a/tests/linda_perf.lua b/tests/linda_perf.lua index be582ce..ebe9eac 100644 --- a/tests/linda_perf.lua +++ b/tests/linda_perf.lua @@ -84,8 +84,10 @@ local tests = { 4000000, 0, 21}, { 4000000, 0, 44},]] } +print "tests #1" for k, v in pairs( tests) do local pre, loop, batch = v[1], v[2], v[3] + print( "testing", pre, loop, batch) print( pre, loop, batch, "duration = " .. ziva( pre, loop, batch)) end @@ -188,8 +190,11 @@ local tests2 = { 4000000, 0, 21}, { 4000000, 0, 44}, } + +print "tests #2" for k, v in pairs( tests2) do local pre, loop, batch = v[1], v[2], v[3] + print( "testing", pre, loop, batch) print( pre, loop, batch, "duration = " .. ziva2( pre, loop, batch)) end diff --git a/tests/require.lua b/tests/require.lua index 1d081b4..656a7dd 100644 --- a/tests/require.lua +++ b/tests/require.lua @@ -4,9 +4,10 @@ -- Test that 'require' works from sublanes -- lanes = require "lanes" -lanes.configure() +lanes.configure{with_timers = false} local function a_lane() + print "IN A LANE" -- To require 'math' we still actually need to have it initialized for -- the lane. -- @@ -15,7 +16,7 @@ local function a_lane() assert( math.sqrt(4)==2 ) assert( lanes==nil ) - local lanes = require "lanes".configure() + local lanes = require "lanes".configure{with_timers = false} assert( lanes and lanes.gen ) local h= lanes.gen( function() return 42 end ) () @@ -24,7 +25,7 @@ local function a_lane() return v==42 end -local gen= lanes.gen( "math,package,string,table", a_lane ) +local gen= lanes.gen( "math,package,string,table", {package={}},a_lane ) local h= gen() local ret= h[1] -- cgit v1.2.3-55-g6feb