From 21d4f6679725eb19f800b134d7207cd0257302d0 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Tue, 2 Apr 2024 10:03:01 +0200 Subject: C++ migration: more atomics --- src/lanes.cpp | 50 +++++++++++++------------- src/macros_and_utils.h | 2 +- src/state.cpp | 34 +++++++++--------- src/tools.cpp | 95 ++++++++++++++++++++++++++------------------------ src/universe.h | 4 +-- 5 files changed, 94 insertions(+), 91 deletions(-) (limited to 'src') diff --git a/src/lanes.cpp b/src/lanes.cpp index 47ca79a..5fb81a3 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -434,7 +434,7 @@ static bool selfdestruct_remove(Lane* lane_) *ref = lane_->selfdestruct_next; lane_->selfdestruct_next = nullptr; // the terminal shutdown should wait until the lane is done with its lua_close() - ++lane_->U->selfdestructing_count; + lane_->U->selfdestructing_count.fetch_add(1, std::memory_order_release); found = true; break; } @@ -526,7 +526,7 @@ static int universe_gc( lua_State* L) // If some lanes are currently cleaning after themselves, wait until they are done. // They are no longer listed in the selfdestruct chain, but they still have to lua_close(). - while (U->selfdestructing_count > 0) + while (U->selfdestructing_count.load(std::memory_order_acquire) > 0) { YIELD(); } @@ -569,7 +569,7 @@ static int universe_gc( lua_State* L) // If some lanes are currently cleaning after themselves, wait until they are done. // They are no longer listed in the selfdestruct chain, but they still have to lua_close(). - while( U->selfdestructing_count > 0) + while (U->selfdestructing_count.load(std::memory_order_acquire) > 0) { YIELD(); } @@ -956,7 +956,7 @@ static THREAD_RETURN_T THREAD_CALLCONV lane_main(void* vs) lane->U->selfdestruct_cs.lock(); // done with lua_close(), terminal shutdown sequence may proceed - --lane->U->selfdestructing_count; + lane->U->selfdestructing_count.fetch_sub(1, std::memory_order_release); lane->U->selfdestruct_cs.unlock(); delete lane; @@ -1000,13 +1000,13 @@ LUAG_FUNC(require) DEBUGSPEW_CODE(Universe* U = universe_get(L)); STACK_CHECK_START_REL(L, 0); DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lanes.require %s BEGIN\n" INDENT_END, name)); - DEBUGSPEW_CODE(++U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); lua_pushvalue(L, lua_upvalueindex(1)); // "name" require lua_insert(L, 1); // require "name" lua_call(L, nargs, 1); // module populate_func_lookup_table(L, -1, name); DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lanes.require %s END\n" INDENT_END, name)); - DEBUGSPEW_CODE(--U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); STACK_CHECK(L, 0); return 1; } @@ -1026,10 +1026,10 @@ LUAG_FUNC(register) DEBUGSPEW_CODE(Universe* U = universe_get(L)); STACK_CHECK_START_REL(L, 0); // "name" mod_table DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lanes.register %s BEGIN\n" INDENT_END, name)); - DEBUGSPEW_CODE(++U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); populate_func_lookup_table(L, -1, name); DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lanes.register %s END\n" INDENT_END, name)); - DEBUGSPEW_CODE(--U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); STACK_CHECK(L, 0); return 0; } @@ -1076,7 +1076,7 @@ LUAG_FUNC(lane_new) /* --- Create and prepare the sub state --- */ DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: setup\n" INDENT_END)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); // populate with selected libraries at the same time lua_State* const L2{ luaG_newstate(U, L, libs_str) }; // L // L2 @@ -1162,6 +1162,7 @@ LUAG_FUNC(lane_new) void success() { prepareUserData(); + m_lane->m_ready.count_down(); m_lane = nullptr; } } onExit{ L, lane, gc_cb_idx }; @@ -1193,7 +1194,7 @@ LUAG_FUNC(lane_new) { int nbRequired = 1; DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: require 'required' list\n" INDENT_END)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); // should not happen, was checked in lanes.lua before calling lane_new() if (lua_type(L, required_idx) != LUA_TTABLE) { @@ -1238,7 +1239,7 @@ LUAG_FUNC(lane_new) lua_pop(L, 1); // func libs priority globals package required gc_cb [... args ...] n ++ nbRequired; } // func libs priority globals package required gc_cb [... args ...] - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); } STACK_CHECK(L, 0); STACK_CHECK(L2, 0); // @@ -1254,7 +1255,7 @@ LUAG_FUNC(lane_new) return luaL_error(L, "Expected table, got %s", luaL_typename(L, globals_idx)); } - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); lua_pushnil(L); // func libs priority globals package required gc_cb [... args ...] nil // Lua 5.2 wants us to push the globals table on the stack lua_pushglobaltable(L2); // _G @@ -1267,7 +1268,7 @@ LUAG_FUNC(lane_new) } // func libs priority globals package required gc_cb [... args ...] lua_pop( L2, 1); // - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); } STACK_CHECK(L, 0); STACK_CHECK(L2, 0); @@ -1275,11 +1276,11 @@ LUAG_FUNC(lane_new) // Lane main function if (lua_type(L, 1) == LUA_TFUNCTION) { - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane body\n" INDENT_END)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane body\n" INDENT_END)); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); lua_pushvalue(L, 1); // func libs priority globals package required gc_cb [... args ...] func int const res{ luaG_inter_move(U, L, L2, 1, LookupMode::LaneBody) };// func libs priority globals package required gc_cb [... args ...] // func - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); if (res != 0) { return luaL_error(L, "tried to copy unsupported types"); @@ -1302,10 +1303,10 @@ LUAG_FUNC(lane_new) if (nargs > 0) { int res; - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane arguments\n" INDENT_END)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane arguments\n" INDENT_END)); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); res = luaG_inter_move(U, L, L2, nargs, LookupMode::LaneBody); // func libs priority globals package required gc_cb // func [... args ...] - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); if (res != 0) { return luaL_error(L, "tried to copy unsupported types"); @@ -1323,8 +1324,7 @@ LUAG_FUNC(lane_new) onExit.success(); // we should have the lane userdata on top of the stack STACK_CHECK(L, 1); - lane->m_ready.count_down(); - DEBUGSPEW_CODE(--U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); return 1; } @@ -1922,13 +1922,13 @@ LUAG_FUNC(configure) STACK_GROW(L, 4); STACK_CHECK_START_ABS(L, 1); // settings - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "%p: lanes.configure() BEGIN\n" INDENT_END, L)); - DEBUGSPEW_CODE( if (U) ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "%p: lanes.configure() BEGIN\n" INDENT_END, L)); + DEBUGSPEW_CODE(if (U) U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); if(U == nullptr) { U = universe_create( L); // settings universe - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); lua_newtable( L); // settings universe mt lua_getfield(L, 1, "shutdown_timeout"); // settings universe mt shutdown_timeout lua_pushcclosure(L, universe_gc, 1); // settings universe mt universe_gc @@ -2070,7 +2070,7 @@ LUAG_FUNC(configure) CONFIG_REGKEY.setValue(L, [](lua_State* L) { lua_pushvalue(L, -2); }); STACK_CHECK(L, 1); DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "%p: lanes.configure() END\n" INDENT_END, L)); - DEBUGSPEW_CODE(--U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); // Return the settings table return 1; } diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h index 31027d6..e29e7fb 100644 --- a/src/macros_and_utils.h +++ b/src/macros_and_utils.h @@ -18,7 +18,7 @@ extern "C" { #if USE_DEBUG_SPEW() extern char const* debugspew_indent; #define INDENT_BEGIN "%.*s " -#define INDENT_END , (U ? U->debugspew_indent_depth : 0), debugspew_indent +#define INDENT_END , (U ? U->debugspew_indent_depth.load(std::memory_order_relaxed) : 0), debugspew_indent #define DEBUGSPEW_CODE(_code) _code #define DEBUGSPEW_PARAM_COMMA( param_) param_, #define DEBUGSPEW_COMMA_PARAM( param_) , param_ diff --git a/src/state.cpp b/src/state.cpp index 55540c8..512009a 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -200,8 +200,8 @@ static void copy_one_time_settings( Universe* U, lua_State* L, lua_State* L2) STACK_CHECK_START_REL(L, 0); STACK_CHECK_START_REL(L2, 0); - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "copy_one_time_settings()\n" INDENT_END)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "copy_one_time_settings()\n" INDENT_END)); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); CONFIG_REGKEY.pushValue(L); // config // copy settings from from source to destination registry @@ -213,7 +213,7 @@ static void copy_one_time_settings( Universe* U, lua_State* L, lua_State* L2) CONFIG_REGKEY.setValue(L2, [](lua_State* L) { lua_insert(L, -2); }); // config STACK_CHECK( L2, 0); STACK_CHECK( L, 0); - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); } void initialize_on_state_create( Universe* U, lua_State* L) @@ -349,8 +349,8 @@ lua_State* luaG_newstate( Universe* U, lua_State* from_, char const* libs_) return L; } - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "luaG_newstate()\n" INDENT_END)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "luaG_newstate()\n" INDENT_END)); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); // copy settings (for example because it may contain a Lua on_state_create function) copy_one_time_settings( U, from_, L); @@ -423,22 +423,22 @@ lua_State* luaG_newstate( Universe* U, lua_State* from_, char const* libs_) #if 0 && USE_DEBUG_SPEW() // dump the lookup database contents - lua_getfield( L, LUA_REGISTRYINDEX, LOOKUP_REGKEY); // {} - lua_pushnil( L); // {} nil - while( lua_next( L, -2)) // {} k v + lua_getfield(L, LUA_REGISTRYINDEX, LOOKUP_REGKEY); // {} + lua_pushnil(L); // {} nil + while (lua_next(L, -2)) // {} k v { - lua_getglobal( L, "print"); // {} k v print - lua_pushlstring( L, debugspew_indent, U->debugspew_indent_depth); // {} k v print " " - lua_pushvalue( L, -4); // {} k v print " " k - lua_pushvalue( L, -4); // {} k v print " " k v - lua_call( L, 3, 0); // {} k v - lua_pop( L, 1); // {} k + lua_getglobal(L, "print"); // {} k v print + lua_pushlstring(L, debugspew_indent, U->debugspew_indent_depth.load(std::memory_order_relaxed)); // {} k v print " " + lua_pushvalue(L, -4); // {} k v print " " k + lua_pushvalue(L, -4); // {} k v print " " k v + lua_call(L, 3, 0); // {} k v + lua_pop(L, 1); // {} k } - lua_pop( L, 1); // {} + lua_pop(L, 1); // {} #endif // USE_DEBUG_SPEW() - lua_pop( L, 1); + lua_pop(L, 1); STACK_CHECK(L, 0); - DEBUGSPEW_CODE(--U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); return L; } diff --git a/src/tools.cpp b/src/tools.cpp index df7602e..ac5f7c5 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -359,9 +359,9 @@ static void update_lookup_entry( DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* size_t prevNameLength, newNameLength; char const* prevName; - DEBUGSPEW_CODE( char const *newName); - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "update_lookup_entry()\n" INDENT_END)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(char const *newName); + DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "update_lookup_entry()\n" INDENT_END)); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); STACK_CHECK_START_REL(L, 0); // first, raise an error if the function is already known @@ -420,8 +420,8 @@ static void update_lookup_entry( DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* lua_rawseti( L, fqn, _depth); // ... {bfc} k } -- _depth; - STACK_CHECK( L, -1); - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + STACK_CHECK(L, -1); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); } // ################################################################################################# @@ -435,8 +435,8 @@ static void populate_func_lookup_table_recur(DEBUGSPEW_PARAM_COMMA(Universe* U) int const cache = _ctx_base + 2; // we need to remember subtables to process them after functions encountered at the current depth (breadth-first search) int const breadth_first_cache = lua_gettop( L) + 1; - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "populate_func_lookup_table_recur()\n" INDENT_END)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "populate_func_lookup_table_recur()\n" INDENT_END)); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); STACK_GROW( L, 6); // slot _i contains a table where we search for functions (or a full userdata with a metatable) @@ -457,8 +457,8 @@ static void populate_func_lookup_table_recur(DEBUGSPEW_PARAM_COMMA(Universe* U) STACK_CHECK( L, 0); if( visit_count > 0) { - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "already visited\n" INDENT_END)); - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "already visited\n" INDENT_END)); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); return; } @@ -513,7 +513,7 @@ static void populate_func_lookup_table_recur(DEBUGSPEW_PARAM_COMMA(Universe* U) { DEBUGSPEW_CODE( char const* key = (lua_type( L, -2) == LUA_TSTRING) ? lua_tostring( L, -2) : "not a string"); DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "table '%s'\n" INDENT_END, key)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); // un-visit this table in case we do need to process it lua_pushvalue( L, -1); // ... {_i} {bfc} k {} {} lua_rawget( L, cache); // ... {_i} {bfc} k {} n @@ -536,7 +536,7 @@ static void populate_func_lookup_table_recur(DEBUGSPEW_PARAM_COMMA(Universe* U) populate_func_lookup_table_recur( DEBUGSPEW_PARAM_COMMA( U) L, _ctx_base, lua_gettop( L), _depth); lua_pop( L, 1); // ... {_i} {bfc} k STACK_CHECK( L, 2); - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); } // remove table name from fqn stack lua_pushnil( L); // ... {_i} {bfc} nil @@ -546,7 +546,7 @@ static void populate_func_lookup_table_recur(DEBUGSPEW_PARAM_COMMA(Universe* U) lua_pop( L, 1); // ... {_i} STACK_CHECK( L, 0); // we are done // ... {_i} {bfc} - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); } // ################################################################################################# @@ -561,7 +561,7 @@ void populate_func_lookup_table( lua_State* L, int _i, char const* name_) int start_depth = 0; DEBUGSPEW_CODE( Universe* U = universe_get( L)); DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "%p: populate_func_lookup_table('%s')\n" INDENT_END, L, name_ ? name_ : "nullptr")); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); STACK_GROW( L, 3); STACK_CHECK_START_REL(L, 0); LOOKUP_REGKEY.pushValue(L); // {} @@ -612,7 +612,7 @@ void populate_func_lookup_table( lua_State* L, int _i, char const* name_) (void) luaL_error( L, "unsupported module type %s", lua_typename( L, lua_type( L, in_base))); } STACK_CHECK( L, 0); - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); } // ################################################################################################# @@ -1774,10 +1774,10 @@ static bool inter_copy_function(Universe* U, lua_State* L2, int L2_cache_i, lua_ } else // regular function { - DEBUGSPEW_CODE( fprintf( stderr, "FUNCTION %s\n", upName_)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(fprintf( stderr, "FUNCTION %s\n", upName_)); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); copy_cached_func( U, L2, L2_cache_i, L, source_i_, mode_, upName_); // ... f - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); } STACK_CHECK( L2, 1); STACK_CHECK( L, 0); @@ -1868,7 +1868,7 @@ bool inter_copy_one(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, in STACK_CHECK_START_REL(L2, 0); // L // L2 DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "inter_copy_one()\n" INDENT_END)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "%s %s: " INDENT_END, lua_type_names[val_type], vt_names[static_cast(vt_)])); // Non-POD can be skipped if its metatable contains { __lanesignore = true } @@ -1967,7 +1967,7 @@ bool inter_copy_one(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, in break; } - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); STACK_CHECK( L2, ret ? 1 : 0); STACK_CHECK( L, 0); @@ -1991,13 +1991,13 @@ int luaG_inter_copy(Universe* U, lua_State* L, lua_State* L2, int n, LookupMode bool copyok{ true }; DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "luaG_inter_copy()\n" INDENT_END)); - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); if( n > top_L) { // 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); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); return -1; } @@ -2026,7 +2026,7 @@ int luaG_inter_copy(Universe* U, lua_State* L, lua_State* L2, int n, LookupMode } STACK_CHECK( L, 0); - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); if( copyok) { @@ -2051,54 +2051,57 @@ int luaG_inter_move(Universe* U, lua_State* L, lua_State* L2, int n, LookupMode return ret; } -int luaG_inter_copy_package( Universe* U, lua_State* L, lua_State* L2, int package_idx_, LookupMode mode_) +int 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); + DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "luaG_inter_copy_package()\n" INDENT_END)); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); // package STACK_CHECK_START_REL(L, 0); STACK_CHECK_START_REL(L2, 0); - package_idx_ = lua_absindex( L, package_idx_); - if( lua_type( L, package_idx_) != LUA_TTABLE) + package_idx_ = lua_absindex(L, package_idx_); + if (lua_type(L, package_idx_) != LUA_TTABLE) { - lua_pushfstring( L, "expected package as table, got %s", luaL_typename( L, package_idx_)); - STACK_CHECK( L, 1); + lua_pushfstring(L, "expected package as table, got %s", luaL_typename(L, package_idx_)); + STACK_CHECK(L, 1); // raise the error when copying from lane to lane, else just leave it on the stack to be raised later return (mode_ == LookupMode::LaneBody) ? lua_error(L) : 1; } - lua_getglobal( L2, "package"); - if( !lua_isnil( L2, -1)) // package library not loaded: do nothing + lua_getglobal(L2, "package"); + if (!lua_isnil(L2, -1)) // package library not loaded: do nothing { - int i; // package.loaders is renamed package.searchers in Lua 5.2 // but don't copy it anyway, as the function names change depending on the slot index! // users should provide an on_state_create function to setup custom loaders instead // don't copy package.preload in keeper states (they don't know how to translate functions) char const* entries[] = { "path", "cpath", (mode_ == LookupMode::LaneBody) ? "preload" : nullptr /*, (LUA_VERSION_NUM == 501) ? "loaders" : "searchers"*/, nullptr }; - for( i = 0; entries[i]; ++ i) + for (char const* const entry : entries) { - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "package.%s\n" INDENT_END, entries[i])); - lua_getfield( L, package_idx_, entries[i]); - if( lua_isnil( L, -1)) + if (!entry) + { + continue; + } + DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "package.%s\n" INDENT_END, entry)); + lua_getfield(L, package_idx_, entry); + if (lua_isnil(L, -1)) { - lua_pop( L, 1); + lua_pop(L, 1); } else { - DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); - luaG_inter_move( U, L, L2, 1, mode_); // moves the entry to L2 - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); - lua_setfield( L2, -2, entries[i]); // set package[entries[i]] + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); + luaG_inter_move(U, L, L2, 1, mode_); // moves the entry to L2 + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); + lua_setfield(L2, -2, entry); // set package[entry] } } } else { - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "'package' not loaded, nothing to do\n" INDENT_END)); + DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "'package' not loaded, nothing to do\n" INDENT_END)); } - lua_pop( L2, 1); - STACK_CHECK( L2, 0); - STACK_CHECK( L, 0); - DEBUGSPEW_CODE( -- U->debugspew_indent_depth); + lua_pop(L2, 1); + STACK_CHECK(L2, 0); + STACK_CHECK(L, 0); + DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); return 0; } diff --git a/src/universe.h b/src/universe.h index 6a65888..38885cb 100644 --- a/src/universe.h +++ b/src/universe.h @@ -158,13 +158,13 @@ struct Universe std::atomic next_mt_id{ 1 }; #if USE_DEBUG_SPEW() - int debugspew_indent_depth{ 0 }; + std::atomic debugspew_indent_depth{ 0 }; #endif // USE_DEBUG_SPEW() Lane* volatile selfdestruct_first{ nullptr }; // After a lane has removed itself from the chain, it still performs some processing. // The terminal desinit sequence should wait for all such processing to terminate before force-killing threads - int volatile selfdestructing_count{ 0 }; + std::atomic selfdestructing_count{ 0 }; }; // ################################################################################################ -- cgit v1.2.3-55-g6feb From c64f9dcd61c1ad7bef3dbf5b7647a2a2da23ac0f Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Fri, 5 Apr 2024 08:49:48 +0200 Subject: Enable manual control of GC inside keeper states --- Makefile | 22 +++++++++--------- docs/index.html | 14 +++++++++++- src/keeper.cpp | 54 +++++++++++++++++++++++++++++++++++++------- src/keeper.h | 1 + src/lanes.lua | 5 ++++ src/linda.cpp | 17 ++++++++++---- tests/keeper.lua | 15 +++++++++++- tests/linda_perf.lua | 64 ++++++++++++++-------------------------------------- 8 files changed, 119 insertions(+), 73 deletions(-) (limited to 'src') diff --git a/Makefile b/Makefile index 868c481..0750401 100644 --- a/Makefile +++ b/Makefile @@ -72,25 +72,25 @@ rock: #--- Testing --- # test: - $(MAKE) errhangtest - $(MAKE) irayo_recursive - $(MAKE) irayo_closure + $(MAKE) atexit + $(MAKE) atomic $(MAKE) basic $(MAKE) cancel - $(MAKE) fifo - $(MAKE) keeper - $(MAKE) timer - $(MAKE) atomic $(MAKE) cyclic - $(MAKE) objects + $(MAKE) errhangtest $(MAKE) fibonacci - $(MAKE) recursive + $(MAKE) fifo $(MAKE) func_is_string - $(MAKE) atexit + $(MAKE) irayo_recursive + $(MAKE) irayo_closure + $(MAKE) keeper $(MAKE) linda_perf - $(MAKE) rupval + $(MAKE) objects $(MAKE) package $(MAKE) pingpong + $(MAKE) recursive + $(MAKE) rupval + $(MAKE) timer basic: tests/basic.lua $(_TARGET_SO) $(_PREFIX) $(LUA) $< diff --git a/docs/index.html b/docs/index.html index 24fa4ef..ee5acfa 100644 --- a/docs/index.html +++ b/docs/index.html @@ -290,6 +290,18 @@ + + + .keepers_gc_threshold + + integer + + If <0, GC runs automatically. This is the default.
+ If 0, GC runs after *every* keeper operation.
+ If >0, Keepers run GC manually with lua_gc(LUA_GCCOLLECT) whenever memory usage reported by lua_gc(LUA_GCCOUNT) reaches this threshold. Check is made after every keeper operation (see below). If memory usage remains above threshold after the GC cycle, an error is raised. + + + .with_timers @@ -1784,4 +1796,4 @@ int luaD_new_clonable( lua_State* L)

- \ No newline at end of file + diff --git a/src/keeper.cpp b/src/keeper.cpp index 244cb6a..937d190 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp @@ -652,12 +652,18 @@ void init_keepers(Universe* U, lua_State* L) { STACK_CHECK_START_REL(L, 0); // L K lua_getfield(L, 1, "nb_keepers"); // nb_keepers - int nb_keepers{ static_cast(lua_tointeger(L, -1)) }; + int const nb_keepers{ static_cast(lua_tointeger(L, -1)) }; lua_pop(L, 1); // if (nb_keepers < 1) { std::ignore = luaL_error(L, "Bad number of keepers (%d)", nb_keepers); } + STACK_CHECK(L, 0); + + lua_getfield(L, 1, "keepers_gc_threshold"); // keepers_gc_threshold + int const keepers_gc_threshold{ static_cast(lua_tointeger(L, -1)) }; + lua_pop(L, 1); // + STACK_CHECK(L, 0); // Keepers contains an array of 1 Keeper, adjust for the actual number of keeper states { @@ -668,6 +674,7 @@ void init_keepers(Universe* U, lua_State* L) std::ignore = luaL_error(L, "init_keepers() failed while creating keeper array; out of memory"); } memset(U->keepers, 0, bytes); + U->keepers->gc_threshold = keepers_gc_threshold; U->keepers->nb_keepers = nb_keepers; } for (int i = 0; i < nb_keepers; ++i) // keepersUD @@ -685,6 +692,11 @@ void init_keepers(Universe* U, lua_State* L) // therefore, we need a recursive mutex. MUTEX_RECURSIVE_INIT(&U->keepers->keeper_array[i].keeper_cs); + if (U->keepers->gc_threshold >= 0) + { + lua_gc(K, LUA_GCSTOP, 0); + } + STACK_CHECK_START_ABS(K, 0); // copy the universe pointer in the keeper itself @@ -735,8 +747,12 @@ void init_keepers(Universe* U, lua_State* L) 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 nullptr; } // ################################################################################################## @@ -745,11 +761,7 @@ 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 nullptr; - } - else + if (nbKeepers) { /* * Any hashing will do that maps pointers to 0..GNbKeepers-1 @@ -765,6 +777,7 @@ Keeper* keeper_acquire(Keepers* keepers_, uintptr_t magic_) //++ K->count; return K; } + return nullptr; } // ################################################################################################## @@ -843,5 +856,30 @@ int keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, voi } // 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)) [[unlikely]] + { + // 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) [[unlikely]] + { + lua_gc(K, LUA_GCSTEP, 0); + } + else if (gc_threshold > 0) [[likely]] + { + 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) [[unlikely]] + { + 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 e081bea..f7e3951 100644 --- a/src/keeper.h +++ b/src/keeper.h @@ -24,6 +24,7 @@ struct Keeper struct Keepers { + int gc_threshold{ 0 }; int nb_keepers; Keeper keeper_array[1]; }; diff --git a/src/lanes.lua b/src/lanes.lua index b4c0070..6af286a 100644 --- a/src/lanes.lua +++ b/src/lanes.lua @@ -70,6 +70,7 @@ lanes.configure = function( settings_) local default_params = { nb_keepers = 1, + keepers_gc_threshold = -1, on_state_create = nil, shutdown_timeout = 0.25, with_timers = true, @@ -91,6 +92,10 @@ lanes.configure = function( settings_) -- nb_keepers should be a number > 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 diff --git a/src/linda.cpp b/src/linda.cpp index 37a74b0..5ee4768 100644 --- a/src/linda.cpp +++ b/src/linda.cpp @@ -885,15 +885,22 @@ static void* linda_id( lua_State* L, DeepOp op_) { Linda* const linda{ lua_tolightuserdata(L, 1) }; ASSERT_L(linda); - - // Clean associated structures in the keeper state. - Keeper* const K{ keeper_acquire(linda->U->keepers, linda->hashSeed()) }; - if (K && K->L) // can be nullptr if this happens during main state shutdown (lanes is GC'ed -> no keepers -> no need to cleanup) + Keeper* const myK{ which_keeper(linda->U->keepers, linda->hashSeed()) }; + // if collected after the universe, keepers are already destroyed, and there is nothing to clear + 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 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->hashSeed()) : 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); + if (need_acquire_release) + { + keeper_release(K); + } } - keeper_release(K); delete linda; // operator delete overload ensures things go as expected return nullptr; diff --git a/tests/keeper.lua b/tests/keeper.lua index 9b38f02..6dbbd15 100644 --- a/tests/keeper.lua +++ b/tests/keeper.lua @@ -4,7 +4,7 @@ -- Test program for Lua Lanes -- -local lanes = require "lanes".configure{ with_timers = false, nb_keepers = 200} +local lanes = require "lanes".configure{ with_timers = false, nb_keepers = 1, keepers_gc_threshold = 500} do print "Linda names test:" @@ -12,7 +12,20 @@ do local unnamedLinda2 = lanes.linda("") local veeeerrrryyyylooongNamedLinda= lanes.linda( "veeeerrrryyyylooongNamedLinda", 1) print(unnamedLinda, unnamedLinda2, veeeerrrryyyylooongNamedLinda) + print "GC deadlock test start" + -- store a linda in another linda (-> in a keeper) + unnamedLinda:set("here", lanes.linda("temporary linda")) + -- repeatedly add and remove stuff in the linda so that a GC happens during the keeper operation + for i = 1, 1000 do + for j = 1, 1000 do -- send 1000 tables + unnamedLinda:send("here", {"a", "table", "with", "some", "stuff"}) + end + unnamedLinda:set("here") -- clear everything + end end +print "collecting garbage" +collectgarbage() +print "GC deadlock test done" local print_id = 0 local PRINT = function(...) diff --git a/tests/linda_perf.lua b/tests/linda_perf.lua index 9177852..c736428 100644 --- a/tests/linda_perf.lua +++ b/tests/linda_perf.lua @@ -1,5 +1,5 @@ local lanes = require "lanes" -lanes.configure{ with_timers = false } +lanes.configure{ with_timers = false, keepers_gc_threshold=20000 } -- set TEST1, PREFILL1, FILL1, TEST2, PREFILL2, FILL2 from the command line @@ -17,6 +17,8 @@ local finalizer = function(err, stk) end end +--################################################################################################## + -- this lane eats items in the linda one by one local eater = function( l, loop) set_finalizer(finalizer) @@ -32,6 +34,8 @@ local eater = function( l, loop) print("eater: done ("..val..")") end +--################################################################################################## + -- this lane eats items in the linda in batches local gobbler = function( l, loop, batch) set_finalizer(finalizer) @@ -47,9 +51,13 @@ local gobbler = function( l, loop, batch) print("gobbler: done ("..val..")") end +--################################################################################################## + local lane_eater_gen = lanes.gen( "*", {priority = 3}, eater) local lane_gobbler_gen = lanes.gen( "*", {priority = 3}, gobbler) +--################################################################################################## + -- main thread writes data while a lane reads it local function ziva( preloop, loop, batch) -- prefill the linda a bit to increase fifo stress @@ -94,6 +102,8 @@ local function ziva( preloop, loop, batch) return lanes.now_secs() - t1 end +--################################################################################################## + TEST1 = TEST1 or 1000 PREFILL1 = PREFILL1 or 10000 FILL1 = FILL1 or 2000000 @@ -109,6 +119,7 @@ local tests1 = { PREFILL1, FILL1, 13}, { PREFILL1, FILL1, 21}, { PREFILL1, FILL1, 44}, + { PREFILL1, FILL1, 65}, } print "############################################ tests #1" for i, v in ipairs( tests1) do @@ -119,38 +130,7 @@ for i, v in ipairs( tests1) do print("DURATION = " .. ziva( pre, loop, batch) .. "\n") end ---[[ - V 2.1.0: - ziva( 20000, 0) -> 4s ziva( 10000, 20000) -> 3s - ziva( 30000, 0) -> 8s ziva( 20000, 30000) -> 7s - ziva( 40000, 0) -> 15s ziva( 30000, 40000) -> 15s - ziva( 50000, 0) -> 24s ziva( 40000, 50000) -> 23s - ziva( 60000, 0) -> 34s ziva( 50000, 60000) -> 33s - - SIMPLIFIED: - ziva( 20000, 0) -> 4s ziva( 10000, 20000) -> 3s - ziva( 30000, 0) -> 9s ziva( 20000, 30000) -> 8s - ziva( 40000, 0) -> 15s ziva( 30000, 40000) -> 15s - ziva( 50000, 0) -> 25s ziva( 40000, 50000) -> 24s - ziva( 60000, 0) -> 35s ziva( 50000, 60000) -> 35s - - FIFO: - ziva( 2000000, 0) -> 9s ziva( 1000000, 2000000) -> 33s - ziva( 3000000, 0) -> 14s ziva( 2000000, 3000000) -> 40s - ziva( 4000000, 0) -> 20s ziva( 3000000, 4000000) -> 27s - ziva( 5000000, 0) -> 24s ziva( 4000000, 5000000) -> 42s - ziva( 6000000, 0) -> 29s ziva( 5000000, 6000000) -> 55s - - FIFO BATCHED: - ziva( 4000000, 0, 1) -> 20s - ziva( 4000000, 0, 2) -> 11s - ziva( 4000000, 0, 3) -> 7s - ziva( 4000000, 0, 5) -> 5s - ziva( 4000000, 0, 8) -> 3s - ziva( 4000000, 0, 13) -> 3s - ziva( 4000000, 0, 21) -> 3s - ziva( 4000000, 0, 44) -> 2s -]] +--################################################################################################## -- sequential write/read (no parallelization involved) local function ziva2( preloop, loop, batch) @@ -183,7 +163,7 @@ local function ziva2( preloop, loop, batch) for i = 1, preloop, step do batch_send() end - print( "stored " .. (l:count( "key") or 0) .. " items in the linda before starting consumer lane") + print( "stored " .. (l:count( "key") or 0) .. " items in the linda before starting the alternating reads and writes") -- loop that alternatively sends and reads data off the linda if loop > preloop then for i = preloop + 1, loop, step do @@ -198,25 +178,14 @@ local function ziva2( preloop, loop, batch) return lanes.now_secs() - t1 end +--################################################################################################## + TEST2 = TEST2 or 1000 PREFILL2 = PREFILL2 or 0 FILL2 = FILL2 or 4000000 local tests2 = { - -- prefill, then consume everything - --[[ - { 4000000, 0}, - { 4000000, 0, 1}, - { 4000000, 0, 2}, - { 4000000, 0, 3}, - { 4000000, 0, 5}, - { 4000000, 0, 8}, - { 4000000, 0, 13}, - { 4000000, 0, 21}, - { 4000000, 0, 44}, - --]] - -- alternatively fill and consume { PREFILL2, FILL2}, { PREFILL2, FILL2, 1}, { PREFILL2, FILL2, 2}, @@ -226,6 +195,7 @@ local tests2 = { PREFILL2, FILL2, 13}, { PREFILL2, FILL2, 21}, { PREFILL2, FILL2, 44}, + { PREFILL2, FILL2, 65}, } print "############################################ tests #2" -- cgit v1.2.3-55-g6feb From b6c00dc091ec43971a78b043ed57204beba59e9b Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Fri, 5 Apr 2024 09:45:00 +0200 Subject: C++ migration: one-time inits are sequenced with std::atomic_flag --- src/lanes.cpp | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/lanes.cpp b/src/lanes.cpp index 5fb81a3..08584a2 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -99,6 +99,8 @@ THE SOFTWARE. # include #endif +#include + // forwarding (will do things better later) static void tracking_add(Lane* lane_); @@ -1866,7 +1868,9 @@ static void init_once_LOCKED( void) // ################################################################################################# -static volatile long s_initCount = 0; +// we are C++20, the flags are default-initialized to 'clear' +std::atomic_flag s_insideInit; +std::atomic_flag s_initDone; // upvalue 1: module name // upvalue 2: module table @@ -1885,39 +1889,19 @@ LUAG_FUNC(configure) ** there is no problem. But if the host is multithreaded, we need to lock around the ** initializations. */ -#if THREADAPI == THREADAPI_WINDOWS + if (s_insideInit.test_and_set()) { - static volatile int /*bool*/ go_ahead; // = 0 - if (InterlockedCompareExchange(&s_initCount, 1, 0) == 0) - { - init_once_LOCKED(); - go_ahead = 1; // let others pass - } - else - { - while (!go_ahead) - { - Sleep(1); - } // changes threads - } + // blocks until flag value is no longer the one passed in parameter + s_initDone.wait(false); } -#else // THREADAPI == THREADAPI_PTHREAD - if (s_initCount == 0) + else { - static pthread_mutex_t my_lock = PTHREAD_MUTEX_INITIALIZER; - pthread_mutex_lock(&my_lock); - { - // Recheck now that we're within the lock - // - if (s_initCount == 0) - { - init_once_LOCKED(); - s_initCount = 1; - } - } - pthread_mutex_unlock(&my_lock); + // we are the first to enter here, because s_insideInit was false. + // and we are the only one, because it's now true. + init_once_LOCKED(); + std::ignore = s_initDone.test_and_set(); + s_initDone.notify_all(); } -#endif // THREADAPI == THREADAPI_PTHREAD STACK_GROW(L, 4); STACK_CHECK_START_ABS(L, 1); // settings -- cgit v1.2.3-55-g6feb From 96daea993eeea17f0c64325491943e48795ff751 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Mon, 8 Apr 2024 16:57:53 +0200 Subject: C++ migration: use std::jthread, std::condition_variable, std::chrono. win32 pthread support is gone new setting configure.shutdown_mode for cancellation of free-running threads at shutdown. no more hard thread termination! If a thread doesn't cooperate, an error is raised. lane.status "killed" is gone lane:cancel can't force-kill. --- docs/index.html | 49 +++-- src/cancel.cpp | 178 ++++++++--------- src/cancel.h | 8 +- src/keeper.cpp | 19 +- src/keeper.h | 10 +- src/lanes.cpp | 337 ++++++++++++------------------- src/lanes.lua | 451 +++++++++++++++++++++--------------------- src/lanes_private.h | 43 ++-- src/linda.cpp | 92 +++++---- src/macros_and_utils.h | 5 + src/threading.cpp | 525 +++---------------------------------------------- src/threading.h | 58 +----- tests/cancel.lua | 20 +- 13 files changed, 588 insertions(+), 1207 deletions(-) (limited to 'src') diff --git a/docs/index.html b/docs/index.html index ee5acfa..d24d3d7 100644 --- a/docs/index.html +++ b/docs/index.html @@ -425,7 +425,18 @@ number >= 0 - Sets the duration in seconds Lanes will wait for graceful termination of running lanes at application shutdown. Irrelevant for builds using pthreads. Default is 0.25. + Sets the duration in seconds Lanes will wait for graceful termination of running lanes at application shutdown. Default is 0.25. + + + + + .shutdown_mode + + + "hard"/"soft"/"call"/"ret"/"line"/"count" + + + Select the cancellation mode used at Lanes shutdown to request free running lane termination. See lane cancellation. Default is "hard". @@ -875,16 +886,6 @@ received cancellation and finished itself. - - - - "killed" - - - - was forcefully killed by lane_h:cancel() - -

@@ -996,36 +997,33 @@

Cancelling

-	bool[,reason] = lane_h:cancel( "soft" [, timeout] [, wake_bool])
-	bool[,reason] = lane_h:cancel( "hard" [, timeout] [, force [, forcekill_timeout]])
-	bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, force [, forcekill_timeout]])
+	bool[,reason] = lane_h:cancel( "soft" [, timeout] [, wake_lane])
+	bool[,reason] = lane_h:cancel( "hard" [, timeout] [, wake_lane])
+	bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, wake_lane])
 

cancel() sends a cancellation request to the lane.
- First argument is a mode can be one of "hard", "soft", "count", "line", "call", "ret". + First argument is a mode can be one of "hard", "soft", "call", "ret", "line", "count". If mode is not specified, it defaults to "hard". + If wake_lane is true, the lane is also signalled so that execution returns from any pending linda operation. Linda operations detecting the cancellation request return lanes.cancel_error.

If mode is "soft", cancellation will only cause cancel_test() to return true, so that the lane can cleanup manually.
- If wake_bool is true, the lane is also signalled so that execution returns from any pending linda operation. Linda operations detecting the cancellation request return lanes.cancel_error.

If mode is "hard", waits for the request to be processed, or a timeout to occur. Linda operations detecting the cancellation request will raise a special cancellation error (meaning they won't return in that case).
- timeout defaults to 0 if not specified. + wake_lane defaults to true, and timeout defaults to 0 if not specified.

Other values of mode will asynchronously install the corresponding hook, then behave as "hard".

-

- If force_kill_bool is true, forcekill_timeout can be set to tell how long lanes will wait for the OS thread to terminate before raising an error. Windows threads always terminate immediately, but it might not always be the case with some pthread implementations. -

Returns true, lane_h.status if lane was already done (in "done", "error" or "cancelled" status), or the cancellation was fruitful within timeout_secs timeout period.
Returns false, "timeout" otherwise.

- If the lane is still running after the timeout expired and force_kill is true, the OS thread running the lane is forcefully killed. This means no GC, probable OS resource leaks (thread stack, locks, DLL notifications), and should generally be the last resort. + If the lane is still running after the timeout expired, there is a chance lanes will raise an error at shutdown when failing to terminate all free-running lanes within the specified timeout.

Cancellation is tested before going to sleep in receive() or send() calls and after executing cancelstep Lua statements. A pending receive()or send() call is awakened. @@ -1396,6 +1394,14 @@ events to a common Linda, but... :). Default duration is null, which should only cause a thread context switch.

+
+	number = lanes.now_secs()
+
+ +

+ Returns the current value of the clock used by timers and lindas. +

+

Locks etc.

@@ -1797,3 +1803,4 @@ int luaD_new_clonable( lua_State* L) + \ No newline at end of file diff --git a/src/cancel.cpp b/src/cancel.cpp index 4667f07..6a94343 100644 --- a/src/cancel.cpp +++ b/src/cancel.cpp @@ -92,7 +92,7 @@ static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar) // ################################################################################################ //--- -// = thread_cancel( lane_ud [,timeout_secs=0.0] [,force_kill_bool=false] ) +// = thread_cancel( lane_ud [,timeout_secs=0.0] [,wake_lindas_bool=false] ) // // The originator thread asking us specifically to cancel the other thread. // @@ -100,9 +100,8 @@ static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar) // 0.0: just signal it to cancel, no time waited // >0: time to wait for the lane to detect cancellation // -// 'force_kill': if true, and lane does not detect cancellation within timeout, -// it is forcefully killed. Using this with 0.0 timeout means just kill -// (unless the lane is already finished). +// 'wake_lindas_bool': if true, signal any linda the thread is waiting on +// instead of waiting for its timeout (if any) // // Returns: true if the lane was already finished (DONE/ERROR_ST/CANCELLED) or if we // managed to cancel it. @@ -111,76 +110,47 @@ static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar) // ################################################################################################ -static CancelResult thread_cancel_soft(Lane* lane_, double secs_, bool wake_lindas_) +static CancelResult thread_cancel_soft(Lane* lane_, lua_Duration duration_, bool wake_lane_) { lane_->cancel_request = CancelRequest::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 - if (wake_lindas_) // wake the thread so that execution returns from any pending linda operation if desired + if (wake_lane_) // wake the thread so that execution returns from any pending linda operation if desired { - SIGNAL_T* const waiting_on{ lane_->waiting_on }; + std::condition_variable* const waiting_on{ lane_->m_waiting_on }; if (lane_->status == WAITING && waiting_on != nullptr) { - SIGNAL_ALL( waiting_on); + waiting_on->notify_all(); } } - return THREAD_WAIT(&lane_->thread, secs_, &lane_->done_signal, &lane_->done_lock, &lane_->status) ? CancelResult::Cancelled : CancelResult::Timeout; + return lane_->waitForCompletion(duration_) ? CancelResult::Cancelled : CancelResult::Timeout; } // ################################################################################################ -static CancelResult thread_cancel_hard(lua_State* L, Lane* lane_, double secs_, bool force_, double waitkill_timeout_) +static CancelResult thread_cancel_hard(Lane* lane_, lua_Duration duration_, bool wake_lane_) { lane_->cancel_request = CancelRequest::Hard; // it's now signaled to stop + //lane_->m_thread.get_stop_source().request_stop(); + if (wake_lane_) // wake the thread so that execution returns from any pending linda operation if desired { - SIGNAL_T* waiting_on = lane_->waiting_on; + std::condition_variable* waiting_on = lane_->m_waiting_on; if (lane_->status == WAITING && waiting_on != nullptr) { - SIGNAL_ALL( waiting_on); + waiting_on->notify_all(); } } - CancelResult result{ THREAD_WAIT(&lane_->thread, secs_, &lane_->done_signal, &lane_->done_lock, &lane_->status) ? CancelResult::Cancelled : CancelResult::Timeout }; - - if ((result == CancelResult::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(&lane_->thread); -#if THREADAPI == THREADAPI_PTHREAD - // pthread: make sure the thread is really stopped! - // note that this may block forever if the lane doesn't call a cancellation point and pthread doesn't honor PTHREAD_CANCEL_ASYNCHRONOUS - result = THREAD_WAIT(&lane_->thread, waitkill_timeout_, &lane_->done_signal, &lane_->done_lock, &lane_->status) ? CancelResult::Killed : CancelResult::Timeout; - if (result == CancelResult::Timeout) - { - std::ignore = luaL_error( L, "force-killed lane failed to terminate within %f second%s", waitkill_timeout_, waitkill_timeout_ > 1 ? "s" : ""); - } -#else - (void) waitkill_timeout_; // unused - (void) L; // unused -#endif // THREADAPI == THREADAPI_PTHREAD - lane_->mstatus = Lane::Killed; // mark 'gc' to wait for it - // note that lane_->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 = CancelResult::Killed; - } + CancelResult result{ lane_->waitForCompletion(duration_) ? CancelResult::Cancelled : CancelResult::Timeout }; return result; } // ################################################################################################ -CancelResult thread_cancel(lua_State* L, Lane* lane_, CancelOp op_, double secs_, bool force_, double waitkill_timeout_) +CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hook_count_, lua_Duration duration_, bool wake_lane_) { // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here // We can read 'lane_->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) - if (lane_->mstatus == Lane::Killed) - { - return CancelResult::Killed; - } - if (lane_->status >= DONE) { // say "ok" by default, including when lane is already done @@ -191,48 +161,57 @@ CancelResult thread_cancel(lua_State* L, Lane* lane_, CancelOp op_, double secs_ // let us hope we never land here with a pointer on a linda that has been destroyed... if (op_ == CancelOp::Soft) { - return thread_cancel_soft(lane_, secs_, force_); + return thread_cancel_soft(lane_, duration_, wake_lane_); + } + else if (static_cast(op_) > static_cast(CancelOp::Soft)) + { + lua_sethook(lane_->L, cancel_hook, static_cast(op_), hook_count_); } - return thread_cancel_hard(L, lane_, secs_, force_, waitkill_timeout_); + return thread_cancel_hard(lane_, duration_, wake_lane_); } // ################################################################################################ // ################################################################################################ -// > 0: the mask -// = 0: soft -// < 0: hard -static CancelOp which_op(lua_State* L, int idx_) +CancelOp which_cancel_op(char const* op_string_) +{ + CancelOp op{ CancelOp::Invalid }; + if (strcmp(op_string_, "hard") == 0) + { + op = CancelOp::Hard; + } + else if (strcmp(op_string_, "soft") == 0) + { + op = CancelOp::Soft; + } + else if (strcmp(op_string_, "call") == 0) + { + op = CancelOp::MaskCall; + } + else if (strcmp(op_string_, "ret") == 0) + { + op = CancelOp::MaskRet; + } + else if (strcmp(op_string_, "line") == 0) + { + op = CancelOp::MaskLine; + } + else if (strcmp(op_string_, "count") == 0) + { + op = CancelOp::MaskCount; + } + return op; +} + +// ################################################################################################ + +static CancelOp which_cancel_op(lua_State* L, int idx_) { if (lua_type(L, idx_) == LUA_TSTRING) { - CancelOp op{ CancelOp::Invalid }; - char const* str = lua_tostring(L, idx_); - if (strcmp(str, "hard") == 0) - { - op = CancelOp::Hard; - } - else if (strcmp(str, "soft") == 0) - { - op = CancelOp::Soft; - } - else if (strcmp(str, "call") == 0) - { - op = CancelOp::MaskCall; - } - else if (strcmp(str, "ret") == 0) - { - op = CancelOp::MaskRet; - } - else if (strcmp(str, "line") == 0) - { - op = CancelOp::MaskLine; - } - else if (strcmp(str, "count") == 0) - { - op = CancelOp::MaskCount; - } + char const* const str{ lua_tostring(L, idx_) }; + CancelOp op{ which_cancel_op(str) }; lua_remove(L, idx_); // argument is processed, remove it if (op == CancelOp::Invalid) { @@ -245,53 +224,60 @@ static CancelOp which_op(lua_State* L, int idx_) // ################################################################################################ -// bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, force [, forcekill_timeout]]) +// bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, wake_lindas]) LUAG_FUNC(thread_cancel) { Lane* const lane{ lua_toLane(L, 1) }; - CancelOp const op{ which_op(L, 2) }; // this removes the op string from the stack + CancelOp const op{ which_cancel_op(L, 2) }; // this removes the op string from the stack + int hook_count{ 0 }; if (static_cast(op) > static_cast(CancelOp::Soft)) // hook is requested { - int const hook_count{ static_cast(lua_tointeger(L, 2)) }; + hook_count = static_cast(luaL_checkinteger(L, 2)); lua_remove(L, 2); // argument is processed, remove it if (hook_count < 1) { return luaL_error(L, "hook count cannot be < 1"); } - lua_sethook(lane->L, cancel_hook, static_cast(op), hook_count); } - double secs{ 0.0 }; + lua_Duration wait_timeout{ 0.0 }; if (lua_type(L, 2) == LUA_TNUMBER) { - secs = lua_tonumber(L, 2); + wait_timeout = lua_Duration{ lua_tonumber(L, 2) }; lua_remove(L, 2); // argument is processed, remove it - if (secs < 0.0) + if (wait_timeout.count() < 0.0) { return luaL_error(L, "cancel timeout cannot be < 0"); } } - - bool const force{ lua_toboolean(L, 2) ? true : false }; // false if nothing there - double const forcekill_timeout{ luaL_optnumber(L, 3, 0.0) }; - switch (thread_cancel(L, lane, op, secs, force, forcekill_timeout)) + // we wake by default in "hard" mode (remember that hook is hard too), but this can be turned off if desired + bool wake_lane{ op != CancelOp::Soft }; + if (lua_gettop(L) >= 2) + { + if (!lua_isboolean(L, 2)) + { + return luaL_error(L, "wake_lindas parameter is not a boolean"); + } + wake_lane = lua_toboolean(L, 2); + lua_remove(L, 2); // argument is processed, remove it + } + switch (thread_cancel(lane, op, hook_count, wait_timeout, wake_lane)) { + default: // should never happen unless we added a case and forgot to handle it + ASSERT_L(false); + break; + case CancelResult::Timeout: lua_pushboolean(L, 0); lua_pushstring(L, "timeout"); - return 2; + break; case CancelResult::Cancelled: lua_pushboolean(L, 1); push_thread_status(L, lane); - return 2; - - case CancelResult::Killed: - lua_pushboolean(L, 1); - push_thread_status(L, lane); - return 2; + break; } // should never happen, only here to prevent the compiler from complaining of "not all control paths returning a value" - return 0; + return 2; } diff --git a/src/cancel.h b/src/cancel.h index 884e193..954b04e 100644 --- a/src/cancel.h +++ b/src/cancel.h @@ -13,6 +13,8 @@ extern "C" { #include "uniquekey.h" #include "macros_and_utils.h" +#include + // ################################################################################################ class Lane; // forward @@ -30,8 +32,7 @@ enum class CancelRequest enum class CancelResult { Timeout, - Cancelled, - Killed + Cancelled }; enum class CancelOp @@ -48,7 +49,8 @@ enum class CancelOp // crc64/we of string "CANCEL_ERROR" generated at http://www.nitrxgen.net/hashgen/ static constexpr UniqueKey CANCEL_ERROR{ 0xe97d41626cc97577ull }; // 'raise_cancel_error' sentinel -CancelResult thread_cancel(lua_State* L, Lane* lane_, CancelOp op_, double secs_, bool force_, double waitkill_timeout_); +CancelOp which_cancel_op(char const* op_string_); +CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hook_count_, lua_Duration secs_, bool wake_lindas_); [[noreturn]] static inline void raise_cancel_error(lua_State* L) { diff --git a/src/keeper.cpp b/src/keeper.cpp index 937d190..0aea18e 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp @@ -627,7 +627,7 @@ void close_keepers(Universe* U) } for (int i = 0; i < nbKeepers; ++i) { - MUTEX_FREE(&U->keepers->keeper_array[i].keeper_cs); + U->keepers->keeper_array[i].~Keeper(); } // free the keeper bookkeeping structure U->internal_allocator.free(U->keepers, sizeof(Keepers) + (nbKeepers - 1) * sizeof(Keeper)); @@ -673,9 +673,14 @@ void init_keepers(Universe* U, lua_State* L) { std::ignore = luaL_error(L, "init_keepers() failed while creating keeper array; out of memory"); } - memset(U->keepers, 0, bytes); + U->keepers->Keepers::Keepers(); U->keepers->gc_threshold = keepers_gc_threshold; U->keepers->nb_keepers = nb_keepers; + + for (int i = 0; i < nb_keepers; ++i) + { + U->keepers->keeper_array[i].Keeper::Keeper(); + } } for (int i = 0; i < nb_keepers; ++i) // keepersUD { @@ -687,10 +692,6 @@ 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); if (U->keepers->gc_threshold >= 0) { @@ -772,8 +773,7 @@ Keeper* keeper_acquire(Keepers* keepers_, uintptr_t magic_) */ unsigned int i = (unsigned int)((magic_ >> KEEPER_MAGIC_SHIFT) % nbKeepers); Keeper* K = &keepers_->keeper_array[i]; - - MUTEX_LOCK( &K->keeper_cs); + K->m_mutex.lock(); //++ K->count; return K; } @@ -787,7 +787,7 @@ void keeper_release(Keeper* K) //-- K->count; if (K) { - MUTEX_UNLOCK(&K->keeper_cs); + K->m_mutex.unlock(); } } @@ -843,7 +843,6 @@ int keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, voi if ((args == 0) || luaG_inter_copy(U, L, K, args, LookupMode::ToKeeper) == 0) // L->K { lua_call(K, 1 + args, LUA_MULTRET); - retvals = lua_gettop(K) - Ktos; // note that this can raise a luaL_error while the keeper state (and its mutex) is acquired // this may interrupt a lane, causing the destruction of the underlying OS thread diff --git a/src/keeper.h b/src/keeper.h index f7e3951..931c1d5 100644 --- a/src/keeper.h +++ b/src/keeper.h @@ -11,21 +11,23 @@ extern "C" { #include "threading.h" #include "uniquekey.h" +#include + // forwards enum class LookupMode; struct Universe; struct Keeper { - MUTEX_T keeper_cs; - lua_State* L; + std::mutex m_mutex; + lua_State* L{ nullptr }; // int count; }; struct Keepers { int gc_threshold{ 0 }; - int nb_keepers; + int nb_keepers{ 0 }; Keeper keeper_array[1]; }; @@ -38,7 +40,7 @@ void close_keepers(Universe* U); Keeper* which_keeper(Keepers* keepers_, uintptr_t magic_); Keeper* keeper_acquire(Keepers* keepers_, uintptr_t magic_); -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_, uintptr_t magic_); diff --git a/src/lanes.cpp b/src/lanes.cpp index 08584a2..4dd9b46 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -108,11 +108,6 @@ Lane::Lane(Universe* U_, lua_State* L_) : U{ U_ } , L{ L_ } { -#if THREADWAIT_METHOD == THREADWAIT_CONDVAR - MUTEX_INIT(&done_lock); - SIGNAL_INIT(&done_signal); -#endif // THREADWAIT_METHOD == THREADWAIT_CONDVAR - #if HAVE_LANE_TRACKING() if (U->tracking_first) { @@ -121,6 +116,29 @@ Lane::Lane(Universe* U_, lua_State* L_) #endif // HAVE_LANE_TRACKING() } +bool Lane::waitForCompletion(lua_Duration duration_) +{ + std::chrono::time_point until{ std::chrono::time_point::max() }; + if (duration_.count() >= 0.0) + { + until = std::chrono::steady_clock::now() + std::chrono::duration_cast(duration_); + } + + std::unique_lock lock{ m_done_mutex }; + //std::stop_token token{ m_thread.get_stop_token() }; + //return m_done_signal.wait_for(lock, token, secs_, [this](){ return status >= DONE; }); + return m_done_signal.wait_until(lock, until, [this](){ return status >= DONE; }); +} + +static void lane_main(Lane* lane); +void Lane::startThread(int priority_) +{ + m_thread = std::jthread([this]() { lane_main(this); }); + if (priority_ != THREAD_PRIO_DEFAULT) + { + JTHREAD_SET_PRIORITY(m_thread, priority_); + } +} /* Do you want full call stacks, or just the line where the error happened? * @@ -144,7 +162,7 @@ static void securize_debug_threadname(lua_State* L, Lane* lane_) } #if ERROR_FULL_STACK -static int lane_error( lua_State* L); +static int lane_error(lua_State* L); // crc64/we of string "STACKTRACE_REGKEY" generated at http://www.nitrxgen.net/hashgen/ static constexpr UniqueKey STACKTRACE_REGKEY{ 0x534af7d3226a429full }; #endif // ERROR_FULL_STACK @@ -255,11 +273,6 @@ Lane::~Lane() { // Clean up after a (finished) thread // -#if THREADWAIT_METHOD == THREADWAIT_CONDVAR - SIGNAL_FREE(&done_signal); - MUTEX_FREE(&done_lock); -#endif // THREADWAIT_METHOD == THREADWAIT_CONDVAR - #if HAVE_LANE_TRACKING() if (U->tracking_first != nullptr) { @@ -455,26 +468,27 @@ static bool selfdestruct_remove(Lane* lane_) static int universe_gc( lua_State* L) { Universe* const U{ lua_tofulluserdata(L, 1) }; + lua_Duration const shutdown_timeout{ lua_tonumber(L, lua_upvalueindex(1)) }; + [[maybe_unused]] char const* const op_string{ lua_tostring(L, lua_upvalueindex(2)) }; + CancelOp const op{ which_cancel_op(op_string) }; - while (U->selfdestruct_first != SELFDESTRUCT_END) // true at most once! + if (U->selfdestruct_first != SELFDESTRUCT_END) { + // Signal _all_ still running threads to exit (including the timer thread) // { std::lock_guard guard{ U->selfdestruct_cs }; Lane* lane{ U->selfdestruct_first }; + lua_Duration timeout{ 1us }; while (lane != SELFDESTRUCT_END) { - // attempt a regular unforced hard cancel with a small timeout - bool const cancelled{ THREAD_ISNULL(lane->thread) || thread_cancel(L, lane, CancelOp::Hard, 0.0001, false, 0.0) != CancelResult::Timeout }; - // if we failed, and we know the thread is waiting on a linda - if (cancelled == false && lane->status == WAITING && lane->waiting_on != nullptr) + // attempt the requested cancel with a small timeout. + // if waiting on a linda, they will raise a cancel_error. + // if a cancellation hook is desired, it will be installed to try to raise an error + if (lane->m_thread.joinable()) { - // signal the linda to 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... - SIGNAL_T* const waiting_on{ lane->waiting_on }; - // lane->waiting_on = nullptr; // useful, or not? - SIGNAL_ALL(waiting_on); + std::ignore = thread_cancel(lane, op, 1, timeout, true); } lane = lane->selfdestruct_next; } @@ -482,47 +496,32 @@ static int universe_gc( lua_State* L) // When noticing their cancel, the lanes will remove themselves from // the selfdestruct chain. - - // TBD: Not sure if Windows (multi core) will require the timed approach, - // or single Yield. I don't have machine to test that (so leaving - // for timed approach). -- AKa 25-Oct-2008 - - // OS X 10.5 (Intel) needs more to avoid segfaults. - // - // "make test" is okay. 100's of "make require" are okay. - // - // Tested on MacBook Core Duo 2GHz and 10.5.5: - // -- AKa 25-Oct-2008 - // { - lua_Number const shutdown_timeout = lua_tonumber(L, lua_upvalueindex(1)); - double const t_until = now_secs() + shutdown_timeout; + std::chrono::time_point t_until{ std::chrono::steady_clock::now() + std::chrono::duration_cast(shutdown_timeout) }; while (U->selfdestruct_first != SELFDESTRUCT_END) { - YIELD(); // give threads time to act on their cancel + // give threads time to act on their cancel + YIELD(); + // count the number of cancelled thread that didn't have the time to act yet + int n{ 0 }; { - // count the number of cancelled thread that didn't have the time to act yet - int n = 0; - double t_now = 0.0; + std::lock_guard guard{ U->selfdestruct_cs }; + Lane* lane{ U->selfdestruct_first }; + while (lane != SELFDESTRUCT_END) { - std::lock_guard guard{ U->selfdestruct_cs }; - Lane* lane{ U->selfdestruct_first }; - while (lane != SELFDESTRUCT_END) - { - if (lane->cancel_request == CancelRequest::Hard) - ++n; - lane = lane->selfdestruct_next; - } - } - // if timeout elapsed, or we know all threads have acted, stop waiting - t_now = now_secs(); - if (n == 0 || (t_now >= t_until)) - { - DEBUGSPEW_CODE(fprintf(stderr, "%d uncancelled lane(s) remain after waiting %fs at process end.\n", n, shutdown_timeout - (t_until - t_now))); - break; + if (lane->cancel_request != CancelRequest::None) + ++n; + lane = lane->selfdestruct_next; } } + // if timeout elapsed, or we know all threads have acted, stop waiting + std::chrono::time_point t_now = std::chrono::steady_clock::now(); + if (n == 0 || (t_now >= t_until)) + { + DEBUGSPEW_CODE(fprintf(stderr, "%d uncancelled lane(s) remain after waiting %fs at process end.\n", n, shutdown_timeout.count())); + break; + } } } @@ -532,48 +531,17 @@ static int universe_gc( lua_State* L) { YIELD(); } - - //--- - // Kill the still free running threads - // - if (U->selfdestruct_first != SELFDESTRUCT_END) - { - unsigned int n = 0; - // first thing we did was to raise the linda signals the threads were waiting on (if any) - // therefore, any well-behaved thread should be in CANCELLED state - // these are not running, and the state can be closed - { - std::lock_guard guard{ U->selfdestruct_cs }; - Lane* lane{ U->selfdestruct_first }; - while (lane != SELFDESTRUCT_END) - { - Lane* const next_s{ lane->selfdestruct_next }; - lane->selfdestruct_next = nullptr; // detach from selfdestruct chain - if (!THREAD_ISNULL(lane->thread)) // can be nullptr if previous 'soft' termination succeeded - { - THREAD_KILL(&lane->thread); -#if THREADAPI == THREADAPI_PTHREAD - // pthread: make sure the thread is really stopped! - THREAD_WAIT(&lane->thread, -1, &lane->done_signal, &lane->done_lock, &lane->status); -#endif // THREADAPI == THREADAPI_PTHREAD - } - // NO lua_close() in this case because we don't know where execution of the state was interrupted - delete lane; - lane = next_s; - ++n; - } - U->selfdestruct_first = SELFDESTRUCT_END; - } - - DEBUGSPEW_CODE(fprintf(stderr, "Killed %d lane(s) at process end.\n", n)); - } } - // If some lanes are currently cleaning after themselves, wait until they are done. - // They are no longer listed in the selfdestruct chain, but they still have to lua_close(). - while (U->selfdestructing_count.load(std::memory_order_acquire) > 0) + // If after all this, we still have some free-running lanes, it's an external user error, they should have stopped appropriately { - YIELD(); + std::lock_guard guard{ U->selfdestruct_cs }; + Lane* lane{ U->selfdestruct_first }; + if (lane != SELFDESTRUCT_END) + { + // this causes a leak because we don't call U's destructor (which could be bad if the still running lanes are accessing it) + std::ignore = luaL_error(L, "Zombie thread %s refuses to die!", lane->debug_name); + } } // necessary so that calling free_deep_prelude doesn't crash because linda_id expects a linda lightuserdata at absolute slot 1 @@ -874,20 +842,8 @@ static char const* get_errcode_name( int _code) } #endif // USE_DEBUG_SPEW() -#if THREADWAIT_METHOD == THREADWAIT_CONDVAR // implies THREADAPI == THREADAPI_PTHREAD -static void thread_cleanup_handler(void* opaque) +static void lane_main(Lane* lane) { - Lane* lane{ (Lane*) opaque }; - MUTEX_LOCK(&lane->done_lock); - lane->status = CANCELLED; - SIGNAL_ONE(&lane->done_signal); // wake up master (while 'lane->done_lock' is on) - MUTEX_UNLOCK(&lane->done_lock); -} -#endif // THREADWAIT_METHOD == THREADWAIT_CONDVAR - -static THREAD_RETURN_T THREAD_CALLCONV lane_main(void* vs) -{ - Lane* lane{ (Lane*) vs }; lua_State* const L{ lane->L }; // wait until the launching thread has finished preparing L lane->m_ready.wait(); @@ -897,8 +853,6 @@ static THREAD_RETURN_T THREAD_CALLCONV lane_main(void* vs) // At this point, the lane function and arguments are on the stack int const nargs{ lua_gettop(L) - 1 }; DEBUGSPEW_CODE(Universe* U = universe_get(L)); - THREAD_MAKE_ASYNCH_CANCELLABLE(); - THREAD_CLEANUP_PUSH(thread_cleanup_handler, lane); lane->status = RUNNING; // PENDING -> RUNNING // Tie "set_finalizer()" to the state @@ -949,18 +903,19 @@ static THREAD_RETURN_T THREAD_CALLCONV lane_main(void* vs) // the finalizer generated an error, and left its own error message [and stack trace] on the stack rc = rc2; // we're overruling the earlier script error or normal return } - lane->waiting_on = nullptr; // just in case + lane->m_waiting_on = nullptr; // just in case if (selfdestruct_remove(lane)) // check and remove (under lock!) { // We're a free-running thread and no-one's there to clean us up. - // lua_close(lane->L); - + lane->L = nullptr; // just in case lane->U->selfdestruct_cs.lock(); // done with lua_close(), terminal shutdown sequence may proceed lane->U->selfdestructing_count.fetch_sub(1, std::memory_order_release); lane->U->selfdestruct_cs.unlock(); + // we destroy our jthread member from inside the thread body, so we have to detach so that we don't try to join, as this doesn't seem a good idea + lane->m_thread.detach(); delete lane; lane = nullptr; } @@ -972,21 +927,14 @@ static THREAD_RETURN_T THREAD_CALLCONV lane_main(void* vs) enum e_status st = (rc == 0) ? DONE : CANCEL_ERROR.equals(L, 1) ? CANCELLED : ERROR_ST; // Posix no PTHREAD_TIMEDJOIN: - // 'done_lock' protects the -> DONE|ERROR_ST|CANCELLED state change + // 'm_done_mutex' protects the -> DONE|ERROR_ST|CANCELLED state change // -#if THREADWAIT_METHOD == THREADWAIT_CONDVAR - MUTEX_LOCK(&lane->done_lock); { -#endif // THREADWAIT_METHOD == THREADWAIT_CONDVAR + std::lock_guard lock{ lane->m_done_mutex }; lane->status = st; -#if THREADWAIT_METHOD == THREADWAIT_CONDVAR - SIGNAL_ONE(&lane->done_signal); // wake up master (while 'lane->done_lock' is on) + lane->m_done_signal.notify_one();// wake up master (while 'lane->m_done_mutex' is on) } - MUTEX_UNLOCK(&lane->done_lock); -#endif // THREADWAIT_METHOD == THREADWAIT_CONDVAR } - THREAD_CLEANUP_POP(false); - return 0; // ignored } // ################################################################################################# @@ -1115,13 +1063,11 @@ LUAG_FUNC(lane_new) // leave a single cancel_error on the stack for the caller lua_settop(m_lane->L, 0); CANCEL_ERROR.pushKey(m_lane->L); -#if THREADWAIT_METHOD == THREADWAIT_CONDVAR - MUTEX_LOCK(&m_lane->done_lock); -#endif // THREADWAIT_METHOD == THREADWAIT_CONDVAR - m_lane->status = CANCELLED; -#if THREADWAIT_METHOD == THREADWAIT_CONDVAR - MUTEX_UNLOCK(&m_lane->done_lock); -#endif // THREADWAIT_METHOD == THREADWAIT_CONDVAR + { + std::lock_guard lock{ m_lane->m_done_mutex }; + m_lane->status = CANCELLED; + m_lane->m_done_signal.notify_one(); // wake up master (while 'lane->m_done_mutex' is on) + } // unblock the thread so that it can terminate gracefully m_lane->m_ready.count_down(); } @@ -1170,7 +1116,7 @@ LUAG_FUNC(lane_new) } onExit{ L, lane, gc_cb_idx }; // launch the thread early, it will sync with a std::latch to parallelize OS thread warmup and L2 preparation DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: launching thread\n" INDENT_END)); - THREAD_CREATE(&lane->thread, lane_main, lane, priority); + lane->startThread(priority); STACK_GROW( L2, nargs + 3); // STACK_CHECK_START_REL(L2, 0); @@ -1347,7 +1293,7 @@ LUAG_FUNC(lane_new) static int lane_gc(lua_State* L) { bool have_gc_cb{ false }; - Lane* lane{ lua_toLane(L, 1) }; // ud + Lane* const lane{ lua_toLane(L, 1) }; // ud // if there a gc callback? lua_getiuservalue(L, 1, 1); // ud uservalue @@ -1365,30 +1311,7 @@ static int lane_gc(lua_State* L) } // We can read 'lane->status' without locks, but not wait for it - // test Killed state first, as it doesn't need to enter the selfdestruct chain - if (lane->mstatus == Lane::Killed) - { - // Make sure a kill has proceeded, before cleaning up the data structure. - // - // NO lua_close() in this case because we don't know where execution of the state was interrupted - DEBUGSPEW_CODE(fprintf(stderr, "** Joining with a killed thread (needs testing) **")); - // make sure the thread is no longer running, just like thread_join() - if (!THREAD_ISNULL(lane->thread)) - { - THREAD_WAIT(&lane->thread, -1, &lane->done_signal, &lane->done_lock, &lane->status); - } - if (lane->status >= DONE && lane->L) - { - // we know the thread was killed while the Lua VM was not doing anything: we should be able to close it without crashing - // now, thread_cancel() will not forcefully kill a lane with lane->status >= DONE, so I am not sure it can ever happen - lua_close(lane->L); - lane->L = nullptr; - // just in case, but s will be freed soon so... - lane->debug_name = ""; - } - DEBUGSPEW_CODE(fprintf(stderr, "** Joined ok **")); - } - else if (lane->status < DONE) + if (lane->status < DONE) { // still running: will have to be cleaned up later selfdestruct_add(lane); @@ -1437,7 +1360,6 @@ static char const * thread_status_string(Lane* lane_) { enum e_status const st{ lane_->status }; // read just once (volatile) char const* str = - (lane_->mstatus == Lane::Killed) ? "killed" : // new to v3.3.0! (st == PENDING) ? "pending" : (st == RUNNING) ? "running" : // like in 'co.status()' (st == WAITING) ? "waiting" : @@ -1471,9 +1393,10 @@ int push_thread_status(lua_State* L, Lane* lane_) LUAG_FUNC(thread_join) { Lane* const lane{ lua_toLane(L, 1) }; - lua_Number const wait_secs{ luaL_optnumber(L, 2, -1.0) }; + lua_Duration const duration{ luaL_optnumber(L, 2, -1.0) }; lua_State* const L2{ lane->L }; - bool const done{ THREAD_ISNULL(lane->thread) || THREAD_WAIT(&lane->thread, wait_secs, &lane->done_signal, &lane->done_lock, &lane->status) }; + + bool const done{ !lane->m_thread.joinable() || lane->waitForCompletion(duration) }; if (!done || !L2) { STACK_GROW(L, 2); @@ -1486,58 +1409,47 @@ LUAG_FUNC(thread_join) // Thread is DONE/ERROR_ST/CANCELLED; all ours now int ret{ 0 }; - if (lane->mstatus == Lane::Killed) // OS thread was killed if thread_cancel was forced - { - // in that case, even if the thread was killed while DONE/ERROR_ST/CANCELLED, ignore regular return values - STACK_GROW(L, 2); - lua_pushnil(L); - lua_pushliteral(L, "killed"); - ret = 2; - } - else + Universe* const U{ lane->U }; + // debug_name is a pointer to string possibly interned in the lane's state, that no longer exists when the state is closed + // so store it in the userdata uservalue at a key that can't possibly collide + securize_debug_threadname(L, lane); + switch (lane->status) { - Universe* const U{ lane->U }; - // debug_name is a pointer to string possibly interned in the lane's state, that no longer exists when the state is closed - // so store it in the userdata uservalue at a key that can't possibly collide - securize_debug_threadname(L, lane); - switch (lane->status) + case DONE: { - case DONE: + int const n{ lua_gettop(L2) }; // whole L2 stack + if ((n > 0) && (luaG_inter_move(U, L2, L, n, LookupMode::LaneBody) != 0)) { - int const n{ lua_gettop(L2) }; // whole L2 stack - if ((n > 0) && (luaG_inter_move(U, L2, L, n, LookupMode::LaneBody) != 0)) - { - return luaL_error(L, "tried to copy unsupported types"); - } - ret = n; + return luaL_error(L, "tried to copy unsupported types"); } - break; + ret = n; + } + break; - case ERROR_ST: + case ERROR_ST: + { + int const n{ lua_gettop(L2) }; + 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, LookupMode::LaneBody) != 0) // nil "err" [trace] { - int const n{ lua_gettop(L2) }; - 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, LookupMode::LaneBody) != 0) // nil "err" [trace] - { - return luaL_error(L, "tried to copy unsupported types: %s", lua_tostring(L, -n)); - } - ret = 1 + n; + return luaL_error(L, "tried to copy unsupported types: %s", lua_tostring(L, -n)); } - break; + ret = 1 + n; + } + break; - case CANCELLED: - ret = 0; - break; + case CANCELLED: + ret = 0; + break; - default: - DEBUGSPEW_CODE(fprintf(stderr, "Status: %d\n", lane->status)); - ASSERT_L(false); - ret = 0; - } - lua_close(L2); + default: + DEBUGSPEW_CODE(fprintf(stderr, "Status: %d\n", lane->status)); + ASSERT_L(false); + ret = 0; } + lua_close(L2); lane->L = nullptr; STACK_CHECK(L, ret); return ret; @@ -1596,15 +1508,12 @@ LUAG_FUNC(thread_index) switch (lane->status) { default: - if (lane->mstatus != Lane::Killed) - { - // this is an internal error, we probably never get here - lua_settop(L, 0); - lua_pushliteral(L, "Unexpected status: "); - lua_pushstring(L, thread_status_string(lane)); - lua_concat(L, 2); - raise_lua_error(L); - } + // this is an internal error, we probably never get here + lua_settop(L, 0); + lua_pushliteral(L, "Unexpected status: "); + lua_pushstring(L, thread_status_string(lane)); + lua_concat(L, 2); + raise_lua_error(L); [[fallthrough]]; // fall through if we are killed, as we got nil, "killed" on the stack case DONE: // got regular return values @@ -1790,8 +1699,7 @@ LUAG_FUNC(wakeup_conv) lua_pop(L,1); STACK_CHECK(L, 0); - struct tm t; - memset(&t, 0, sizeof(t)); + std::tm t{}; t.tm_year = year - 1900; t.tm_mon= month-1; // 0..11 t.tm_mday= day; // 1..31 @@ -1800,7 +1708,7 @@ LUAG_FUNC(wakeup_conv) t.tm_sec= sec; // 0..60 t.tm_isdst= isdst; // 0/1/negative - lua_pushnumber(L, static_cast(mktime(&t))); // ms=0 + lua_pushnumber(L, static_cast(std::mktime(&t))); // resolution: 1 second return 1; } @@ -1909,13 +1817,14 @@ LUAG_FUNC(configure) DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "%p: lanes.configure() BEGIN\n" INDENT_END, L)); DEBUGSPEW_CODE(if (U) U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); - if(U == nullptr) + if (U == nullptr) { - U = universe_create( L); // settings universe + U = universe_create(L); // settings universe DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); lua_newtable( L); // settings universe mt lua_getfield(L, 1, "shutdown_timeout"); // settings universe mt shutdown_timeout - lua_pushcclosure(L, universe_gc, 1); // settings universe mt universe_gc + lua_getfield(L, 1, "shutdown_mode"); // settings universe mt shutdown_timeout shutdown_mode + lua_pushcclosure(L, universe_gc, 2); // settings universe mt universe_gc lua_setfield(L, -2, "__gc"); // settings universe mt lua_setmetatable(L, -2); // settings universe lua_pop(L, 1); // settings diff --git a/src/lanes.lua b/src/lanes.lua index 6af286a..fd3d22b 100644 --- a/src/lanes.lua +++ b/src/lanes.lua @@ -73,6 +73,7 @@ lanes.configure = function( settings_) keepers_gc_threshold = -1, on_state_create = nil, shutdown_timeout = 0.25, + shutdown_mode = "hard", with_timers = true, track_lanes = false, demote_full_userdata = nil, @@ -113,6 +114,11 @@ lanes.configure = function( settings_) -- shutdown_timeout should be a number >= 0 return type( val_) == "number" and val_ >= 0 end, + shutdown_mode = function( val_) + local valid_hooks = { soft = true, hard = true, call = true, ret = true, line = true, count = true } + -- shutdown_mode should be a known hook mask + return valid_hooks[val_] + end, track_lanes = boolean_param_checker, demote_full_userdata = boolean_param_checker, verbose_errors = boolean_param_checker @@ -367,262 +373,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" - -- - -- 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) - - -- - -- 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) + return r + end -- get_timers() - local linda_deep = linda:deep() - assert( linda_deep) - - -- 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 bcc3014..01d43c0 100644 --- a/src/lanes_private.h +++ b/src/lanes_private.h @@ -4,27 +4,26 @@ #include "uniquekey.h" #include "universe.h" +#include +#include #include +#include +#include // NOTE: values to be changed by either thread, during execution, without // locking, are marked "volatile" // class Lane { - private: - - enum class ThreadStatus - { - Normal, // normal master side state - Killed // issued an OS kill - }; - public: - using enum ThreadStatus; - - THREAD_T thread; + // the thread + std::jthread m_thread; + // a latch to wait for the lua_State to be ready std::latch m_ready{ 1 }; + // to wait for stop requests through m_thread's stop_source + std::mutex m_done_mutex; + std::condition_variable m_done_signal; // use condition_variable_any if waiting for a stop_token // // M: sub-thread OS thread // S: not used @@ -42,7 +41,7 @@ class Lane // M: sets to PENDING (before launching) // S: updates -> RUNNING/WAITING -> DONE/ERROR_ST/CANCELLED - SIGNAL_T* volatile waiting_on{ nullptr }; + std::condition_variable* volatile m_waiting_on{ nullptr }; // // When status is WAITING, points on the linda's signal the thread waits on, else nullptr @@ -51,23 +50,6 @@ class Lane // M: sets to false, flags true for cancel request // S: reads to see if cancel is requested -#if THREADWAIT_METHOD == THREADWAIT_CONDVAR - SIGNAL_T done_signal; - // - // M: Waited upon at lane ending (if Posix with no PTHREAD_TIMEDJOIN) - // S: sets the signal once cancellation is noticed (avoids a kill) - - MUTEX_T done_lock; - // - // Lock required by 'done_signal' condition variable, protecting - // lane status changes to DONE/ERROR_ST/CANCELLED. -#endif // THREADWAIT_METHOD == THREADWAIT_CONDVAR - - volatile ThreadStatus mstatus{ Normal }; - // - // M: sets to Normal, if issued a kill changes to Killed - // S: not used - Lane* volatile selfdestruct_next{ nullptr }; // // M: sets to non-nullptr if facing lane handle '__gc' cycle but the lane @@ -88,6 +70,9 @@ class Lane Lane(Universe* U_, lua_State* L_); ~Lane(); + + bool waitForCompletion(lua_Duration duration_); + void startThread(int priority_); }; // xxh64 of string "LANE_POINTER_REGKEY" generated at https://www.pelock.com/products/hash-calculator diff --git a/src/linda.cpp b/src/linda.cpp index 5ee4768..ea1410e 100644 --- a/src/linda.cpp +++ b/src/linda.cpp @@ -61,8 +61,8 @@ class Linda : public DeepPrelude // Deep userdata MUST start with this header public: - SIGNAL_T read_happened; - SIGNAL_T write_happened; + std::condition_variable m_read_happened; + std::condition_variable m_write_happened; Universe* const U; // the universe this linda belongs to uintptr_t const group; // a group to control keeper allocation between lindas CancelRequest simulate_cancel{ CancelRequest::None }; @@ -81,17 +81,11 @@ class Linda : public DeepPrelude // Deep userdata MUST start with this header : U{ U_ } , group{ group_ << KEEPER_MAGIC_SHIFT } { - SIGNAL_INIT(&read_happened); - SIGNAL_INIT(&write_happened); - setName(name_, len_); } ~Linda() { - // There aren't any lanes waiting on these lindas, since all proxies have been gc'ed. Right? - SIGNAL_FREE(&read_happened); - SIGNAL_FREE(&write_happened); if (std::holds_alternative(m_name)) { AllocatedName& name = std::get(m_name); @@ -216,15 +210,19 @@ LUAG_FUNC(linda_protected_call) LUAG_FUNC(linda_send) { Linda* const linda{ lua_toLinda(L, 1) }; - time_d timeout{ -1.0 }; + std::chrono::time_point until{ std::chrono::time_point::max() }; int key_i{ 2 }; // index of first key, if timeout not there if (lua_type(L, 2) == LUA_TNUMBER) // we don't want to use lua_isnumber() because of autocoercion { - timeout = SIGNAL_TIMEOUT_PREPARE(lua_tonumber(L, 2)); + lua_Duration const duration{ lua_tonumber(L, 2) }; + if (duration.count() >= 0.0) + { + until = std::chrono::steady_clock::now() + std::chrono::duration_cast(duration); + } ++key_i; } - else if (lua_isnil(L, 2)) // alternate explicit "no timeout" by passing nil before the key + else if (lua_isnil(L, 2)) // alternate explicit "infinite timeout" by passing nil before the key { ++key_i; } @@ -266,6 +264,7 @@ LUAG_FUNC(linda_send) lua_State* const KL{ K ? K->L : nullptr }; if (KL == nullptr) return 0; + STACK_CHECK_START_REL(KL, 0); for (bool try_again{ true };;) { @@ -295,12 +294,12 @@ LUAG_FUNC(linda_send) if (ret) { // Wake up ALL waiting threads - SIGNAL_ALL(&linda->write_happened); + linda->m_write_happened.notify_all(); break; } // instant timout to bypass the wait syscall - if (timeout == 0.0) + if (std::chrono::steady_clock::now() >= until) { break; /* no wait; instant timeout */ } @@ -314,14 +313,17 @@ LUAG_FUNC(linda_send) prev_status = lane->status; // RUNNING, most likely ASSERT_L(prev_status == RUNNING); // but check, just in case lane->status = WAITING; - ASSERT_L(lane->waiting_on == nullptr); - lane->waiting_on = &linda->read_happened; + ASSERT_L(lane->m_waiting_on == nullptr); + lane->m_waiting_on = &linda->m_read_happened; } // could not send because no room: wait until some data was read before trying again, or until timeout is reached - try_again = SIGNAL_WAIT(&linda->read_happened, &K->keeper_cs, timeout); + std::unique_lock keeper_lock{ K->m_mutex, std::adopt_lock }; + std::cv_status const status{ linda->m_read_happened.wait_until(keeper_lock, until) }; + keeper_lock.release(); // we don't want to release the lock! + try_again = (status == std::cv_status::no_timeout); // detect spurious wakeups if (lane != nullptr) { - lane->waiting_on = nullptr; + lane->m_waiting_on = nullptr; lane->status = prev_status; } } @@ -369,21 +371,24 @@ static constexpr UniqueKey BATCH_SENTINEL{ 0x2DDFEE0968C62AA7ull }; LUAG_FUNC(linda_receive) { Linda* const linda{ lua_toLinda(L, 1) }; - - time_d timeout{ -1.0 }; - int key_i{ 2 }; + std::chrono::time_point until{ std::chrono::time_point::max() }; + int key_i{ 2 }; // index of first key, if timeout not there if (lua_type(L, 2) == LUA_TNUMBER) // we don't want to use lua_isnumber() because of autocoercion { - timeout = SIGNAL_TIMEOUT_PREPARE(lua_tonumber(L, 2)); + lua_Duration const duration{ lua_tonumber(L, 2) }; + if (duration.count() >= 0.0) + { + until = std::chrono::steady_clock::now() + std::chrono::duration_cast(duration); + } ++key_i; } - else if (lua_isnil(L, 2)) // alternate explicit "no timeout" by passing nil before the key + else if (lua_isnil(L, 2)) // alternate explicit "infinite timeout" by passing nil before the key { ++key_i; } - keeper_api_t keeper_receive; + keeper_api_t selected_keeper_receive{ nullptr }; int expected_pushed_min{ 0 }, expected_pushed_max{ 0 }; // are we in batched mode? BATCH_SENTINEL.pushKey(L); @@ -396,7 +401,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); @@ -413,17 +418,20 @@ 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; } Lane* const lane{ LANE_POINTER_REGKEY.readLightUserDataValue(L) }; Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) }; - if (K == nullptr) + lua_State* const KL{ K ? K->L : nullptr }; + if (KL == nullptr) return 0; + CancelRequest cancel{ CancelRequest::None }; int pushed{ 0 }; + STACK_CHECK_START_REL(KL, 0); for (bool try_again{ true };;) { if (lane != nullptr) @@ -439,7 +447,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, KL, selected_keeper_receive, L, linda, key_i); if (pushed < 0) { break; @@ -451,11 +459,11 @@ LUAG_FUNC(linda_receive) keeper_toggle_nil_sentinels(L, lua_gettop(L) - pushed, LookupMode::FromKeeper); // To be done from within the 'K' locking area // - SIGNAL_ALL(&linda->read_happened); + linda->m_read_happened.notify_all(); break; } - if (timeout == 0.0) + if (std::chrono::steady_clock::now() >= until) { break; /* instant timeout */ } @@ -469,18 +477,22 @@ LUAG_FUNC(linda_receive) prev_status = lane->status; // RUNNING, most likely ASSERT_L(prev_status == RUNNING); // but check, just in case lane->status = WAITING; - ASSERT_L(lane->waiting_on == nullptr); - lane->waiting_on = &linda->write_happened; + ASSERT_L(lane->m_waiting_on == nullptr); + lane->m_waiting_on = &linda->m_write_happened; } // not enough data to read: wakeup when data was sent, or when timeout is reached - try_again = SIGNAL_WAIT(&linda->write_happened, &K->keeper_cs, timeout); + std::unique_lock keeper_lock{ K->m_mutex, std::adopt_lock }; + std::cv_status const status{ linda->m_write_happened.wait_until(keeper_lock, until) }; + keeper_lock.release(); // we don't want to release the lock! + try_again = (status == std::cv_status::no_timeout); // detect spurious wakeups if (lane != nullptr) { - lane->waiting_on = nullptr; + lane->m_waiting_on = nullptr; lane->status = prev_status; } } } + STACK_CHECK(KL, 0); if (pushed < 0) { @@ -537,13 +549,13 @@ LUAG_FUNC(linda_set) if (has_value) { // we put some data in the slot, tell readers that they should wake - SIGNAL_ALL(&linda->write_happened); // To be done from within the 'K' locking area + linda->m_write_happened.notify_all(); // To be done from within the 'K' locking area } if (pushed == 1) { // the key was full, but it is no longer the case, tell writers they should wake 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 + linda->m_read_happened.notify_all(); // To be done from within the 'K' locking area } } } @@ -648,7 +660,7 @@ LUAG_FUNC( linda_limit) 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 + linda->m_read_happened.notify_all(); // To be done from within the 'K' locking area } } else // linda is cancelled @@ -678,8 +690,8 @@ LUAG_FUNC(linda_cancel) linda->simulate_cancel = CancelRequest::Soft; if (strcmp(who, "both") == 0) // tell everyone writers to wake up { - SIGNAL_ALL(&linda->write_happened); - SIGNAL_ALL(&linda->read_happened); + linda->m_write_happened.notify_all(); + linda->m_read_happened.notify_all(); } else if (strcmp(who, "none") == 0) // reset flag { @@ -687,11 +699,11 @@ LUAG_FUNC(linda_cancel) } else if (strcmp(who, "read") == 0) // tell blocked readers to wake up { - SIGNAL_ALL(&linda->write_happened); + linda->m_write_happened.notify_all(); } else if (strcmp(who, "write") == 0) // tell blocked writers to wake up { - SIGNAL_ALL(&linda->read_happened); + linda->m_read_happened.notify_all(); } else { diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h index e29e7fb..997b452 100644 --- a/src/macros_and_utils.h +++ b/src/macros_and_utils.h @@ -11,9 +11,12 @@ extern "C" { #endif // __cplusplus #include +#include #include #include +using namespace std::chrono_literals; + #define USE_DEBUG_SPEW() 0 #if USE_DEBUG_SPEW() extern char const* debugspew_indent; @@ -167,3 +170,5 @@ T* lua_newuserdatauv(lua_State* L, int nuvalue_) std::ignore = lua_error(L); // doesn't return assert(false); // we should never get here, but i'm paranoid } + +using lua_Duration = std::chrono::template duration; diff --git a/src/threading.cpp b/src/threading.cpp index afeb184..fc20931 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -93,9 +93,6 @@ THE SOFTWARE. # pragma warning( disable : 4054 ) #endif -//#define THREAD_CREATE_RETRIES_MAX 20 - // loops (maybe retry forever?) - /* * FAIL is for unexpected API return values - essentially programming * error in _this_ code. @@ -196,36 +193,6 @@ time_d now_secs(void) { } -/* -*/ -time_d SIGNAL_TIMEOUT_PREPARE( double secs ) { - if (secs<=0.0) return secs; - else return now_secs() + secs; -} - - -#if THREADAPI == THREADAPI_PTHREAD -/* -* Prepare 'abs_secs' kind of timeout to 'timespec' format -*/ -static void prepare_timeout( struct timespec *ts, time_d abs_secs ) { - assert(ts); - assert( abs_secs >= 0.0 ); - - if (abs_secs==0.0) - abs_secs= now_secs(); - - ts->tv_sec= (time_t) floor( abs_secs ); - ts->tv_nsec= ((long)((abs_secs - ts->tv_sec) * 1000.0 +0.5)) * 1000000UL; // 1ms = 1000000ns - if (ts->tv_nsec == 1000000000UL) - { - ts->tv_nsec = 0; - ts->tv_sec = ts->tv_sec + 1; - } -} -#endif // THREADAPI == THREADAPI_PTHREAD - - /*---=== Threading ===---*/ //--- @@ -268,30 +235,6 @@ static void prepare_timeout( struct timespec *ts, time_d abs_secs ) { #if THREADAPI == THREADAPI_WINDOWS -#if _WIN32_WINNT < 0x0600 // CONDITION_VARIABLE aren't available - // - void MUTEX_INIT( MUTEX_T *ref ) { - *ref= CreateMutex( nullptr /*security attr*/, false /*not locked*/, nullptr ); - if (!ref) FAIL( "CreateMutex", GetLastError() ); - } - void MUTEX_FREE( MUTEX_T *ref ) { - if (!CloseHandle(*ref)) FAIL( "CloseHandle (mutex)", GetLastError() ); - *ref= nullptr; - } - void MUTEX_LOCK( MUTEX_T *ref ) - { - DWORD rc = WaitForSingleObject( *ref, INFINITE); - // ERROR_WAIT_NO_CHILDREN means a thread was killed (lane terminated because of error raised during a linda transfer for example) while having grabbed this mutex - // this is not a big problem as we will grab it just the same, so ignore this particular error - if( rc != 0 && rc != ERROR_WAIT_NO_CHILDREN) - FAIL( "WaitForSingleObject", (rc == WAIT_FAILED) ? GetLastError() : rc); - } - void MUTEX_UNLOCK( MUTEX_T *ref ) { - if (!ReleaseMutex(*ref)) - FAIL( "ReleaseMutex", GetLastError() ); - } -#endif // CONDITION_VARIABLE aren't available - static int const gs_prio_remap[] = { THREAD_PRIORITY_IDLE, @@ -303,37 +246,7 @@ static int const gs_prio_remap[] = THREAD_PRIORITY_TIME_CRITICAL }; -/* MSDN: "If you would like to use the CRT in ThreadProc, use the -_beginthreadex function instead (of CreateThread)." -MSDN: "you can create at most 2028 threads" -*/ -// Note: Visual C++ requires '__stdcall' where it is -void THREAD_CREATE( THREAD_T* ref, THREAD_RETURN_T (__stdcall *func)( void*), void* data, int prio /* -3..+3 */) -{ - HANDLE h = (HANDLE) _beginthreadex(nullptr, // security - _THREAD_STACK_SIZE, - func, - data, - 0, // flags (0/CREATE_SUSPENDED) - nullptr // thread id (not used) - ); - - if (h == nullptr) // _beginthreadex returns 0L on failure instead of -1L (like _beginthread) - { - FAIL( "CreateThread", GetLastError()); - } - - if (prio != THREAD_PRIO_DEFAULT) - { - if (!SetThreadPriority( h, gs_prio_remap[prio + 3])) - { - FAIL( "SetThreadPriority", GetLastError()); - } - } - - *ref = h; -} - +// ############################################################################################### void THREAD_SET_PRIORITY( int prio) { @@ -344,42 +257,26 @@ void THREAD_SET_PRIORITY( int prio) } } -void THREAD_SET_AFFINITY( unsigned int aff) +// ############################################################################################### + +void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_) { - if( !SetThreadAffinityMask( GetCurrentThread(), aff)) + // prio range [-3,+3] was checked by the caller + if (!SetThreadPriority(thread_.native_handle(), gs_prio_remap[prio_ + 3])) { - FAIL( "THREAD_SET_AFFINITY", GetLastError()); + FAIL("JTHREAD_SET_PRIORITY", GetLastError()); } } -bool THREAD_WAIT_IMPL( THREAD_T *ref, double secs) -{ - DWORD ms = (secs<0.0) ? INFINITE : (DWORD)((secs*1000.0)+0.5); +// ############################################################################################### - DWORD rc= WaitForSingleObject( *ref, ms /*timeout*/ ); - // - // (WAIT_ABANDONED) - // WAIT_OBJECT_0 success (0) - // WAIT_TIMEOUT - // WAIT_FAILED more info via GetLastError() - - if (rc == WAIT_TIMEOUT) return false; - if( rc !=0) FAIL( "WaitForSingleObject", rc==WAIT_FAILED ? GetLastError() : rc); - *ref = nullptr; // thread no longer usable - return true; - } - // - void THREAD_KILL( THREAD_T *ref ) +void THREAD_SET_AFFINITY(unsigned int aff) +{ + if( !SetThreadAffinityMask( GetCurrentThread(), aff)) { - // nonexistent on Xbox360, simply disable until a better solution is found - #if !defined( PLATFORM_XBOX) - // in theory no-one should call this as it is very dangerous (memory and mutex leaks, no notification of DLLs, etc.) - if (!TerminateThread( *ref, 0 )) FAIL("TerminateThread", GetLastError()); - #endif // PLATFORM_XBOX - *ref = nullptr; + FAIL( "THREAD_SET_AFFINITY", GetLastError()); } - - void THREAD_MAKE_ASYNCH_CANCELLABLE() {} // nothing to do for windows threads, we can cancel them anytime we want +} #if !defined __GNUC__ //see http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx @@ -414,158 +311,6 @@ bool THREAD_WAIT_IMPL( THREAD_T *ref, double secs) #endif // !__GNUC__ } -#if _WIN32_WINNT < 0x0600 // CONDITION_VARIABLE aren't available - - void SIGNAL_INIT( SIGNAL_T* ref) - { - InitializeCriticalSection( &ref->signalCS); - InitializeCriticalSection( &ref->countCS); - if( 0 == (ref->waitEvent = CreateEvent( 0, true, false, 0))) // manual-reset - FAIL( "CreateEvent", GetLastError()); - if( 0 == (ref->waitDoneEvent = CreateEvent( 0, false, false, 0))) // auto-reset - FAIL( "CreateEvent", GetLastError()); - ref->waitersCount = 0; - } - - void SIGNAL_FREE( SIGNAL_T* ref) - { - CloseHandle( ref->waitDoneEvent); - CloseHandle( ref->waitEvent); - DeleteCriticalSection( &ref->countCS); - DeleteCriticalSection( &ref->signalCS); - } - - bool SIGNAL_WAIT( SIGNAL_T* ref, MUTEX_T* mu_ref, time_d abs_secs) - { - DWORD errc; - DWORD ms; - - if( abs_secs < 0.0) - ms = INFINITE; - else if( abs_secs == 0.0) - ms = 0; - else - { - time_d msd = (abs_secs - now_secs()) * 1000.0 + 0.5; - // If the time already passed, still try once (ms==0). A short timeout - // may have turned negative or 0 because of the two time samples done. - ms = msd <= 0.0 ? 0 : (DWORD)msd; - } - - EnterCriticalSection( &ref->signalCS); - EnterCriticalSection( &ref->countCS); - ++ ref->waitersCount; - LeaveCriticalSection( &ref->countCS); - LeaveCriticalSection( &ref->signalCS); - - errc = SignalObjectAndWait( *mu_ref, ref->waitEvent, ms, false); - - EnterCriticalSection( &ref->countCS); - if( 0 == -- ref->waitersCount) - { - // we're the last one leaving... - ResetEvent( ref->waitEvent); - SetEvent( ref->waitDoneEvent); - } - LeaveCriticalSection( &ref->countCS); - MUTEX_LOCK( mu_ref); - - switch( errc) - { - case WAIT_TIMEOUT: - return false; - case WAIT_OBJECT_0: - return true; - } - - FAIL( "SignalObjectAndWait", GetLastError()); - return false; - } - - void SIGNAL_ALL( SIGNAL_T* ref) - { - DWORD errc = WAIT_OBJECT_0; - - EnterCriticalSection( &ref->signalCS); - EnterCriticalSection( &ref->countCS); - - if( ref->waitersCount > 0) - { - ResetEvent( ref->waitDoneEvent); - SetEvent( ref->waitEvent); - LeaveCriticalSection( &ref->countCS); - errc = WaitForSingleObject( ref->waitDoneEvent, INFINITE); - } - else - { - LeaveCriticalSection( &ref->countCS); - } - - LeaveCriticalSection( &ref->signalCS); - - if( WAIT_OBJECT_0 != errc) - FAIL( "WaitForSingleObject", GetLastError()); - } - -#else // CONDITION_VARIABLE are available, use them - - // - void SIGNAL_INIT( SIGNAL_T *ref ) - { - InitializeConditionVariable( ref); - } - - void SIGNAL_FREE( SIGNAL_T *ref ) - { - // nothing to do - (void)ref; - } - - bool SIGNAL_WAIT( SIGNAL_T *ref, MUTEX_T *mu_ref, time_d abs_secs) - { - long ms; - - if( abs_secs < 0.0) - ms = INFINITE; - else if( abs_secs == 0.0) - ms = 0; - else - { - ms = (long) ((abs_secs - now_secs())*1000.0 + 0.5); - - // If the time already passed, still try once (ms==0). A short timeout - // may have turned negative or 0 because of the two time samples done. - // - if( ms < 0) - ms = 0; - } - - if( !SleepConditionVariableCS( ref, mu_ref, ms)) - { - if( GetLastError() == ERROR_TIMEOUT) - { - return false; - } - else - { - FAIL( "SleepConditionVariableCS", GetLastError()); - } - } - return true; - } - - void SIGNAL_ONE( SIGNAL_T *ref ) - { - WakeConditionVariable( ref); - } - - void SIGNAL_ALL( SIGNAL_T *ref ) - { - WakeAllConditionVariable( ref); - } - -#endif // CONDITION_VARIABLE are available - #else // THREADAPI == THREADAPI_PTHREAD // PThread (Linux, OS X, ...) // @@ -607,44 +352,6 @@ bool THREAD_WAIT_IMPL( THREAD_T *ref, double secs) abort(); } #define PT_CALL( call ) { int rc= call; if (rc!=0) _PT_FAIL( rc, #call, __FILE__, __LINE__ ); } - // - void SIGNAL_INIT( SIGNAL_T *ref ) { - PT_CALL(pthread_cond_init(ref, nullptr /*attr*/)); - } - void SIGNAL_FREE( SIGNAL_T *ref ) { - PT_CALL( pthread_cond_destroy(ref) ); - } - // - /* - * Timeout is given as absolute since we may have fake wakeups during - * a timed out sleep. A Linda with some other key read, or just because - * PThread cond vars can wake up unwantedly. - */ - bool SIGNAL_WAIT( SIGNAL_T *ref, pthread_mutex_t *mu, time_d abs_secs ) { - if (abs_secs<0.0) { - PT_CALL( pthread_cond_wait( ref, mu ) ); // infinite - } else { - int rc; - struct timespec ts; - - assert( abs_secs != 0.0 ); - prepare_timeout( &ts, abs_secs ); - - rc= pthread_cond_timedwait( ref, mu, &ts ); - - if (rc==ETIMEDOUT) return false; - if (rc) { _PT_FAIL( rc, "pthread_cond_timedwait()", __FILE__, __LINE__ ); } - } - return true; - } - // - void SIGNAL_ONE( SIGNAL_T *ref ) { - PT_CALL( pthread_cond_signal(ref) ); // wake up ONE (or no) waiting thread - } - // - void SIGNAL_ALL( SIGNAL_T *ref ) { - PT_CALL( pthread_cond_broadcast(ref) ); // wake up ALL waiting threads - } // array of 7 thread priority values, hand-tuned by platform so that we offer a uniform [-3,+3] public priority range static int const gs_prio_remap[] = @@ -775,129 +482,36 @@ static int select_prio(int prio /* -3..+3 */) return gs_prio_remap[prio + 3]; } -void THREAD_CREATE( THREAD_T* ref, THREAD_RETURN_T (*func)( void*), void* data, int prio /* -3..+3 */) +void THREAD_SET_PRIORITY( int prio) { - pthread_attr_t a; - bool const change_priority = #ifdef PLATFORM_LINUX - sudo && // only root-privileged process can change priorities -#endif - (prio != THREAD_PRIO_DEFAULT); - - PT_CALL( pthread_attr_init( &a)); - -#ifndef PTHREAD_TIMEDJOIN - // We create a NON-JOINABLE thread. This is mainly due to the lack of - // 'pthread_timedjoin()', but does offer other benefits (s.a. earlier - // freeing of the thread's resources). - // - PT_CALL( pthread_attr_setdetachstate( &a, PTHREAD_CREATE_DETACHED)); -#endif // PTHREAD_TIMEDJOIN - - // Use this to find a system's default stack size (DEBUG) -#if 0 - { - size_t n; - pthread_attr_getstacksize( &a, &n); - fprintf( stderr, "Getstack: %u\n", (unsigned int)n); - } - // 524288 on OS X - // 2097152 on Linux x86 (Ubuntu 7.04) - // 1048576 on FreeBSD 6.2 SMP i386 -#endif // 0 - -#if defined _THREAD_STACK_SIZE && _THREAD_STACK_SIZE > 0 - PT_CALL( pthread_attr_setstacksize( &a, _THREAD_STACK_SIZE)); -#endif - - if (change_priority) + if( sudo) // only root-privileged process can change priorities +#endif // PLATFORM_LINUX { struct sched_param sp; - // "The specified scheduling parameters are only used if the scheduling - // parameter inheritance attribute is PTHREAD_EXPLICIT_SCHED." - // -#if !defined __ANDROID__ || ( defined __ANDROID__ && __ANDROID_API__ >= 28 ) - PT_CALL( pthread_attr_setinheritsched( &a, PTHREAD_EXPLICIT_SCHED)); -#endif - -#ifdef _PRIO_SCOPE - PT_CALL( pthread_attr_setscope( &a, _PRIO_SCOPE)); -#endif // _PRIO_SCOPE - - PT_CALL( pthread_attr_setschedpolicy( &a, _PRIO_MODE)); - - sp.sched_priority = select_prio(prio); - PT_CALL( pthread_attr_setschedparam( &a, &sp)); - } - - //--- - // Seems on OS X, _POSIX_THREAD_THREADS_MAX is some kind of system - // thread limit (not userland thread). Actual limit for us is way higher. - // PTHREAD_THREADS_MAX is not defined (even though man page refers to it!) - // -# ifndef THREAD_CREATE_RETRIES_MAX - // Don't bother with retries; a failure is a failure - // - { - int rc = pthread_create( ref, &a, func, data); - if( rc) _PT_FAIL( rc, "pthread_create()", __FILE__, __LINE__ - 1); + // prio range [-3,+3] was checked by the caller + sp.sched_priority = gs_prio_remap[ prio + 3]; + PT_CALL( pthread_setschedparam( pthread_self(), _PRIO_MODE, &sp)); } -# else -# error "This code deprecated" - /* - // Wait slightly if thread creation has exchausted the system - // - { int retries; - for( retries=0; retries(thread_.native_handle()), _PRIO_MODE, &sp)); } } +// ################################################################################################# + void THREAD_SET_AFFINITY( unsigned int aff) { int bit = 0; @@ -929,93 +543,6 @@ void THREAD_SET_AFFINITY( unsigned int aff) #endif } - /* - * Wait for a thread to finish. - * - * 'mu_ref' is a lock we should use for the waiting; initially unlocked. - * Same lock as passed to THREAD_EXIT. - * - * Returns true for successful wait, false for timed out - */ -bool THREAD_WAIT( THREAD_T *ref, double secs , SIGNAL_T *signal_ref, MUTEX_T *mu_ref, volatile enum e_status *st_ref) -{ - struct timespec ts_store; - const struct timespec* timeout = nullptr; - bool done; - - // Do timeout counting before the locks - // -#if THREADWAIT_METHOD == THREADWAIT_TIMEOUT - if (secs>=0.0) -#else // THREADWAIT_METHOD == THREADWAIT_CONDVAR - if (secs>0.0) -#endif // THREADWAIT_METHOD == THREADWAIT_CONDVAR - { - prepare_timeout( &ts_store, now_secs()+secs ); - timeout= &ts_store; - } - -#if THREADWAIT_METHOD == THREADWAIT_TIMEOUT - /* Thread is joinable - */ - if (!timeout) { - PT_CALL(pthread_join(*ref, nullptr /*ignore exit value*/)); - done = true; - } else { - int rc = PTHREAD_TIMEDJOIN(*ref, nullptr, timeout); - if ((rc!=0) && (rc!=ETIMEDOUT)) { - _PT_FAIL( rc, "PTHREAD_TIMEDJOIN", __FILE__, __LINE__-2 ); - } - done= rc==0; - } -#else // THREADWAIT_METHOD == THREADWAIT_CONDVAR - /* Since we've set the thread up as PTHREAD_CREATE_DETACHED, we cannot - * join with it. Use the cond.var. - */ - (void) ref; // unused - MUTEX_LOCK( mu_ref ); - - // 'secs'==0.0 does not need to wait, just take the current status - // within the 'mu_ref' locks - // - if (secs != 0.0) { - while( *st_ref < DONE ) { - if (!timeout) { - PT_CALL( pthread_cond_wait( signal_ref, mu_ref )); - } else { - int rc= pthread_cond_timedwait( signal_ref, mu_ref, timeout ); - if (rc==ETIMEDOUT) break; - if (rc!=0) _PT_FAIL( rc, "pthread_cond_timedwait", __FILE__, __LINE__-2 ); - } - } - } - done= *st_ref >= DONE; // DONE|ERROR_ST|CANCELLED - - MUTEX_UNLOCK( mu_ref ); -#endif // THREADWAIT_METHOD == THREADWAIT_CONDVAR - return done; - } - // - void THREAD_KILL( THREAD_T *ref ) { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Cannot kill thread!"); -#else - pthread_cancel( *ref ); -#endif - } - - void THREAD_MAKE_ASYNCH_CANCELLABLE() - { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Cannot make thread async cancellable!"); -#else - // that's the default, but just in case... - pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr); - // we want cancellation to take effect immediately if possible, instead of waiting for a cancellation point (which is the default) - pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, nullptr); -#endif - } - void THREAD_SETNAME( char const* _name) { // exact API to set the thread name is platform-dependant diff --git a/src/threading.h b/src/threading.h index 38a021f..e9f302a 100644 --- a/src/threading.h +++ b/src/threading.h @@ -1,13 +1,9 @@ #pragma once -/* - * win32-pthread: - * define HAVE_WIN32_PTHREAD and PTW32_INCLUDE_WINDOWS_H in your project configuration when building for win32-pthread. - * link against pthreadVC2.lib, and of course have pthreadVC2.dll somewhere in your path. - */ #include "platform.h" #include +#include /* Note: ERROR is a defined entity on Win32 PENDING: The Lua VM hasn't done anything yet. @@ -19,7 +15,7 @@ enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED }; #define THREADAPI_WINDOWS 1 #define THREADAPI_PTHREAD 2 -#if( defined( PLATFORM_XBOX) || defined( PLATFORM_WIN32) || defined( PLATFORM_POCKETPC)) && !defined( HAVE_WIN32_PTHREAD) +#if( defined( PLATFORM_XBOX) || defined( PLATFORM_WIN32) || defined( PLATFORM_POCKETPC)) //#pragma message ( "THREADAPI_WINDOWS" ) #define THREADAPI THREADAPI_WINDOWS #else // (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) @@ -68,16 +64,9 @@ enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED }; }; - #define MUTEX_T HANDLE - void MUTEX_INIT( MUTEX_T* ref); - void MUTEX_FREE( MUTEX_T* ref); - void MUTEX_LOCK( MUTEX_T* ref); - void MUTEX_UNLOCK( MUTEX_T* ref); - #else // CONDITION_VARIABLE are available, use them #define SIGNAL_T CONDITION_VARIABLE - #define MUTEX_T CRITICAL_SECTION #define MUTEX_INIT( ref) InitializeCriticalSection( ref) #define MUTEX_FREE( ref) DeleteCriticalSection( ref) #define MUTEX_LOCK( ref) EnterCriticalSection( ref) @@ -111,7 +100,6 @@ enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED }; # define _MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE #endif - #define MUTEX_T pthread_mutex_t #define MUTEX_INIT(ref) pthread_mutex_init(ref, nullptr) #define MUTEX_RECURSIVE_INIT(ref) \ { pthread_mutexattr_t a; pthread_mutexattr_init( &a ); \ @@ -126,8 +114,6 @@ enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED }; using SIGNAL_T = pthread_cond_t; - void SIGNAL_ONE( SIGNAL_T *ref ); - // Yield is non-portable: // // OS X 10.4.8/9 has pthread_yield_np() @@ -143,10 +129,6 @@ enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED }; #define THREAD_CALLCONV #endif //THREADAPI == THREADAPI_PTHREAD -void SIGNAL_INIT( SIGNAL_T *ref ); -void SIGNAL_FREE( SIGNAL_T *ref ); -void SIGNAL_ALL( SIGNAL_T *ref ); - /* * 'time_d': <0.0 for no timeout * 0.0 for instant check @@ -155,11 +137,6 @@ void SIGNAL_ALL( SIGNAL_T *ref ); using time_d = double; time_d now_secs(void); -time_d SIGNAL_TIMEOUT_PREPARE( double rel_secs ); - -bool SIGNAL_WAIT( SIGNAL_T *ref, MUTEX_T *mu, time_d timeout ); - - /*---=== Threading ===--- */ @@ -167,16 +144,9 @@ bool SIGNAL_WAIT( SIGNAL_T *ref, MUTEX_T *mu, time_d timeout ); #if THREADAPI == THREADAPI_WINDOWS - using THREAD_T = HANDLE; -# define THREAD_ISNULL( _h) (_h == 0) - void THREAD_CREATE( THREAD_T* ref, THREAD_RETURN_T (__stdcall *func)( void*), void* data, int prio /* -3..+3 */); - # define THREAD_PRIO_MIN (-3) # define THREAD_PRIO_MAX (+3) -# define THREAD_CLEANUP_PUSH( cb_, val_) -# define THREAD_CLEANUP_POP( execute_) - #else // THREADAPI == THREADAPI_PTHREAD /* Platforms that have a timed 'pthread_join()' can get away with a simpler @@ -195,11 +165,6 @@ bool SIGNAL_WAIT( SIGNAL_T *ref, MUTEX_T *mu, time_d timeout ); # endif # endif - using THREAD_T = pthread_t; -# define THREAD_ISNULL( _h) 0 // pthread_t may be a structure: never 'null' by itself - - void THREAD_CREATE( THREAD_T* ref, THREAD_RETURN_T (*func)( void*), void* data, int prio /* -3..+3 */); - # if defined(PLATFORM_LINUX) extern volatile bool sudo; # ifdef LINUX_SCHED_RR @@ -213,13 +178,6 @@ bool SIGNAL_WAIT( SIGNAL_T *ref, MUTEX_T *mu, time_d timeout ); # define THREAD_PRIO_MAX (+3) # endif -# if THREADWAIT_METHOD == THREADWAIT_CONDVAR -# define THREAD_CLEANUP_PUSH( cb_, val_) pthread_cleanup_push( cb_, val_) -# define THREAD_CLEANUP_POP( execute_) pthread_cleanup_pop( execute_) -# else -# define THREAD_CLEANUP_PUSH( cb_, val_) { -# define THREAD_CLEANUP_POP( execute_) } -# endif // THREADWAIT_METHOD == THREADWAIT_CONDVAR #endif // THREADAPI == THREADAPI_WINDOWS /* @@ -236,16 +194,8 @@ bool SIGNAL_WAIT( SIGNAL_T *ref, MUTEX_T *mu, time_d timeout ); #endif // THREADAPI == THREADAPI_WINDOWS || (defined PTHREAD_TIMEDJOIN) -#if THREADWAIT_METHOD == THREADWAIT_TIMEOUT -bool THREAD_WAIT_IMPL( THREAD_T *ref, double secs); -#define THREAD_WAIT( a, b, c, d, e) THREAD_WAIT_IMPL( a, b) -#else // THREADWAIT_METHOD == THREADWAIT_CONDVAR -bool THREAD_WAIT_IMPL( THREAD_T *ref, double secs, SIGNAL_T *signal_ref, MUTEX_T *mu_ref, volatile enum e_status *st_ref); -#define THREAD_WAIT THREAD_WAIT_IMPL -#endif // // THREADWAIT_METHOD == THREADWAIT_CONDVAR - -void THREAD_KILL( THREAD_T* ref); void THREAD_SETNAME( char const* _name); -void THREAD_MAKE_ASYNCH_CANCELLABLE(); void THREAD_SET_PRIORITY( int prio); void THREAD_SET_AFFINITY( unsigned int aff); + +void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_); diff --git a/tests/cancel.lua b/tests/cancel.lua index c5bb761..c22103f 100644 --- a/tests/cancel.lua +++ b/tests/cancel.lua @@ -139,6 +139,7 @@ if not next(which_tests) or which_tests.linda then linda:receive( 1, "yeah") -- linda cancel: linda:receive() returns cancel_error immediately + print "cancelling" linda:cancel( "both") -- wait until cancellation is effective. @@ -163,6 +164,7 @@ if not next(which_tests) or which_tests.soft then waitCancellation( h, "waiting") -- soft cancel, this time awakens waiting linda operations, which returns cancel_error immediately, no timeout. + print "cancelling" h:cancel( "soft", true) -- wait until cancellation is effective. the lane will interrupt its loop and print the exit message @@ -177,6 +179,7 @@ if not next(which_tests) or which_tests.hook then linda:receive( 2, "yeah") -- count hook cancel after some instruction instructions + print "cancelling" h:cancel( "line", 300, 5.0) -- wait until cancellation is effective. the lane will interrupt its loop and print the exit message @@ -193,6 +196,7 @@ if not next(which_tests) or which_tests.hard then linda:receive( 2, "yeah") -- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it + print "cancelling" h:cancel() -- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error @@ -209,27 +213,13 @@ if not next(which_tests) or which_tests.hard_unprotected then linda:receive( 2, "yeah") -- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it + print "cancelling" h:cancel() -- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error waitCancellation( h, "cancelled") end -if not next(which_tests) or which_tests.kill then - remaining_tests.kill = nil - print "\n\n####################################################################\nbegin kill cancel test\n" - h = lanes.gen( "*", laneBody)( "busy", 50000000) -- start a pure Lua busy loop lane - - -- wait 1/3s before cancelling the lane, before the busy loop can finish - print "wait 0.3s" - linda:receive( 0.3, "yeah") - - -- hard cancel with kill: the lane thread will be forcefully terminated. kill timeout is pthread-specific - h:cancel( true, 1.0) - - -- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error - waitCancellation( h, "killed") -end --#################################################################### local unknown_test, val = next(remaining_tests) -- cgit v1.2.3-55-g6feb From 6efef88d0c7c155690dc2ac478d52e75da97d147 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Mon, 8 Apr 2024 18:26:47 +0200 Subject: C++ migration: lanes.now_secs uses std::chrono::sytem_clock. plus more enum class cleanup. --- src/cancel.cpp | 17 ++++++------ src/lanes.cpp | 74 +++++++++++++++++++++++++------------------------- src/lanes_private.h | 26 ++++++++++++++---- src/linda.cpp | 20 +++++++------- src/threading.cpp | 78 ----------------------------------------------------- src/threading.h | 15 ----------- 6 files changed, 77 insertions(+), 153 deletions(-) (limited to 'src') diff --git a/src/cancel.cpp b/src/cancel.cpp index 6a94343..437a6f0 100644 --- a/src/cancel.cpp +++ b/src/cancel.cpp @@ -33,13 +33,14 @@ THE SOFTWARE. ]]-- */ -#include -#include +#include "cancel.h" + +// #include +//#include +#include "lanes_private.h" #include "threading.h" -#include "cancel.h" #include "tools.h" -#include "lanes_private.h" // ################################################################################################ // ################################################################################################ @@ -103,7 +104,7 @@ static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar) // 'wake_lindas_bool': if true, signal any linda the thread is waiting on // instead of waiting for its timeout (if any) // -// Returns: true if the lane was already finished (DONE/ERROR_ST/CANCELLED) or if we +// Returns: true if the lane was already finished (Done/Error/Cancelled) or if we // managed to cancel it. // false if the cancellation timed out, or a kill was needed. // @@ -117,7 +118,7 @@ static CancelResult thread_cancel_soft(Lane* lane_, lua_Duration duration_, bool if (wake_lane_) // wake the thread so that execution returns from any pending linda operation if desired { std::condition_variable* const waiting_on{ lane_->m_waiting_on }; - if (lane_->status == WAITING && waiting_on != nullptr) + if (lane_->m_status == Lane::Waiting && waiting_on != nullptr) { waiting_on->notify_all(); } @@ -135,7 +136,7 @@ static CancelResult thread_cancel_hard(Lane* lane_, lua_Duration duration_, bool if (wake_lane_) // wake the thread so that execution returns from any pending linda operation if desired { std::condition_variable* waiting_on = lane_->m_waiting_on; - if (lane_->status == WAITING && waiting_on != nullptr) + if (lane_->m_status == Lane::Waiting && waiting_on != nullptr) { waiting_on->notify_all(); } @@ -151,7 +152,7 @@ CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hook_count_, lua_Durat { // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here // We can read 'lane_->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) - if (lane_->status >= DONE) + if (lane_->m_status >= Lane::Done) { // say "ok" by default, including when lane is already done return CancelResult::Cancelled; diff --git a/src/lanes.cpp b/src/lanes.cpp index 4dd9b46..12cd7ef 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -126,8 +126,8 @@ bool Lane::waitForCompletion(lua_Duration duration_) std::unique_lock lock{ m_done_mutex }; //std::stop_token token{ m_thread.get_stop_token() }; - //return m_done_signal.wait_for(lock, token, secs_, [this](){ return status >= DONE; }); - return m_done_signal.wait_until(lock, until, [this](){ return status >= DONE; }); + //return m_done_signal.wait_until(lock, token, secs_, [this](){ return m_status >= Lane::Done; }); + return m_done_signal.wait_until(lock, until, [this](){ return m_status >= Lane::Done; }); } static void lane_main(Lane* lane); @@ -848,12 +848,12 @@ static void lane_main(Lane* lane) // wait until the launching thread has finished preparing L lane->m_ready.wait(); int rc{ LUA_ERRRUN }; - if (lane->status == PENDING) // nothing wrong happened during preparation, we can work + if (lane->m_status == Lane::Pending) // nothing wrong happened during preparation, we can work { // At this point, the lane function and arguments are on the stack int const nargs{ lua_gettop(L) - 1 }; DEBUGSPEW_CODE(Universe* U = universe_get(L)); - lane->status = RUNNING; // PENDING -> RUNNING + lane->m_status = Lane::Running; // Pending -> Running // Tie "set_finalizer()" to the state lua_pushcfunction(L, LG_set_finalizer); @@ -924,14 +924,12 @@ static void lane_main(Lane* lane) { // leave results (1..top) or error message + stack trace (1..2) on the stack - master will copy them - enum e_status st = (rc == 0) ? DONE : CANCEL_ERROR.equals(L, 1) ? CANCELLED : ERROR_ST; + Lane::Status st = (rc == LUA_OK) ? Lane::Done : CANCEL_ERROR.equals(L, 1) ? Lane::Cancelled : Lane::Error; - // Posix no PTHREAD_TIMEDJOIN: - // 'm_done_mutex' protects the -> DONE|ERROR_ST|CANCELLED state change - // { + // 'm_done_mutex' protects the -> Done|Error|Cancelled state change std::lock_guard lock{ lane->m_done_mutex }; - lane->status = st; + lane->m_status = st; lane->m_done_signal.notify_one();// wake up master (while 'lane->m_done_mutex' is on) } } @@ -1045,13 +1043,15 @@ LUAG_FUNC(lane_new) lua_State* const m_L; Lane* m_lane{ nullptr }; int const m_gc_cb_idx; + DEBUGSPEW_CODE(Universe* const U); // for DEBUGSPEW only (hence the absence of m_ prefix) public: - OnExit(lua_State* L_, Lane* lane_, int gc_cb_idx_) + OnExit(lua_State* L_, Lane* lane_, int gc_cb_idx_ DEBUGSPEW_COMMA_PARAM(Universe* U_)) : m_L{ L_ } , m_lane{ lane_ } , m_gc_cb_idx{ gc_cb_idx_ } + DEBUGSPEW_COMMA_PARAM(U{ U_ }) {} ~OnExit() @@ -1065,7 +1065,7 @@ LUAG_FUNC(lane_new) CANCEL_ERROR.pushKey(m_lane->L); { std::lock_guard lock{ m_lane->m_done_mutex }; - m_lane->status = CANCELLED; + m_lane->m_status = Lane::Cancelled; m_lane->m_done_signal.notify_one(); // wake up master (while 'lane->m_done_mutex' is on) } // unblock the thread so that it can terminate gracefully @@ -1113,7 +1113,7 @@ LUAG_FUNC(lane_new) m_lane->m_ready.count_down(); m_lane = nullptr; } - } onExit{ L, lane, gc_cb_idx }; + } onExit{ L, lane, gc_cb_idx DEBUGSPEW_COMMA_PARAM(U) }; // launch the thread early, it will sync with a std::latch to parallelize OS thread warmup and L2 preparation DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: launching thread\n" INDENT_END)); lane->startThread(priority); @@ -1311,7 +1311,7 @@ static int lane_gc(lua_State* L) } // We can read 'lane->status' without locks, but not wait for it - if (lane->status < DONE) + if (lane->m_status < Lane::Done) { // still running: will have to be cleaned up later selfdestruct_add(lane); @@ -1358,14 +1358,14 @@ static int lane_gc(lua_State* L) // static char const * thread_status_string(Lane* lane_) { - enum e_status const st{ lane_->status }; // read just once (volatile) + Lane::Status const st{ lane_->m_status }; // read just once (volatile) char const* str = - (st == PENDING) ? "pending" : - (st == RUNNING) ? "running" : // like in 'co.status()' - (st == WAITING) ? "waiting" : - (st == DONE) ? "done" : - (st == ERROR_ST) ? "error" : - (st == CANCELLED) ? "cancelled" : nullptr; + (st == Lane::Pending) ? "pending" : + (st == Lane::Running) ? "running" : // like in 'co.status()' + (st == Lane::Waiting) ? "waiting" : + (st == Lane::Done) ? "done" : + (st == Lane::Error) ? "error" : + (st == Lane::Cancelled) ? "cancelled" : nullptr; return str; } @@ -1406,16 +1406,16 @@ LUAG_FUNC(thread_join) } STACK_CHECK_START_REL(L, 0); - // Thread is DONE/ERROR_ST/CANCELLED; all ours now + // Thread is Done/Error/Cancelled; all ours now int ret{ 0 }; Universe* const U{ lane->U }; // debug_name is a pointer to string possibly interned in the lane's state, that no longer exists when the state is closed // so store it in the userdata uservalue at a key that can't possibly collide securize_debug_threadname(L, lane); - switch (lane->status) + switch (lane->m_status) { - case DONE: + case Lane::Done: { int const n{ lua_gettop(L2) }; // whole L2 stack if ((n > 0) && (luaG_inter_move(U, L2, L, n, LookupMode::LaneBody) != 0)) @@ -1426,7 +1426,7 @@ LUAG_FUNC(thread_join) } break; - case ERROR_ST: + case Lane::Error: { int const n{ lua_gettop(L2) }; STACK_GROW(L, 3); @@ -1440,12 +1440,12 @@ LUAG_FUNC(thread_join) } break; - case CANCELLED: + case Lane::Cancelled: ret = 0; break; default: - DEBUGSPEW_CODE(fprintf(stderr, "Status: %d\n", lane->status)); + DEBUGSPEW_CODE(fprintf(stderr, "Status: %d\n", lane->m_status)); ASSERT_L(false); ret = 0; } @@ -1505,7 +1505,7 @@ LUAG_FUNC(thread_index) lua_pushcfunction(L, LG_thread_join); lua_pushvalue(L, UD); lua_call(L, 1, LUA_MULTRET); // all return values are on the stack, at slots 4+ - switch (lane->status) + switch (lane->m_status) { default: // this is an internal error, we probably never get here @@ -1516,7 +1516,7 @@ LUAG_FUNC(thread_index) raise_lua_error(L); [[fallthrough]]; // fall through if we are killed, as we got nil, "killed" on the stack - case DONE: // got regular return values + case Lane::Done: // got regular return values { int const nvalues{ lua_gettop(L) - 3 }; for (int i = nvalues; i > 0; --i) @@ -1527,7 +1527,7 @@ LUAG_FUNC(thread_index) } break; - case ERROR_ST: // got 3 values: nil, errstring, callstack table + case Lane::Error: // got 3 values: nil, errstring, callstack table // me[-2] could carry the stack table, but even // me[-1] is rather unnecessary (and undocumented); // use ':join()' instead. --AKa 22-Jan-2009 @@ -1538,7 +1538,7 @@ LUAG_FUNC(thread_index) lua_rawset(L, USR); break; - case CANCELLED: + case Lane::Cancelled: // do nothing break; } @@ -1648,13 +1648,17 @@ LUAG_FUNC(threads) */ /* -* secs= now_secs() +* secs = now_secs() * -* Returns the current time, as seconds (millisecond resolution). +* Returns the current time, as seconds. Resolution depends on std::system_clock implementation +* Can't use std::chrono::steady_clock because we need the same baseline as std::mktime */ LUAG_FUNC(now_secs) { - lua_pushnumber(L, now_secs()); + auto const now{ std::chrono::system_clock::now() }; + lua_Duration duration { now.time_since_epoch() }; + + lua_pushnumber(L, duration.count()); return 1; } @@ -1739,10 +1743,6 @@ static const struct luaL_Reg lanes_functions[] = */ static void init_once_LOCKED( void) { -#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) - now_secs(); // initialize 'now_secs()' internal offset -#endif - #if (defined PLATFORM_OSX) && (defined _UTILBINDTHREADTOCPU) chudInitialize(); #endif diff --git a/src/lanes_private.h b/src/lanes_private.h index 01d43c0..3ed52fe 100644 --- a/src/lanes_private.h +++ b/src/lanes_private.h @@ -17,6 +17,22 @@ class Lane { public: + /* + Pending: The Lua VM hasn't done anything yet. + Running, Waiting: Thread is inside the Lua VM. If the thread is forcefully stopped, we can't lua_close() the Lua State. + Done, Error, Cancelled: Thread execution is outside the Lua VM. It can be lua_close()d. + */ + enum class Status + { + Pending, + Running, + Waiting, + Done, + Error, + Cancelled + }; + using enum Status; + // the thread std::jthread m_thread; // a latch to wait for the lua_State to be ready @@ -36,16 +52,16 @@ class Lane // M: prepares the state, and reads results // S: while S is running, M must keep out of modifying the state - volatile enum e_status status{ PENDING }; + Status volatile m_status{ Pending }; // - // M: sets to PENDING (before launching) - // S: updates -> RUNNING/WAITING -> DONE/ERROR_ST/CANCELLED + // M: sets to Pending (before launching) + // S: updates -> Running/Waiting -> Done/Error/Cancelled std::condition_variable* volatile m_waiting_on{ nullptr }; // - // When status is WAITING, points on the linda's signal the thread waits on, else nullptr + // When status is Waiting, points on the linda's signal the thread waits on, else nullptr - volatile CancelRequest cancel_request{ CancelRequest::None }; + CancelRequest volatile cancel_request{ CancelRequest::None }; // // M: sets to false, flags true for cancel request // S: reads to see if cancel is requested diff --git a/src/linda.cpp b/src/linda.cpp index ea1410e..fb74abe 100644 --- a/src/linda.cpp +++ b/src/linda.cpp @@ -306,13 +306,13 @@ LUAG_FUNC(linda_send) // storage limit hit, wait until timeout or signalled that we should try again { - enum e_status prev_status = ERROR_ST; // prevent 'might be used uninitialized' warnings + Lane::Status prev_status{ Lane::Error }; // prevent 'might be used uninitialized' warnings if (lane != nullptr) { // change status of lane to "waiting" - prev_status = lane->status; // RUNNING, most likely - ASSERT_L(prev_status == RUNNING); // but check, just in case - lane->status = WAITING; + prev_status = lane->m_status; // Running, most likely + ASSERT_L(prev_status == Lane::Running); // but check, just in case + lane->m_status = Lane::Waiting; ASSERT_L(lane->m_waiting_on == nullptr); lane->m_waiting_on = &linda->m_read_happened; } @@ -324,7 +324,7 @@ LUAG_FUNC(linda_send) if (lane != nullptr) { lane->m_waiting_on = nullptr; - lane->status = prev_status; + lane->m_status = prev_status; } } } @@ -470,13 +470,13 @@ LUAG_FUNC(linda_receive) // nothing received, wait until timeout or signalled that we should try again { - enum e_status prev_status = ERROR_ST; // prevent 'might be used uninitialized' warnings + Lane::Status prev_status{ Lane::Error }; // prevent 'might be used uninitialized' warnings if (lane != nullptr) { // change status of lane to "waiting" - prev_status = lane->status; // RUNNING, most likely - ASSERT_L(prev_status == RUNNING); // but check, just in case - lane->status = WAITING; + prev_status = lane->m_status; // Running, most likely + ASSERT_L(prev_status == Lane::Running); // but check, just in case + lane->m_status = Lane::Waiting; ASSERT_L(lane->m_waiting_on == nullptr); lane->m_waiting_on = &linda->m_write_happened; } @@ -488,7 +488,7 @@ LUAG_FUNC(linda_receive) if (lane != nullptr) { lane->m_waiting_on = nullptr; - lane->status = prev_status; + lane->m_status = prev_status; } } } diff --git a/src/threading.cpp b/src/threading.cpp index fc20931..bc1852f 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -115,84 +115,6 @@ static void FAIL( char const* funcname, int rc) #endif // win32 build -/* -* Returns millisecond timing (in seconds) for the current time. -* -* Note: This function should be called once in single-threaded mode in Win32, -* to get it initialized. -*/ -time_d now_secs(void) { - -#if defined( PLATFORM_XBOX) || defined( PLATFORM_WIN32) || defined( PLATFORM_POCKETPC) - /* - * Windows FILETIME values are "100-nanosecond intervals since - * January 1, 1601 (UTC)" (MSDN). Well, we'd want Unix Epoch as - * the offset and it seems, so would they: - * - * - */ - SYSTEMTIME st; - FILETIME ft; - ULARGE_INTEGER uli; - static ULARGE_INTEGER uli_epoch; // Jan 1st 1970 0:0:0 - - if (uli_epoch.HighPart==0) { - st.wYear= 1970; - st.wMonth= 1; // Jan - st.wDay= 1; - st.wHour= st.wMinute= st.wSecond= st.wMilliseconds= 0; - - if (!SystemTimeToFileTime( &st, &ft )) - FAIL( "SystemTimeToFileTime", GetLastError() ); - - uli_epoch.LowPart= ft.dwLowDateTime; - uli_epoch.HighPart= ft.dwHighDateTime; - } - - GetSystemTime( &st ); // current system date/time in UTC - if (!SystemTimeToFileTime( &st, &ft )) - FAIL( "SystemTimeToFileTime", GetLastError() ); - - uli.LowPart= ft.dwLowDateTime; - uli.HighPart= ft.dwHighDateTime; - - /* 'double' has less accuracy than 64-bit int, but if it were to degrade, - * it would do so gracefully. In practice, the integer accuracy is not - * of the 100ns class but just 1ms (Windows XP). - */ -# if 1 - // >= 2.0.3 code - return (double) ((uli.QuadPart - uli_epoch.QuadPart)/10000) / 1000.0; -# elif 0 - // fix from Kriss Daniels, see: - // - // - // "seem to be getting negative numbers from the old version, probably number - // conversion clipping, this fixes it and maintains ms resolution" - // - // This was a bad fix, and caused timer test 5 sec timers to disappear. - // --AKa 25-Jan-2009 - // - return ((double)((signed)((uli.QuadPart/10000) - (uli_epoch.QuadPart/10000)))) / 1000.0; -# else - // <= 2.0.2 code - return (double)(uli.QuadPart - uli_epoch.QuadPart) / 10000000.0; -# endif -#else // !(defined( PLATFORM_WIN32) || defined( PLATFORM_POCKETPC)) - struct timeval tv; - // { - // time_t tv_sec; /* seconds since Jan. 1, 1970 */ - // suseconds_t tv_usec; /* and microseconds */ - // }; - - int rc = gettimeofday(&tv, nullptr /*time zone not used any more (in Linux)*/); - assert( rc==0 ); - - return ((double)tv.tv_sec) + ((tv.tv_usec)/1000) / 1000.0; -#endif // !(defined( PLATFORM_WIN32) || defined( PLATFORM_POCKETPC)) -} - - /*---=== Threading ===---*/ //--- diff --git a/src/threading.h b/src/threading.h index e9f302a..82c8f52 100644 --- a/src/threading.h +++ b/src/threading.h @@ -5,13 +5,6 @@ #include #include -/* Note: ERROR is a defined entity on Win32 - PENDING: The Lua VM hasn't done anything yet. - RUNNING, WAITING: Thread is inside the Lua VM. If the thread is forcefully stopped, we can't lua_close() the Lua State. - DONE, ERROR_ST, CANCELLED: Thread execution is outside the Lua VM. It can be lua_close()d. -*/ -enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED }; - #define THREADAPI_WINDOWS 1 #define THREADAPI_PTHREAD 2 @@ -129,14 +122,6 @@ enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED }; #define THREAD_CALLCONV #endif //THREADAPI == THREADAPI_PTHREAD -/* -* 'time_d': <0.0 for no timeout -* 0.0 for instant check -* >0.0 absolute timeout in secs + ms -*/ -using time_d = double; -time_d now_secs(void); - /*---=== Threading ===--- */ -- cgit v1.2.3-55-g6feb From 9b5c27af793a08fd17d1fde93e9512e76536f484 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Tue, 9 Apr 2024 09:03:30 +0200 Subject: C++ migration: YIELD() → std::this_thread::yield. plus more threading code cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lanes.cpp | 4 +- src/threading.cpp | 300 ++++++++++++++++++++++++------------------------------ src/threading.h | 208 ++++++++++--------------------------- 3 files changed, 193 insertions(+), 319 deletions(-) (limited to 'src') diff --git a/src/lanes.cpp b/src/lanes.cpp index 12cd7ef..f5d5130 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -502,7 +502,7 @@ static int universe_gc( lua_State* L) while (U->selfdestruct_first != SELFDESTRUCT_END) { // give threads time to act on their cancel - YIELD(); + std::this_thread::yield(); // count the number of cancelled thread that didn't have the time to act yet int n{ 0 }; { @@ -529,7 +529,7 @@ static int universe_gc( lua_State* L) // They are no longer listed in the selfdestruct chain, but they still have to lua_close(). while (U->selfdestructing_count.load(std::memory_order_acquire) > 0) { - YIELD(); + std::this_thread::yield(); } } diff --git a/src/threading.cpp b/src/threading.cpp index bc1852f..aab2fa7 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -97,64 +97,28 @@ THE SOFTWARE. * FAIL is for unexpected API return values - essentially programming * error in _this_ code. */ -#if defined( PLATFORM_XBOX) || defined( PLATFORM_WIN32) || defined( PLATFORM_POCKETPC) -static void FAIL( char const* funcname, int rc) -{ -#if defined( PLATFORM_XBOX) - fprintf( stderr, "%s() failed! (%d)\n", funcname, rc ); +#if defined(PLATFORM_XBOX) || defined(PLATFORM_WIN32) || defined(PLATFORM_POCKETPC) + static void FAIL(char const* funcname, int rc) + { +#if defined(PLATFORM_XBOX) + fprintf(stderr, "%s() failed! (%d)\n", funcname, rc); #else // PLATFORM_XBOX - char buf[256]; - FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, 256, nullptr); - fprintf( stderr, "%s() failed! [GetLastError() -> %d] '%s'", funcname, rc, buf); + char buf[256]; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, 256, nullptr); + fprintf(stderr, "%s() failed! [GetLastError() -> %d] '%s'", funcname, rc, buf); #endif // PLATFORM_XBOX #ifdef _MSC_VER - __debugbreak(); // give a chance to the debugger! + __debugbreak(); // give a chance to the debugger! #endif // _MSC_VER - abort(); -} + abort(); + } #endif // win32 build /*---=== Threading ===---*/ -//--- -// It may be meaningful to explicitly limit the new threads' C stack size. -// We should know how much Lua needs in the C stack, all Lua side allocations -// are done in heap so they don't count. -// -// Consequence of _not_ limiting the stack is running out of virtual memory -// with 1000-5000 threads on 32-bit systems. -// -// Note: using external C modules may be affected by the stack size check. -// if having problems, set back to '0' (default stack size of the system). -// -// Win32: 64K (?) -// Win64: xxx -// -// Linux x86: 2MB Ubuntu 7.04 via 'pthread_getstacksize()' -// Linux x64: xxx -// Linux ARM: xxx -// -// OS X 10.4.9: 512K -// valid values N * 4KB -// -#ifndef _THREAD_STACK_SIZE -# if defined( PLATFORM_XBOX) || defined( PLATFORM_WIN32) || defined( PLATFORM_POCKETPC) || defined( PLATFORM_CYGWIN) -# define _THREAD_STACK_SIZE 0 - // Win32: does it work with less? -# elif (defined PLATFORM_OSX) -# define _THREAD_STACK_SIZE (524288/2) // 262144 - // OS X: "make test" works on 65536 and even below - // "make perftest" works on >= 4*65536 == 262144 (not 3*65536) -# elif (defined PLATFORM_LINUX) && (defined __i386) -# define _THREAD_STACK_SIZE (2097152/16) // 131072 - // Linux x86 (Ubuntu 7.04): "make perftest" works on /16 (not on /32) -# elif (defined PLATFORM_BSD) && (defined __i386) -# define _THREAD_STACK_SIZE (1048576/8) // 131072 - // FreeBSD 6.2 SMP i386: ("gmake perftest" works on /8 (not on /16) -# endif -#endif - +// ################################################################################################## +// ################################################################################################## #if THREADAPI == THREADAPI_WINDOWS static int const gs_prio_remap[] = @@ -170,12 +134,12 @@ static int const gs_prio_remap[] = // ############################################################################################### -void THREAD_SET_PRIORITY( int prio) +void THREAD_SET_PRIORITY(int prio) { // prio range [-3,+3] was checked by the caller - if (!SetThreadPriority( GetCurrentThread(), gs_prio_remap[prio + 3])) + if (!SetThreadPriority(GetCurrentThread(), gs_prio_remap[prio + 3])) { - FAIL( "THREAD_SET_PRIORITY", GetLastError()); + FAIL("THREAD_SET_PRIORITY", GetLastError()); } } @@ -194,86 +158,94 @@ void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_) void THREAD_SET_AFFINITY(unsigned int aff) { - if( !SetThreadAffinityMask( GetCurrentThread(), aff)) + if (!SetThreadAffinityMask(GetCurrentThread(), aff)) { - FAIL( "THREAD_SET_AFFINITY", GetLastError()); + FAIL("THREAD_SET_AFFINITY", GetLastError()); } } +// ############################################################################################### + #if !defined __GNUC__ - //see http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx - #define MS_VC_EXCEPTION 0x406D1388 - #pragma pack(push,8) - typedef struct tagTHREADNAME_INFO - { - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. - } THREADNAME_INFO; - #pragma pack(pop) +//see http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx +#define MS_VC_EXCEPTION 0x406D1388 +#pragma pack(push,8) +typedef struct tagTHREADNAME_INFO +{ + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. +} THREADNAME_INFO; +#pragma pack(pop) #endif // !__GNUC__ - void THREAD_SETNAME( char const* _name) - { +void THREAD_SETNAME(char const* _name) +{ #if !defined __GNUC__ - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = _name; - info.dwThreadID = GetCurrentThreadId(); - info.dwFlags = 0; + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = _name; + info.dwThreadID = GetCurrentThreadId(); + info.dwFlags = 0; - __try - { - RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info ); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - } -#endif // !__GNUC__ + __try + { + RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info ); } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } +#endif // !__GNUC__ +} +// ################################################################################################## +// ################################################################################################## #else // THREADAPI == THREADAPI_PTHREAD - // PThread (Linux, OS X, ...) - // - // On OS X, user processes seem to be able to change priorities. - // On Linux, SCHED_RR and su privileges are required.. !-( - // - #include - #include - -# if (defined(__MINGW32__) || defined(__MINGW64__)) && defined pthread_attr_setschedpolicy -# if pthread_attr_setschedpolicy( A, S) == ENOTSUP - // from the mingw-w64 team: - // Well, we support pthread_setschedparam by which you can specify - // threading-policy. Nevertheless, yes we lack this function. In - // general its implementation is pretty much trivial, as on Win32 target - // just SCHED_OTHER can be supported. - #undef pthread_attr_setschedpolicy - static int pthread_attr_setschedpolicy( pthread_attr_t* attr, int policy) +// ################################################################################################## +// ################################################################################################## + +// PThread (Linux, OS X, ...) +// +// On OS X, user processes seem to be able to change priorities. +// On Linux, SCHED_RR and su privileges are required.. !-( +// +#include +#include + +#if (defined(__MINGW32__) || defined(__MINGW64__)) && defined pthread_attr_setschedpolicy +#if pthread_attr_setschedpolicy(A, S) == ENOTSUP +// from the mingw-w64 team: +// Well, we support pthread_setschedparam by which you can specify +// threading-policy. Nevertheless, yes we lack this function. In +// general its implementation is pretty much trivial, as on Win32 target +// just SCHED_OTHER can be supported. +#undef pthread_attr_setschedpolicy +static int pthread_attr_setschedpolicy(pthread_attr_t* attr, int policy) +{ + if (policy != SCHED_OTHER) { - if( policy != SCHED_OTHER) - { - return ENOTSUP; - } - return 0; + return ENOTSUP; } -# endif // pthread_attr_setschedpolicy() -# endif // defined(__MINGW32__) || defined(__MINGW64__) + return 0; +} +#endif // pthread_attr_setschedpolicy() +#endif // defined(__MINGW32__) || defined(__MINGW64__) - static void _PT_FAIL( int rc, const char *name, const char *file, int line ) { +static void _PT_FAIL( int rc, const char *name, const char *file, int line ) +{ const char *why= (rc==EINVAL) ? "EINVAL" : - (rc==EBUSY) ? "EBUSY" : - (rc==EPERM) ? "EPERM" : - (rc==ENOMEM) ? "ENOMEM" : - (rc==ESRCH) ? "ESRCH" : - (rc==ENOTSUP) ? "ENOTSUP": - //... - ""; + (rc==EBUSY) ? "EBUSY" : + (rc==EPERM) ? "EPERM" : + (rc==ENOMEM) ? "ENOMEM" : + (rc==ESRCH) ? "ESRCH" : + (rc==ENOTSUP) ? "ENOTSUP": + //... + ""; fprintf( stderr, "%s %d: %s failed, %d %s\n", file, line, name, rc, why ); abort(); - } - #define PT_CALL( call ) { int rc= call; if (rc!=0) _PT_FAIL( rc, #call, __FILE__, __LINE__ ); } +} +#define PT_CALL( call ) { int rc= call; if (rc!=0) _PT_FAIL( rc, #call, __FILE__, __LINE__ ); } // array of 7 thread priority values, hand-tuned by platform so that we offer a uniform [-3,+3] public priority range static int const gs_prio_remap[] = @@ -376,14 +348,6 @@ static int const gs_prio_remap[] = // // TBD: Find right values for Cygwin // -# elif defined( PLATFORM_WIN32) || defined( PLATFORM_POCKETPC) - // any other value not supported by win32-pthread as of version 2.9.1 -# define _PRIO_MODE SCHED_OTHER - - // PTHREAD_SCOPE_PROCESS not supported by win32-pthread as of version 2.9.1 - //#define _PRIO_SCOPE PTHREAD_SCOPE_SYSTEM // but do we need this at all to start with? - THREAD_PRIORITY_IDLE, THREAD_PRIORITY_LOWEST, THREAD_PRIORITY_BELOW_NORMAL, THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_HIGHEST, THREAD_PRIORITY_TIME_CRITICAL - # else # error "Unknown OS: not implemented!" # endif @@ -404,17 +368,17 @@ static int select_prio(int prio /* -3..+3 */) return gs_prio_remap[prio + 3]; } -void THREAD_SET_PRIORITY( int prio) +void THREAD_SET_PRIORITY(int prio) { #ifdef PLATFORM_LINUX - if( sudo) // only root-privileged process can change priorities + if (!sudo) // only root-privileged process can change priorities + return; #endif // PLATFORM_LINUX - { - struct sched_param sp; - // prio range [-3,+3] was checked by the caller - sp.sched_priority = gs_prio_remap[ prio + 3]; - PT_CALL( pthread_setschedparam( pthread_self(), _PRIO_MODE, &sp)); - } + + struct sched_param sp; + // prio range [-3,+3] was checked by the caller + sp.sched_priority = gs_prio_remap[prio + 3]; + PT_CALL(pthread_setschedparam(pthread_self(), _PRIO_MODE, &sp)); } // ################################################################################################# @@ -422,69 +386,75 @@ void THREAD_SET_PRIORITY( int prio) void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_) { #ifdef PLATFORM_LINUX - if (sudo) // only root-privileged process can change priorities + if (!sudo) // only root-privileged process can change priorities + return; #endif // PLATFORM_LINUX - { - struct sched_param sp; - // prio range [-3,+3] was checked by the caller - sp.sched_priority = gs_prio_remap[prio_ + 3]; - PT_CALL(pthread_setschedparam(static_cast(thread_.native_handle()), _PRIO_MODE, &sp)); - } + + struct sched_param sp; + // prio range [-3,+3] was checked by the caller + sp.sched_priority = gs_prio_remap[prio_ + 3]; + PT_CALL(pthread_setschedparam(static_cast(thread_.native_handle()), _PRIO_MODE, &sp)); } // ################################################################################################# -void THREAD_SET_AFFINITY( unsigned int aff) +void THREAD_SET_AFFINITY(unsigned int aff) { int bit = 0; #ifdef __NetBSD__ - cpuset_t *cpuset = cpuset_create(); + cpuset_t* cpuset = cpuset_create(); if (cpuset == nullptr) - _PT_FAIL( errno, "cpuset_create", __FILE__, __LINE__-2 ); + _PT_FAIL(errno, "cpuset_create", __FILE__, __LINE__ - 2); #define CPU_SET(b, s) cpuset_set(b, *(s)) #else cpu_set_t cpuset; - CPU_ZERO( &cpuset); + CPU_ZERO(&cpuset); #endif - while( aff != 0) + while (aff != 0) { - if( aff & 1) + if (aff & 1) { - CPU_SET( bit, &cpuset); + CPU_SET(bit, &cpuset); } - ++ bit; + ++bit; aff >>= 1; } #ifdef __ANDROID__ - PT_CALL( sched_setaffinity( pthread_self(), sizeof(cpu_set_t), &cpuset)); + PT_CALL(sched_setaffinity(pthread_self(), sizeof(cpu_set_t), &cpuset)); #elif defined(__NetBSD__) - PT_CALL( pthread_setaffinity_np( pthread_self(), cpuset_size(cpuset), cpuset)); - cpuset_destroy( cpuset); + PT_CALL(pthread_setaffinity_np(pthread_self(), cpuset_size(cpuset), cpuset)); + cpuset_destroy(cpuset); #else - PT_CALL( pthread_setaffinity_np( pthread_self(), sizeof(cpu_set_t), &cpuset)); + PT_CALL(pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset)); #endif } - void THREAD_SETNAME( char const* _name) - { - // exact API to set the thread name is platform-dependant - // if you need to fix the build, or if you know how to fill a hole, tell me (bnt.germain@gmail.com) so that I can submit the fix in github. +// ################################################################################################# + +void THREAD_SETNAME(char const* _name) +{ + // exact API to set the thread name is platform-dependant + // if you need to fix the build, or if you know how to fill a hole, tell me (bnt.germain@gmail.com) so that I can submit the fix in github. #if defined PLATFORM_BSD && !defined __NetBSD__ - pthread_set_name_np( pthread_self(), _name); + pthread_set_name_np(pthread_self(), _name); #elif defined PLATFORM_BSD && defined __NetBSD__ - pthread_setname_np( pthread_self(), "%s", (void *)_name); + pthread_setname_np(pthread_self(), "%s", (void*) _name); #elif defined PLATFORM_LINUX - #if LINUX_USE_PTHREAD_SETNAME_NP - pthread_setname_np( pthread_self(), _name); - #else // LINUX_USE_PTHREAD_SETNAME_NP - prctl(PR_SET_NAME, _name, 0, 0, 0); - #endif // LINUX_USE_PTHREAD_SETNAME_NP +#if LINUX_USE_PTHREAD_SETNAME_NP + pthread_setname_np(pthread_self(), _name); +#else // LINUX_USE_PTHREAD_SETNAME_NP + prctl(PR_SET_NAME, _name, 0, 0, 0); +#endif // LINUX_USE_PTHREAD_SETNAME_NP #elif defined PLATFORM_QNX || defined PLATFORM_CYGWIN - pthread_setname_np( pthread_self(), _name); + pthread_setname_np(pthread_self(), _name); #elif defined PLATFORM_OSX - pthread_setname_np(_name); -#elif defined PLATFORM_WIN32 || defined PLATFORM_POCKETPC - PT_CALL( pthread_setname_np( pthread_self(), _name)); + pthread_setname_np(_name); +#else + fprintf(stderr, "THREAD_SETNAME: unsupported platform\n"); + abort(); #endif - } +} + #endif // THREADAPI == THREADAPI_PTHREAD +// ################################################################################################# +// ################################################################################################# diff --git a/src/threading.h b/src/threading.h index 82c8f52..c443c82 100644 --- a/src/threading.h +++ b/src/threading.h @@ -16,22 +16,24 @@ #define THREADAPI THREADAPI_PTHREAD #endif // (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) -/*---=== Locks & Signals ===--- -*/ +static constexpr int THREAD_PRIO_DEFAULT{ -999 }; +// ################################################################################################## +// ################################################################################################## #if THREADAPI == THREADAPI_WINDOWS - #if defined( PLATFORM_XBOX) - #include - #else // !PLATFORM_XBOX - #define WIN32_LEAN_AND_MEAN - // CONDITION_VARIABLE needs version 0x0600+ - // _WIN32_WINNT value is already defined by MinGW, but not by MSVC - #ifndef _WIN32_WINNT - #define _WIN32_WINNT 0x0600 - #endif // _WIN32_WINNT - #include - #endif // !PLATFORM_XBOX - #include + +#if defined(PLATFORM_XBOX) +#include +#else // !PLATFORM_XBOX +#define WIN32_LEAN_AND_MEAN +// CONDITION_VARIABLE needs version 0x0600+ +// _WIN32_WINNT value is already defined by MinGW, but not by MSVC +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 +#endif // _WIN32_WINNT +#include +#endif // !PLATFORM_XBOX +#include /* #define XSTR(x) STR(x) @@ -39,148 +41,50 @@ #pragma message( "The value of _WIN32_WINNT: " XSTR(_WIN32_WINNT)) */ - // MSDN: http://msdn2.microsoft.com/en-us/library/ms684254.aspx - // - // CRITICAL_SECTION can be used for simple code protection. Mutexes are - // needed for use with the SIGNAL system. - // - - #if _WIN32_WINNT < 0x0600 // CONDITION_VARIABLE aren't available, use a signal - - struct SIGNAL_T - { - CRITICAL_SECTION signalCS; - CRITICAL_SECTION countCS; - HANDLE waitEvent; - HANDLE waitDoneEvent; - LONG waitersCount; - }; - - - #else // CONDITION_VARIABLE are available, use them +static constexpr int THREAD_PRIO_MIN{ -3 }; +static constexpr int THREAD_PRIO_MAX{ +3 }; - #define SIGNAL_T CONDITION_VARIABLE - #define MUTEX_INIT( ref) InitializeCriticalSection( ref) - #define MUTEX_FREE( ref) DeleteCriticalSection( ref) - #define MUTEX_LOCK( ref) EnterCriticalSection( ref) - #define MUTEX_UNLOCK( ref) LeaveCriticalSection( ref) - - #endif // CONDITION_VARIABLE are available - - #define MUTEX_RECURSIVE_INIT(ref) MUTEX_INIT(ref) /* always recursive in Win32 */ - - using THREAD_RETURN_T = unsigned int; - - #define YIELD() Sleep(0) - #define THREAD_CALLCONV __stdcall +// ################################################################################################## +// ################################################################################################## #else // THREADAPI == THREADAPI_PTHREAD - // PThread (Linux, OS X, ...) - - // looks like some MinGW installations don't support PTW32_INCLUDE_WINDOWS_H, so let's include it ourselves, just in case - #if defined(PLATFORM_WIN32) - #include - #endif // PLATFORM_WIN32 - #include - - #ifdef PLATFORM_LINUX - #if defined(__GLIBC__) - # define _MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP - #else - # define _MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE - #endif - #else - /* OS X, ... */ - # define _MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE - #endif - - #define MUTEX_INIT(ref) pthread_mutex_init(ref, nullptr) - #define MUTEX_RECURSIVE_INIT(ref) \ - { pthread_mutexattr_t a; pthread_mutexattr_init( &a ); \ - pthread_mutexattr_settype( &a, _MUTEX_RECURSIVE ); \ - pthread_mutex_init(ref,&a); pthread_mutexattr_destroy( &a ); \ - } - #define MUTEX_FREE(ref) pthread_mutex_destroy(ref) - #define MUTEX_LOCK(ref) pthread_mutex_lock(ref) - #define MUTEX_UNLOCK(ref) pthread_mutex_unlock(ref) - - using THREAD_RETURN_T = void *; - - using SIGNAL_T = pthread_cond_t; - - // Yield is non-portable: - // - // OS X 10.4.8/9 has pthread_yield_np() - // Linux 2.4 has pthread_yield() if _GNU_SOURCE is #defined - // FreeBSD 6.2 has pthread_yield() - // ... - // - #if defined( PLATFORM_OSX) - #define YIELD() pthread_yield_np() - #else - #define YIELD() sched_yield() - #endif - #define THREAD_CALLCONV -#endif //THREADAPI == THREADAPI_PTHREAD - -/*---=== Threading ===--- -*/ - -#define THREAD_PRIO_DEFAULT (-999) - -#if THREADAPI == THREADAPI_WINDOWS - -# define THREAD_PRIO_MIN (-3) -# define THREAD_PRIO_MAX (+3) - -#else // THREADAPI == THREADAPI_PTHREAD - - /* Platforms that have a timed 'pthread_join()' can get away with a simpler - * implementation. Others will use a condition variable. - */ -# if defined __WINPTHREADS_VERSION -//# define USE_PTHREAD_TIMEDJOIN -# endif // __WINPTHREADS_VERSION - -# ifdef USE_PTHREAD_TIMEDJOIN -# ifdef PLATFORM_OSX -# error "No 'pthread_timedjoin()' on this system" -# else - /* Linux, ... */ -# define PTHREAD_TIMEDJOIN pthread_timedjoin_np -# endif -# endif - -# if defined(PLATFORM_LINUX) - extern volatile bool sudo; -# ifdef LINUX_SCHED_RR -# define THREAD_PRIO_MIN (sudo ? -3 : 0) -# else -# define THREAD_PRIO_MIN (0) -# endif -# define THREAD_PRIO_MAX (sudo ? +3 : 0) -# else -# define THREAD_PRIO_MIN (-3) -# define THREAD_PRIO_MAX (+3) -# endif +// ################################################################################################## +// ################################################################################################## + +// PThread (Linux, OS X, ...) + +// looks like some MinGW installations don't support PTW32_INCLUDE_WINDOWS_H, so let's include it ourselves, just in case +#if defined(PLATFORM_WIN32) +#include +#endif // PLATFORM_WIN32 +#include + +// Yield is non-portable: +// +// OS X 10.4.8/9 has pthread_yield_np() +// Linux 2.4 has pthread_yield() if _GNU_SOURCE is #defined +// FreeBSD 6.2 has pthread_yield() +// ... +// + +#if defined(PLATFORM_LINUX) +extern volatile bool sudo; +# ifdef LINUX_SCHED_RR +# define THREAD_PRIO_MIN (sudo ? -3 : 0) +# else +static constexpr int THREAD_PRIO_MIN{ 0 }; +#endif +# define THREAD_PRIO_MAX (sudo ? +3 : 0) +#else +static constexpr int THREAD_PRIO_MIN{ -3 }; +static constexpr int THREAD_PRIO_MAX{ +3 }; +#endif #endif // THREADAPI == THREADAPI_WINDOWS +// ################################################################################################## +// ################################################################################################## -/* -* Win32 and PTHREAD_TIMEDJOIN allow waiting for a thread with a timeout. -* Posix without PTHREAD_TIMEDJOIN needs to use a condition variable approach. -*/ -#define THREADWAIT_TIMEOUT 1 -#define THREADWAIT_CONDVAR 2 - -#if THREADAPI == THREADAPI_WINDOWS || (defined PTHREAD_TIMEDJOIN) -#define THREADWAIT_METHOD THREADWAIT_TIMEOUT -#else // THREADAPI == THREADAPI_WINDOWS || (defined PTHREAD_TIMEDJOIN) -#define THREADWAIT_METHOD THREADWAIT_CONDVAR -#endif // THREADAPI == THREADAPI_WINDOWS || (defined PTHREAD_TIMEDJOIN) - - -void THREAD_SETNAME( char const* _name); -void THREAD_SET_PRIORITY( int prio); -void THREAD_SET_AFFINITY( unsigned int aff); +void THREAD_SETNAME(char const* _name); +void THREAD_SET_PRIORITY(int prio); +void THREAD_SET_AFFINITY(unsigned int aff); void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_); -- cgit v1.2.3-55-g6feb From 23f25f3a9d327153e4c16ca86d937ec334803a10 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Tue, 9 Apr 2024 10:12:30 +0200 Subject: C++ migration: still more threading code cleanup. 'sudo' global moved in the Universe --- src/deep.h | 2 +- src/keeper.h | 2 +- src/lanes.cpp | 32 +++----------------------------- src/state.h | 2 +- src/threading.cpp | 22 ++++++++-------------- src/threading.h | 24 +++++------------------- src/tools.h | 2 +- src/universe.cpp | 29 +++++++++++++++++++++++++++++ src/universe.h | 20 ++++++++++++++++++-- 9 files changed, 67 insertions(+), 68 deletions(-) (limited to 'src') diff --git a/src/deep.h b/src/deep.h index 1799cf0..c09af23 100644 --- a/src/deep.h +++ b/src/deep.h @@ -19,7 +19,7 @@ extern "C" { #include // forwards -struct Universe; +class Universe; enum class LookupMode { diff --git a/src/keeper.h b/src/keeper.h index 931c1d5..ba5a57b 100644 --- a/src/keeper.h +++ b/src/keeper.h @@ -15,7 +15,7 @@ extern "C" { // forwards enum class LookupMode; -struct Universe; +class Universe; struct Keeper { diff --git a/src/lanes.cpp b/src/lanes.cpp index f5d5130..28f95f6 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -136,7 +136,7 @@ void Lane::startThread(int priority_) m_thread = std::jthread([this]() { lane_main(this); }); if (priority_ != THREAD_PRIO_DEFAULT) { - JTHREAD_SET_PRIORITY(m_thread, priority_); + JTHREAD_SET_PRIORITY(m_thread, priority_, U->m_sudo); } } @@ -784,7 +784,7 @@ LUAG_FUNC(get_debug_threadname) LUAG_FUNC(set_thread_priority) { - int const prio{ (int) luaL_checkinteger(L, 1) }; + lua_Integer const prio{ luaL_checkinteger(L, 1) }; // public Lanes API accepts a generic range -3/+3 // that will be remapped into the platform-specific scheduler priority scheme // On some platforms, -3 is equivalent to -2 and +3 to +2 @@ -792,7 +792,7 @@ LUAG_FUNC(set_thread_priority) { return luaL_error(L, "priority out of range: %d..+%d (%d)", THREAD_PRIO_MIN, THREAD_PRIO_MAX, prio); } - THREAD_SET_PRIORITY(prio); + THREAD_SET_PRIORITY(static_cast(prio), universe_get(L)->m_sudo); return 0; } @@ -1746,32 +1746,6 @@ static void init_once_LOCKED( void) #if (defined PLATFORM_OSX) && (defined _UTILBINDTHREADTOCPU) chudInitialize(); #endif - - //--- - // Linux needs SCHED_RR to change thread priorities, and that is only - // allowed for sudo'ers. SCHED_OTHER (default) has no priorities. - // SCHED_OTHER threads are always lower priority than SCHED_RR. - // - // ^-- those apply to 2.6 kernel. IF **wishful thinking** these - // constraints will change in the future, non-sudo priorities can - // be enabled also for Linux. - // -#ifdef PLATFORM_LINUX - sudo = (geteuid() == 0); // we are root? - - // If lower priorities (-2..-1) are wanted, we need to lift the main - // thread to SCHED_RR and 50 (medium) level. Otherwise, we're always below - // the launched threads (even -2). - // -#ifdef LINUX_SCHED_RR - if (sudo) - { - struct sched_param sp; - sp.sched_priority = _PRIO_0; - PT_CALL(pthread_setschedparam(pthread_self(), SCHED_RR, &sp)); - } -#endif // LINUX_SCHED_RR -#endif // PLATFORM_LINUX } // ################################################################################################# diff --git a/src/state.h b/src/state.h index 0e35e89..0e069da 100644 --- a/src/state.h +++ b/src/state.h @@ -3,7 +3,7 @@ #include "macros_and_utils.h" // forwards -struct Universe; +class Universe; void serialize_require(DEBUGSPEW_PARAM_COMMA(Universe* U) lua_State* L); diff --git a/src/threading.cpp b/src/threading.cpp index aab2fa7..4d210d6 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -65,12 +65,6 @@ THE SOFTWARE. # include #endif -/* Linux needs to check, whether it's been run as root -*/ -#ifdef PLATFORM_LINUX - volatile bool sudo; -#endif - #ifdef PLATFORM_OSX # include "threading_osx.h" #endif @@ -134,10 +128,10 @@ static int const gs_prio_remap[] = // ############################################################################################### -void THREAD_SET_PRIORITY(int prio) +void THREAD_SET_PRIORITY(int prio_, [[maybe_unused]] bool sudo_) { // prio range [-3,+3] was checked by the caller - if (!SetThreadPriority(GetCurrentThread(), gs_prio_remap[prio + 3])) + if (!SetThreadPriority(GetCurrentThread(), gs_prio_remap[prio_ + 3])) { FAIL("THREAD_SET_PRIORITY", GetLastError()); } @@ -145,7 +139,7 @@ void THREAD_SET_PRIORITY(int prio) // ############################################################################################### -void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_) +void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_, [[maybe_unused]] bool sudo_) { // prio range [-3,+3] was checked by the caller if (!SetThreadPriority(thread_.native_handle(), gs_prio_remap[prio_ + 3])) @@ -368,25 +362,25 @@ static int select_prio(int prio /* -3..+3 */) return gs_prio_remap[prio + 3]; } -void THREAD_SET_PRIORITY(int prio) +void THREAD_SET_PRIORITY(int prio_, [[maybe_unused]] bool sudo_) { #ifdef PLATFORM_LINUX - if (!sudo) // only root-privileged process can change priorities + if (!sudo_) // only root-privileged process can change priorities return; #endif // PLATFORM_LINUX struct sched_param sp; // prio range [-3,+3] was checked by the caller - sp.sched_priority = gs_prio_remap[prio + 3]; + sp.sched_priority = gs_prio_remap[prio_ + 3]; PT_CALL(pthread_setschedparam(pthread_self(), _PRIO_MODE, &sp)); } // ################################################################################################# -void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_) +void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_, [[maybe_unused]] bool sudo_) { #ifdef PLATFORM_LINUX - if (!sudo) // only root-privileged process can change priorities + if (!sudo_) // only root-privileged process can change priorities return; #endif // PLATFORM_LINUX diff --git a/src/threading.h b/src/threading.h index c443c82..f38b2de 100644 --- a/src/threading.h +++ b/src/threading.h @@ -58,33 +58,19 @@ static constexpr int THREAD_PRIO_MAX{ +3 }; #endif // PLATFORM_WIN32 #include -// Yield is non-portable: -// -// OS X 10.4.8/9 has pthread_yield_np() -// Linux 2.4 has pthread_yield() if _GNU_SOURCE is #defined -// FreeBSD 6.2 has pthread_yield() -// ... -// - -#if defined(PLATFORM_LINUX) -extern volatile bool sudo; -# ifdef LINUX_SCHED_RR -# define THREAD_PRIO_MIN (sudo ? -3 : 0) -# else +#if defined(PLATFORM_LINUX) && !defined(LINUX_SCHED_RR) static constexpr int THREAD_PRIO_MIN{ 0 }; -#endif -# define THREAD_PRIO_MAX (sudo ? +3 : 0) #else static constexpr int THREAD_PRIO_MIN{ -3 }; -static constexpr int THREAD_PRIO_MAX{ +3 }; #endif +static constexpr int THREAD_PRIO_MAX{ +3 }; -#endif // THREADAPI == THREADAPI_WINDOWS +#endif // THREADAPI == THREADAPI_PTHREAD // ################################################################################################## // ################################################################################################## void THREAD_SETNAME(char const* _name); -void THREAD_SET_PRIORITY(int prio); +void THREAD_SET_PRIORITY(int prio_, bool sudo_); void THREAD_SET_AFFINITY(unsigned int aff); -void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_); +void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_, bool sudo_); diff --git a/src/tools.h b/src/tools.h index c1a8534..7d9aaab 100644 --- a/src/tools.h +++ b/src/tools.h @@ -6,7 +6,7 @@ #include "macros_and_utils.h" // forwards -struct Universe; +class Universe; // ################################################################################################ diff --git a/src/universe.cpp b/src/universe.cpp index 66da147..290e547 100644 --- a/src/universe.cpp +++ b/src/universe.cpp @@ -43,6 +43,35 @@ static constexpr UniqueKey UNIVERSE_LIGHT_REGKEY{ 0x3663C07C742CEB81ull }; // ################################################################################################ +Universe::Universe() +{ + //--- + // Linux needs SCHED_RR to change thread priorities, and that is only + // allowed for sudo'ers. SCHED_OTHER (default) has no priorities. + // SCHED_OTHER threads are always lower priority than SCHED_RR. + // + // ^-- those apply to 2.6 kernel. IF **wishful thinking** these + // constraints will change in the future, non-sudo priorities can + // be enabled also for Linux. + // +#ifdef PLATFORM_LINUX + // If lower priorities (-2..-1) are wanted, we need to lift the main + // thread to SCHED_RR and 50 (medium) level. Otherwise, we're always below + // the launched threads (even -2). + // +#ifdef LINUX_SCHED_RR + if (m_sudo) + { + struct sched_param sp; + sp.sched_priority = _PRIO_0; + PT_CALL(pthread_setschedparam(pthread_self(), SCHED_RR, &sp)); + } +#endif // LINUX_SCHED_RR +#endif // PLATFORM_LINUX +} + +// ################################################################################################ + // only called from the master state Universe* universe_create(lua_State* L) { diff --git a/src/universe.h b/src/universe.h index 38885cb..f4211af 100644 --- a/src/universe.h +++ b/src/universe.h @@ -119,9 +119,17 @@ class ProtectedAllocator : public AllocatorDefinition // everything regarding the Lanes universe is stored in that global structure // held as a full userdata in the master Lua state that required it for the first time -// don't forget to initialize all members in LG_configure() -struct Universe +class Universe { + public: + +#ifdef PLATFORM_LINUX + // Linux needs to check, whether it's been run as root + bool const m_sudo{ geteuid() == 0 }; +#else + bool const m_sudo{ false }; +#endif // PLATFORM_LINUX + // for verbose errors bool verboseErrors{ false }; @@ -155,6 +163,7 @@ struct Universe // require() serialization std::recursive_mutex require_cs; + // metatable unique identifiers std::atomic next_mt_id{ 1 }; #if USE_DEBUG_SPEW() @@ -165,6 +174,13 @@ struct Universe // After a lane has removed itself from the chain, it still performs some processing. // The terminal desinit sequence should wait for all such processing to terminate before force-killing threads std::atomic selfdestructing_count{ 0 }; + + Universe(); + ~Universe() = default; + Universe(Universe const&) = delete; + Universe(Universe&&) = delete; + Universe& operator=(Universe const&) = delete; + Universe& operator=(Universe&&) = delete; }; // ################################################################################################ -- cgit v1.2.3-55-g6feb From 98ff4191c2cd215c7d6a429e9ddd66f7a6a30316 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Tue, 9 Apr 2024 11:39:25 +0200 Subject: C++ migration: simpler one-time initializations --- src/lanes.cpp | 51 ++++++++++++++------------------------------------- 1 file changed, 14 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/lanes.cpp b/src/lanes.cpp index 28f95f6..cf443f2 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -1736,55 +1736,32 @@ static const struct luaL_Reg lanes_functions[] = { nullptr, nullptr } }; -/* - * One-time initializations - * settings table it at position 1 on the stack - * pushes an error string on the stack in case of problem - */ -static void init_once_LOCKED( void) -{ -#if (defined PLATFORM_OSX) && (defined _UTILBINDTHREADTOCPU) - chudInitialize(); -#endif -} - // ################################################################################################# -// we are C++20, the flags are default-initialized to 'clear' -std::atomic_flag s_insideInit; -std::atomic_flag s_initDone; - // upvalue 1: module name // upvalue 2: module table // param 1: settings table LUAG_FUNC(configure) { + // start with one-time initializations. + { + // C++ guarantees that the static variable initialization is threadsafe. + static auto _ = std::invoke( + []() + { +#if (defined PLATFORM_OSX) && (defined _UTILBINDTHREADTOCPU) + chudInitialize(); +#endif + return false; + } + ); + } + Universe* U = universe_get(L); bool const from_master_state{ U == nullptr }; char const* name = luaL_checkstring(L, lua_upvalueindex(1)); ASSERT_L(lua_type(L, 1) == LUA_TTABLE); - /* - ** Making one-time initializations. - ** - ** When the host application is single-threaded (and all threading happens via Lanes) - ** there is no problem. But if the host is multithreaded, we need to lock around the - ** initializations. - */ - if (s_insideInit.test_and_set()) - { - // blocks until flag value is no longer the one passed in parameter - s_initDone.wait(false); - } - else - { - // we are the first to enter here, because s_insideInit was false. - // and we are the only one, because it's now true. - init_once_LOCKED(); - std::ignore = s_initDone.test_and_set(); - s_initDone.notify_all(); - } - STACK_GROW(L, 4); STACK_CHECK_START_ABS(L, 1); // settings -- cgit v1.2.3-55-g6feb From 95932749a53f46ae5901798a17e1f0c4c33ac6b2 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Tue, 9 Apr 2024 15:49:18 +0200 Subject: C++ migration: use strong type safety for source and destination states in transfer functions --- CHANGES | 744 +----------------------------------------------- lanes-3.16.3-0.rockspec | 78 ----- lanes-4.0.0-0.rockspec | 78 +++++ src/cancel.cpp | 7 +- src/compat.cpp | 35 ++- src/deep.cpp | 40 +-- src/deep.h | 4 +- src/keeper.cpp | 16 +- src/keeper.h | 2 +- src/lanes.cpp | 20 +- src/lanes_private.h | 4 +- src/linda.cpp | 10 +- src/macros_and_utils.h | 18 +- src/state.cpp | 29 +- src/state.h | 3 +- src/threading.cpp | 12 +- src/threading.h | 1 - src/tools.cpp | 85 +++--- src/tools.h | 14 +- src/universe.cpp | 8 +- 20 files changed, 237 insertions(+), 971 deletions(-) delete mode 100644 lanes-3.16.3-0.rockspec create mode 100644 lanes-4.0.0-0.rockspec (limited to 'src') diff --git a/CHANGES b/CHANGES index d93f5b4..3725674 100644 --- a/CHANGES +++ b/CHANGES @@ -1,746 +1,6 @@ CHANGES: -CHANGE 159: BGe 19-Mar-24 - * fix small internal issue with when hitting timeout on thread kill during thread_cancel() in pthread implementation - -CHANGE 158: BGe 22-Feb-24 - * naive luajit detection in PUC-Lua-based builds, and vice-versa to detect mismatches - * internal version bumped to 3.16.3 - -CHANGE 157: Mitalie 17-Aug-23 - * Prevent crash on linux as non-root - * internal version bumped to 3.16.2 - -CHANGE 156: BGe 9-Aug-23 - * new configuration option .internal_allocator to help LuaJIT users. - * internal version bumped to 3.16.1 - -CHANGE 155: BGe 28-Jul-23 - * tweaks to linux thread priority management: do nothing if not super-user. if super-user, do nothing if nothing is provided (instead of trying to force a prio when LINUX_SCHED_RR is defined). - -CHANGE 154: eligovision 1-Mar-22 - * Fix 3-parametrized __lanesclone - -CHANGE 153: BGe 17-Feb-22 - * NEVER use allocator obtained from lua_getallocf to allocate stuff manually when compiling for LuaJIT - -CHANGE 152: BGe 7-Feb-22 - * bumped version to 3.16.0 - * __lanesclone is now called only once with 3 parameters dest, source, size -> BREAKS CUSTOM DEEP USERDATA API - -CHANGE 151: BGe 7-Feb-22 - * bumped version to 3.15.2 - * Lanes no longer relies on malloc/free for internal allocations, but uses the primary alloc function from the master Lua state - -CHANGE 150: BGe 22-Sep-21 - * fix require() wrapper to return all values returned by original require() - -CHANGE 149: BGe 8-Jul-21 - * bumped version to 3.15.1 - * fix function transfer with lua_dump for Lua 5.4 failing for functions big enough to necessitate a buffer reallocation - -CHANGE 148: BGe 23-Jun-21 - * __lanesclone now receives the original as light userdata the first time it is called -> BREAKS CUSTOM DEEP USERDATA API - -CHANGE 147: BGe 16-Jun-21 - * changed lanes.threads() output so that several lanes with the same name don't clobber each other in the result table -> BREAKS API - * bumped version to 3.15 because of the API change - -CHANGE 146: BGe 26-Apr-19 - * lane:cancel() rework (see doc). - * opt.cancelstep is gone, hook is installed by lane:cancel() if requested - -CHANGE 145: BGe 28-Nov-18 - * more code refacto - * don't test __lanesignore for POD types (-> slightly faster when trasnfering lots of data) - -CHANGE 144: BGe 28-Nov-18 - * some code refacto - -CHANGE 143: BGe 27-Nov-18 - * Lua 5.4 support - * __lanesclone and lanes.nameof support userdata uservalue(s) - -CHANGE 142: BGe 26-Nov-18 - * Version is available in public header - -CHANGE 141: BGe 25-Nov-18 - * protect_allocator configure option is gone, long live allocator (more embedders-friendly) - -CHANGE 140: BGe 22-Nov-18 - * Raise an error instead of crashing when attempting to transfer a non-deep full userdata - -CHANGE 139: BGe 21-Nov-18 - * more DEBUGSPEW - -CHANGE 138: BGe 19-Nov-18 - * Registry access code utility macros - * CONFIG_REGKEY and LOOKUP_REGKEY are now lightuserdata instead of strings - * Stack checking debug macros improvements - -CHANGE 137: BGe 15-Nov-18 - * Deep userdata must embed DeepPrelude to save an allocation (also changes Deep protocol) - -CHANGE 136: BGe 15-Nov-18 - * split linda code in a separate file - * rockspec for version v3.13.0 - -CHANGE 135: BGe 11-Nov-18 - * fix a bunch of compilation warnings - -CHANGE 134: BGe 3-Dec-13 - * new API lanes.set_thread_affinity() - * set_debug_threadname implemented with win32 pthread - -CHANGE 133: BGe 8-Nov-18 - * Make sure any linda operation that can raise an error won't ever leave a mutex unreleased - * lane:join() now returns nil, "timeout" in case of timeout - -CHANGE 132: BGe 7-Nov-18 - * __lanesclone mechanism should actually work now - -CHANGE 131: BGe 7-Nov-18 - * Fix potential crash at application shutdown when deep userdata were created before Lanes is required - -CHANGE 130: BGe 2-Nov-18 - * always duplicate the config structure in new lanes even when no libraries are initialized by the generator - (fixes an internal error trying to call on_state_create in a lane without any libs loaded) - -CHANGE 129: BGe 2-Nov-18 - * Bumped version to 3.13 - * fix error when autodetecting protect_allocator when running under LuaJIT - -CHANGE 128: BGe 31-Oct-18 - * Better default value autodetection for protect_allocator setting - -CHANGE 127: BGe 30-Oct-18 - * restrict internal light userdata constants to 47 significant bits when building against LuaJIT-x64 - -CHANGE 126: Bge 29-Oct-18 - * Add deep user data cloning support - -CHANGE 125: BGe 25-Oct-18 - * Fix Lanes build by reorganizing types around a bit - -CHANGE 124: BGe 9-Jul-18 - * Fix a stack overflow when copying large tables with verbose_errors option enabled - * Support for integer formatting in verbose errors - -CHANGE 123: BGe 2-Aug-17 - * added support for user-provided __gc in deep userdata - * more complete deep userdata sample - -CHANGE 122: BGe 1-Aug-17 - * fix crash trying to use a deep-aware module while not requiring Lanes - * bumped version to 3.12 - -CHANGE 121: BGe 13-Jun-17 - * no longer internally assert when an error message is not a string - -CHANGE 120: BGe 5-Jun-17 - * new API function lanes.register( "name", module) to manually register a module table after it was required - * Transfering registered module tables will link the equivalent in the destination state instead of cloning it - * bumped version to 3.11 - -CHANGE 119: BGe 10-May-17 - * Fixed some compilation warnings - * Improved LuaJIT support - -CHANGE 118: trukanduk 21-Nov-16 - * bumped version to 3.10.1 - * objects with a metatable that contains __lanesignore are skipped during data transfers - -CHANGE 117: mpeterv 21-Nov-16 - * Fix an implicit number-to-string conversion - -CHANGE 116: BGe, mpeterv 27-Apr-15 - * bumped version to 3.10.0 - * segfault fixed in LG_lane_new - * Lua 5.3 support - -CHANGE 115: BGe 18-Sep-14 - * bumped version to 3.9.7 - * new function lanes.sleep() - -CHANGE 114: BGe 8-Jul-14 - * Postponed _G scan for function lookup database to after on_state_create invocation - * Fixed a crash when USE_DEBUG_SPEW == 1 - -CHANGE 113: BGe 17-Jun-14 - * bumped version to 3.9.6 - * separate deep userdata code in a dedicated file to allow external modules to implement Lanes-compatible deep userdata without requiring a binary dependency against the Lanes module - because of this linda_id function(eDO_metatable) must push 2 values on the stack: a metatable and a deep version string obtained from luaG_pushdeepversion() - -CHANGE 112 BGe 16-May-14 - * bumped version to 3.9.5 - * fix linda.__towatch to return non-nil when the linda is empty - * lanes.gen() error reporting improvements - -CHANGE 111 BGe 24-Apr-14 - * fixed linda:send() possibly returning an undefined value - -CHANGE 110 Stepets 20-Apr-14 - * fix LuaJIT detection issues - -CHANGE 109 BGe 03-Apr-14 - * moved some Lua-version compatibility code in separate source files - -CHANGE 108: BGe 20-Mar-14 - * bumped version to 3.9.4 - * set_finalizer throws an error if provided finalizer isn't a function - * fix error handling when the error doesn't generate an error handler call (IOW, all errors but LUA_ERRRUN) - * provide callstack if LUA_ERRRUN occurs inside a finalizer - -CHANGE 107: BGe 19-Mar-14 - * Make sure we don't mutex-wrap require() more than once, just in case - -CHANGE 106: BGe 17-Mar-14 - * Fixed crash when using protect_allocator option - -CHANGE 105: BGe 27-Feb-14 - * Bumped version to 3.9.3 - * new exposed variable linda.null that exposes the internal NIL_SENTINEL marker - * linda:send() interprets send key linda.null as authorization to silently send a single nil when not provided with anything to send - (useful when sending results of a function that can return nothing) - -CHANGE 104: BGe 25-Feb-14 - * Bumped version to 3.9.2 - * Internal rework: the whole Lanes engine now works "per universe" to allow concurrent Lanes execution in more than one embedded master state - * this universe is a full userdata created in the master state, selfdestruct_gc is the __gc for this userdata - * most of what was initialized only once is now per-universe - * Fixed potential crashes at desinit if problems occur during keeper states initialisation - * Fixed require() not always serialized properly - * Raise an error instead of crashing on deep userdata prelude memory allocation failure - * Added forgotten mutex desinitialisation at universe shutdown - -CHANGE 103: BGe 24-Feb-14 - * Fix lookup database table not being created when it should if Lanes is required in more than one Lua master state - -CHANGE 102: BGe 18-Feb-14 - * raise an error instead of dereferencing a NULL pointer on deep userdata creation and lane struct creation - -CHANGE 101: BGe 18-Feb-14 - * version 3.9.1 - * removed some keeper desinit legacy dead code - * keeper array is allocated with master state's alloc function instead of malloc()/free() - * prevent application crash when specifying a very large number of keepers in the configuration options - * any error occuring during one-time inits is raised outside the one-time mutex protected code region - -CHANGE 100: BGe 17-Feb-14 - * lanes.linda() accepts an optional integer group to give control on keeper state repartition - -CHANGE 99: BGe 17-Feb-14 - * version 3.9.0 - * keepers now require "package", receive package.path & package.cpath, and call on_state_create() if it is a C function - * changed the deep public API (improved deep idfunc signature, renamed luaG_deep_userdata to luaG_newdeepuserdata) - * if an error occurs while copying a deep userdata, don't raise inside the keeper state - * fixed situations where raised errors could lead to memory leaks (deep gc) - -CHANGE 98: BGe 13-Feb-14 - * version 3.8.5 - * linda:limit() returns lanes.cancel_error on a limited linda - * lanes.genlock() and lanes.genatomic() support cancelled lindas by returning lanes.cancel_error whenever appropriate - * fixed a possible Lua stack overflow when calling linda:dump() - * fixed cases where linda:send() and linda:receive() would not return lanes.cancel_error when they should - -CHANGE 97: BGe 10-Feb-14 - * version 3.8.4 - * new API linda:cancel("read"|"write"|"both"|"none") - * all linda operations return lanes.cancel_error on a cancelled linda - * raised an internal string length so that longer linda names are fully output before truncation applies when doing tostring( linda) - -CHANGE 96: BGe 24-Jan-14 - * another Lua stack overflow fix when sending complex function through lindas or as lane body - -CHANGE 95: BGe 22-Jan-14 - * version 3.8.3 - * fixed a possible Lua stack overflow when sending complex function through lindas or as lane body - * experimental: lanes.nameof() scans the registry if a regular search didn't yield anything interesting - * fixed lanes.nameof() misbehaving when encountering a LUA_TTHREAD object - -CHANGE 94: BGe 22-Jan-14 - * version 3.8.2 - * new lane launcher option gc_cb to set a callback that is invoked when a lane is garbage collected - * Fix more invalid memory accesses when fetching the name of a joined lane with lanes:threads() (because its lua_State is closed) - -CHANGE 93: BGe 20-Jan-14 - * slightly improve linda performance when the producer/consumer scenario leaves leave the key empty - -CHANGE 92: BGe 20-Jan-14 - * version 3.8.1 - * new function lane:get_debug_threadname() - * Fix invalid memory accesses when fetching the name of a joined lane with lanes:threads() (because its lua_State is closed) - * use luaL_newmetatable() to create the metatable for lane objects - * prevent malicious code from crashing by calling lane methods without passing the lane as first argument (raise an error instead) - * set_debug_threadname() is no longer registered in the function lookup databases because it holds a C pointer as upvalue and it might crash if used maliciously - -CHANGE 91: BGe 20-Jan-14 - * version 3.8.0 - * linda:set() accepts multiple values to set in the specified slot - * linda:get() accepts an optional count to peek several values at once - -CHANGE 90: BGe 16-Jan-14 - * version 3.7.8 - * lane:cancel() now accepts a boolean second argument when soft cancelling (negative timeout) to wake the thread if necessary - * if a blocked linda send() or receive() call is interrupted by a cancellation request, - it returns CANCEL_ERROR so that this case can be differentiated from a simple timeout - * fixed WIN32 THREAD_CREATE() wrong _beginthreadex() error detection - * fatal WIN32 threading errors retrieve and output the error description string with FormatMessage() - * fixed missing lanes.set_singlethreaded - * fixed perftest.lua - * added test/cancel.lua - -CHANGE 89: BGe 09-Jan-14 - * version 3.7.7 - * fix crash when calling linda:count() on unknown keys - * purge key storage with linda:set( key, nil) on an unlimited key to reduce memory usage with lots of keys - * linda:limit() wakes write-blocked threads if necessary when the new limit enables writes to occur again - * linda:set() wakes write-blocked threads if necessary if the operation created some room to write into - -CHANGE 88: BGe 06-Jan-14 - * version 3.7.6 - * if config.on_state_create() is a C function, call it by direct C closure reconstruction in newly created states - -CHANGE 87: BGe 20-Dec-13 - * version 3.7.5 - * fixed a crash that can occur at shutdown when an object stored inside a keeper state performs a linda operation on a linda making use of another keeper - * new setting demote_full_userdata to select between light userdata demotion or raising an error when attempting to transfer a non-deep full userdata - -CHANGE 86: BGe 3-Dec-13 - * version 3.7.4 - * internal refactoring of pthread priority management code - * new API lanes.set_thread_priority() - -CHANGE 85: BGe 28-Nov-13 - * version 3.7.3 - * set pthread thread cancel type to PTHREAD_CANCEL_ASYNCHRONOUS - * lane_h:cancel() accepts a 3rd timeout argument used when waiting for actual thread termination (hitting the timeout raises an error) - * added PROPAGATE_ALLOCF macro to select state creation mode (lua_newstate or luaL_newstate) - -CHANGE 84: BGe 18-Nov-13 - * Fix a deadlock when GCing during a linda operation. - * Fix a compilation warning about an unused variable - * Get rid of uintptr_t to remove dependency on stdint.h - * Fix internal error at lane creation when the generator doesn't open any base library - -CHANGE 83: BGe 16-Nov-13 - * version 3.7.2 - * Fixed function returned by lanes.genlock() not handling numeric keys properly when release lock - * Enable lanes.genlock() to attempt lock with an optional "try" mode - * make EnableCrashingOnCrashes a one-time operation - -CHANGE 82: BGe 13-Nov-13 - * Fix a case where an error could be raised inside a keeper state - -CHANGE 81: BGe 07-Nov-13 - * Make set_finalizer(), set_debug_threadname(), cancel_test() and set_error_reporting() transferable from lane to lane - * Improved some DEBUGSPEW output - -CHANGE 80: BGe 06-Nov-13 - * Fix a few compilation warnings about uninitialized variables - * Fixed a bad extern variable declaration resulting in multiple instances (crashes the Pelles-C build) - -CHANGE 79: BGe 04-Nov-13 - * Fix lanes.nameof() crashing when encountering a light userdata - -CHANGE 78: BGe 25-Oct-13 - * Fix windows build not exporting public 'deep' API - * Don't call on_state_create in keeper states, as it is no longer necessary - * Remove inclusion of stdint.h - * Fix windows build for WINVER > 0x400 - -CHANGE 77: BGe 22-Oct-13 - * version 3.7.1 - * errors inside finalizers generate a full stack just like any other error - -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 - * version 3.6.6 - * properly handle cases when a Lua C module is a C function - -CHANGE 70: BGe 27-Step-13 - * no longer call core.configure with dummy params when requiring lanes more than once (fixes potential multithreading issues with LuaJIT allocator) - * activated EnableCrashingOnCrashes() is active on Win32 debug builds - * fixed some comments in code - -CHANGE 69: BGe 26-Sept-13 - * version 3.6.5 - * Reduce memory footprint, simplify module order setup in conjuction with Lanes, and send over native functions a bit faster as well - * Lanes no longer has to internally require modules inside the keeper states because they no longer need a lookup database - the lookup name is stored as-is and actually converted in the destination state - * optimisation: bypass cache when sending native functions over - * removed all the KEEPER_MODEL_LUA code, as it can no longer work anyway - -CHANGE 68: BGe 24-Sept-13 - * version 3.6.4 - * Fix possible application hang at shutdown if a keeper state referenced a linda. - -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 - -CHANGE 63: BGe 20-May-13 - * version 3.6.2 - * WIN32 builds use condition variables instead of PulseEvent() when available. - * first steps toward fixing make-vc.cmd - -CHANGE 62: BGe 05-Apr-13 - * version 3.6.1 - * function lookup database population keeps the 'smaller' name in case of multiple hits, to remove the no-LUA_COMPAT_ALL restriction on Lua5.2 builds - -CHANGE 61: BGe 14-Mar-13 - * version 3.6.0 - * protect_allocator is an API change -> version bump - * bugfix: allocator protection should be done once per primary Lua state, not once only the first time ever Lanes is required - -CHANGE 60: BGe 13-Mar-13 - * version 3.5.2 - * stricter validation of with_timers config option: validator was accepting any non-boolean value - * new configuration option protect_allocator for VMs with thread unsafe allocators (such as LuaJIT) - * removed some obsolete bits of dead code - -CHANGE 59: BGe 12-Feb-13 - * version 3.5.1 - * new lanes.h header and API call luaopen_lanes_embedded() for embedders - * "lanes.core" is an acceptable library in the generator libs argument - * library "*" wildcard also opens lanes.core - * tweaked code for Xbox 360 build - -CHANGE 58: BGe 30-Jan-13 - * version 3.5.0 - * new: API lanes.require(), use it instead of regular require() for modules that export C functions you need to send over. - * new: lanes no longer require 'lanes.core' by default in every created state. Use {required={"lanes.core"}} if you need to transfer lanes functions. - * internal: because of the above, reworked the timer implementation to remove upvalue-dependency on lanes.core - * new: API lanes.timer_lane, to be able to operate on timer lane if need be - * improved: if a module is a full userdata, scan its metatable for function database population - * improved: on_state_create can be a Lua function - * changed: on_state_create is called after the base libraries are loaded - * package[loaders|searchers] is no longer transfered as function naming depends on slot order - * internal: changed separator from '.' to '/' in lookup databases to be able to distinguish search levels and dot coming from module names - * added some mode debug spew - * updated tests to reflect the above changes - -CHANGE 57: BGe 28-Jan-13 - * More detailed DEBUG_SPEW logs - * A bit of code cosmetics - -CHANGE 56: BGe 25-Jan-13 - * version 3.4.4 - * bugfix: take into account the fact that "coroutine" is no longer part of base library in Lua 5.2 - * bugfix: if "bit32" was listed in the libraries, it wouldn't open (library list parsing failing on digits) - * bugfix: Use luaL_requiref() to open standard libraries in Lua 5.2 as we should - * bugfix: any Lua state created by Lanes reuses the allocator function of the originating state - * bugfix: don't call on_state_create() while GC is suspended during lua state initialization - -CHANGE 55: BGe 24-Jan-13 - * version 3.4.3 - * raise an error if lane generator libs specification contains a lib more than once - * bit32 is a valid lib name in the libs specification (silently ignored by the Lua 5.1 build) - * improved lanes.nameof to search inside table- and userdata- metatables for an object's name - * bugfix: fixed an unwarranted error when trying to discover a function name upon a failed transfer - * contents of package.[path,cpath,preload,loaders|searchers] are pulled *only once* inside keeper states at initialisation - * Lua function upvalues equal to the global environment aren't copied by value, but bound to the destination's global environment - especially useful for Lua 5.2 _ENV - * bugfix: fixed loading of base libraries that didn't create the global tables when built for Lua 5.2 - -CHANGE 54: BGe 10-Jan-13 - * version 3.4.2 - * Don't pull "package" settings in the timer lane - * removed a limitation preventing Lua functions with indirect recursive upvalue references from being transferable - -CHANGE 53: BGe 11-Dec-2012 - * version 3.4.1 - * new function lanes.timers(), returns a list of all active timers. - -CHANGE 52: BGe 03-Dec-2012 - * linda:send() and linda:receive() no longer triggers string->number autocoercion when checking for the optional timeout argument: - a string is always a linda slot, even if coercible. - -CHANGE 51: BGe 27-Nov-2012 - * linux flavors with older glibc use prctl instead of pthread_setname_np - * selfdestruct chain handling is now the same on all platforms - -CHANGE 50: BGe 22-Nov-2012 - * bugfix: linda:set() no longer clears the storage limit - -CHANGE 49: BGe 21-Nov-2012 - * fix application shutdown crash by not registering anything with atexit() - * rockspec for version v3.4.0 - -CHANGE 48: BGe 25-Sep-2012 - * version 3.4.0 - * new method linda:dump() that outputs the full contents of a linda as a table, also linked to __towatch for Decoda support - * linda:receive() API change! - * instead of [val, key], linda:receive( timeout, key) returns [key, val] - * instead of [val, [...]], linda:receive( timeout, linda.batched key) returns [key, val[, ...]] - this is to unify the return values of regular and batched mode, and to be able to tell when batched mode is interrupted by a lane cancellation - * fixed Lua 5.2 build to take into account the "loaders"->"searchers" name change in 'package' module. - * a bit of html cleanup and added some infos in the documentation regarding the Lanes internals - -CHANGE 47: BGe 13-Sep-2012 - * implemented set_debug_threadname() for pthread builds where possible - * refactored linda __tostring and __concat - -CHANGE 46: BGe 10-Sep-2012 - * version 3.3.0 - * lane.status can return "killed" if lane was forcefully killed with lanes:cancel() - * lane:join(): return nil, "killed" if called on a killed lane. - * lane[]: produces [1] = nil, [2] = "killed" if the lane was killed - * lane:join(): fixed an assertion in debug builds when joining a lane forcefully cancelled with lane:cancel( , true). - * indexing a lane with a string other than "join", "cancel" or "status" raises an error. - * fixed configure() to correctly apply defaults when they are missing from the provided settings - * added a shutdown_timeout to control the duration Lanes will wait for graceful termination of running lanes at application shutdown. Default is 0.25. - -CHANGE 45: BGe 21-Aug-2012 - * keeper internals implemented in C instead of Lua for better performances - * fixed arguments checks in linda:limit() and linda:set() - -CHANGE 44: BGe 13-Aug-2012 - * lanes code updated to build against Lua 5.1 and Lua 5.2 - * removed the search for MSVCR80.DLL when building for MinGW32 since it no longer seems to be necessary - -CHANGE 43: BGe 09-Aug-2012 - * fix possible crash at application shutdown when a race condition causes linda objects to be collected after the keeper states are cleaned up. - -CHANGE 42: BGe 06-Aug-2012 - * lanes.linda() accepts an optional name for debug purposes - -CHANGE 41: BGe 07-Jul-2012 - * lua51-lanes renamed lanes/core - * keeper state microcode is no longer embedded inside lanes.core, but located and loaded with package.loaders[2] - * changed rockspec build type from "make" to "builtin" - -CHANGE 40: BGe 26-Jun-2012 - * when a transfered function is not found in source, guess its name to help the user find out what's wrong - * new function lanes.nameof() - -CHANGE 39: BGe 23-Jun-2012 - * lanes.timer() accepts a first_secs=nil to stop a timer - * timer lane catches errors and prints them - * fixed some typos in manual - -CHANGE 38: BGe 11-Jun-2012 - * linda:receive() batched mode now accepts a max_count optional argument - -CHANGE 37: BGe 4-Jun-2012 (fix and idea courtesy of sonoro1234) - * fixed thread_cancel() not working when called without argument - * new lane-global function set_error_reporting() to enable more data detailed data provided by lane_error() - -CHANGE 36 BGe 26-Apr-2012 - * improved LuaJIT2 compatibility by handling "*" library set through luaL_openlibs() - -CHANGE 35 BGe 17-Feb-2012 - * changed lanes.configure signature to receive a table instead of individual parameters - * added support for an on_state_create callback called to load custom functions in a state in addition to the base libraries - -CHANGE 34 BGe 14-Nov-2011 - * removed packagepath and packagecpath options, replaced by a package table, whose fields path, cpath, loaders, preload are transfered - * code cleanup to facilitate transition between WIN32 and PTHREAD implementations - * tentative fix for desinit crashes when free running lanes are killed at process shutdown - -CHANGE 33 BGe 5-Nov-2011: Lanes version 3.0-beta - * process exit change: close everything at GC when main state closes, not when atexit() handlers are processed - * Lua 5.2-style module: - * module() is no longer used to implement lanes.lua - * a global "lanes" variable is no longer created when the module is required - * the Lanes module table is returned instead - * Lanes must be initialized before used: - * the first occurence of 'require "lanes"' produces a minimal interface that only contains a configure() function - * the remainder of the interface is made available once this function is called - * subsequent calls to configure() do nothing - * configure() controls the number of keeper states and the startup of timers - * LuaJIT 2 compatibility - * non-Lua functions are no longer copied by creating a C closure from a C pointer, but through 2-way lookup tables - * this means that if a lane function body pulls non-Lua functions, the lane generator description must contain the list of libraries and modules that exports them - * introduces a change in configuration .globals management: contents are copied *after* std libs are loaded - * new .required configuration entry to list modules that must be require()'ed before lane body is transferred - * lane:cancel() wakes up waiting lindas like what is done at lane shutdown - -CHANGE 32 BGe 14-May-2011 - * raise an error when linda:send() has nothing to send - -CHANGE 31 BGe 17-Apr-2011 - * linda uses a fast FIFO implementation to speed up data exchanges - * new linda:count() method - * new linda batched data read mode - * proper key type check in all linda methods - * fix setup-vc.cmd to support Visual Studio 2010 and Windows 7 64 bits - * bugfix: release keeper state mutex at desinit - -CHANGE 30 BGe 30-Mar-2011 - * linda honors __tostring and __concat - * new accessor linda:keys(), to retrieve the list of keys with pending data inside a linda - * new lanes options packagepath and packagecpath, in case one needs to set them differently than the default - -CHANGE 29 BGe 1-Mar-2011 - fixed potential crash at application shutdown when calling lua_close() on a killed thread's VM. - exposed cancel_test() in the lanes to enable manual testing for cancellation requests. - removed kludgy {globals={threadName}} support, replaced with a new function set_debug_threadname(). - -CHANGE 28 BGe 18-Feb-2011 - - moved keeper-related code in a separate source file - - keeper.lua is now embedded in text form instead of bytecode to improve LuaJIT2-compatibility - -CHANGE 27 BGe 17-Feb-2011 - - we know Lanes is loaded in the master state, so we don't force it - to be required in every lane too when a linda deep userdata is copied - - Refactor lane proxy implementation: it is now a full userdata instead - of a table, and its methods are implemented in C instead of Lua - * its metatable is no longer accessible - * writing to the proxy raises an error - * it is no longer possible to overwrite its join() and cancel() methods - - when a deep userdata idfunc requests a module to be required, manually - check that it is not loaded before requiring it instead of relying on - the require function's loop detection feature - - when a module must be required, raise an error if the 'require' function - is not found in the target state - -CHANGE 26 BGe 14-Feb-2011: - Fixed application hang-up because keeper state was not released in case of errors thrown by - inter-state data copy for unsupported types - -CHANGE 25 BGe 12-Feb-2011: - Changed idfunc signature and contract to clarify that fact it is not lua-callable - and to be able to require the module it was exported from in the target lanes - -CHANGE 24 DPtr 25-Jan-2011: - Changed lanes.c to export functions as a module rather than writing them directly to the globals table. - -CHANGE 23 DPtr 23-Jan-2011: - Fixed bug where reference to Linda object was dropped for a short time ( crashing if GC was run during that time ). - Changed the atexit code to trip the timer thread's write signal. - -CHANGE 22 DPtr 19-Jan-2011: - Changed luaG_push_proxy to cache deep userdata proxies. - -CHANGE 21 (bugfixes) BGe 3-Jan-2011: - Several fixes by Martin Krpan: - - linda_send was waiting on the wrong signal - - buildfix when using i586-mingw32msvc-gcc cross compiler - - lanes_h:cancel() returns a boolean as it should - - timers could get blocked sometimes because they were initialized with negative values - - prepare_timeout could generate an illegal setting - -CHANGE 20 BGe 3-Dec-2010: - Enable to specify a string as lane code instead of a function so that we dont use lua_dump, which - isn't supported by LuaJIT. - -CHANGE 19 BGe 2-Dec-2010: - No longer rely on global function 'tostring' to generate unique identifiers when caching data being transfered through la linda. Should fix a compatilibity issue with LuaJIT2. - -CHANGE 18 BGe 6-Oct-2010: - Fixed 'memory leak' in some situations where a free running lane is collected before application shutdown - A bit of code cleanup - -CHANGE 17 BGe 21-Sept-2010: - Fixed stupid compilation errors. - -CHANGE 16 PLM 24-Aug-2010: - Releasing memory at gc / atexit. - Finalizers actually get error strings. - Fixed missing argument propagation in lane:cancel - Added variable threadName sent trough globals-table. Set in s_lane, and in debuggers on windows. - Added argument checking for linda-objects, where missing them caused crashes. - -CHANGE 15 (minor) BGe 27-Jul-2010: - Version bump for a true upgrade release (2.0.4 package was only a renamed 2.0.3) - -CHANGE 14 (bug fix) BGe 09-Jul-2010: - Fixed lane status to be correctly returned as "waiting" when it should. - -CHANGE 13 (fix for multithreaded host apps) AKa 24-Jun-2009: - mentioned Lanes expects the host application to be singlethreaded, - and there are troubles if Lanes is used from multiple threads, opened by the host - (before requiring Lanes). This is true, and fix should now be in place. - -CHANGE 12 (bug fix on Windows, 2.0.3) AKa 25-Jan-2009: - Did CHANGE 9 the way it should be done. - -CHANGE 11 (new feature, 2.0.3) AKa 23-Jan-2009: - Finalizers ('set_finalizer()') for being able to do cleanup of a lane's - resources, whether it returned succesfully or via an error. - -CHANGE 10 (new feature, 2.0.3) AKa 23-Jan-2009: - Call stack showing where an error occurred is not merged with the error - message, but delivered as a separate stack table ({ "filename:line" [, ...] }). - Getting call stacks of errorred lanes is now possible. - -CHANGE 9 (bug fix on Windows) AKa 10-Dec-2008 (> 2.0.2): - Applied patch from Kriss Daniels to avoid issues on 'now_time()' in Win32 - (http://luaforge.net/forum/forum.php?thread_id=22704&forum_id=1781). - -CHANGE 8 (bug fix) AKa 26-Oct-2008: - Avoids occasional segfault at process exit (on multicore CPUs). Does this - by keeping track of "free running" threads (s.a. the time thread) and - cancelling them at process exit. - - Tested (2.0.2) on Linux 64,x86, OS X, WinXP. - -CHANGE 7 (bug fix) AKa 15-Oct-2008: - Recursive functions that use themselves as direct upvalue can now be - passed to other lanes, and used as a lane function. - -CHANGE 6 (bug fix) AKa 15-Oct-2008: - Added local caches of the following to src/lanes.lua (was otherwise getting - errors at least in 'tests/irayo_recursive.lua'). - - local assert= assert - local string_gmatch= assert( string.gmatch ) - local select= assert( select ) - local type= assert( type ) - local pairs= assert( pairs ) - local tostring= assert( tostring ) - local error= assert( error ) - local setmetatable= assert( setmetatable ) - local rawget= assert( rawget ) - - Thanks to Irayo for detecting and reporting this. - -CHANGE 5 (new feature): - Modifying Makefile so it's better suited to LuaRocks. - -CHANGE 4 (new feature): - Metatable copying, allowing Lua objects to be copied across lanes. - -CHANGE 3 (bug fix) AKa 5-Aug-2008: - The '__gc' method was not tied to thread userdata, at all. Caused memory - lifespan problems at least on OS X when threads were cancelled (EINVAL). - -CHANGE 2 (bug fix) AKa 5-Aug-2008: - Better calculation of timeouts, always making them absolute (even in Win32) - to allow for events that wake the lane up but don't read/write the Linda - key that it was observing. - -CHANGE 1 (bug fix) AKa 4-Aug-2008: - Signalling woke up only one waiting thread, not all. This caused i.e. - receive to not wake up if there was another thread waiting on the same - Linda object. - - PThread fix: using 'pthread_cond_broadcast()' instead of 'pthread_cond_signal()' - Win32 fix: using manual events and 'PulseEvent()' +CHANGE 1: BGe 9-Apr-24 + * reset changelog, next entry will list API changes since last C-implementation. (end) diff --git a/lanes-3.16.3-0.rockspec b/lanes-3.16.3-0.rockspec deleted file mode 100644 index 106a2d9..0000000 --- a/lanes-3.16.3-0.rockspec +++ /dev/null @@ -1,78 +0,0 @@ --- --- Lanes rockspec --- --- Ref: --- --- - -package = "Lanes" - -version = "3.16.3-0" - -source= { - url= "git+https://github.com/LuaLanes/lanes.git", - branch= "v3.16.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/LuaJIT, 5.2, 5.3 and 5.4 -} - -build = { - type = "builtin", - platforms = - { - linux = - { - modules = - { - ["lanes.core"] = - { - libraries = "pthread" - }, - } - } - }, - modules = - { - ["lanes.core"] = - { - sources = - { - "src/cancel.cpp", - "src/compat.cpp", - "src/deep.cpp", - "src/keeper.cpp", - "src/lanes.cpp", - "src/linda.cpp", - "src/tools.cpp", - "src/state.cpp", - "src/threading.cpp", - "src/universe.cpp" - }, - incdirs = { "src"}, - }, - lanes = "src/lanes.lua" - } -} diff --git a/lanes-4.0.0-0.rockspec b/lanes-4.0.0-0.rockspec new file mode 100644 index 0000000..4e1b370 --- /dev/null +++ b/lanes-4.0.0-0.rockspec @@ -0,0 +1,78 @@ +-- +-- Lanes rockspec +-- +-- Ref: +-- +-- + +package = "Lanes" + +version = "4.0.0-0" + +source= { + url= "git+https://github.com/LuaLanes/lanes.git", + branch= "v4.0.0" +} + +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/LuaJIT, 5.2, 5.3 and 5.4 +} + +build = { + type = "builtin", + platforms = + { + linux = + { + modules = + { + ["lanes.core"] = + { + libraries = "pthread" + }, + } + } + }, + modules = + { + ["lanes.core"] = + { + sources = + { + "src/cancel.cpp", + "src/compat.cpp", + "src/deep.cpp", + "src/keeper.cpp", + "src/lanes.cpp", + "src/linda.cpp", + "src/tools.cpp", + "src/state.cpp", + "src/threading.cpp", + "src/universe.cpp" + }, + incdirs = { "src"}, + }, + lanes = "src/lanes.lua" + } +} diff --git a/src/cancel.cpp b/src/cancel.cpp index 437a6f0..e08e975 100644 --- a/src/cancel.cpp +++ b/src/cancel.cpp @@ -1,6 +1,6 @@ /* -- --- CANCEL.C +-- CANCEL.CPP -- -- Lane cancellation support -- @@ -9,7 +9,7 @@ --[[ =============================================================================== -Copyright (C) 2011-2019 Benoit Germain +Copyright (C) 2011-2024 Benoit Germain Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -35,9 +35,6 @@ THE SOFTWARE. #include "cancel.h" -// #include -//#include - #include "lanes_private.h" #include "threading.h" #include "tools.h" diff --git a/src/compat.cpp b/src/compat.cpp index 47fe37e..8acab25 100644 --- a/src/compat.cpp +++ b/src/compat.cpp @@ -1,6 +1,6 @@ /* * ############################################################################################### - * ######################################### Lua 5.1/5.2 ######################################### + * ####################################### Lua 5.1/5.2/5.3 ####################################### * ############################################################################################### */ #include "compat.h" @@ -9,7 +9,12 @@ /* ** 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 +31,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,24 +50,30 @@ 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) { ASSERT_L( nuvalue <= 1); - return lua_newuserdata( L, sz); + 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); + lua_pushnil(L); return LUA_TNONE; } - lua_getuservalue( L, idx); + lua_getuservalue(L, idx); #if LUA_VERSION_NUM == 501 /* default environment is not a nil (see lua_getfenv) */ @@ -68,30 +81,32 @@ int lua_getiuservalue(lua_State* L, int idx, int n) if (lua_rawequal(L, -2, -1) || lua_rawequal(L, -2, LUA_GLOBALSINDEX)) { lua_pop(L, 2); - lua_pushnil( L); + lua_pushnil(L); return LUA_TNONE; } lua_pop(L, 1); /* remove package */ #endif - return lua_type( L, -1); + return lua_type(L, -1); } +// ################################################################################################ + // pop stack top, sets it a uservalue #n of full userdata at idx -int lua_setiuservalue( lua_State* L, int idx, int n) +int lua_setiuservalue(lua_State* L, int idx, int n) { if( n > 1 #if LUA_VERSION_NUM == 501 - || lua_type( L, -1) != LUA_TTABLE + || lua_type(L, -1) != LUA_TTABLE #endif ) { - lua_pop( L, 1); + lua_pop(L, 1); return 0; } - (void) lua_setuservalue( L, idx); + std::ignore = lua_setuservalue(L, idx); return 1; // I guess anything non-0 is ok } diff --git a/src/deep.cpp b/src/deep.cpp index ac2905e..55063b3 100644 --- a/src/deep.cpp +++ b/src/deep.cpp @@ -1,5 +1,5 @@ /* - * DEEP.C Copyright (c) 2017, Benoit Germain + * DEEP.CPP Copyright (c) 2024, Benoit Germain * * Deep userdata support, separate in its own source file to help integration * without enforcing a Lanes dependency @@ -9,7 +9,7 @@ =============================================================================== Copyright (C) 2002-10 Asko Kauppi - 2011-17 Benoit Germain + 2011-24 Benoit Germain Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -41,13 +41,6 @@ THE SOFTWARE. #include #include -#include -#include -#include -#include -#if !defined(__APPLE__) -#include -#endif /*-- Metatable copying --*/ @@ -73,7 +66,7 @@ static constexpr UniqueKey DEEP_PROXY_CACHE_KEY{ 0x05773d6fc26be106ull }; * Sets up [-1]<->[-2] two-way lookups, and ensures the lookup table exists. * Pops the both values off the stack. */ -static void set_deep_lookup( lua_State* L) +static void set_deep_lookup(lua_State* L) { STACK_GROW( L, 3); STACK_CHECK_START_REL(L, 2); // a b @@ -88,11 +81,13 @@ static void set_deep_lookup( lua_State* L) STACK_CHECK( L, 0); } +// ################################################################################################ + /* * Pops the key (metatable or idfunc) off the stack, and replaces with the * deep lookup value (idfunc/metatable/nil). */ -static void get_deep_lookup( lua_State* L) +static void get_deep_lookup(lua_State* L) { STACK_GROW( L, 1); STACK_CHECK_START_REL(L, 1); // a @@ -106,11 +101,13 @@ static void get_deep_lookup( lua_State* L) STACK_CHECK( L, 1); } +// ################################################################################################ + /* * Return the registered ID function for 'index' (deep userdata proxy), * or nullptr if 'index' is not a deep userdata proxy. */ -static inline luaG_IdFunction get_idfunc( lua_State* L, int index, LookupMode mode_) +static inline luaG_IdFunction get_idfunc(lua_State* L, int index, LookupMode mode_) { // when looking inside a keeper, we are 100% sure the object is a deep userdata if (mode_ == LookupMode::FromKeeper) @@ -142,8 +139,9 @@ static inline luaG_IdFunction get_idfunc( lua_State* L, int index, LookupMode mo } } +// ################################################################################################ -void free_deep_prelude( lua_State* L, DeepPrelude* prelude_) +void free_deep_prelude(lua_State* L, DeepPrelude* prelude_) { ASSERT_L(prelude_->idfunc); STACK_CHECK_START_REL(L, 0); @@ -154,6 +152,7 @@ void free_deep_prelude( lua_State* L, DeepPrelude* prelude_) STACK_CHECK(L, 0); } +// ################################################################################################ /* * void= mt.__gc( proxy_ud ) @@ -161,7 +160,7 @@ void free_deep_prelude( lua_State* L, DeepPrelude* prelude_) * End of life for a proxy object; reduce the deep reference count and clean it up if reaches 0. * */ -static int deep_userdata_gc( lua_State* L) +static int deep_userdata_gc(lua_State* L) { DeepPrelude** const proxy{ lua_tofulluserdata(L, 1) }; DeepPrelude* p = *proxy; @@ -193,6 +192,7 @@ static int deep_userdata_gc( lua_State* L) return 0; } +// ################################################################################################ /* * Push a proxy userdata on the stack. @@ -203,7 +203,7 @@ static int deep_userdata_gc( lua_State* L) * used in this Lua state (metatable, registring it). Otherwise, increments the * reference count. */ -char const* push_deep_proxy(lua_State* L, DeepPrelude* prelude, int nuv_, LookupMode mode_) +char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_) { // Check if a proxy already exists push_registry_subtable_mode( L, DEEP_PROXY_CACHE_KEY, "v"); // DPC @@ -278,7 +278,7 @@ char const* push_deep_proxy(lua_State* L, DeepPrelude* prelude, int nuv_, Lookup // this is needed because we must make sure the shared library is still loaded as long as we hold a pointer on the idfunc { int oldtop_module = lua_gettop( L); - modname = (char const*) prelude->idfunc( L, DeepOp::Module); // DPC proxy metatable + modname = (char const*) prelude->idfunc( L, DeepOp::Module); // DPC proxy metatable // make sure the function pushed nothing on the stack! if( lua_gettop( L) - oldtop_module != 0) { @@ -348,6 +348,8 @@ char const* push_deep_proxy(lua_State* L, DeepPrelude* prelude, int nuv_, Lookup return nullptr; } +// ################################################################################################ + /* * Create a deep userdata * @@ -370,7 +372,7 @@ char const* push_deep_proxy(lua_State* L, DeepPrelude* prelude, int nuv_, Lookup * * Returns: 'proxy' userdata for accessing the deep data via 'luaG_todeep()' */ -int luaG_newdeepuserdata( lua_State* L, luaG_IdFunction idfunc, int nuv_) +int luaG_newdeepuserdata(Dest L, luaG_IdFunction idfunc, int nuv_) { STACK_GROW( L, 1); STACK_CHECK_START_REL(L, 0); @@ -409,6 +411,7 @@ int luaG_newdeepuserdata( lua_State* L, luaG_IdFunction idfunc, int nuv_) return 1; } +// ################################################################################################ /* * Access deep userdata through a proxy. @@ -430,6 +433,7 @@ DeepPrelude* luaG_todeep(lua_State* L, luaG_IdFunction idfunc, int index) return *proxy; } +// ################################################################################################ /* * Copy deep userdata between two separate Lua states (from L to L2) @@ -438,7 +442,7 @@ DeepPrelude* luaG_todeep(lua_State* L, luaG_IdFunction idfunc, int index) * the id function of the copied value, or nullptr for non-deep userdata * (not copied) */ -bool copydeep(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, int i, LookupMode mode_, char const* upName_) +bool copydeep(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode mode_, char const* upName_) { luaG_IdFunction const idfunc { get_idfunc(L, i, mode_) }; if (idfunc == nullptr) diff --git a/src/deep.h b/src/deep.h index c09af23..6cf33ec 100644 --- a/src/deep.h +++ b/src/deep.h @@ -54,8 +54,8 @@ struct DeepPrelude std::atomic m_refcount{ 0 }; }; -char const* push_deep_proxy(lua_State* L, DeepPrelude* prelude, int nuv_, LookupMode mode_); +char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_); void free_deep_prelude( lua_State* L, DeepPrelude* prelude_); -LANES_API int luaG_newdeepuserdata( lua_State* L, luaG_IdFunction idfunc, int nuv_); +LANES_API int luaG_newdeepuserdata(Dest L, luaG_IdFunction idfunc, int nuv_); LANES_API DeepPrelude* luaG_todeep(lua_State* L, luaG_IdFunction idfunc, int index); diff --git a/src/keeper.cpp b/src/keeper.cpp index 0aea18e..9718bda 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp @@ -14,7 +14,7 @@ --[[ =============================================================================== - Copyright (C) 2011-2023 Benoit Germain + Copyright (C) 2011-2024 Benoit Germain Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -207,10 +207,10 @@ static void push_table(lua_State* L, int idx_) // ################################################################################################## -int keeper_push_linda_storage(Universe* U, lua_State* L, void* ptr_, uintptr_t magic_) +int keeper_push_linda_storage(Universe* U, Dest L, void* ptr_, uintptr_t magic_) { Keeper* const K{ which_keeper(U->keepers, magic_) }; - lua_State* const KL{ K ? K->L : nullptr }; + Source const KL{ K ? K->L : nullptr }; if (KL == nullptr) return 0; STACK_GROW(KL, 4); @@ -412,7 +412,7 @@ int keepercall_limit(lua_State* L) // set the new limit fifo->limit = limit; // return 0 or 1 value - return lua_gettop( L); + return lua_gettop(L); } // ################################################################################################## @@ -485,7 +485,7 @@ int keepercall_set(lua_State* L) lua_insert(L, 3); // fifos key fifotbl [val [, ...]] fifo_push(L, fifo, count); // fifos key fifotbl } - return should_wake_writers ? (lua_pushboolean( L, 1), 1) : 0; + return should_wake_writers ? (lua_pushboolean(L, 1), 1) : 0; } // ################################################################################################## @@ -717,7 +717,7 @@ void init_keepers(Universe* U, lua_State* L) if (!lua_isnil(L, -1)) { // when copying with mode LookupMode::ToKeeper, error message is pushed at the top of the stack, not raised immediately - if (luaG_inter_copy_package(U, L, K, -1, LookupMode::ToKeeper)) + if (luaG_inter_copy_package(U, Source{ L }, Dest{ K }, -1, LookupMode::ToKeeper)) { // if something went wrong, the error message is at the top of the stack lua_remove(L, -2); // error_msg @@ -840,7 +840,7 @@ int keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, voi lua_pushlightuserdata(K, linda); - if ((args == 0) || luaG_inter_copy(U, L, K, args, LookupMode::ToKeeper) == 0) // L->K + if ((args == 0) || luaG_inter_copy(U, Source{ L }, Dest{ K }, args, LookupMode::ToKeeper) == 0) // L->K { lua_call(K, 1 + args, LUA_MULTRET); retvals = lua_gettop(K) - Ktos; @@ -848,7 +848,7 @@ int keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, voi // 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, LookupMode::FromKeeper) != 0) // K->L + if ((retvals > 0) && luaG_inter_move(U, Source{ K }, Dest{ L }, retvals, LookupMode::FromKeeper) != 0) // K->L { retvals = -1; } diff --git a/src/keeper.h b/src/keeper.h index ba5a57b..89fa2ab 100644 --- a/src/keeper.h +++ b/src/keeper.h @@ -42,7 +42,7 @@ Keeper* which_keeper(Keepers* keepers_, uintptr_t magic_); Keeper* keeper_acquire(Keepers* keepers_, uintptr_t magic_); 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_, uintptr_t magic_); +int keeper_push_linda_storage(Universe* U, Dest L, void* ptr_, uintptr_t magic_); using keeper_api_t = lua_CFunction; #define KEEPER_API(_op) keepercall_##_op diff --git a/src/lanes.cpp b/src/lanes.cpp index cf443f2..3d0c70d 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -1027,7 +1027,7 @@ LUAG_FUNC(lane_new) DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); // populate with selected libraries at the same time - lua_State* const L2{ luaG_newstate(U, L, libs_str) }; // L // L2 + lua_State* const L2{ luaG_newstate(U, Source{ L }, libs_str) }; // L // L2 // 'lane' is allocated from heap, not Lua, since its life span may surpass the handle's (if free running thread) Lane* const lane{ new (U) Lane{ U, L2 } }; @@ -1134,7 +1134,7 @@ LUAG_FUNC(lane_new) { DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: update 'package'\n" INDENT_END)); // when copying with mode LookupMode::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, LookupMode::LaneBody); + std::ignore = luaG_inter_copy_package(U, Source{ L }, Dest{ L2 }, package_idx, LookupMode::LaneBody); } // modules to require in the target lane *before* the function is transfered! @@ -1150,7 +1150,7 @@ LUAG_FUNC(lane_new) } lua_pushnil(L); // func libs priority globals package required gc_cb [... args ...] nil - while( lua_next(L, required_idx) != 0) // func libs priority globals package required gc_cb [... args ...] n "modname" + while (lua_next(L, required_idx) != 0) // func libs priority globals package required gc_cb [... args ...] n "modname" { if (lua_type(L, -1) != LUA_TSTRING || lua_type(L, -2) != LUA_TNUMBER || lua_tonumber(L, -2) != nbRequired) { @@ -1176,7 +1176,7 @@ LUAG_FUNC(lane_new) if (lua_pcall( L2, 1, 1, 0) != LUA_OK) // ret/errcode { // propagate error to main state if any - luaG_inter_move(U, L2, L, 1, LookupMode::LaneBody); // func libs priority globals package required gc_cb [... args ...] n "modname" error + luaG_inter_move(U, Source{ L2 }, Dest{ L }, 1, LookupMode::LaneBody); // func libs priority globals package required gc_cb [... args ...] n "modname" error raise_lua_error(L); } // after requiring the module, register the functions it exported in our name<->function database @@ -1209,7 +1209,7 @@ LUAG_FUNC(lane_new) lua_pushglobaltable(L2); // _G while( lua_next(L, globals_idx)) // func libs priority globals package required gc_cb [... args ...] k v { - luaG_inter_copy(U, L, L2, 2, LookupMode::LaneBody); // _G k v + luaG_inter_copy(U, Source{ L }, Dest{ L2 }, 2, LookupMode::LaneBody); // _G k v // assign it in L2's globals table lua_rawset(L2, -3); // _G lua_pop(L, 1); // func libs priority globals package required gc_cb [... args ...] k @@ -1227,7 +1227,7 @@ LUAG_FUNC(lane_new) DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane body\n" INDENT_END)); DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); lua_pushvalue(L, 1); // func libs priority globals package required gc_cb [... args ...] func - int const res{ luaG_inter_move(U, L, L2, 1, LookupMode::LaneBody) };// func libs priority globals package required gc_cb [... args ...] // func + int const res{ luaG_inter_move(U, Source{ L }, Dest{ L2 }, 1, LookupMode::LaneBody) }; // func libs priority globals package required gc_cb [... args ...] // func DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); if (res != 0) { @@ -1253,7 +1253,7 @@ LUAG_FUNC(lane_new) int res; DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane arguments\n" INDENT_END)); DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); - res = luaG_inter_move(U, L, L2, nargs, LookupMode::LaneBody); // func libs priority globals package required gc_cb // func [... args ...] + res = luaG_inter_move(U, Source{ L }, Dest{ L2 }, nargs, LookupMode::LaneBody); // func libs priority globals package required gc_cb // func [... args ...] DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); if (res != 0) { @@ -1418,7 +1418,7 @@ LUAG_FUNC(thread_join) case Lane::Done: { int const n{ lua_gettop(L2) }; // whole L2 stack - if ((n > 0) && (luaG_inter_move(U, L2, L, n, LookupMode::LaneBody) != 0)) + if ((n > 0) && (luaG_inter_move(U, Source{ L2 }, Dest{ L }, n, LookupMode::LaneBody) != 0)) { return luaL_error(L, "tried to copy unsupported types"); } @@ -1432,7 +1432,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, LookupMode::LaneBody) != 0) // nil "err" [trace] + if (luaG_inter_move(U, Source{ L2 }, Dest{ L }, n, LookupMode::LaneBody) != 0) // nil "err" [trace] { return luaL_error(L, "tried to copy unsupported types: %s", lua_tostring(L, -n)); } @@ -1832,7 +1832,7 @@ LUAG_FUNC(configure) STACK_CHECK(L, 2); { - char const* errmsg{ push_deep_proxy(L, U->timer_deep, 0, LookupMode::LaneBody) }; // settings M timer_deep + char const* errmsg{ push_deep_proxy(Dest{ L }, U->timer_deep, 0, LookupMode::LaneBody) }; // settings M timer_deep if (errmsg != nullptr) { return luaL_error(L, errmsg); diff --git a/src/lanes_private.h b/src/lanes_private.h index 3ed52fe..0fcbbfc 100644 --- a/src/lanes_private.h +++ b/src/lanes_private.h @@ -98,6 +98,6 @@ static constexpr UniqueKey LANE_POINTER_REGKEY{ 0xB3022205633743BCull }; // used // '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"))) +#define lua_toLane(L, i) (*((Lane**) luaL_checkudata( L, i, "Lane"))) -int push_thread_status( lua_State* L, Lane* s); +int push_thread_status(lua_State* L, Lane* s); diff --git a/src/linda.cpp b/src/linda.cpp index fb74abe..39977bc 100644 --- a/src/linda.cpp +++ b/src/linda.cpp @@ -804,7 +804,7 @@ LUAG_FUNC(linda_concat) LUAG_FUNC(linda_dump) { Linda* const linda{ lua_toLinda(L, 1) }; - return keeper_push_linda_storage(linda->U, L, linda, linda->hashSeed()); + return keeper_push_linda_storage(linda->U, Dest{ L }, linda, linda->hashSeed()); } // ################################################################################################# @@ -816,7 +816,7 @@ LUAG_FUNC(linda_dump) LUAG_FUNC(linda_towatch) { Linda* const linda{ lua_toLinda(L, 1) }; - int pushed{ keeper_push_linda_storage(linda->U, L, linda, linda->hashSeed()) }; + int pushed{ keeper_push_linda_storage(linda->U, Dest{ L }, linda, linda->hashSeed()) }; if (pushed == 0) { // if the linda is empty, don't return nil @@ -1009,11 +1009,11 @@ static void* linda_id( lua_State* L, DeepOp op_) */ LUAG_FUNC(linda) { - int const top = lua_gettop(L); + int const top{ lua_gettop(L) }; luaL_argcheck(L, top <= 2, top, "too many arguments"); if (top == 1) { - int const t = lua_type(L, 1); + int const t{ lua_type(L, 1) }; luaL_argcheck(L, t == LUA_TSTRING || t == LUA_TNUMBER, 1, "wrong parameter (should be a string or a number)"); } else if (top == 2) @@ -1021,5 +1021,5 @@ LUAG_FUNC(linda) luaL_checktype(L, 1, LUA_TSTRING); luaL_checktype(L, 2, LUA_TNUMBER); } - return luaG_newdeepuserdata(L, linda_id, 0); + return luaG_newdeepuserdata(Dest{ L }, linda_id, 0); } diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h index 997b452..47ce90c 100644 --- a/src/macros_and_utils.h +++ b/src/macros_and_utils.h @@ -44,8 +44,8 @@ extern char const* debugspew_indent; #else // NDEBUG -#define _ASSERT_L( L, cond_) if( (cond_) == 0) { (void) luaL_error( L, "ASSERT failed: %s:%d '%s'", __FILE__, __LINE__, #cond_);} -#define STACK_DUMP( L) luaG_dump( L) +#define _ASSERT_L(L, cond_) if( (cond_) == 0) { (void) luaL_error(L, "ASSERT failed: %s:%d '%s'", __FILE__, __LINE__, #cond_);} +#define STACK_DUMP(L) luaG_dump(L) class StackChecker { @@ -172,3 +172,17 @@ T* lua_newuserdatauv(lua_State* L, int nuvalue_) } using lua_Duration = std::chrono::template duration; + +// ################################################################################################# + +template +struct Unique +{ + T m_val; + Unique() = default; + operator T() const { return m_val; } + explicit Unique(T b_) : m_val{ b_ } {} +}; + +using Source = Unique; +using Dest = Unique; diff --git a/src/state.cpp b/src/state.cpp index 512009a..6a9ada7 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -1,5 +1,5 @@ /* -* STATE.C +* STATE.CPP * * Lua tools to support Lanes. */ @@ -31,20 +31,11 @@ THE SOFTWARE. =============================================================================== */ -#include -#include -#include -#include -#include -#if !defined(__APPLE__) -#include -#endif // __APPLE__ - -#include "compat.h" -#include "macros_and_utils.h" -#include "universe.h" -#include "tools.h" +#include "state.h" + #include "lanes.h" +#include "tools.h" +#include "universe.h" // ################################################################################################ @@ -194,9 +185,9 @@ static void open1lib( DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* L, char con // just like lua_xmove, args are (from, to) -static void copy_one_time_settings( Universe* U, lua_State* L, lua_State* L2) +static void copy_one_time_settings(Universe* U, Source L, Dest L2) { - STACK_GROW( L, 2); + STACK_GROW(L, 2); STACK_CHECK_START_REL(L, 0); STACK_CHECK_START_REL(L2, 0); @@ -326,11 +317,11 @@ void call_on_state_create(Universe* U, lua_State* L, lua_State* from_, LookupMod * *NOT* called for keeper states! * */ -lua_State* luaG_newstate( Universe* U, lua_State* from_, char const* libs_) +lua_State* luaG_newstate(Universe* U, Source from_, char const* libs_) { - lua_State* L = create_state( U, from_); + Dest const L{ create_state(U, from_) }; - STACK_GROW( L, 2); + STACK_GROW(L, 2); STACK_CHECK_START_ABS(L, 0); // copy the universe as a light userdata (only the master state holds the full userdata) diff --git a/src/state.h b/src/state.h index 0e069da..2601f77 100644 --- a/src/state.h +++ b/src/state.h @@ -3,6 +3,7 @@ #include "macros_and_utils.h" // forwards +enum class LookupMode; class Universe; void serialize_require(DEBUGSPEW_PARAM_COMMA(Universe* U) lua_State* L); @@ -10,7 +11,7 @@ void serialize_require(DEBUGSPEW_PARAM_COMMA(Universe* U) lua_State* L); // ################################################################################################ lua_State* create_state(Universe* U, lua_State* from_); -lua_State* luaG_newstate(Universe* U, lua_State* _from, char const* libs); +lua_State* luaG_newstate(Universe* U, Source _from, char const* libs); // ################################################################################################ diff --git a/src/threading.cpp b/src/threading.cpp index 4d210d6..d278bb1 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -1,6 +1,6 @@ /* - * THREADING.C Copyright (c) 2007-08, Asko Kauppi - * Copyright (C) 2009-19, Benoit Germain + * THREADING.CPP Copyright (c) 2007-08, Asko Kauppi + * Copyright (C) 2009-24, Benoit Germain * * Lua Lanes OS threading specific code. * @@ -12,7 +12,7 @@ =============================================================================== Copyright (C) 2007-10 Asko Kauppi -Copyright (C) 2009-14, Benoit Germain +Copyright (C) 2009-24, Benoit Germain Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -47,12 +47,6 @@ THE SOFTWARE. #endif // __linux__ -#include -#include -#include -#include -#include - #include "threading.h" #if !defined( PLATFORM_XBOX) && !defined( PLATFORM_WIN32) && !defined( PLATFORM_POCKETPC) diff --git a/src/threading.h b/src/threading.h index f38b2de..fc35730 100644 --- a/src/threading.h +++ b/src/threading.h @@ -2,7 +2,6 @@ #include "platform.h" -#include #include #define THREADAPI_WINDOWS 1 diff --git a/src/tools.cpp b/src/tools.cpp index ac5f7c5..07f9ae6 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -31,24 +31,12 @@ THE SOFTWARE. =============================================================================== */ -#include -#include -#include -#include -#include -#if !defined(__APPLE__) -#include -#endif // __APPLE__ - #include "tools.h" -#include "compat.h" + #include "universe.h" -#include "keeper.h" -#include "lanes.h" -#include "uniquekey.h" // functions implemented in deep.c -extern bool copydeep(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, int i, LookupMode mode_, char const* upName_); +extern bool copydeep(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode mode_, char const* upName_); extern void push_registry_subtable( lua_State* L, UniqueKey key_); DEBUGSPEW_CODE( char const* debugspew_indent = "----+----!----+----!----+----!----+----!----+----!----+----!----+----!----+"); @@ -554,10 +542,10 @@ static void populate_func_lookup_table_recur(DEBUGSPEW_PARAM_COMMA(Universe* U) /* * create a "fully.qualified.name" <-> function equivalence database */ -void populate_func_lookup_table( lua_State* L, int _i, char const* name_) +void populate_func_lookup_table(lua_State* L, int i_, char const* name_) { - int const ctx_base = lua_gettop( L) + 1; - int const in_base = lua_absindex( L, _i); + int const ctx_base = lua_gettop(L) + 1; + int const in_base = lua_absindex(L, i_); int start_depth = 0; DEBUGSPEW_CODE( Universe* U = universe_get( L)); DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "%p: populate_func_lookup_table('%s')\n" INDENT_END, L, name_ ? name_ : "nullptr")); @@ -765,7 +753,7 @@ static char const* find_lookup_name(lua_State* L, int i, LookupMode mode_, char /* * Push a looked-up table, or nothing if we found nothing */ -static bool lookup_table(lua_State* L2, lua_State* L, int i, LookupMode mode_, char const* upName_) +static bool lookup_table(Dest L2, Source L, int i, LookupMode mode_, char const* upName_) { // get the name of the table we want to send size_t len; @@ -775,7 +763,7 @@ static bool lookup_table(lua_State* L2, lua_State* L, int i, LookupMode mode_, c return false; } // push the equivalent table in the destination's stack, retrieved from the lookup table - STACK_CHECK_START_REL(L2, 0); // L // L2 + STACK_CHECK_START_REL(L2, 0); // L // L2 STACK_GROW( L2, 3); // up to 3 slots are necessary on error switch( mode_) { @@ -785,34 +773,34 @@ static bool lookup_table(lua_State* L2, lua_State* L, int i, LookupMode mode_, c case LookupMode::ToKeeper: // push a sentinel closure that holds the lookup name as upvalue - lua_pushlstring( L2, fqn, len); // "f.q.n" - lua_pushcclosure( L2, table_lookup_sentinel, 1); // f + lua_pushlstring(L2, fqn, len); // "f.q.n" + lua_pushcclosure(L2, table_lookup_sentinel, 1); // f break; case LookupMode::LaneBody: case LookupMode::FromKeeper: - LOOKUP_REGKEY.pushValue(L2); // {} - STACK_CHECK( L2, 1); - ASSERT_L( lua_istable( L2, -1)); - lua_pushlstring( L2, fqn, len); // {} "f.q.n" - lua_rawget( L2, -2); // {} t + LOOKUP_REGKEY.pushValue(L2); // {} + STACK_CHECK(L2, 1); + ASSERT_L(lua_istable(L2, -1)); + lua_pushlstring(L2, fqn, len); // {} "f.q.n" + lua_rawget(L2, -2); // {} t // we accept destination lookup failures in the case of transfering the Lanes body function (this will result in the source table being cloned instead) // but not when we extract something out of a keeper, as there is nothing to clone! if (lua_isnil(L2, -1) && mode_ == LookupMode::LaneBody) { - lua_pop( L2, 2); // - STACK_CHECK( L2, 0); + lua_pop(L2, 2); // + STACK_CHECK(L2, 0); return false; } - else if( !lua_istable( L2, -1)) + else if( !lua_istable(L2, -1)) { char const* from, *to; - lua_getglobal( L, "decoda_name"); // ... t ... decoda_name - from = lua_tostring( L, -1); - lua_pop( L, 1); // ... t ... - lua_getglobal( L2, "decoda_name"); // {} t decoda_name + lua_getglobal(L, "decoda_name"); // ... t ... decoda_name + from = lua_tostring(L, -1); + lua_pop(L, 1); // ... t ... + lua_getglobal(L2, "decoda_name"); // {} t decoda_name to = lua_tostring( L2, -1); - lua_pop( L2, 1); // {} t + lua_pop(L2, 1); // {} t // when mode_ == LookupMode::FromKeeper, L is a keeper state and L2 is not, therefore L2 is the state where we want to raise the error (void) luaL_error( (mode_ == LookupMode::FromKeeper) ? L2 : L @@ -823,7 +811,7 @@ static bool lookup_table(lua_State* L2, lua_State* L, int i, LookupMode mode_, c ); return false; } - lua_remove( L2, -2); // t + lua_remove(L2, -2); // t break; } STACK_CHECK( L2, 1); @@ -1194,7 +1182,7 @@ static int buf_writer( lua_State* L, void const* b, size_t size, void* ud) // ################################################################################################# -static void copy_func(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, int i, LookupMode mode_, char const* upName_) +static void copy_func(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode mode_, char const* upName_) { int n, needToPush; luaL_Buffer B; @@ -1348,7 +1336,7 @@ static void copy_func(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, * * Always pushes a function to 'L2'. */ -static void copy_cached_func(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, int i, LookupMode mode_, char const* upName_) +static void copy_cached_func(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode mode_, char const* upName_) { FuncSubType funcSubType; /*lua_CFunction cfunc =*/ luaG_tocfunction( L, i, &funcSubType); // nullptr for LuaJIT-fast && bytecode functions @@ -1403,7 +1391,7 @@ static void copy_cached_func(Universe* U, lua_State* L2, int L2_cache_i, lua_Sta // ################################################################################################# -static bool push_cached_metatable(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, int i, LookupMode mode_, char const* upName_) +static bool push_cached_metatable(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode mode_, char const* upName_) { STACK_CHECK_START_REL(L, 0); if( lua_getmetatable( L, i)) // ... mt @@ -1454,7 +1442,7 @@ static bool push_cached_metatable(Universe* U, lua_State* L2, int L2_cache_i, lu // ################################################################################################# -static void inter_copy_keyvaluepair(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, VT vt_, LookupMode mode_, char const* upName_) +static void inter_copy_keyvaluepair(Universe* U, Dest L2, int L2_cache_i, Source L, VT vt_, LookupMode mode_, char const* upName_) { int val_i = lua_gettop(L); int key_i = val_i - 1; @@ -1526,7 +1514,7 @@ static void inter_copy_keyvaluepair(Universe* U, lua_State* L2, int L2_cache_i, */ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; -static bool copyclone(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, int source_i_, LookupMode mode_, char const* upName_) +static bool copyclone(Universe* U, Dest L2, int L2_cache_i, Source L, int source_i_, LookupMode mode_, char const* upName_) { void* const source = lua_touserdata( L, source_i_); source_i_ = lua_absindex( L, source_i_); @@ -1641,7 +1629,7 @@ static bool copyclone(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, // ################################################################################################# -static bool inter_copy_userdata(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, int i, VT vt_, LookupMode mode_, char const* upName_) +static bool inter_copy_userdata(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_) { STACK_CHECK_START_REL(L, 0); STACK_CHECK_START_REL(L2, 0); @@ -1691,7 +1679,7 @@ static bool inter_copy_userdata(Universe* U, lua_State* L2, int L2_cache_i, lua_ // ################################################################################################# -static bool inter_copy_function(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, int source_i_, VT vt_, LookupMode mode_, char const* upName_) +static bool inter_copy_function(Universe* U, Dest L2, int L2_cache_i, Source L, int source_i_, VT vt_, LookupMode mode_, char const* upName_) { if (vt_ == VT::KEY) { @@ -1786,7 +1774,7 @@ static bool inter_copy_function(Universe* U, lua_State* L2, int L2_cache_i, lua_ // ################################################################################################# -static bool inter_copy_table(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, int i, VT vt_, LookupMode mode_, char const* upName_) +static bool inter_copy_table(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_) { if (vt_ == VT::KEY) { @@ -1858,7 +1846,7 @@ static bool inter_copy_table(Universe* U, lua_State* L2, int L2_cache_i, lua_Sta * * Returns true if value was pushed, false if its type is non-supported. */ -bool inter_copy_one(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, int i, VT vt_, LookupMode mode_, char const* upName_) +bool inter_copy_one(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_) { bool ret{ true }; int val_type = lua_type( L, i); @@ -1974,6 +1962,8 @@ bool inter_copy_one(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, in return ret; } +// ################################################################################################# + /* * Akin to 'lua_xmove' but copies values between _any_ Lua states. * @@ -1981,7 +1971,7 @@ bool inter_copy_one(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, in * * 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, int n, LookupMode mode_) +int luaG_inter_copy(Universe* U, Source L, Dest L2, int n, LookupMode mode_) { int top_L = lua_gettop(L); // ... {}n int top_L2 = lua_gettop(L2); // ... @@ -2043,15 +2033,16 @@ int luaG_inter_copy(Universe* U, lua_State* L, lua_State* L2, int n, LookupMode return -2; } +// ################################################################################################# -int luaG_inter_move(Universe* U, lua_State* L, lua_State* L2, int n, LookupMode mode_) +int luaG_inter_move(Universe* U, Source L, Dest L2, int n, LookupMode mode_) { int 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_) +int luaG_inter_copy_package(Universe* U, Source L, Dest 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.fetch_add(1, std::memory_order_relaxed)); diff --git a/src/tools.h b/src/tools.h index 7d9aaab..8e95a4f 100644 --- a/src/tools.h +++ b/src/tools.h @@ -25,19 +25,19 @@ enum class VT KEY, METATABLE }; -bool inter_copy_one(Universe* U, lua_State* L2, int L2_cache_i, lua_State* L, int i, VT vt_, LookupMode mode_, char const* upName_); +bool inter_copy_one(Universe* U, Dest L2, int L2_cache_i, Source L, int i, 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_package(Universe* U, Source L, Dest L2, int package_idx_, LookupMode mode_); -int luaG_inter_copy(Universe* U, lua_State* L, lua_State* L2, int n, LookupMode mode_); -int luaG_inter_move(Universe* U, lua_State* L, lua_State* L2, int n, LookupMode mode_); +int luaG_inter_copy(Universe* U, Source L, Dest L2, int n, LookupMode mode_); +int luaG_inter_move(Universe* U, Source L, Dest L2, int n, LookupMode mode_); -int luaG_nameof( lua_State* L); +int luaG_nameof(lua_State* L); -void populate_func_lookup_table( lua_State* L, int _i, char const* _name); -void initialize_allocator_function( Universe* U, lua_State* L); +void populate_func_lookup_table(lua_State* L, int _i, char const* _name); +void initialize_allocator_function(Universe* U, lua_State* L); // ################################################################################################ diff --git a/src/universe.cpp b/src/universe.cpp index 290e547..4c53987 100644 --- a/src/universe.cpp +++ b/src/universe.cpp @@ -80,7 +80,7 @@ Universe* universe_create(lua_State* L) U->Universe::Universe(); STACK_CHECK_START_REL(L, 1); UNIVERSE_FULL_REGKEY.setValue(L, [](lua_State* L) { lua_pushvalue(L, -2); }); - UNIVERSE_LIGHT_REGKEY.setValue(L, [U](lua_State* L) { lua_pushlightuserdata( L, U); }); + UNIVERSE_LIGHT_REGKEY.setValue(L, [U](lua_State* L) { lua_pushlightuserdata(L, U); }); STACK_CHECK(L, 1); return U; } @@ -91,8 +91,8 @@ void universe_store(lua_State* L, Universe* U) { ASSERT_L(!U || universe_get(L) == nullptr); STACK_CHECK_START_REL(L, 0); - UNIVERSE_LIGHT_REGKEY.setValue(L, [U](lua_State* L) { U ? lua_pushlightuserdata( L, U) : lua_pushnil( L); }); - STACK_CHECK( L, 0); + UNIVERSE_LIGHT_REGKEY.setValue(L, [U](lua_State* L) { U ? lua_pushlightuserdata(L, U) : lua_pushnil(L); }); + STACK_CHECK(L, 0); } // ################################################################################################ @@ -101,6 +101,6 @@ Universe* universe_get(lua_State* L) { STACK_CHECK_START_REL(L, 0); Universe* const universe{ UNIVERSE_LIGHT_REGKEY.readLightUserDataValue(L) }; - STACK_CHECK( L, 0); + STACK_CHECK(L, 0); return universe; } -- cgit v1.2.3-55-g6feb From aa9b3594e11498ca0977d87a9d0875d7fc107253 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Tue, 9 Apr 2024 17:08:13 +0200 Subject: C++ migration: [[nodiscard]] everywhere. still have to check all std::ignore --- deep_test/deep_test.cpp | 278 ++++++++++++++++++++++++------------------------ src/cancel.cpp | 12 +-- src/cancel.h | 4 +- src/compat.cpp | 6 +- src/deep.cpp | 12 ++- src/deep.h | 10 +- src/keeper.cpp | 58 +++++----- src/keeper.h | 26 ++--- src/lanes.cpp | 55 +++++----- src/lanes.h | 2 +- src/lanes_private.h | 11 +- src/linda.cpp | 12 +-- src/macros_and_utils.h | 15 +-- src/state.cpp | 6 +- src/state.h | 4 +- src/threading.cpp | 4 +- src/threading_osx.h | 10 +- src/tools.cpp | 260 ++++++++++++++++++++++---------------------- src/tools.h | 10 +- src/universe.h | 10 +- 20 files changed, 410 insertions(+), 395 deletions(-) (limited to 'src') diff --git a/deep_test/deep_test.cpp b/deep_test/deep_test.cpp index 7c8180f..3467939 100644 --- a/deep_test/deep_test.cpp +++ b/deep_test/deep_test.cpp @@ -10,114 +10,114 @@ // a lanes-deep userdata. needs DeepPrelude and luaG_newdeepuserdata from Lanes code. struct MyDeepUserdata : public DeepPrelude // Deep userdata MUST start with a DeepPrelude { - lua_Integer val{ 0 }; + lua_Integer val{ 0 }; }; // ################################################################################################ -static void* deep_test_id( lua_State* L, DeepOp op_) +[[nodiscard]] static void* deep_test_id(lua_State* L, DeepOp op_) { - switch( op_) - { - case DeepOp::New: - { - MyDeepUserdata* deep_test = new MyDeepUserdata; - return deep_test; - } - - case DeepOp::Delete: - { - MyDeepUserdata* deep_test = static_cast(lua_touserdata( L, 1)); - delete deep_test; - return nullptr; - } - - case DeepOp::Metatable: - { - luaL_getmetatable( L, "deep"); // mt - return nullptr; - } - - case DeepOp::Module: - return (void*)"deep_test"; - - default: - { - return nullptr; - } - } + switch( op_) + { + case DeepOp::New: + { + MyDeepUserdata* deep_test = new MyDeepUserdata; + return deep_test; + } + + case DeepOp::Delete: + { + MyDeepUserdata* deep_test = static_cast(lua_touserdata( L, 1)); + delete deep_test; + return nullptr; + } + + case DeepOp::Metatable: + { + luaL_getmetatable( L, "deep"); // mt + return nullptr; + } + + case DeepOp::Module: + return (void*)"deep_test"; + + default: + { + return nullptr; + } + } } // ################################################################################################ -static int deep_set( lua_State* L) +[[nodiscard]] static int deep_set(lua_State* L) { - MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); - lua_Integer i = lua_tointeger( L, 2); - self->val = i; - return 0; + MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); + lua_Integer i = lua_tointeger( L, 2); + self->val = i; + return 0; } // ################################################################################################ // won't actually do anything as deep userdata don't have uservalue slots -static int deep_setuv( lua_State* L) +[[nodiscard]] static int deep_setuv(lua_State* L) { - MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); - int uv = (int) luaL_optinteger(L, 2, 1); - lua_settop( L, 3); - lua_pushboolean( L, lua_setiuservalue( L, 1, uv) != 0); - return 1; + MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); + int uv = (int) luaL_optinteger(L, 2, 1); + lua_settop( L, 3); + lua_pushboolean( L, lua_setiuservalue( L, 1, uv) != 0); + return 1; } // ################################################################################################ // won't actually do anything as deep userdata don't have uservalue slots -static int deep_getuv( lua_State* L) +[[nodiscard]] static int deep_getuv(lua_State* L) { - MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); - int uv = (int) luaL_optinteger(L, 2, 1); - lua_getiuservalue( L, 1, uv); - return 1; + MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); + int uv = (int) luaL_optinteger(L, 2, 1); + lua_getiuservalue( L, 1, uv); + return 1; } // ################################################################################################ -static int deep_tostring( lua_State* L) +[[nodiscard]] static int deep_tostring(lua_State* L) { - MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); - lua_pushfstring(L, "%p:deep(%d)", lua_topointer(L, 1), self->val); - return 1; + MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); + lua_pushfstring(L, "%p:deep(%d)", lua_topointer(L, 1), self->val); + return 1; } // ################################################################################################ -static int deep_gc( lua_State* L) +[[nodiscard]] static int deep_gc(lua_State* L) { - MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); - return 0; + MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); + return 0; } // ################################################################################################ static luaL_Reg const deep_mt[] = { - { "__tostring", deep_tostring}, - { "__gc", deep_gc}, - { "set", deep_set}, - { "setuv", deep_setuv}, - { "getuv", deep_getuv}, - { nullptr, nullptr } + { "__tostring", deep_tostring}, + { "__gc", deep_gc}, + { "set", deep_set}, + { "setuv", deep_setuv}, + { "getuv", deep_getuv}, + { nullptr, nullptr } }; // ################################################################################################ int luaD_new_deep( lua_State* L) { - int nuv = (int) luaL_optinteger( L, 1, 0); - // no additional parameter to luaG_newdeepuserdata! - lua_settop( L, 0); - return luaG_newdeepuserdata( L, deep_test_id, nuv); + int const nuv{ static_cast(luaL_optinteger(L, 1, 0)) }; + // no additional parameter to luaG_newdeepuserdata! + lua_settop(L, 0); + return luaG_newdeepuserdata(Dest{ L }, deep_test_id, nuv); } // ################################################################################################ @@ -125,101 +125,101 @@ int luaD_new_deep( lua_State* L) struct MyClonableUserdata { - lua_Integer val; + lua_Integer val; }; // ################################################################################################ -static int clonable_set( lua_State* L) +[[nodiscard]] static int clonable_set(lua_State* L) { - MyClonableUserdata* self = static_cast(lua_touserdata(L, 1)); - lua_Integer i = lua_tointeger(L, 2); - self->val = i; - return 0; + MyClonableUserdata* self = static_cast(lua_touserdata(L, 1)); + lua_Integer i = lua_tointeger(L, 2); + self->val = i; + return 0; } // ################################################################################################ -static int clonable_setuv( lua_State* L) +[[nodiscard]] static int clonable_setuv(lua_State* L) { - MyClonableUserdata* self = static_cast(lua_touserdata(L, 1)); - int uv = (int) luaL_optinteger(L, 2, 1); - lua_settop( L, 3); - lua_pushboolean( L, lua_setiuservalue( L, 1, uv) != 0); - return 1; + MyClonableUserdata* self = static_cast(lua_touserdata(L, 1)); + int uv = (int) luaL_optinteger(L, 2, 1); + lua_settop( L, 3); + lua_pushboolean( L, lua_setiuservalue( L, 1, uv) != 0); + return 1; } // ################################################################################################ -static int clonable_getuv( lua_State* L) +[[nodiscard]] static int clonable_getuv(lua_State* L) { - MyClonableUserdata* self = static_cast(lua_touserdata(L, 1)); - int uv = (int) luaL_optinteger(L, 2, 1); - lua_getiuservalue( L, 1, uv); - return 1; + MyClonableUserdata* self = static_cast(lua_touserdata(L, 1)); + int uv = (int) luaL_optinteger(L, 2, 1); + lua_getiuservalue( L, 1, uv); + return 1; } // ################################################################################################ -static int clonable_tostring(lua_State* L) +[[nodiscard]] static int clonable_tostring(lua_State* L) { - MyClonableUserdata* self = static_cast(lua_touserdata(L, 1)); - lua_pushfstring(L, "%p:clonable(%d)", lua_topointer(L, 1), self->val); - return 1; + MyClonableUserdata* self = static_cast(lua_touserdata(L, 1)); + lua_pushfstring(L, "%p:clonable(%d)", lua_topointer(L, 1), self->val); + return 1; } // ################################################################################################ -static int clonable_gc( lua_State* L) +[[nodiscard]] static int clonable_gc(lua_State* L) { - MyClonableUserdata* self = static_cast(lua_touserdata(L, 1)); - return 0; + MyClonableUserdata* self = static_cast(lua_touserdata(L, 1)); + return 0; } // ################################################################################################ // this is all we need to make a userdata lanes-clonable. no dependency on Lanes code. -static int clonable_lanesclone( lua_State* L) +[[nodiscard]] static int clonable_lanesclone(lua_State* L) { - switch( lua_gettop( L)) - { - case 3: - { - MyClonableUserdata* self = static_cast(lua_touserdata(L, 1)); - MyClonableUserdata* from = static_cast(lua_touserdata(L, 2)); - size_t len = lua_tointeger( L, 3); - assert( len == sizeof(MyClonableUserdata)); - *self = *from; - } - return 0; - - default: - (void) luaL_error( L, "Lanes called clonable_lanesclone with unexpected parameters"); - } - return 0; + switch( lua_gettop( L)) + { + case 3: + { + MyClonableUserdata* self = static_cast(lua_touserdata(L, 1)); + MyClonableUserdata* from = static_cast(lua_touserdata(L, 2)); + size_t len = lua_tointeger( L, 3); + assert( len == sizeof(MyClonableUserdata)); + *self = *from; + } + return 0; + + default: + (void) luaL_error( L, "Lanes called clonable_lanesclone with unexpected parameters"); + } + return 0; } // ################################################################################################ static luaL_Reg const clonable_mt[] = { - { "__tostring", clonable_tostring}, - { "__gc", clonable_gc}, - { "__lanesclone", clonable_lanesclone}, - { "set", clonable_set}, - { "setuv", clonable_setuv}, - { "getuv", clonable_getuv}, - { nullptr, nullptr } + { "__tostring", clonable_tostring}, + { "__gc", clonable_gc}, + { "__lanesclone", clonable_lanesclone}, + { "set", clonable_set}, + { "setuv", clonable_setuv}, + { "getuv", clonable_getuv}, + { nullptr, nullptr } }; // ################################################################################################ int luaD_new_clonable( lua_State* L) { - int nuv = (int) luaL_optinteger( L, 1, 1); - lua_newuserdatauv( L, sizeof(MyClonableUserdata), nuv); - luaL_setmetatable( L, "clonable"); - return 1; + int const nuv{ static_cast(luaL_optinteger(L, 1, 1)) }; + lua_newuserdatauv( L, sizeof(MyClonableUserdata), nuv); + luaL_setmetatable( L, "clonable"); + return 1; } // ################################################################################################ @@ -227,33 +227,33 @@ int luaD_new_clonable( lua_State* L) static luaL_Reg const deep_module[] = { - { "new_deep", luaD_new_deep}, - { "new_clonable", luaD_new_clonable}, - { nullptr, nullptr } + { "new_deep", luaD_new_deep}, + { "new_clonable", luaD_new_clonable}, + { nullptr, nullptr } }; // ################################################################################################ LANES_API int luaopen_deep_test(lua_State* L) { - luaL_newlib( L, deep_module); // M - - // preregister the metatables for the types we can instantiate so that Lanes can know about them - if( luaL_newmetatable( L, "clonable")) // M mt - { - luaL_setfuncs( L, clonable_mt, 0); - lua_pushvalue(L, -1); // M mt mt - lua_setfield(L, -2, "__index"); // M mt - } - lua_setfield(L, -2, "__clonableMT"); // M - - if( luaL_newmetatable( L, "deep")) // mt - { - luaL_setfuncs( L, deep_mt, 0); - lua_pushvalue(L, -1); // mt mt - lua_setfield(L, -2, "__index"); // mt - } - lua_setfield(L, -2, "__deepMT"); // M - - return 1; + luaL_newlib( L, deep_module); // M + + // preregister the metatables for the types we can instantiate so that Lanes can know about them + if( luaL_newmetatable( L, "clonable")) // M mt + { + luaL_setfuncs( L, clonable_mt, 0); + lua_pushvalue(L, -1); // M mt mt + lua_setfield(L, -2, "__index"); // M mt + } + lua_setfield(L, -2, "__clonableMT"); // M + + if( luaL_newmetatable( L, "deep")) // mt + { + luaL_setfuncs( L, deep_mt, 0); + lua_pushvalue(L, -1); // mt mt + lua_setfield(L, -2, "__index"); // mt + } + lua_setfield(L, -2, "__deepMT"); // M + + return 1; } diff --git a/src/cancel.cpp b/src/cancel.cpp index e08e975..2f3c22e 100644 --- a/src/cancel.cpp +++ b/src/cancel.cpp @@ -51,7 +51,7 @@ THE SOFTWARE. * Returns CANCEL_SOFT/HARD if any locks are to be exited, and 'raise_cancel_error()' called, * to make execution of the lane end. */ -static inline CancelRequest cancel_test(lua_State* L) +[[nodiscard]] static inline CancelRequest cancel_test(lua_State* L) { Lane* const lane{ LANE_POINTER_REGKEY.readLightUserDataValue(L) }; // 'lane' is nullptr for the original main state (and no-one can cancel that) @@ -76,7 +76,7 @@ LUAG_FUNC( cancel_test) // ################################################################################################ // ################################################################################################ -static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar) +[[nodiscard]] static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar) { DEBUGSPEW_CODE(fprintf(stderr, "cancel_hook\n")); if (cancel_test(L) != CancelRequest::None) @@ -108,7 +108,7 @@ static void cancel_hook(lua_State* L, [[maybe_unused]] lua_Debug* ar) // ################################################################################################ -static CancelResult thread_cancel_soft(Lane* lane_, lua_Duration duration_, bool wake_lane_) +[[nodiscard]] static CancelResult thread_cancel_soft(Lane* lane_, lua_Duration duration_, bool wake_lane_) { lane_->cancel_request = CancelRequest::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 @@ -126,7 +126,7 @@ static CancelResult thread_cancel_soft(Lane* lane_, lua_Duration duration_, bool // ################################################################################################ -static CancelResult thread_cancel_hard(Lane* lane_, lua_Duration duration_, bool wake_lane_) +[[nodiscard]] static CancelResult thread_cancel_hard(Lane* lane_, lua_Duration duration_, bool wake_lane_) { lane_->cancel_request = CancelRequest::Hard; // it's now signaled to stop //lane_->m_thread.get_stop_source().request_stop(); @@ -204,7 +204,7 @@ CancelOp which_cancel_op(char const* op_string_) // ################################################################################################ -static CancelOp which_cancel_op(lua_State* L, int idx_) +[[nodiscard]] static CancelOp which_cancel_op(lua_State* L, int idx_) { if (lua_type(L, idx_) == LUA_TSTRING) { @@ -273,7 +273,7 @@ LUAG_FUNC(thread_cancel) case CancelResult::Cancelled: lua_pushboolean(L, 1); - push_thread_status(L, lane); + std::ignore = push_thread_status(L, lane); break; } // should never happen, only here to prevent the compiler from complaining of "not all control paths returning a value" diff --git a/src/cancel.h b/src/cancel.h index 954b04e..060edb3 100644 --- a/src/cancel.h +++ b/src/cancel.h @@ -49,8 +49,8 @@ enum class CancelOp // crc64/we of string "CANCEL_ERROR" generated at http://www.nitrxgen.net/hashgen/ static constexpr UniqueKey CANCEL_ERROR{ 0xe97d41626cc97577ull }; // 'raise_cancel_error' sentinel -CancelOp which_cancel_op(char const* op_string_); -CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hook_count_, lua_Duration secs_, bool wake_lindas_); +[[nodiscard]] CancelOp which_cancel_op(char const* op_string_); +[[nodiscard]] CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hook_count_, lua_Duration secs_, bool wake_lindas_); [[noreturn]] static inline void raise_cancel_error(lua_State* L) { diff --git a/src/compat.cpp b/src/compat.cpp index 8acab25..9807390 100644 --- a/src/compat.cpp +++ b/src/compat.cpp @@ -15,7 +15,7 @@ // ################################################################################################ // ################################################################################################ -static int luaL_getsubtable (lua_State *L, int idx, const char *fname) +[[nodiscard]] static int luaL_getsubtable(lua_State* L, int idx, const char* fname) { lua_getfield(L, idx, fname); if (lua_istable(L, -1)) @@ -33,12 +33,12 @@ 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) +void luaL_requiref(lua_State *L, const char *modname, lua_CFunction openf, int glb) { lua_pushcfunction(L, openf); lua_pushstring(L, modname); /* argument to open function */ lua_call(L, 1, 1); /* open module */ - luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + std::ignore = luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); lua_pushvalue(L, -2); /* make copy of module (call result) */ lua_setfield(L, -2, modname); /* _LOADED[modname] = module */ lua_pop(L, 1); /* remove _LOADED table */ diff --git a/src/deep.cpp b/src/deep.cpp index 55063b3..a3806aa 100644 --- a/src/deep.cpp +++ b/src/deep.cpp @@ -107,7 +107,7 @@ static void get_deep_lookup(lua_State* L) * Return the registered ID function for 'index' (deep userdata proxy), * or nullptr if 'index' is not a deep userdata proxy. */ -static inline luaG_IdFunction get_idfunc(lua_State* L, int index, LookupMode mode_) +[[nodiscard]] static inline luaG_IdFunction get_idfunc(lua_State* L, int index, LookupMode mode_) { // when looking inside a keeper, we are 100% sure the object is a deep userdata if (mode_ == LookupMode::FromKeeper) @@ -160,7 +160,7 @@ void free_deep_prelude(lua_State* L, DeepPrelude* prelude_) * End of life for a proxy object; reduce the deep reference count and clean it up if reaches 0. * */ -static int deep_userdata_gc(lua_State* L) +[[nodiscard]] static int deep_userdata_gc(lua_State* L) { DeepPrelude** const proxy{ lua_tofulluserdata(L, 1) }; DeepPrelude* p = *proxy; @@ -470,10 +470,14 @@ bool copydeep(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode 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 + std::ignore = 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]* // this pops the value from the stack - lua_setiuservalue( L2, clone_i, nuv); // u + lua_setiuservalue(L2, clone_i, nuv); // u -- nuv; } } diff --git a/src/deep.h b/src/deep.h index 6cf33ec..7be5c5d 100644 --- a/src/deep.h +++ b/src/deep.h @@ -36,7 +36,7 @@ enum class DeepOp Module, }; -using luaG_IdFunction = void*(*)( lua_State* L, DeepOp op_); +using luaG_IdFunction = void*(*)(lua_State* L, DeepOp op_); // ################################################################################################ @@ -54,8 +54,8 @@ struct DeepPrelude std::atomic m_refcount{ 0 }; }; -char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_); -void free_deep_prelude( lua_State* L, DeepPrelude* prelude_); +[[nodiscard]] char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_); +void free_deep_prelude(lua_State* L, DeepPrelude* prelude_); -LANES_API int luaG_newdeepuserdata(Dest L, luaG_IdFunction idfunc, int nuv_); -LANES_API DeepPrelude* luaG_todeep(lua_State* L, luaG_IdFunction idfunc, int index); +LANES_API [[nodiscard]] int luaG_newdeepuserdata(Dest L, luaG_IdFunction idfunc, int nuv_); +LANES_API [[nodiscard]] DeepPrelude* luaG_todeep(lua_State* L, luaG_IdFunction idfunc, int index); diff --git a/src/keeper.cpp b/src/keeper.cpp index 9718bda..19fbd06 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp @@ -61,12 +61,12 @@ class keeper_fifo int limit{ -1 }; // a fifo full userdata has one uservalue, the table that holds the actual fifo contents - static void* operator new([[maybe_unused]] size_t size_, lua_State* L) noexcept { return lua_newuserdatauv(L, 1); } + [[nodiscard]] static void* operator new([[maybe_unused]] size_t size_, lua_State* L) noexcept { return lua_newuserdatauv(L, 1); } // always embedded somewhere else or "in-place constructed" as a full userdata // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception static void operator delete([[maybe_unused]] void* p_, lua_State* L) { ASSERT_L(!"should never be called") }; - static keeper_fifo* getPtr(lua_State* L, int idx_) + [[nodiscard]] static keeper_fifo* getPtr(lua_State* L, int idx_) { return lua_tofulluserdata(L, idx_); } @@ -77,7 +77,7 @@ static constexpr int CONTENTS_TABLE{ 1 }; // ################################################################################################## // replaces the fifo ud by its uservalue on the stack -static keeper_fifo* prepare_fifo_access(lua_State* L, int idx_) +[[nodiscard]] static keeper_fifo* prepare_fifo_access(lua_State* L, int idx_) { keeper_fifo* const fifo{ keeper_fifo::getPtr(L, idx_) }; if (fifo != nullptr) @@ -95,7 +95,7 @@ static keeper_fifo* prepare_fifo_access(lua_State* L, int idx_) // in: nothing // out: { first = 1, count = 0, limit = -1} -static keeper_fifo* fifo_new(lua_State* L) +[[nodiscard]] static keeper_fifo* fifo_new(lua_State* L) { STACK_GROW(L, 2); STACK_CHECK_START_REL(L, 0); @@ -213,46 +213,46 @@ int keeper_push_linda_storage(Universe* U, Dest L, void* ptr_, uintptr_t magic_) Source const KL{ K ? K->L : nullptr }; if (KL == nullptr) return 0; - STACK_GROW(KL, 4); + STACK_GROW(KL, 4); // KEEPER MAIN STACK_CHECK_START_REL(KL, 0); - FIFOS_KEY.pushValue(KL); // fifos - lua_pushlightuserdata(KL, ptr_); // fifos ud - lua_rawget(KL, -2); // fifos storage - lua_remove(KL, -2); // storage + FIFOS_KEY.pushValue(KL); // fifos + lua_pushlightuserdata(KL, ptr_); // fifos ud + lua_rawget(KL, -2); // fifos storage + lua_remove(KL, -2); // storage if (!lua_istable(KL, -1)) { - lua_pop(KL, 1); // + lua_pop(KL, 1); // STACK_CHECK(KL, 0); return 0; } - // move data from keeper to destination state KEEPER MAIN - lua_pushnil(KL); // storage nil + // move data from keeper to destination state + lua_pushnil(KL); // storage nil STACK_GROW(L, 5); STACK_CHECK_START_REL(L, 0); lua_newtable(L); // out - while (lua_next(KL, -2)) // storage key fifo + while (lua_next(KL, -2)) // storage key fifo { - keeper_fifo* fifo = prepare_fifo_access(KL, -1); // storage key fifotbl - lua_pushvalue(KL, -2); // storage key fifotbl key - luaG_inter_move(U, KL, L, 1, LookupMode::FromKeeper); // storage key fifotbl // out key + keeper_fifo* fifo = prepare_fifo_access(KL, -1); // storage key fifotbl + lua_pushvalue(KL, -2); // storage key fifotbl key + std::ignore = luaG_inter_move(U, KL, L, 1, LookupMode::FromKeeper); // storage key fifotbl // out key STACK_CHECK(L, 2); - lua_newtable(L); // out key keyout - luaG_inter_move(U, KL, L, 1, LookupMode::FromKeeper); // storage key // out key keyout fifotbl - lua_pushinteger(L, fifo->first); // out key keyout fifotbl first + lua_newtable(L); // out key keyout + std::ignore = luaG_inter_move(U, KL, L, 1, LookupMode::FromKeeper); // storage key // out key keyout fifotbl + lua_pushinteger(L, fifo->first); // out key keyout fifotbl first STACK_CHECK(L, 5); - lua_setfield(L, -3, "first"); // out key keyout fifotbl - lua_pushinteger(L, fifo->count); // out key keyout fifobtl count + lua_setfield(L, -3, "first"); // out key keyout fifotbl + lua_pushinteger(L, fifo->count); // out key keyout fifobtl count STACK_CHECK(L, 5); - lua_setfield(L, -3, "count"); // out key keyout fifotbl - lua_pushinteger(L, fifo->limit); // out key keyout fifotbl limit + lua_setfield(L, -3, "count"); // out key keyout fifotbl + lua_pushinteger(L, fifo->limit); // out key keyout fifotbl limit STACK_CHECK(L, 5); - lua_setfield(L, -3, "limit"); // out key keyout fifotbl - lua_setfield(L, -2, "fifo"); // out key keyout - lua_rawset(L, -3); // out + lua_setfield(L, -3, "limit"); // out key keyout fifotbl + lua_setfield(L, -2, "fifo"); // out key keyout + lua_rawset(L, -3); // out STACK_CHECK(L, 1); } STACK_CHECK(L, 1); - lua_pop(KL, 1); // + lua_pop(KL, 1); // STACK_CHECK(KL, 0); return 1; } @@ -287,7 +287,7 @@ int keepercall_send(lua_State* L) if( lua_isnil(L, -1)) { lua_pop(L, 1); // ud key ... fifos - fifo_new(L); // ud key ... fifos fifo + std::ignore = fifo_new(L); // ud key ... fifos fifo lua_pushvalue(L, 2); // ud key ... fifos fifo key lua_pushvalue(L, -2); // ud key ... fifos fifo key fifo lua_rawset(L, -4); // ud key ... fifos fifo @@ -465,7 +465,7 @@ int keepercall_set(lua_State* L) { // fifos key [val [, ...]] nil // no need to wake writers in that case, because a writer can't wait on an inexistent key lua_pop(L, 1); // fifos key [val [, ...]] - fifo_new(L); // fifos key [val [, ...]] fifo + std::ignore = fifo_new(L); // fifos key [val [, ...]] fifo lua_pushvalue(L, 2); // fifos key [val [, ...]] fifo key lua_pushvalue(L, -2); // fifos key [val [, ...]] fifo key fifo lua_rawset(L, 1); // fifos key [val [, ...]] fifo diff --git a/src/keeper.h b/src/keeper.h index 89fa2ab..627c7ea 100644 --- a/src/keeper.h +++ b/src/keeper.h @@ -38,23 +38,23 @@ static constexpr UniqueKey NIL_SENTINEL{ 0x7eaafa003a1d11a1ull }; void init_keepers(Universe* U, lua_State* L); void close_keepers(Universe* U); -Keeper* which_keeper(Keepers* keepers_, uintptr_t magic_); -Keeper* keeper_acquire(Keepers* keepers_, uintptr_t magic_); +[[nodiscard]] Keeper* which_keeper(Keepers* keepers_, uintptr_t magic_); +[[nodiscard]] Keeper* keeper_acquire(Keepers* keepers_, uintptr_t magic_); 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, Dest L, void* ptr_, uintptr_t magic_); +[[nodiscard]] int keeper_push_linda_storage(Universe* U, Dest L, void* ptr_, uintptr_t magic_); using keeper_api_t = lua_CFunction; #define KEEPER_API(_op) keepercall_##_op #define PUSH_KEEPER_FUNC lua_pushcfunction // lua_Cfunctions to run inside a keeper state -int keepercall_clear(lua_State* L); -int keepercall_send(lua_State* L); -int keepercall_receive(lua_State* L); -int keepercall_receive_batched(lua_State* L); -int keepercall_limit(lua_State* L); -int keepercall_get(lua_State* L); -int keepercall_set(lua_State* L); -int keepercall_count(lua_State* L); - -int keeper_call(Universe* U, lua_State* K, keeper_api_t _func, lua_State* L, void* linda, int starting_index); +[[nodiscard]] int keepercall_clear(lua_State* L); +[[nodiscard]] int keepercall_send(lua_State* L); +[[nodiscard]] int keepercall_receive(lua_State* L); +[[nodiscard]] int keepercall_receive_batched(lua_State* L); +[[nodiscard]] int keepercall_limit(lua_State* L); +[[nodiscard]] int keepercall_get(lua_State* L); +[[nodiscard]] int keepercall_set(lua_State* L); +[[nodiscard]] int keepercall_count(lua_State* L); + +[[nodiscard]] int keeper_call(Universe* U, lua_State* K, keeper_api_t _func, lua_State* L, void* linda, int starting_index); diff --git a/src/lanes.cpp b/src/lanes.cpp index 3d0c70d..2a5ebfd 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -162,7 +162,7 @@ static void securize_debug_threadname(lua_State* L, Lane* lane_) } #if ERROR_FULL_STACK -static int lane_error(lua_State* L); +[[nodiscard]] static int lane_error(lua_State* L); // crc64/we of string "STACKTRACE_REGKEY" generated at http://www.nitrxgen.net/hashgen/ static constexpr UniqueKey STACKTRACE_REGKEY{ 0x534af7d3226a429full }; #endif // ERROR_FULL_STACK @@ -188,7 +188,7 @@ static constexpr UniqueKey FINALIZER_REGKEY{ 0x188fccb8bf348e09ull }; * Returns: true if a table was pushed * false if no table found, not created, and nothing pushed */ -static bool push_registry_table( lua_State* L, UniqueKey key, bool create) +[[nodiscard]] static bool push_registry_table(lua_State* L, UniqueKey key, bool create) { STACK_GROW(L, 3); STACK_CHECK_START_REL(L, 0); @@ -237,7 +237,7 @@ static void tracking_add(Lane* lane_) /* * A free-running lane has ended; remove it from tracking chain */ -static bool tracking_remove(Lane* lane_) +[[nodiscard]] static bool tracking_remove(Lane* lane_) { bool found{ false }; std::lock_guard guard{ lane_->U->tracking_cs }; @@ -277,7 +277,7 @@ Lane::~Lane() if (U->tracking_first != nullptr) { // Lane was cleaned up, no need to handle at process termination - tracking_remove(this); + std::ignore = tracking_remove(this); } #endif // HAVE_LANE_TRACKING() } @@ -300,10 +300,10 @@ LUAG_FUNC( set_finalizer) { luaL_argcheck(L, lua_isfunction(L, 1), 1, "finalizer should be a function"); luaL_argcheck(L, lua_gettop( L) == 1, 1, "too many arguments"); - // Get the current finalizer table (if any) - push_registry_table(L, FINALIZER_REGKEY, true /*do create if none*/); // finalizer {finalisers} + // Get the current finalizer table (if any), create one if it doesn't exist + std::ignore = push_registry_table(L, FINALIZER_REGKEY, true); // finalizer {finalisers} STACK_GROW(L, 2); - lua_pushinteger(L, lua_rawlen(L, -1) + 1); // finalizer {finalisers} idx + lua_pushinteger(L, lua_rawlen(L, -1) + 1); // finalizer {finalisers} idx lua_pushvalue(L, 1); // finalizer {finalisers} idx finalizer lua_rawset(L, -3); // finalizer {finalisers} lua_pop(L, 2); // @@ -326,7 +326,7 @@ LUAG_FUNC( set_finalizer) // static void push_stack_trace( lua_State* L, int rc_, int stk_base_); -static int run_finalizers( lua_State* L, int lua_rc) +[[nodiscard]] static int run_finalizers(lua_State* L, int lua_rc) { int finalizers_index; int n; @@ -430,7 +430,7 @@ static void selfdestruct_add(Lane* lane_) /* * A free-running lane has ended; remove it from selfdestruct chain */ -static bool selfdestruct_remove(Lane* lane_) +[[nodiscard]] static bool selfdestruct_remove(Lane* lane_) { bool found{ false }; std::lock_guard guard{ lane_->U->selfdestruct_cs }; @@ -465,7 +465,7 @@ static bool selfdestruct_remove(Lane* lane_) /* * Process end; cancel any still free-running threads */ -static int universe_gc( lua_State* L) +[[nodiscard]] static int universe_gc(lua_State* L) { Universe* const U{ lua_tofulluserdata(L, 1) }; lua_Duration const shutdown_timeout{ lua_tonumber(L, lua_upvalueindex(1)) }; @@ -638,7 +638,7 @@ LUAG_FUNC( set_error_reporting) return 0; } -static int lane_error(lua_State* L) +[[nodiscard]] static int lane_error(lua_State* L) { // error message (any type) STACK_CHECK_START_ABS(L, 1); // some_error @@ -1176,7 +1176,10 @@ LUAG_FUNC(lane_new) if (lua_pcall( L2, 1, 1, 0) != LUA_OK) // ret/errcode { // propagate error to main state if any - luaG_inter_move(U, Source{ L2 }, Dest{ L }, 1, LookupMode::LaneBody); // func libs priority globals package required gc_cb [... args ...] n "modname" error + std::ignore = luaG_inter_move(U + , Source{ L2 }, Dest{ L } + , 1, LookupMode::LaneBody + ); // func libs priority globals package required gc_cb [... args ...] n "modname" error raise_lua_error(L); } // after requiring the module, register the functions it exported in our name<->function database @@ -1209,7 +1212,7 @@ LUAG_FUNC(lane_new) lua_pushglobaltable(L2); // _G while( lua_next(L, globals_idx)) // func libs priority globals package required gc_cb [... args ...] k v { - luaG_inter_copy(U, Source{ L }, Dest{ L2 }, 2, LookupMode::LaneBody); // _G k v + std::ignore = luaG_inter_copy(U, Source{ L }, Dest{ L2 }, 2, LookupMode::LaneBody); // _G k v // assign it in L2's globals table lua_rawset(L2, -3); // _G lua_pop(L, 1); // func libs priority globals package required gc_cb [... args ...] k @@ -1290,7 +1293,7 @@ LUAG_FUNC(lane_new) // and the issue of canceling/killing threads at gc is not very nice, either // (would easily cause waits at gc cycle, which we don't want). // -static int lane_gc(lua_State* L) +[[nodiscard]] static int lane_gc(lua_State* L) { bool have_gc_cb{ false }; Lane* const lane{ lua_toLane(L, 1) }; // ud @@ -1356,7 +1359,7 @@ static int lane_gc(lua_State* L) // / "error" finished at an error, error value is there // / "cancelled" execution cancelled by M (state gone) // -static char const * thread_status_string(Lane* lane_) +[[nodiscard]] static char const* thread_status_string(Lane* lane_) { Lane::Status const st{ lane_->m_status }; // read just once (volatile) char const* str = @@ -1624,20 +1627,20 @@ LUAG_FUNC(threads) { Lane* lane{ U->tracking_first }; int index = 0; - lua_newtable(L); // {} + lua_newtable(L); // {} while (lane != TRACKING_END) { // insert a { name, status } tuple, so that several lanes with the same name can't clobber each other - lua_newtable(L); // {} {} - lua_pushstring(L, lane->debug_name); // {} {} "name" - lua_setfield(L, -2, "name"); // {} {} - push_thread_status(L, lane); // {} {} "status" - lua_setfield(L, -2, "status"); // {} {} - lua_rawseti(L, -2, ++index); // {} + lua_newtable(L); // {} {} + lua_pushstring(L, lane->debug_name); // {} {} "name" + lua_setfield(L, -2, "name"); // {} {} + std::ignore = push_thread_status(L, lane); // {} {} "status" + lua_setfield(L, -2, "status"); // {} {} + lua_rawseti(L, -2, ++index); // {} lane = lane->tracking_next; } } - return lua_gettop(L) - top; // 0 or 1 + return lua_gettop(L) - top; // 0 or 1 } #endif // HAVE_LANE_TRACKING() @@ -1723,7 +1726,7 @@ LUAG_FUNC(wakeup_conv) */ extern int LG_linda(lua_State* L); -static const struct luaL_Reg lanes_functions[] = +static struct luaL_Reg const lanes_functions[] = { { "linda", LG_linda }, { "now_secs", LG_now_secs }, @@ -2022,9 +2025,9 @@ LANES_API int luaopen_lanes_core( lua_State* L) return 1; } -static int default_luaopen_lanes( lua_State* L) +[[nodiscard]] static int default_luaopen_lanes(lua_State* L) { - int rc = luaL_loadfile(L, "lanes.lua") || lua_pcall(L, 0, 1, 0); + int const rc{ luaL_loadfile(L, "lanes.lua") || lua_pcall(L, 0, 1, 0) }; if (rc != LUA_OK) { return luaL_error(L, "failed to initialize embedded Lanes"); diff --git a/src/lanes.h b/src/lanes.h index 05a0a5c..bc8de55 100644 --- a/src/lanes.h +++ b/src/lanes.h @@ -20,7 +20,7 @@ extern "C" { #define LANES_VERSION_GREATER_THAN(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_GREATER_OR_EQUAL(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR>MAJOR) || (LANES_VERSION_MAJOR==MAJOR && (LANES_VERSION_MINOR>MINOR || (LANES_VERSION_MINOR==MINOR && LANES_VERSION_PATCH>=PATCH)))) -LANES_API int luaopen_lanes_core(lua_State* L); +LANES_API [[nodiscard]] int luaopen_lanes_core(lua_State* L); // Call this to work with embedded Lanes instead of calling luaopen_lanes_core() LANES_API void luaopen_lanes_embedded(lua_State* L, lua_CFunction _luaopen_lanes); diff --git a/src/lanes_private.h b/src/lanes_private.h index 0fcbbfc..ba40e44 100644 --- a/src/lanes_private.h +++ b/src/lanes_private.h @@ -78,7 +78,7 @@ class Lane // // For tracking only - static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internal_allocator.alloc(size_); } + [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internal_allocator.alloc(size_); } // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception static void operator delete(void* p_, Universe* U_) { U_->internal_allocator.free(p_, sizeof(Lane)); } // this one is for us, to make sure memory is freed by the correct allocator @@ -87,7 +87,7 @@ class Lane Lane(Universe* U_, lua_State* L_); ~Lane(); - bool waitForCompletion(lua_Duration duration_); + [[nodiscard]] bool waitForCompletion(lua_Duration duration_); void startThread(int priority_); }; @@ -98,6 +98,9 @@ static constexpr UniqueKey LANE_POINTER_REGKEY{ 0xB3022205633743BCull }; // used // '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"))) +[[nodiscard]] inline Lane* lua_toLane(lua_State* L, int i_) +{ + return *(static_cast(luaL_checkudata(L, i_, "Lane"))); +} -int push_thread_status(lua_State* L, Lane* s); +[[nodiscard]] int push_thread_status(lua_State* L, Lane* s); diff --git a/src/linda.cpp b/src/linda.cpp index 39977bc..50964ad 100644 --- a/src/linda.cpp +++ b/src/linda.cpp @@ -70,7 +70,7 @@ class Linda : public DeepPrelude // Deep userdata MUST start with this header public: // a fifo full userdata has one uservalue, the table that holds the actual fifo contents - static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internal_allocator.alloc(size_); } + [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internal_allocator.alloc(size_); } // always embedded somewhere else or "in-place constructed" as a full userdata // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception static void operator delete(void* p_, Universe* U_) { U_->internal_allocator.free(p_, sizeof(Linda)); } @@ -137,10 +137,10 @@ class Linda : public DeepPrelude // Deep userdata MUST start with this header return nullptr; } }; -static void* linda_id( lua_State*, DeepOp); +[[nodiscard]] static void* linda_id(lua_State*, DeepOp); template -static inline Linda* lua_toLinda(lua_State* L, int idx_) +[[nodiscard]] static inline Linda* lua_toLinda(lua_State* L, int idx_) { Linda* const linda{ static_cast(luaG_todeep(L, linda_id, idx_)) }; if (!OPT) @@ -742,7 +742,7 @@ LUAG_FUNC(linda_deep) */ template -static int linda_tostring(lua_State* L, int idx_) +[[nodiscard]] static int linda_tostring(lua_State* L, int idx_) { Linda* const linda{ lua_toLinda(L, idx_) }; if (linda != nullptr) @@ -851,7 +851,7 @@ LUAG_FUNC(linda_towatch) * For any other strings, the ID function must not react at all. This allows * future extensions of the system. */ -static void* linda_id( lua_State* L, DeepOp op_) +[[nodiscard]] static void* linda_id(lua_State* L, DeepOp op_) { switch( op_) { @@ -907,7 +907,7 @@ static void* linda_id( lua_State* L, DeepOp op_) // Clean associated structures in the keeper state. Keeper* const K{ need_acquire_release ? keeper_acquire(linda->U->keepers, linda->hashSeed()) : 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); + std::ignore = keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0); if (need_acquire_release) { keeper_release(K); diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h index 47ce90c..31ae8bd 100644 --- a/src/macros_and_utils.h +++ b/src/macros_and_utils.h @@ -23,11 +23,13 @@ extern char const* debugspew_indent; #define INDENT_BEGIN "%.*s " #define INDENT_END , (U ? U->debugspew_indent_depth.load(std::memory_order_relaxed) : 0), debugspew_indent #define DEBUGSPEW_CODE(_code) _code -#define DEBUGSPEW_PARAM_COMMA( param_) param_, +#define DEBUGSPEW_OR_NOT(a_, b_) a_ +#define DEBUGSPEW_PARAM_COMMA(param_) param_, #define DEBUGSPEW_COMMA_PARAM( param_) , param_ #else // USE_DEBUG_SPEW() #define DEBUGSPEW_CODE(_code) -#define DEBUGSPEW_PARAM_COMMA( param_) +#define DEBUGSPEW_OR_NOT(a_, b_) b_ +#define DEBUGSPEW_PARAM_COMMA(param_) #define DEBUGSPEW_COMMA_PARAM( param_) #endif // USE_DEBUG_SPEW() @@ -130,20 +132,20 @@ inline void STACK_GROW(lua_State* L, int n_) } } -#define LUAG_FUNC( func_name) int LG_##func_name( lua_State* L) +#define LUAG_FUNC(func_name) [[nodiscard]] int LG_##func_name(lua_State* L) // ################################################################################################# // a small helper to extract a full userdata pointer from the stack in a safe way template -T* lua_tofulluserdata(lua_State* L, int index_) +[[nodiscard]] T* lua_tofulluserdata(lua_State* L, int index_) { ASSERT_L(lua_isnil(L, index_) || lua_type(L, index_) == LUA_TUSERDATA); return static_cast(lua_touserdata(L, index_)); } template -auto lua_tolightuserdata(lua_State* L, int index_) +[[nodiscard]] auto lua_tolightuserdata(lua_State* L, int index_) { ASSERT_L(lua_isnil(L, index_) || lua_islightuserdata(L, index_)); if constexpr (std::is_pointer_v) @@ -157,7 +159,7 @@ auto lua_tolightuserdata(lua_State* L, int index_) } template -T* lua_newuserdatauv(lua_State* L, int nuvalue_) +[[nodiscard]] T* lua_newuserdatauv(lua_State* L, int nuvalue_) { return static_cast(lua_newuserdatauv(L, sizeof(T), nuvalue_)); } @@ -175,6 +177,7 @@ using lua_Duration = std::chrono::template duration; // ################################################################################################# +// A unique type generator template struct Unique { diff --git a/src/state.cpp b/src/state.cpp index 6a9ada7..496e21e 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -49,7 +49,7 @@ THE SOFTWARE. // // Upvalues: [1]: original 'require' function // -static int luaG_new_require( lua_State* L) +[[nodiscard]] static int luaG_new_require(lua_State* L) { int rc; int const args = lua_gettop( L); // args @@ -110,7 +110,7 @@ void serialize_require(DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* L) /*---=== luaG_newstate ===---*/ -static int require_lanes_core( lua_State* L) +[[nodiscard]] static int require_lanes_core(lua_State* L) { // leaves a copy of 'lanes.core' module table on the stack luaL_requiref( L, "lanes.core", luaopen_lanes_core, 0); @@ -118,7 +118,7 @@ static int require_lanes_core( lua_State* L) } -static const luaL_Reg libs[] = +static luaL_Reg const libs[] = { { LUA_LOADLIBNAME, luaopen_package}, { LUA_TABLIBNAME, luaopen_table}, diff --git a/src/state.h b/src/state.h index 2601f77..e1c311a 100644 --- a/src/state.h +++ b/src/state.h @@ -10,8 +10,8 @@ void serialize_require(DEBUGSPEW_PARAM_COMMA(Universe* U) lua_State* L); // ################################################################################################ -lua_State* create_state(Universe* U, lua_State* from_); -lua_State* luaG_newstate(Universe* U, Source _from, char const* libs); +[[nodiscard]] lua_State* create_state(Universe* U, lua_State* from_); +[[nodiscard]] lua_State* luaG_newstate(Universe* U, Source _from, char const* libs); // ################################################################################################ diff --git a/src/threading.cpp b/src/threading.cpp index d278bb1..259693a 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -209,7 +209,7 @@ void THREAD_SETNAME(char const* _name) // general its implementation is pretty much trivial, as on Win32 target // just SCHED_OTHER can be supported. #undef pthread_attr_setschedpolicy -static int pthread_attr_setschedpolicy(pthread_attr_t* attr, int policy) +[[nodiscard]] static int pthread_attr_setschedpolicy(pthread_attr_t* attr, int policy) { if (policy != SCHED_OTHER) { @@ -348,7 +348,7 @@ static int const gs_prio_remap[] = #endif // _PRIO_0 }; -static int select_prio(int prio /* -3..+3 */) +[[nodiscard]] static int select_prio(int prio /* -3..+3 */) { if (prio == THREAD_PRIO_DEFAULT) prio = 0; diff --git a/src/threading_osx.h b/src/threading_osx.h index b47d2f6..f4d41e0 100644 --- a/src/threading_osx.h +++ b/src/threading_osx.h @@ -2,8 +2,7 @@ * THREADING_OSX.H * http://yyshen.github.io/2015/01/18/binding_threads_to_cores_osx.html */ -#ifndef __threading_osx_h__ -#define __threading_osx_h__ 1 +#pragma once #include #include @@ -18,9 +17,9 @@ struct cpu_set_t static inline void CPU_ZERO(cpu_set_t *cs) { cs->count = 0; } static inline void CPU_SET(int num, cpu_set_t *cs) { cs->count |= (1 << num); } -static inline int CPU_ISSET(int num, cpu_set_t *cs) { return (cs->count & (1 << num)); } +[[nodiscard]] static inline int CPU_ISSET(int num, cpu_set_t *cs) { return (cs->count & (1 << num)); } -int sched_getaffinity(pid_t pid, size_t cpu_size, cpu_set_t *cpu_set) +[[nodiscard]] int sched_getaffinity(pid_t pid, size_t cpu_size, cpu_set_t *cpu_set) { int32_t core_count = 0; size_t len = sizeof(core_count); @@ -39,7 +38,7 @@ int sched_getaffinity(pid_t pid, size_t cpu_size, cpu_set_t *cpu_set) return 0; } -int pthread_setaffinity_np(pthread_t thread, size_t cpu_size, cpu_set_t *cpu_set) +[[nodiscard]] int pthread_setaffinity_np(pthread_t thread, size_t cpu_size, cpu_set_t *cpu_set) { thread_port_t mach_thread; int core = 0; @@ -57,4 +56,3 @@ int pthread_setaffinity_np(pthread_t thread, size_t cpu_size, cpu_set_t *cpu_set return 0; } -#endif diff --git a/src/tools.cpp b/src/tools.cpp index 07f9ae6..4083a57 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -144,7 +144,7 @@ void luaG_dump( lua_State* L) // ################################################################################################ // same as PUC-Lua l_alloc -extern "C" static void* libc_lua_Alloc([[maybe_unused]] void* ud, [[maybe_unused]] void* ptr_, [[maybe_unused]] size_t osize_, size_t nsize_) +extern "C" [[nodiscard]] static void* libc_lua_Alloc([[maybe_unused]] void* ud, [[maybe_unused]] void* ptr_, [[maybe_unused]] size_t osize_, size_t nsize_) { if (nsize_ == 0) { @@ -159,7 +159,7 @@ extern "C" static void* libc_lua_Alloc([[maybe_unused]] void* ud, [[maybe_unused // ################################################################################################# -static int luaG_provide_protected_allocator(lua_State* L) +[[nodiscard]] static int luaG_provide_protected_allocator(lua_State* L) { Universe* const U{ universe_get(L) }; // push a new full userdata on the stack, giving access to the universe's protected allocator @@ -234,7 +234,7 @@ void initialize_allocator_function(Universe* U, lua_State* L) // ################################################################################################ -static int dummy_writer( lua_State* L, void const* p, size_t sz, void* ud) +[[nodiscard]] static int dummy_writer(lua_State* L, void const* p, size_t sz, void* ud) { (void)L; (void)p; (void)sz; (void) ud; // unused return 666; @@ -291,7 +291,7 @@ FuncSubType luaG_getfuncsubtype( lua_State *L, int _i) // ################################################################################################# -static lua_CFunction luaG_tocfunction(lua_State* L, int _i, FuncSubType* _out) +[[nodiscard]] static lua_CFunction luaG_tocfunction(lua_State* L, int _i, FuncSubType* _out) { lua_CFunction p = lua_tocfunction( L, _i); *_out = luaG_getfuncsubtype( L, _i); @@ -304,7 +304,7 @@ static constexpr UniqueKey LOOKUPCACHE_REGKEY{ 0x837a68dfc6fcb716ull }; // ################################################################################################# // inspired from tconcat() in ltablib.c -static char const* luaG_pushFQN( lua_State* L, int t, int last, size_t* length) +[[nodiscard]] static char const* luaG_pushFQN(lua_State* L, int t, int last, size_t* length) { int i = 1; luaL_Buffer b; @@ -338,7 +338,7 @@ static char const* luaG_pushFQN( lua_State* L, int t, int last, size_t* length) * if we already had an entry of type [o] = ..., replace the name if the new one is shorter * pops the processed object from the stack */ -static void update_lookup_entry( DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* L, int _ctx_base, int _depth) +static void update_lookup_entry(DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* L, int _ctx_base, int _depth) { // slot 1 in the stack contains the table that receives everything we found int const dest = _ctx_base; @@ -362,7 +362,7 @@ static void update_lookup_entry( DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* ++ _depth; lua_rawseti( L, fqn, _depth); // ... {bfc} k o name? // generate name - DEBUGSPEW_CODE( newName =) luaG_pushFQN( L, fqn, _depth, &newNameLength); // ... {bfc} k o name? "f.q.n" + DEBUGSPEW_OR_NOT(newName, std::ignore) = luaG_pushFQN(L, fqn, _depth, &newNameLength);// ... {bfc} k o name? "f.q.n" // Lua 5.2 introduced a hash randomizer seed which causes table iteration to yield a different key order // on different VMs even when the tables are populated the exact same way. // When Lua is built with compatibility options (such as LUA_COMPAT_ALL), @@ -613,7 +613,7 @@ static constexpr UniqueKey REG_MTID{ 0x2e68f9b4751584dcull }; /* * Get a unique ID for metatable at [i]. */ -static lua_Integer get_mt_id( Universe* U, lua_State* L, int i) +[[nodiscard]] static lua_Integer get_mt_id(Universe* U, lua_State* L, int i) { lua_Integer id; @@ -654,25 +654,25 @@ static lua_Integer get_mt_id( Universe* U, lua_State* L, int i) // ################################################################################################# // function sentinel used to transfer native functions from/to keeper states -static int func_lookup_sentinel( lua_State* L) +[[nodiscard]] static int func_lookup_sentinel(lua_State* L) { - return luaL_error( L, "function lookup sentinel for %s, should never be called", lua_tostring( L, lua_upvalueindex( 1))); + return luaL_error(L, "function lookup sentinel for %s, should never be called", lua_tostring(L, lua_upvalueindex(1))); } // ################################################################################################# // function sentinel used to transfer native table from/to keeper states -static int table_lookup_sentinel( lua_State* L) +[[nodiscard]] static int table_lookup_sentinel(lua_State* L) { - return luaL_error( L, "table lookup sentinel for %s, should never be called", lua_tostring( L, lua_upvalueindex( 1))); + return luaL_error(L, "table lookup sentinel for %s, should never be called", lua_tostring(L, lua_upvalueindex(1))); } // ################################################################################################# // function sentinel used to transfer cloned full userdata from/to keeper states -static int userdata_clone_sentinel( lua_State* L) +[[nodiscard]] static int userdata_clone_sentinel(lua_State* L) { - return luaL_error( L, "userdata clone sentinel for %s, should never be called", lua_tostring( L, lua_upvalueindex( 1))); + return luaL_error(L, "userdata clone sentinel for %s, should never be called", lua_tostring(L, lua_upvalueindex(1))); } // ################################################################################################# @@ -680,7 +680,7 @@ static int userdata_clone_sentinel( lua_State* L) /* * retrieve the name of a function/table in the lookup database */ -static char const* find_lookup_name(lua_State* L, int i, LookupMode mode_, char const* upName_, size_t* len_) +[[nodiscard]] static char const* find_lookup_name(lua_State* L, int i, LookupMode mode_, char const* upName_, size_t* len_) { DEBUGSPEW_CODE( Universe* const U = universe_get( L)); char const* fqn; @@ -753,7 +753,7 @@ static char const* find_lookup_name(lua_State* L, int i, LookupMode mode_, char /* * Push a looked-up table, or nothing if we found nothing */ -static bool lookup_table(Dest L2, Source L, int i, LookupMode mode_, char const* upName_) +[[nodiscard]] static bool lookup_table(Dest L2, Source L, int i, LookupMode mode_, char const* upName_) { // get the name of the table we want to send size_t len; @@ -830,13 +830,12 @@ static bool lookup_table(Dest L2, Source L, int i, LookupMode mode_, char const* * Returns true if the table was cached (no need to fill it!); false if * it's a virgin. */ -static bool push_cached_table(lua_State* L2, int L2_cache_i, lua_State* L, int i) +[[nodiscard]] static bool push_cached_table(Dest L2, int L2_cache_i, Source L, int i) { - bool not_found_in_cache; // L2 void const* p{ lua_topointer(L, i) }; ASSERT_L( L2_cache_i != 0); - STACK_GROW( L2, 3); + STACK_GROW( L2, 3); // L2 STACK_CHECK_START_REL(L2, 0); // We don't need to use the from state ('L') in ID since the life span @@ -847,7 +846,7 @@ static bool push_cached_table(lua_State* L2, int L2_cache_i, lua_State* L, int i //fprintf( stderr, "<< ID: %s >>\n", lua_tostring( L2, -1)); lua_rawget( L2, L2_cache_i); // ... {cached|nil} - not_found_in_cache = lua_isnil( L2, -1); + bool const not_found_in_cache{ lua_isnil(L2, -1) }; if( not_found_in_cache) { lua_pop( L2, 1); // ... @@ -866,83 +865,83 @@ static bool push_cached_table(lua_State* L2, int L2_cache_i, lua_State* L, int i /* * Return some name helping to identify an object */ -static int discover_object_name_recur( lua_State* L, int shortest_, int depth_) +[[nodiscard]] static int discover_object_name_recur(lua_State* L, int shortest_, int depth_) { int const what = 1; // o "r" {c} {fqn} ... {?} int const result = 2; int const cache = 3; int const fqn = 4; // no need to scan this table if the name we will discover is longer than one we already know - if( shortest_ <= depth_ + 1) + if (shortest_ <= depth_ + 1) { return shortest_; } - STACK_GROW( L, 3); + STACK_GROW(L, 3); STACK_CHECK_START_REL(L, 0); // stack top contains the table to search in - lua_pushvalue( L, -1); // o "r" {c} {fqn} ... {?} {?} - lua_rawget( L, cache); // o "r" {c} {fqn} ... {?} nil/1 + lua_pushvalue(L, -1); // o "r" {c} {fqn} ... {?} {?} + lua_rawget(L, cache); // o "r" {c} {fqn} ... {?} nil/1 // if table is already visited, we are done - if( !lua_isnil( L, -1)) + if( !lua_isnil(L, -1)) { - lua_pop( L, 1); // o "r" {c} {fqn} ... {?} + lua_pop(L, 1); // o "r" {c} {fqn} ... {?} return shortest_; } // examined table is not in the cache, add it now - lua_pop( L, 1); // o "r" {c} {fqn} ... {?} - lua_pushvalue( L, -1); // o "r" {c} {fqn} ... {?} {?} - lua_pushinteger( L, 1); // o "r" {c} {fqn} ... {?} {?} 1 - lua_rawset( L, cache); // o "r" {c} {fqn} ... {?} + lua_pop(L, 1); // o "r" {c} {fqn} ... {?} + lua_pushvalue(L, -1); // o "r" {c} {fqn} ... {?} {?} + lua_pushinteger(L, 1); // o "r" {c} {fqn} ... {?} {?} 1 + lua_rawset(L, cache); // o "r" {c} {fqn} ... {?} // scan table contents - lua_pushnil( L); // o "r" {c} {fqn} ... {?} nil - while( lua_next( L, -2)) // o "r" {c} {fqn} ... {?} k v + lua_pushnil(L); // o "r" {c} {fqn} ... {?} nil + while (lua_next(L, -2)) // o "r" {c} {fqn} ... {?} k v { - //char const *const strKey = (lua_type( L, -2) == LUA_TSTRING) ? lua_tostring( L, -2) : nullptr; // only for debugging - //lua_Number const numKey = (lua_type( L, -2) == LUA_TNUMBER) ? lua_tonumber( L, -2) : -6666; // only for debugging - STACK_CHECK( L, 2); + //char const *const strKey = (lua_type(L, -2) == LUA_TSTRING) ? lua_tostring(L, -2) : nullptr; // only for debugging + //lua_Number const numKey = (lua_type(L, -2) == LUA_TNUMBER) ? lua_tonumber(L, -2) : -6666; // only for debugging + STACK_CHECK(L, 2); // 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? + 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_CHECK( L, 2); + STACK_CHECK(L, 2); // update shortest name if( depth_ < shortest_) { shortest_ = depth_; - luaG_pushFQN( L, fqn, depth_, nullptr); // o "r" {c} {fqn} ... {?} k v "fqn" - lua_replace( L, result); // o "r" {c} {fqn} ... {?} k v + std::ignore = luaG_pushFQN(L, fqn, depth_, nullptr); // o "r" {c} {fqn} ... {?} k v "fqn" + lua_replace(L, result); // o "r" {c} {fqn} ... {?} k v } // no need to search further at this level - lua_pop( L, 2); // o "r" {c} {fqn} ... {?} - STACK_CHECK( L, 0); + lua_pop(L, 2); // o "r" {c} {fqn} ... {?} + STACK_CHECK(L, 0); break; } - switch( lua_type( L, -1)) // o "r" {c} {fqn} ... {?} k v + switch (lua_type(L, -1)) // o "r" {c} {fqn} ... {?} k v { default: // nil, boolean, light userdata, number and string aren't identifiable break; case LUA_TTABLE: // o "r" {c} {fqn} ... {?} k {} - STACK_CHECK( L, 2); - shortest_ = discover_object_name_recur( L, shortest_, depth_); + STACK_CHECK(L, 2); + shortest_ = discover_object_name_recur(L, shortest_, depth_); // search in the table's metatable too - if( lua_getmetatable( L, -1)) // o "r" {c} {fqn} ... {?} k {} {mt} + if (lua_getmetatable(L, -1)) // o "r" {c} {fqn} ... {?} k {} {mt} { - if( lua_istable( L, -1)) + 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} + 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); // o "r" {c} {fqn} ... {?} k {} + lua_pop(L, 1); // o "r" {c} {fqn} ... {?} k {} } - STACK_CHECK( L, 2); + STACK_CHECK(L, 2); break; case LUA_TTHREAD: // o "r" {c} {fqn} ... {?} k T @@ -950,61 +949,61 @@ static int discover_object_name_recur( lua_State* L, int shortest_, int depth_) break; case LUA_TUSERDATA: // o "r" {c} {fqn} ... {?} k U - STACK_CHECK( L, 2); + STACK_CHECK(L, 2); // search in the object's metatable (some modules are built that way) - if( lua_getmetatable( L, -1)) // o "r" {c} {fqn} ... {?} k U {mt} + if (lua_getmetatable(L, -1)) // o "r" {c} {fqn} ... {?} k U {mt} { - if( lua_istable( L, -1)) + 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} + 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); // o "r" {c} {fqn} ... {?} k U + lua_pop(L, 1); // o "r" {c} {fqn} ... {?} k U } - STACK_CHECK( L, 2); + STACK_CHECK(L, 2); // search in the object's uservalues { int uvi = 1; - while( lua_getiuservalue( L, -1, uvi) != LUA_TNONE) // o "r" {c} {fqn} ... {?} k U {u} + while (lua_getiuservalue(L, -1, uvi) != LUA_TNONE) // o "r" {c} {fqn} ... {?} k U {u} { - if( lua_istable( L, -1)) // if it is a table, look inside + if( lua_istable(L, -1)) // if it is a table, look inside { ++ 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} + 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 + lua_pop(L, 1); // o "r" {c} {fqn} ... {?} k U ++ uvi; } // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now - lua_pop( L, 1); // o "r" {c} {fqn} ... {?} k U + lua_pop(L, 1); // o "r" {c} {fqn} ... {?} k U } - STACK_CHECK( L, 2); + STACK_CHECK(L, 2); break; } // make ready for next iteration - lua_pop( L, 1); // o "r" {c} {fqn} ... {?} k + lua_pop(L, 1); // o "r" {c} {fqn} ... {?} k // remove name from fqn stack - lua_pushnil( L); // o "r" {c} {fqn} ... {?} k nil - lua_rawseti( L, fqn, depth_); // o "r" {c} {fqn} ... {?} k - STACK_CHECK( L, 1); + lua_pushnil(L); // o "r" {c} {fqn} ... {?} k nil + lua_rawseti(L, fqn, depth_); // o "r" {c} {fqn} ... {?} k + STACK_CHECK(L, 1); -- depth_; } // o "r" {c} {fqn} ... {?} - STACK_CHECK( L, 0); + STACK_CHECK(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 - lua_rawset( L, cache); // o "r" {c} {fqn} ... {?} - STACK_CHECK( L, 0); + lua_pushvalue(L, -1); // o "r" {c} {fqn} ... {?} {?} + lua_pushnil(L); // o "r" {c} {fqn} ... {?} {?} nil + lua_rawset(L, cache); // o "r" {c} {fqn} ... {?} + STACK_CHECK(L, 0); return shortest_; } @@ -1169,7 +1168,7 @@ static char const* vt_names[] = // we have to do it that way because we can't unbalance the stack between buffer operations // namely, this means we can't push a function on top of the stack *after* we initialize the buffer! // luckily, this also works with earlier Lua versions -static int buf_writer( lua_State* L, void const* b, size_t size, void* ud) +[[nodiscard]] static int buf_writer(lua_State* L, void const* b, size_t size, void* ud) { luaL_Buffer* B = (luaL_Buffer*) ud; if( !B->L) @@ -1339,7 +1338,7 @@ static void copy_func(Universe* U, Dest L2, int L2_cache_i, Source L, int i, Loo static void copy_cached_func(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode mode_, char const* upName_) { FuncSubType funcSubType; - /*lua_CFunction cfunc =*/ luaG_tocfunction( L, i, &funcSubType); // nullptr for LuaJIT-fast && bytecode functions + std::ignore = luaG_tocfunction(L, i, &funcSubType); // nullptr for LuaJIT-fast && bytecode functions if( funcSubType == FST_Bytecode) { void* const aspointer = (void*)lua_topointer( L, i); @@ -1391,7 +1390,7 @@ static void copy_cached_func(Universe* U, Dest L2, int L2_cache_i, Source L, int // ################################################################################################# -static bool push_cached_metatable(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode mode_, char const* upName_) +[[nodiscard]] static bool push_cached_metatable(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode mode_, char const* upName_) { STACK_CHECK_START_REL(L, 0); if( lua_getmetatable( L, i)) // ... mt @@ -1442,7 +1441,7 @@ static bool push_cached_metatable(Universe* U, Dest L2, int L2_cache_i, Source L // ################################################################################################# -static void inter_copy_keyvaluepair(Universe* U, Dest L2, int L2_cache_i, Source L, VT vt_, LookupMode mode_, char const* upName_) +[[nodiscard]] static void inter_copy_keyvaluepair(Universe* U, Dest L2, int L2_cache_i, Source L, VT vt_, LookupMode mode_, char const* upName_) { int val_i = lua_gettop(L); int key_i = val_i - 1; @@ -1514,7 +1513,7 @@ static void inter_copy_keyvaluepair(Universe* U, Dest L2, int L2_cache_i, Source */ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; -static bool copyclone(Universe* U, Dest L2, int L2_cache_i, Source L, int source_i_, LookupMode mode_, char const* upName_) +[[nodiscard]] static bool copyclone(Universe* U, Dest L2, int L2_cache_i, Source L, int source_i_, LookupMode mode_, char const* upName_) { void* const source = lua_touserdata( L, source_i_); source_i_ = lua_absindex( L, source_i_); @@ -1600,7 +1599,11 @@ static bool copyclone(Universe* U, Dest L2, int L2_cache_i, Source L, int source // assign uservalues while( uvi > 0) { - inter_copy_one(U, L2, L2_cache_i, L, lua_absindex( L, -1), VT::NORMAL, mode_, upName_); // ... u uv + std::ignore = inter_copy_one(U + , L2, L2_cache_i + , L, lua_absindex(L, -1) + , VT::NORMAL, mode_, upName_ + ); // ... u uv lua_pop( L, 1); // ... mt __lanesclone [uv]* // this pops the value from the stack lua_setiuservalue( L2, -2, uvi); // ... u @@ -1629,7 +1632,7 @@ static bool copyclone(Universe* U, Dest L2, int L2_cache_i, Source L, int source // ################################################################################################# -static bool inter_copy_userdata(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_) +[[nodiscard]] static bool inter_copy_userdata(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_) { STACK_CHECK_START_REL(L, 0); STACK_CHECK_START_REL(L2, 0); @@ -1679,7 +1682,7 @@ static bool inter_copy_userdata(Universe* U, Dest L2, int L2_cache_i, Source L, // ################################################################################################# -static bool inter_copy_function(Universe* U, Dest L2, int L2_cache_i, Source L, int source_i_, VT vt_, LookupMode mode_, char const* upName_) +[[nodiscard]] static bool inter_copy_function(Universe* U, Dest L2, int L2_cache_i, Source L, int source_i_, VT vt_, LookupMode mode_, char const* upName_) { if (vt_ == VT::KEY) { @@ -1693,13 +1696,10 @@ static bool inter_copy_function(Universe* U, Dest L2, int L2_cache_i, Source L, if( lua_tocfunction( L, source_i_) == userdata_clone_sentinel) // we are actually copying a clonable full userdata from a keeper { // clone the full userdata again - size_t userdata_size = 0; - void* source; - void* clone; // let's see if we already restored this userdata lua_getupvalue( L, source_i_, 2); // ... u - source = lua_touserdata( L, -1); + void* source = lua_touserdata( L, -1); lua_pushlightuserdata( L2, source); // ... source lua_rawget( L2, L2_cache_i); // ... u? if( !lua_isnil( L2, -1)) @@ -1712,12 +1712,13 @@ static bool inter_copy_function(Universe* U, Dest L2, int L2_cache_i, Source L, lua_pop( L2, 1); // ... // this function has 2 upvalues: the fqn of its metatable, and the userdata itself - lookup_table( L2, L, source_i_, mode_, upName_); // ... mt + std::ignore = lookup_table( L2, L, source_i_, mode_, upName_); // ... mt // originally 'source_i_' slot was the proxy closure, but from now on it indexes the actual userdata we extracted from it source_i_ = lua_gettop( L); source = lua_touserdata( L, -1); + void* clone{ nullptr }; // get the number of bytes to allocate for the clone - userdata_size = (size_t) lua_rawlen( L, -1); + size_t const userdata_size { lua_rawlen(L, -1) }; { // extract uservalues (don't transfer them yet) int uvi = 0; @@ -1738,7 +1739,11 @@ static bool inter_copy_function(Universe* U, Dest L2, int L2_cache_i, Source L, // 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 + std::ignore = inter_copy_one(U + , L2, L2_cache_i + , L, lua_absindex(L, -1) + , vt_, mode_, upName_ + ); // ... mt u uv lua_pop( L, 1); // ... u [uv]* // this pops the value from the stack lua_setiuservalue( L2, -2, uvi); // ... mt u @@ -1774,7 +1779,7 @@ static bool inter_copy_function(Universe* U, Dest L2, int L2_cache_i, Source L, // ################################################################################################# -static bool inter_copy_table(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_) +[[nodiscard]] static bool inter_copy_table(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_) { if (vt_ == VT::KEY) { @@ -1846,7 +1851,7 @@ static bool inter_copy_table(Universe* U, Dest L2, int L2_cache_i, Source L, int * * Returns true if value was pushed, false if its type is non-supported. */ -bool inter_copy_one(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_) +[[nodiscard]] bool inter_copy_one(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_) { bool ret{ true }; int val_type = lua_type( L, i); @@ -1971,78 +1976,77 @@ bool inter_copy_one(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt * * Note: Parameters are in this order ('L' = from first) to be same as 'lua_xmove'. */ -int luaG_inter_copy(Universe* U, Source L, Dest L2, int n, LookupMode mode_) +[[nodiscard]] int luaG_inter_copy(Universe* U, Source L, Dest L2, int n, LookupMode mode_) { - int top_L = lua_gettop(L); // ... {}n - int top_L2 = lua_gettop(L2); // ... - int i, j; + int const top_L{ lua_gettop(L) }; // ... {}n + int const top_L2{ lua_gettop(L2) }; // ... char tmpBuf[16]; - char const* pBuf = U->verboseErrors ? tmpBuf : "?"; - bool copyok{ true }; + char const* pBuf{ U->verboseErrors ? tmpBuf : "?" }; - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "luaG_inter_copy()\n" INDENT_END)); + DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "luaG_inter_copy()\n" INDENT_END)); DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); - if( n > top_L) + if (n > top_L) { // requesting to copy more than is available? - DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "nothing to copy()\n" INDENT_END)); + DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "nothing to copy()\n" INDENT_END)); DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); return -1; } STACK_CHECK_START_REL(L2, 0); - STACK_GROW( L2, n + 1); + STACK_GROW(L2, n + 1); /* * Make a cache table for the duration of this copy. Collects tables and * function entries, avoiding the same entries to be passed on as multiple * copies. ESSENTIAL i.e. for handling upvalue tables in the right manner! */ - lua_newtable( L2); // ... cache + lua_newtable(L2); // ... cache STACK_CHECK_START_REL(L, 0); - for( i = top_L - n + 1, j = 1; i <= top_L; ++ i, ++ j) + bool copyok{ true }; + for (int i = top_L - n + 1, j = 1; i <= top_L; ++i, ++j) { - if( U->verboseErrors) + if (U->verboseErrors) { - sprintf( tmpBuf, "arg_%d", j); + sprintf(tmpBuf, "arg_%d", j); } copyok = inter_copy_one(U, L2, top_L2 + 1, L, i, VT::NORMAL, mode_, pBuf); // ... cache {}n - if( !copyok) + if (!copyok) { break; } } - STACK_CHECK( L, 0); + STACK_CHECK(L, 0); DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); - if( copyok) + if (copyok) { - STACK_CHECK( L2, n + 1); + STACK_CHECK(L2, n + 1); // 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); + lua_remove(L2, top_L2 + 1); return 0; } // error -> pop everything from the target state stack - lua_settop( L2, top_L2); - STACK_CHECK( L2, 0); + lua_settop(L2, top_L2); + STACK_CHECK(L2, 0); return -2; } // ################################################################################################# -int luaG_inter_move(Universe* U, Source L, Dest L2, int n, LookupMode mode_) +[[nodiscard]] int luaG_inter_move(Universe* U, Source L, Dest L2, int n, LookupMode mode_) { - int ret = luaG_inter_copy( U, L, L2, n, mode_); - lua_pop( L, (int) n); + int const ret{ luaG_inter_copy(U, L, L2, n, mode_) }; + lua_pop( L, n); return ret; } -int luaG_inter_copy_package(Universe* U, Source L, Dest L2, int package_idx_, LookupMode mode_) +[[nodiscard]] int luaG_inter_copy_package(Universe* U, Source L, Dest 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.fetch_add(1, std::memory_order_relaxed)); @@ -2080,7 +2084,7 @@ int luaG_inter_copy_package(Universe* U, Source L, Dest L2, int package_idx_, Lo else { DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); - luaG_inter_move(U, L, L2, 1, mode_); // moves the entry to L2 + std::ignore = luaG_inter_move(U, L, L2, 1, mode_); // moves the entry to L2 DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); lua_setfield(L2, -2, entry); // set package[entry] } diff --git a/src/tools.h b/src/tools.h index 8e95a4f..f0de7ec 100644 --- a/src/tools.h +++ b/src/tools.h @@ -25,16 +25,16 @@ enum class VT KEY, METATABLE }; -bool inter_copy_one(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_); +[[nodiscard]] bool inter_copy_one(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_); // ################################################################################################ -int luaG_inter_copy_package(Universe* U, Source L, Dest L2, int package_idx_, LookupMode mode_); +[[nodiscard]] int luaG_inter_copy_package(Universe* U, Source L, Dest L2, int package_idx_, LookupMode mode_); -int luaG_inter_copy(Universe* U, Source L, Dest L2, int n, LookupMode mode_); -int luaG_inter_move(Universe* U, Source L, Dest L2, int n, LookupMode mode_); +[[nodiscard]] int luaG_inter_copy(Universe* U, Source L, Dest L2, int n, LookupMode mode_); +[[nodiscard]] int luaG_inter_move(Universe* U, Source L, Dest L2, int n, LookupMode mode_); -int luaG_nameof(lua_State* L); +[[nodiscard]] int luaG_nameof(lua_State* L); void populate_func_lookup_table(lua_State* L, int _i, char const* _name); void initialize_allocator_function(Universe* U, lua_State* L); diff --git a/src/universe.h b/src/universe.h index f4211af..113ed21 100644 --- a/src/universe.h +++ b/src/universe.h @@ -35,7 +35,7 @@ class AllocatorDefinition lua_Alloc m_allocF{ nullptr }; void* m_allocUD{ nullptr }; - static void* operator new(size_t size_, lua_State* L) noexcept { return lua_newuserdatauv(L, size_, 0); } + [[nodiscard]] static void* operator new(size_t size_, lua_State* L) noexcept { return lua_newuserdatauv(L, size_, 0); } // always embedded somewhere else or "in-place constructed" as a full userdata // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception static void operator delete([[maybe_unused]] void* p_, lua_State* L) { ASSERT_L(!"should never be called") }; @@ -81,7 +81,7 @@ class ProtectedAllocator : public AllocatorDefinition std::mutex m_lock; - static void* protected_lua_Alloc(void* ud_, void* ptr_, size_t osize_, size_t nsize_) + [[nodiscard]] static void* protected_lua_Alloc(void* ud_, void* ptr_, size_t osize_, size_t nsize_) { ProtectedAllocator* const allocator{ static_cast(ud_) }; std::lock_guard guard{ allocator->m_lock }; @@ -91,7 +91,7 @@ class ProtectedAllocator : public AllocatorDefinition public: // we are not like our base class: we can't be created inside a full userdata (or we would have to install a metatable and __gc handler to destroy ourselves properly) - static void* operator new(size_t size_, lua_State* L) noexcept = delete; + [[nodiscard]] static void* operator new(size_t size_, lua_State* L) noexcept = delete; static void operator delete(void* p_, lua_State* L) = delete; AllocatorDefinition makeDefinition() @@ -185,6 +185,6 @@ class Universe // ################################################################################################ -Universe* universe_get(lua_State* L); -Universe* universe_create(lua_State* L); +[[nodiscard]] Universe* universe_get(lua_State* L); +[[nodiscard]] Universe* universe_create(lua_State* L); void universe_store(lua_State* L, Universe* U); -- cgit v1.2.3-55-g6feb From 53454cec9e5fb600b754037c21bf700c1a65a6d2 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Tue, 9 Apr 2024 17:26:27 +0200 Subject: Minor fixes --- docs/index.html | 211 ++++++++++++++++++++++++++++---------------------------- src/uniquekey.h | 2 +- 2 files changed, 106 insertions(+), 107 deletions(-) (limited to 'src') diff --git a/docs/index.html b/docs/index.html index d24d3d7..3e535a6 100644 --- a/docs/index.html +++ b/docs/index.html @@ -64,13 +64,13 @@


- Copyright © 2007-23 Asko Kauppi, Benoit Germain. All rights reserved. + Copyright © 2007-24 Asko Kauppi, Benoit Germain. All rights reserved.
Lua Lanes is published under the same MIT license as Lua 5.1, 5.2, 5.3 and 5.4.

- This document was revised on 29-Mar-24, and applies to version 4.0.0. + This document was revised on 9-Apr-24, and applies to version 4.0.0.

@@ -178,14 +178,14 @@
-
	extern void LANES_API luaopen_lanes_embedded( lua_State* L, lua_CFunction _luaopen_lanes);
+
	extern void LANES_API luaopen_lanes_embedded(lua_State* L, lua_CFunction _luaopen_lanes);

luaopen_lanes_embedded leaves the module table on the stack. lanes.configure() must still be called in order to use Lanes.
- If _luaopen_lanes is NULL, a default loader will simply attempt the equivalent of luaL_dofile( L, "lanes.lua"). + If _luaopen_lanes is NULL, a default loader will simply attempt the equivalent of luaL_dofile(L, "lanes.lua").

@@ -198,30 +198,30 @@

	#include "lanes.h"

-
	int load_lanes_lua( lua_State* L)
+
	int load_lanes_lua(lua_State* L)
	{
		// retrieve lanes.lua from wherever it is stored and return the result of its execution
		// trivial example 1:
-
		luaL_dofile( L, "lanes.lua");
+
		luaL_dofile(L, "lanes.lua");

		// trivial example 2:
-
		luaL_dostring( L, bin2c_lanes_lua);
+
		luaL_dostring(L, bin2c_lanes_lua);
	}

-
	void embed_lanes( lua_State* L)
+
	void embed_lanes(lua_State* L)
	{
		// we need base libraries for Lanes for work
-
		luaL_openlibs( L);
+
		luaL_openlibs(L);
		...
-
		// will attempt luaL_dofile( L, "lanes.lua");
-
		luaopen_lanes_embedded( L, NULL);
-
		lua_pop( L, 1);
+
		// will attempt luaL_dofile(L, "lanes.lua");
+
		luaopen_lanes_embedded(L, nullptr);
+
		lua_pop(L, 1);
		// another example with a custom loader
-
		luaopen_lanes_embedded( L, load_lanes_lua);
-
		lua_pop( L, 1);
+
		luaopen_lanes_embedded(L, load_lanes_lua);
+
		lua_pop(L, 1);

		// a little test to make sure things work as expected
-
		luaL_dostring( L, "local lanes = require 'lanes'.configure{with_timers = false}; local l = lanes.linda()");
+
		luaL_dostring(L, "local lanes = require 'lanes'.configure{with_timers = false}; local l = lanes.linda()");
	}
@@ -266,7 +266,7 @@
-
	lanes.configure( [opt_tbl])
+
	lanes.configure([opt_tbl])
@@ -454,7 +454,7 @@
	local m = lanes.require "modname"
-
	lanes.register( "modname", module)
+
	lanes.register("modname", module)
@@ -471,11 +471,11 @@
	local lanes = require "lanes".configure()

-
	f = lanes.gen( function( n) return 2 * n end)
-
	a = f( 1)
-
	b = f( 2)
+
	f = lanes.gen(function(n) return 2 * n end)
+
	a = f(1)
+
	b = f(2)

-
	print( a[1], b[1] )     -- 2    4
+
	print(a[1], b[1])     -- 2    4
@@ -483,8 +483,8 @@
-
	func = lanes.gen( [libs_str | opt_tbl [, ...],] lane_func)
-
	lane_h = func( ...)
+
	func = lanes.gen([libs_str | opt_tbl [, ...],] lane_func)
+
	lane_h = func(...)
@@ -751,7 +751,7 @@
-
	"type", "name" = lanes.nameof( o)
+
	"type", "name" = lanes.nameof(o)
@@ -765,7 +765,7 @@
-
	lanes.gen( function( params) ... end ) ( ...)
+
	lanes.gen(function(params) ... end ) (...)
@@ -781,7 +781,7 @@
-
	lanes.set_thread_priority( prio)
+
	lanes.set_thread_priority(prio)
@@ -799,7 +799,7 @@
-
	lanes.set_thread_affinity( affinity)
+
	lanes.set_thread_affinity(affinity)
@@ -901,7 +901,7 @@
-
	{{name = "name", status = "status", ...}|nil = lanes.threads()
+
	{name = "name", status = "status", ...}|nil = lanes.threads()
@@ -918,7 +918,7 @@

Results and errors

-	set_error_reporting( "basic"|"extended")
+	set_error_reporting("basic"|"extended")
 

@@ -940,7 +940,7 @@

-	[...]|[nil,err,stack_tbl]= lane_h:join( [timeout_secs] )
+	[...]|[nil,err,stack_tbl]= lane_h:join([timeout_secs])
 

@@ -965,14 +965,14 @@
 	require "lanes".configure()
 
-	f = lanes.gen( function() error "!!!" end)
-	a = f( 1)
+	f = lanes.gen(function() error "!!!" end)
+	a = f(1)
 
-	--print( a[1])   -- propagates error
+	--print(a[1])   -- propagates error
 
 	v, err = a:join()   -- no propagation
 	if v == nil then
-		error( "'a' faced error"..tostring(err))   -- manual propagation
+		error("'a' faced error"..tostring(err))   -- manual propagation
 	end
 
@@ -984,12 +984,12 @@ require "lanes".configure() local sync_linda = lanes.linda() - f = lanes.gen( function() dostuff() sync_linda:send( "done", true) end) + f = lanes.gen(function() dostuff() sync_linda:send("done", true) end) a = f() b = f() c = f() - sync_linda:receive( nil, sync_linda.batched, "done", 3) -- wait for 3 lanes to write something in "done" slot of sync_linda + sync_linda:receive(nil, sync_linda.batched, "done", 3) -- wait for 3 lanes to write something in "done" slot of sync_linda @@ -997,9 +997,9 @@

Cancelling

-	bool[,reason] = lane_h:cancel( "soft" [, timeout] [, wake_lane])
-	bool[,reason] = lane_h:cancel( "hard" [, timeout] [, wake_lane])
-	bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, wake_lane])
+	bool[,reason] = lane_h:cancel("soft" [, timeout] [, wake_lane])
+	bool[,reason] = lane_h:cancel("hard" [, timeout] [, wake_lane])
+	bool[,reason] = lane_h:cancel([mode, hookcount] [, timeout] [, wake_lane])
 

@@ -1041,9 +1041,9 @@

Finalizers

-	set_finalizer( finalizer_func)
+	set_finalizer(finalizer_func)
 
-	void = finalizer_func( [err, stack_tbl])
+	void = finalizer_func([err, stack_tbl])
 

@@ -1063,16 +1063,16 @@
 	local lane_body = function()
-		set_finalizer( function( err, stk)
-			if err and type( err) ~= "userdata" then
+		set_finalizer(function(err, stk)
+			if err and type(err) ~= "userdata" then
 				-- no special error: true error
-				print( " error: "..tostring(err))
-			elseif type( err) == "userdata" then
+				print(" error: "..tostring(err))
+			elseif type(err) == "userdata" then
 				-- lane cancellation is performed by throwing a special userdata as error
-				print( "after cancel")
+				print("after cancel")
 			else
 				-- no error: we just got finalized
-				print( "finalized")
+				print("finalized")
 			end
 		end)
 	end
@@ -1098,22 +1098,22 @@
 
 	local linda = lanes.linda()
 
-	local function loop( max)
+	local function loop(max)
 		for i = 1, max do
-			print( "sending: " .. i)
-			linda:send( "x", i)    -- linda as upvalue
+			print("sending: " .. i)
+			linda:send("x", i)    -- linda as upvalue
 		end
 	end
 
-	a = lanes.gen( "", loop)( 10000)
+	a = lanes.gen("", loop)(10000)
 
 	while true do
-		local key, val = linda:receive( 3.0, "x")    -- timeout in seconds
+		local key, val = linda:receive(3.0, "x")    -- timeout in seconds
 		if val == nil then
-			print( "timed out")
+			print("timed out")
 			break
 		end
-		print( tostring( linda) .. " received: " .. val)
+		print(tostring(linda) .. " received: " .. val)
 	end
 
@@ -1128,23 +1128,23 @@

  • two producer-side methods: :send and :set (not out).
  • send allows for sending multiple values -atomically- to a given key.
  • receive can wait for multiple keys at once.
  • -
  • receive has a batched mode to consume more than one value from a single key, as in linda:receive( 1.0, linda.batched, "key", 3, 6).
  • +
  • receive has a batched mode to consume more than one value from a single key, as in linda:receive(1.0, linda.batched, "key", 3, 6).
  • individual keys' queue length can be limited, balancing speed differences in a producer/consumer scenario (making :send wait).
  • -
  • tostring( linda) returns a string of the form "Linda: <opt_name>"
  • +
  • tostring(linda) returns a string of the form "Linda: <opt_name>"
  • several lindas may share the same keeper state. State assignation can be controlled with the linda's group (an integer). All lindas belonging to the same group will share the same keeper state. One keeper state may be shared by several groups.
  • -	h = lanes.linda( [opt_name, [opt_group]])
    +	h = lanes.linda([opt_name, [opt_group]])
     
    -	[true|lanes.cancel_error] = h:send( [timeout_secs,] [h.null,] key, ...)
    +	[true|lanes.cancel_error] = h:send([timeout_secs,] [h.null,] key, ...)
     
    -	[key, val]|[lanes.cancel_error] = h:receive( [timeout_secs,] key [, ...])
    +	[key, val]|[lanes.cancel_error] = h:receive([timeout_secs,] key [, ...])
     
    -	[key, val [, ...]]|[lanes.cancel_error] = h:receive( timeout, h.batched, key, n_uint_min[, n_uint_max])
    +	[key, val [, ...]]|[lanes.cancel_error] = h:receive(timeout, h.batched, key, n_uint_min[, n_uint_max])
     
    -	[true|lanes.cancel_error] = h:limit( key, n_uint)
    +	[true|lanes.cancel_error] = h:limit(key, n_uint)
     

    @@ -1193,9 +1193,9 @@

    -	bool|lanes.cancel_error = linda_h:set( key [, val [, ...]])
    +	bool|lanes.cancel_error = linda_h:set(key [, val [, ...]])
     
    -	[[val [, ...]]|lanes.cancel_error] = linda_h:get( key [, count = 1])
    +	[[val [, ...]]|lanes.cancel_error] = linda_h:get(key [, count = 1])
     

    @@ -1225,7 +1225,7 @@

    -	[val] = linda_h:count( [key[,...]])
    +	[val] = linda_h:count([key[,...]])
     

    @@ -1315,7 +1315,7 @@ events to a common Linda, but... :).

    Timers

    -	void = lanes.timer( linda_h, key, date_tbl|first_secs [,period_secs])
    +	void = lanes.timer(linda_h, key, date_tbl|first_secs [,period_secs])
     

    @@ -1342,18 +1342,18 @@ events to a common Linda, but... :). -- First timer once a second, not synchronized to wall clock -- - lanes.timer( linda, "sec", 1, 1) + lanes.timer(linda, "sec", 1, 1) -- Timer to a future event (next even minute); wall clock synchronized -- - local t = os.date( "*t", os.time() + 60) -- now + 1min + local t = os.date("*t", os.time() + 60) -- now + 1min t.sec = 0 - lanes.timer( linda, "min", t, 60) -- reoccur every minute (sharp) + lanes.timer(linda, "min", t, 60) -- reoccur every minute (sharp) while true do - local key, v = linda:receive( "sec", "min") - print( "Timer "..key..": "..v) + local key, v = linda:receive("sec", "min") + print("Timer "..key..": "..v) end @@ -1367,7 +1367,7 @@ events to a common Linda, but... :). Having the API as lanes.timer() is intentional. Another alternative would be linda_h:timer() but timers are not traditionally seen to be part of Lindas. Also, it would mean any lane getting a Linda handle would be able to modify timers on it. - A third choice could be abstracting the timers out of Linda realm altogether (timer_h= lanes.timer( date|first_secs, period_secs )) but that would mean separate waiting functions for timers, and lindas. + A third choice could be abstracting the timers out of Linda realm altogether (timer_h= lanes.timer(date|first_secs, period_secs )) but that would mean separate waiting functions for timers, and lindas. Even if a linda object and key was returned, that key couldn't be waited upon simultaneously with one's general linda events. The current system gives maximum capabilities with minimum API, and any smoothenings can easily be crafted in Lua at the application level. @@ -1386,7 +1386,7 @@ events to a common Linda, but... :).

    -	void = lanes.sleep( [seconds|false])
    +	void = lanes.sleep([seconds|false])
     

    @@ -1411,11 +1411,11 @@ events to a common Linda, but... :).

    -	lock_func|lanes.cancel_error = lanes.genlock( linda_h, key [,N_uint=1])
    +	lock_func|lanes.cancel_error = lanes.genlock(linda_h, key [,N_uint=1])
     
    -	bool|lanes.cancel_error = lock_func( M_uint [, "try"] )     -- acquire
    +	bool|lanes.cancel_error = lock_func(M_uint [, "try"] )     -- acquire
     	..
    -	bool|lanes.cancel_error = lock_func( -M_uint)    -- release
    +	bool|lanes.cancel_error = lock_func(-M_uint)    -- release
     

    @@ -1437,9 +1437,9 @@ events to a common Linda, but... :).

    -	atomic_func|lanes.cancel_error = lanes.genatomic( linda_h, key [,initial_num=0.0])
    +	atomic_func|lanes.cancel_error = lanes.genatomic(linda_h, key [,initial_num=0.0])
     
    -	new_num|lanes.cancel_error = atomic_func( [diff_num=+1.0])
    +	new_num|lanes.cancel_error = atomic_func([diff_num=+1.0])
     

    @@ -1505,14 +1505,14 @@ events to a common Linda, but... :).
     	// expects a C function on top of the source Lua stack
    -	copy_func( lua_State *dest, lua_State* source)
    +	copy_func(lua_State *dest, lua_State* source)
     	{
     		// extract C function pointer from source
    -		lua_CFunction func = lua_tocfunction( source, -1);
    +		lua_CFunction func = lua_tocfunction(source, -1);
     		// transfer upvalues
    -		int nup = transfer_upvalues( dest, source);
    +		int nup = transfer_upvalues(dest, source);
     		// dest Lua stack contains a copy of all upvalues
    -		lua_pushcfunction( dest, func, nup);
    +		lua_pushcfunction(dest, func, nup);
     	}
     
    @@ -1524,12 +1524,12 @@ events to a common Linda, but... :).
     	// expects a C function on top of the source Lua stack
    -	copy_func( lua_State *dest, lua_State* source)
    +	copy_func(lua_State *dest, lua_State* source)
     	{
     		// fetch function 'name' from source lookup database
    -		char const* funcname = lookup_func_name( source, -1);
    +		char const* funcname = lookup_func_name(source, -1);
     		// lookup a function bound to this name in the destination state, and push it on the stack
    -		push_resolved_func( dest, funcname);
    +		push_resolved_func(dest, funcname);
     	}
     
    @@ -1590,7 +1590,7 @@ events to a common Linda, but... :).

    -	int luaopen_module( lua_State *L )
    +	int luaopen_module(lua_State *L )
     	{
     		static char been_here;  /* 0 by ANSI C */
     
    @@ -1607,26 +1607,26 @@ events to a common Linda, but... :).
     

    Clonable full userdata in your own apps

    An alternative way of passing full userdata across lanes uses a new __lanesclone metamethod. - When a deep userdata is cloned, Lanes calls __lanesclone once, in the context of the source lane.
    - The call receives the clone and original as light userdata, plus the actual userdata size, as in clone:__lanesclone(original,size), and should perform the actual cloning.
    + When a deep userdata is cloned, Lanes calls __lanesclone once, in the context of the source lane.
    + The call receives the clone and original as light userdata, plus the actual userdata size, as in clone:__lanesclone(original,size), and should perform the actual cloning.
    A typical implementation would look like: @@ -364,7 +376,7 @@
    -static int clonable_lanesclone( lua_State* L)
    +static int clonable_lanesclone(lua_State* L)
     {
    -	switch( lua_gettop( L))
    +	switch(lua_gettop(L))
     	{
     		case 3:
     		{
    -			struct s_MyClonableUserdata* self = lua_touserdata( L, 1);
    -			struct s_MyClonableUserdata* from = lua_touserdata( L, 2);
    -			size_t len = lua_tointeger( L, 3);
    -			assert( len == sizeof(struct s_MyClonableUserdata));
    +			struct s_MyClonableUserdata* self = lua_touserdata(L, 1);
    +			struct s_MyClonableUserdata* from = lua_touserdata(L, 2);
    +			size_t len = lua_tointeger(L, 3);
    +			assert(len == sizeof(struct s_MyClonableUserdata));
     			*self = *from;
     		}
     		return 0;
     
     		default:
    -		(void) luaL_error( L, "Lanes called clonable_lanesclone with unexpected parameters");
    +		std::ignore = luaL_error(L, "Lanes called clonable_lanesclone with unexpected parameters");
     	}
     	return 0;
     }
    @@ -1639,20 +1639,20 @@ static int clonable_lanesclone( lua_State* L)
     
     
    +		
    +			
    +			
    +			
    +		
    +
     		
     int luaopen_deep_test(lua_State* L)
     {
    -	luaL_newlib( L, deep_module);
    +	luaL_newlib(L, deep_module);
     
     	// preregister the metatables for the types we can instanciate so that Lanes can know about them
    -	if( luaL_newmetatable( L, "clonable"))
    +	if (luaL_newmetatable(L, "clonable"))
     	{
    -		luaL_setfuncs( L, clonable_mt, 0);
    +		luaL_setfuncs(L, clonable_mt, 0);
     		lua_pushvalue(L, -1);
     		lua_setfield(L, -2, "__index");
     	}
     	lua_setfield(L, -2, "__clonableMT"); // actual name is not important
     
    -	if( luaL_newmetatable( L, "deep"))
    +	if (luaL_newmetatable(L, "deep"))
     	{
    -		luaL_setfuncs( L, deep_mt, 0);
    +		luaL_setfuncs(L, deep_mt, 0);
     		lua_pushvalue(L, -1);
     		lua_setfield(L, -2, "__index");
     	}
    @@ -1666,10 +1666,10 @@ int luaopen_deep_test(lua_State* L)
     

    Then a new clonable userdata instance can just do like any non-Lanes aware userdata, as long as its metatable contains the aforementionned __lanesclone method.
    -int luaD_new_clonable( lua_State* L)
    +int luaD_new_clonable(lua_State* L)
     {
    -	lua_newuserdata( L, sizeof( struct s_MyClonableUserdata));
    -	luaL_setmetatable( L, "clonable");
    +	lua_newuserdata(L, sizeof(struct s_MyClonableUserdata));
    +	luaL_setmetatable(L, "clonable");
     	return 1;
     }
     
    @@ -1684,7 +1684,7 @@ int luaD_new_clonable( lua_State* L)

    1. Provide an identity function for your userdata, in C. This function is used for creation and deletion of your deep userdata (the shared resource), and for making metatables for the state-specific proxies for accessing it. The prototype is -
      	void* idfunc( lua_State* L, DeepOp op_);
      +
      	void* idfunc(lua_State* L, DeepOp op_);
      op_ can be one of:
      • DeepOp::New: requests the creation of a new object, whose pointer is returned. Said object must derive from DeepPrelude.
      • @@ -1735,8 +1735,8 @@ int luaD_new_clonable( lua_State* L) In multithreaded scenarios, giving multiple parameters to print() or file:write() may cause them to be overlapped in the output, something like this:
        -	A:  print( 1, 2, 3, 4 )
        -	B:  print( 'a', 'b', 'c', 'd' )
        +	A:  print(1, 2, 3, 4 )
        +	B:  print('a', 'b', 'c', 'd' )
         
         	1   a   b   2   3   c   d   4
         
        @@ -1803,4 +1803,3 @@ int luaD_new_clonable( lua_State* L) -
    \ No newline at end of file diff --git a/src/uniquekey.h b/src/uniquekey.h index e592f0a..a89ecd3 100644 --- a/src/uniquekey.h +++ b/src/uniquekey.h @@ -13,7 +13,7 @@ class UniqueKey public: - constexpr UniqueKey(uint64_t val_) + constexpr explicit UniqueKey(uint64_t val_) #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 : m_storage{ static_cast(val_ & 0x7fffffffffffull) } #else // LUAJIT_FLAVOR() -- cgit v1.2.3-55-g6feb From 8f75e0ff26645ced3669f600aa5e9133bb7b49af Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Wed, 10 Apr 2024 09:01:42 +0200 Subject: C++ migration: fixed a few std::ignore --- src/cancel.cpp | 13 +-- src/compat.cpp | 9 +- src/deep.cpp | 15 ++-- src/keeper.cpp | 6 +- src/lanes.cpp | 14 +-- src/lanes_private.h | 2 +- src/tools.cpp | 251 ++++++++++++++++++++++++++-------------------------- 7 files changed, 154 insertions(+), 156 deletions(-) (limited to 'src') diff --git a/src/cancel.cpp b/src/cancel.cpp index 2f3c22e..3a01ac2 100644 --- a/src/cancel.cpp +++ b/src/cancel.cpp @@ -213,7 +213,7 @@ CancelOp which_cancel_op(char const* op_string_) lua_remove(L, idx_); // argument is processed, remove it if (op == CancelOp::Invalid) { - std::ignore = luaL_error(L, "invalid hook option %s", str); + std::ignore = luaL_error(L, "invalid hook option %s", str); // doesn't return } return op; } @@ -260,6 +260,7 @@ LUAG_FUNC(thread_cancel) wake_lane = lua_toboolean(L, 2); lua_remove(L, 2); // argument is processed, remove it } + STACK_CHECK_START_REL(L, 0); switch (thread_cancel(lane, op, hook_count, wait_timeout, wake_lane)) { default: // should never happen unless we added a case and forgot to handle it @@ -267,15 +268,15 @@ LUAG_FUNC(thread_cancel) break; case CancelResult::Timeout: - lua_pushboolean(L, 0); - lua_pushstring(L, "timeout"); + lua_pushboolean(L, 0); // false + lua_pushstring(L, "timeout"); // false "timeout" break; case CancelResult::Cancelled: - lua_pushboolean(L, 1); - std::ignore = push_thread_status(L, lane); + lua_pushboolean(L, 1); // true + push_thread_status(L, lane); // true status break; } - // should never happen, only here to prevent the compiler from complaining of "not all control paths returning a value" + STACK_CHECK(L, 2); return 2; } diff --git a/src/compat.cpp b/src/compat.cpp index 9807390..73d0f6b 100644 --- a/src/compat.cpp +++ b/src/compat.cpp @@ -15,7 +15,7 @@ // ################################################################################################ // ################################################################################################ -[[nodiscard]] static int luaL_getsubtable(lua_State* L, int idx, const char* fname) +static int luaL_getsubtable(lua_State* L, int idx, const char* fname) { lua_getfield(L, idx, fname); if (lua_istable(L, -1)) @@ -38,7 +38,7 @@ void luaL_requiref(lua_State *L, const char *modname, lua_CFunction openf, int g lua_pushcfunction(L, openf); lua_pushstring(L, modname); /* argument to open function */ lua_call(L, 1, 1); /* open module */ - std::ignore = luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); lua_pushvalue(L, -2); /* make copy of module (call result) */ lua_setfield(L, -2, modname); /* _LOADED[modname] = module */ lua_pop(L, 1); /* remove _LOADED table */ @@ -93,7 +93,8 @@ int lua_getiuservalue(lua_State* L, int idx, int n) // ################################################################################################ -// pop stack top, sets it a uservalue #n of full userdata at idx +// 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 @@ -106,7 +107,7 @@ int lua_setiuservalue(lua_State* L, int idx, int n) return 0; } - std::ignore = lua_setuservalue(L, idx); + lua_setuservalue(L, idx); return 1; // I guess anything non-0 is ok } diff --git a/src/deep.cpp b/src/deep.cpp index a3806aa..418724e 100644 --- a/src/deep.cpp +++ b/src/deep.cpp @@ -470,11 +470,10 @@ bool copydeep(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode int const clone_i = lua_gettop( L2); while( nuv) { - std::ignore = 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 + { + 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 @@ -482,14 +481,14 @@ bool copydeep(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode } } - STACK_CHECK( L2, 1); - STACK_CHECK( L, 0); + STACK_CHECK(L2, 1); + STACK_CHECK(L, 0); if (errmsg != nullptr) { // raise the error in the proper state (not the keeper) lua_State* const errL{ (mode_ == LookupMode::FromKeeper) ? L2 : L }; - std::ignore = luaL_error(errL, errmsg); + std::ignore = luaL_error(errL, errmsg); // doesn't return } return true; } \ No newline at end of file diff --git a/src/keeper.cpp b/src/keeper.cpp index 19fbd06..37fcba5 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp @@ -656,7 +656,7 @@ void init_keepers(Universe* U, lua_State* L) lua_pop(L, 1); // if (nb_keepers < 1) { - std::ignore = luaL_error(L, "Bad number of keepers (%d)", nb_keepers); + std::ignore = luaL_error(L, "Bad number of keepers (%d)", nb_keepers); // doesn't return } STACK_CHECK(L, 0); @@ -671,7 +671,7 @@ void init_keepers(Universe* U, lua_State* L) U->keepers = static_cast(U->internal_allocator.alloc(bytes)); if (U->keepers == nullptr) { - std::ignore = luaL_error(L, "init_keepers() failed while creating keeper array; out of memory"); + std::ignore = luaL_error(L, "init_keepers() failed while creating keeper array; out of memory"); // doesn't return } U->keepers->Keepers::Keepers(); U->keepers->gc_threshold = keepers_gc_threshold; @@ -688,7 +688,7 @@ void init_keepers(Universe* U, lua_State* L) lua_State* const K{ create_state(U, L) }; if (K == nullptr) { - std::ignore = luaL_error(L, "init_keepers() failed while creating keeper states; out of memory"); + std::ignore = luaL_error(L, "init_keepers() failed while creating keeper states; out of memory"); // doesn't return } U->keepers->keeper_array[i].L = K; diff --git a/src/lanes.cpp b/src/lanes.cpp index 2a5ebfd..f549359 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -540,7 +540,7 @@ static void selfdestruct_add(Lane* lane_) if (lane != SELFDESTRUCT_END) { // this causes a leak because we don't call U's destructor (which could be bad if the still running lanes are accessing it) - std::ignore = luaL_error(L, "Zombie thread %s refuses to die!", lane->debug_name); + std::ignore = luaL_error(L, "Zombie thread %s refuses to die!", lane->debug_name); // doesn't return } } @@ -1374,13 +1374,12 @@ LUAG_FUNC(lane_new) // ################################################################################################# -int push_thread_status(lua_State* L, Lane* lane_) +void push_thread_status(lua_State* L, Lane* lane_) { char const* const str{ thread_status_string(lane_) }; ASSERT_L(str); - lua_pushstring(L, str); - return 1; + std::ignore = lua_pushstring(L, str); } // ################################################################################################# @@ -1583,11 +1582,12 @@ LUAG_FUNC(thread_index) } if (lua_type(L, KEY) == LUA_TSTRING) { - char const * const keystr = lua_tostring(L, KEY); + char const* const keystr{ lua_tostring(L, KEY) }; lua_settop(L, 2); // keep only our original arguments on the stack if (strcmp( keystr, "status") == 0) { - return push_thread_status(L, lane); // push the string representing the status + push_thread_status(L, lane); // push the string representing the status + return 1; } // return UD.metatable[key] lua_getmetatable(L, UD); // UD KEY mt @@ -1634,7 +1634,7 @@ LUAG_FUNC(threads) lua_newtable(L); // {} {} lua_pushstring(L, lane->debug_name); // {} {} "name" lua_setfield(L, -2, "name"); // {} {} - std::ignore = push_thread_status(L, lane); // {} {} "status" + push_thread_status(L, lane); // {} {} "status" lua_setfield(L, -2, "status"); // {} {} lua_rawseti(L, -2, ++index); // {} lane = lane->tracking_next; diff --git a/src/lanes_private.h b/src/lanes_private.h index ba40e44..18e55fd 100644 --- a/src/lanes_private.h +++ b/src/lanes_private.h @@ -103,4 +103,4 @@ static constexpr UniqueKey LANE_POINTER_REGKEY{ 0xB3022205633743BCull }; // used return *(static_cast(luaL_checkudata(L, i_, "Lane"))); } -[[nodiscard]] int push_thread_status(lua_State* L, Lane* s); +void push_thread_status(lua_State* L, Lane* lane_); diff --git a/src/tools.cpp b/src/tools.cpp index 4083a57..9207df6 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -1393,50 +1393,49 @@ static void copy_cached_func(Universe* U, Dest L2, int L2_cache_i, Source L, int [[nodiscard]] static bool push_cached_metatable(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode mode_, char const* upName_) { STACK_CHECK_START_REL(L, 0); - if( lua_getmetatable( L, i)) // ... mt + if (!lua_getmetatable(L, i)) // ... mt { - lua_Integer const mt_id = get_mt_id( U, L, -1); // Unique id for the metatable + STACK_CHECK( L, 0); + return false; + } + STACK_CHECK(L, 1); - STACK_CHECK_START_REL(L2, 0); - STACK_GROW( L2, 4); - // do we already know this metatable? - push_registry_subtable( L2, REG_MTID); // _R[REG_MTID] - lua_pushinteger( L2, mt_id); // _R[REG_MTID] id - lua_rawget( L2, -2); // _R[REG_MTID] mt? - - STACK_CHECK( L2, 2); - - if( lua_isnil( L2, -1)) - { // L2 did not know the metatable - lua_pop( L2, 1); // _R[REG_MTID] - if (inter_copy_one(U, L2, L2_cache_i, L, lua_gettop( L), VT::METATABLE, mode_, upName_)) // _R[REG_MTID] mt - { - STACK_CHECK( L2, 2); - // mt_id -> metatable - lua_pushinteger( L2, mt_id); // _R[REG_MTID] mt id - lua_pushvalue( L2, -2); // _R[REG_MTID] mt id mt - lua_rawset( L2, -4); // _R[REG_MTID] mt - - // metatable -> mt_id - lua_pushvalue( L2, -1); // _R[REG_MTID] mt mt - lua_pushinteger( L2, mt_id); // _R[REG_MTID] mt mt id - lua_rawset( L2, -4); // _R[REG_MTID] mt - } - else - { - (void) luaL_error( L, "Error copying a metatable"); - } - STACK_CHECK( L2, 2); + lua_Integer const mt_id{ get_mt_id(U, L, -1) }; // Unique id for the metatable + + STACK_CHECK_START_REL(L2, 0); + STACK_GROW(L2, 4); + // do we already know this metatable? + push_registry_subtable(L2, REG_MTID); // _R[REG_MTID] + lua_pushinteger(L2, mt_id); // _R[REG_MTID] id + lua_rawget(L2, -2); // _R[REG_MTID] mt|nil + STACK_CHECK(L2, 2); + + if (lua_isnil(L2, -1)) + { // L2 did not know the metatable + lua_pop(L2, 1); // _R[REG_MTID] + if (!inter_copy_one(U, L2, L2_cache_i, L, lua_gettop(L), VT::METATABLE, mode_, upName_)) // _R[REG_MTID] mt? + { + std::ignore = luaL_error(L, "Error copying a metatable"); // doesn't return } - lua_remove( L2, -2); // mt - lua_pop( L, 1); // ... - STACK_CHECK( L2, 1); - STACK_CHECK( L, 0); - return true; + STACK_CHECK(L2, 2); // _R[REG_MTID] mt + // mt_id -> metatable + lua_pushinteger(L2, mt_id); // _R[REG_MTID] mt id + lua_pushvalue(L2, -2); // _R[REG_MTID] mt id mt + lua_rawset(L2, -4); // _R[REG_MTID] mt + + // metatable -> mt_id + lua_pushvalue(L2, -1); // _R[REG_MTID] mt mt + lua_pushinteger(L2, mt_id); // _R[REG_MTID] mt mt id + lua_rawset(L2, -4); // _R[REG_MTID] mt + STACK_CHECK(L2, 2); } - STACK_CHECK( L, 0); - return false; + lua_remove(L2, -2); // mt + + lua_pop(L, 1); // ... + STACK_CHECK(L2, 1); + STACK_CHECK(L, 0); + return true; } // ################################################################################################# @@ -1599,11 +1598,10 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; // assign uservalues while( uvi > 0) { - std::ignore = 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 + { + std::ignore = luaL_error(L, "Cannot copy upvalue type '%s'", luaL_typename(L, -1)); // doesn't return + } lua_pop( L, 1); // ... mt __lanesclone [uv]* // this pops the value from the stack lua_setiuservalue( L2, -2, uvi); // ... u @@ -1644,39 +1642,39 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; // try clonable userdata first if( copyclone( U, L2, L2_cache_i, L, i, mode_, upName_)) { - STACK_CHECK( L, 0); - STACK_CHECK( L2, 1); + STACK_CHECK(L, 0); + STACK_CHECK(L2, 1); return true; } - STACK_CHECK( L, 0); - STACK_CHECK( L2, 0); + STACK_CHECK(L, 0); + STACK_CHECK(L2, 0); // Allow only deep userdata entities to be copied across - DEBUGSPEW_CODE( fprintf( stderr, "USERDATA\n")); - if( copydeep( U, L2, L2_cache_i, L, i, mode_, upName_)) + DEBUGSPEW_CODE(fprintf(stderr, "USERDATA\n")); + if (copydeep(U, L2, L2_cache_i, L, i, mode_, upName_)) { - STACK_CHECK( L, 0); - STACK_CHECK( L2, 1); + STACK_CHECK(L, 0); + STACK_CHECK(L2, 1); return true; } - STACK_CHECK( L, 0); - STACK_CHECK( L2, 0); + STACK_CHECK(L, 0); + STACK_CHECK(L2, 0); // Not a deep or clonable full userdata - if( U->demoteFullUserdata) // attempt demotion to light userdata + if (U->demoteFullUserdata) // attempt demotion to light userdata { - void* lud = lua_touserdata( L, i); - lua_pushlightuserdata( L2, lud); + void* lud = lua_touserdata(L, i); + lua_pushlightuserdata(L2, lud); } else // raise an error { - (void) luaL_error( L, "can't copy non-deep full userdata across lanes"); + std::ignore = luaL_error(L, "can't copy non-deep full userdata across lanes"); // doesn't return } - STACK_CHECK( L2, 1); - STACK_CHECK( L, 0); + STACK_CHECK(L2, 1); + STACK_CHECK(L, 0); return true; } @@ -1689,91 +1687,90 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; return false; } - STACK_CHECK_START_REL(L, 0); // L (source) // L2 (destination) + STACK_CHECK_START_REL(L, 0); // L (source) // L2 (destination) STACK_CHECK_START_REL(L2, 0); - DEBUGSPEW_CODE( fprintf( stderr, "FUNCTION %s\n", upName_)); + DEBUGSPEW_CODE(fprintf(stderr, "FUNCTION %s\n", upName_)); - if( lua_tocfunction( L, source_i_) == userdata_clone_sentinel) // we are actually copying a clonable full userdata from a keeper + if (lua_tocfunction(L, source_i_) == userdata_clone_sentinel) // we are actually copying a clonable full userdata from a keeper { // clone the full userdata again // let's see if we already restored this userdata - lua_getupvalue( L, source_i_, 2); // ... u - void* source = lua_touserdata( L, -1); - lua_pushlightuserdata( L2, source); // ... source - lua_rawget( L2, L2_cache_i); // ... u? - if( !lua_isnil( L2, -1)) + lua_getupvalue(L, source_i_, 2); // ... u + void* source = lua_touserdata(L, -1); + lua_pushlightuserdata(L2, source); // ... source + lua_rawget(L2, L2_cache_i); // ... u? + if (!lua_isnil(L2, -1)) { - lua_pop( L, 1); // ... - STACK_CHECK( L, 0); - STACK_CHECK( L2, 1); + lua_pop(L, 1); // ... + STACK_CHECK(L, 0); + STACK_CHECK(L2, 1); return true; } - lua_pop( L2, 1); // ... + lua_pop(L2, 1); // ... // this function has 2 upvalues: the fqn of its metatable, and the userdata itself - std::ignore = lookup_table( L2, L, source_i_, mode_, upName_); // ... mt + std::ignore = lookup_table(L2, L, source_i_, mode_, upName_); // ... mt // originally 'source_i_' slot was the proxy closure, but from now on it indexes the actual userdata we extracted from it - source_i_ = lua_gettop( L); - source = lua_touserdata( L, -1); + source_i_ = lua_gettop(L); + source = lua_touserdata(L, -1); void* clone{ nullptr }; // get the number of bytes to allocate for the clone - size_t const userdata_size { lua_rawlen(L, -1) }; + size_t const userdata_size{ lua_rawlen(L, -1) }; { // extract uservalues (don't transfer them yet) int uvi = 0; - while( lua_getiuservalue( L, source_i_, ++ uvi) != LUA_TNONE) {} // ... u uv + while (lua_getiuservalue(L, source_i_, ++uvi) != LUA_TNONE) {} // ... u uv // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now - lua_pop( L, 1); // ... u [uv]* - -- uvi; - STACK_CHECK( L, uvi + 1); + lua_pop(L, 1); // ... u [uv]* + --uvi; + STACK_CHECK(L, uvi + 1); // create the clone userdata with the required number of uservalue slots - clone = lua_newuserdatauv( L2, userdata_size, uvi); // ... mt u + clone = lua_newuserdatauv(L2, userdata_size, uvi); // ... mt u // add it in the cache - lua_pushlightuserdata( L2, source); // ... mt u source - lua_pushvalue( L2, -2); // ... mt u source u - lua_rawset( L2, L2_cache_i); // ... mt u + lua_pushlightuserdata(L2, source); // ... mt u source + lua_pushvalue(L2, -2); // ... mt u source u + lua_rawset(L2, L2_cache_i); // ... mt u // set metatable - lua_pushvalue( L2, -2); // ... mt u mt - lua_setmetatable( L2, -2); // ... mt u + lua_pushvalue(L2, -2); // ... mt u mt + lua_setmetatable(L2, -2); // ... mt u // transfer and assign uservalues - while( uvi > 0) + while (uvi > 0) { - std::ignore = inter_copy_one(U - , L2, L2_cache_i - , L, lua_absindex(L, -1) - , vt_, mode_, upName_ - ); // ... mt u uv - lua_pop( L, 1); // ... u [uv]* + if (!inter_copy_one(U, L2, L2_cache_i, L, lua_absindex(L, -1), vt_, mode_, upName_)) // ... mt u uv + { + std::ignore = luaL_error(L, "Cannot copy upvalue type '%s'", luaL_typename(L, -1)); // doesn't return + } + lua_pop(L, 1); // ... u [uv]* // this pops the value from the stack - lua_setiuservalue( L2, -2, uvi); // ... mt u + lua_setiuservalue(L2, -2, uvi); // ... mt u -- uvi; } // when we are done, all uservalues are popped from the stack, we can pop the source as well - lua_pop( L, 1); // ... - STACK_CHECK( L, 0); - STACK_CHECK( L2, 2); // ... mt u + lua_pop(L, 1); // ... + STACK_CHECK(L, 0); + STACK_CHECK(L2, 2); // ... mt u } // perform the custom cloning part - lua_insert( L2, -2); // ... u mt + lua_insert(L2, -2); // ... u mt // __lanesclone should always exist because we wouldn't be restoring data from a userdata_clone_sentinel closure to begin with - lua_getfield(L2, -1, "__lanesclone"); // ... u mt __lanesclone - lua_remove( L2, -2); // ... u __lanesclone - lua_pushlightuserdata( L2, clone); // ... u __lanesclone clone - lua_pushlightuserdata( L2, source); // ... u __lanesclone clone source - lua_pushinteger( L2, userdata_size); // ... u __lanesclone clone source size + lua_getfield(L2, -1, "__lanesclone"); // ... u mt __lanesclone + lua_remove(L2, -2); // ... u __lanesclone + lua_pushlightuserdata(L2, clone); // ... u __lanesclone clone + lua_pushlightuserdata(L2, source); // ... u __lanesclone clone source + lua_pushinteger(L2, userdata_size); // ... u __lanesclone clone source size // clone:__lanesclone(dest, source, size) - lua_call( L2, 3, 0); // ... u + lua_call(L2, 3, 0); // ... u } else // regular function { DEBUGSPEW_CODE(fprintf( stderr, "FUNCTION %s\n", upName_)); DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); - copy_cached_func( U, L2, L2_cache_i, L, source_i_, mode_, upName_); // ... f + copy_cached_func(U, L2, L2_cache_i, L, source_i_, mode_, upName_); // ... f DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); } - STACK_CHECK( L2, 1); - STACK_CHECK( L, 0); + STACK_CHECK(L2, 1); + STACK_CHECK(L, 0); return true; } @@ -1788,15 +1785,15 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; STACK_CHECK_START_REL(L, 0); STACK_CHECK_START_REL(L2, 0); - DEBUGSPEW_CODE( fprintf( stderr, "TABLE %s\n", upName_)); + DEBUGSPEW_CODE(fprintf(stderr, "TABLE %s\n", upName_)); /* - * First, let's try to see if this table is special (aka is it some table that we registered in our lookup databases during module registration?) - * Note that this table CAN be a module table, but we just didn't register it, in which case we'll send it through the table cloning mechanism - */ - if( lookup_table( L2, L, i, mode_, upName_)) + * First, let's try to see if this table is special (aka is it some table that we registered in our lookup databases during module registration?) + * Note that this table CAN be a module table, but we just didn't register it, in which case we'll send it through the table cloning mechanism + */ + if (lookup_table(L2, L, i, mode_, upName_)) { - ASSERT_L( lua_istable( L2, -1) || (lua_tocfunction( L2, -1) == table_lookup_sentinel)); // from lookup datables // can also be table_lookup_sentinel if this is a table we know + ASSERT_L(lua_istable(L2, -1) || (lua_tocfunction(L2, -1) == table_lookup_sentinel)); // from lookup data. can also be table_lookup_sentinel if this is a table we know return true; } @@ -1809,33 +1806,33 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; * Note: Even metatables need to go through this test; to detect * loops such as those in required module tables (getmetatable(lanes).lanes == lanes) */ - if( push_cached_table( L2, L2_cache_i, L, i)) + if (push_cached_table(L2, L2_cache_i, L, i)) { - ASSERT_L( lua_istable( L2, -1)); // from cache + ASSERT_L(lua_istable(L2, -1)); // from cache return true; } - ASSERT_L( lua_istable( L2, -1)); + ASSERT_L(lua_istable(L2, -1)); - STACK_GROW( L, 2); - STACK_GROW( L2, 2); + STACK_GROW(L, 2); + STACK_GROW(L2, 2); - lua_pushnil( L); // start iteration - while( lua_next( L, i)) + lua_pushnil(L); // start iteration + while (lua_next(L, i)) { // need a function to prevent overflowing the stack with verboseErrors-induced alloca() inter_copy_keyvaluepair(U, L2, L2_cache_i, L, vt_, mode_, upName_); - lua_pop( L, 1); // pop value (next round) + lua_pop(L, 1); // pop value (next round) } - STACK_CHECK( L, 0); - STACK_CHECK( L2, 1); + STACK_CHECK(L, 0); + STACK_CHECK(L2, 1); // Metatables are expected to be immutable, and copied only once. - if( push_cached_metatable( U, L2, L2_cache_i, L, i, mode_, upName_)) // ... t mt? + if (push_cached_metatable(U, L2, L2_cache_i, L, i, mode_, upName_)) // ... t mt? { - lua_setmetatable( L2, -2); // ... t + lua_setmetatable(L2, -2); // ... t } - STACK_CHECK( L2, 1); - STACK_CHECK( L, 0); + STACK_CHECK(L2, 1); + STACK_CHECK(L, 0); return true; } -- cgit v1.2.3-55-g6feb From 408f8a5bf7934e7a5aa113fd3a55899db70dd73a Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Wed, 10 Apr 2024 09:48:59 +0200 Subject: C++ migration: luaG_inter_copy_* now return an enum class instead of an anonymous int --- src/cancel.cpp | 2 +- src/deep.cpp | 2 +- src/keeper.cpp | 12 ++++++------ src/lanes.cpp | 36 ++++++++++++++++++------------------ src/linda.cpp | 2 +- src/macros_and_utils.h | 8 ++++---- src/state.cpp | 34 ++++++++++++++++++++++++---------- src/tools.cpp | 38 ++++++++++++++++++++++++-------------- src/tools.h | 23 ++++++++++++++--------- 9 files changed, 93 insertions(+), 64 deletions(-) (limited to 'src') diff --git a/src/cancel.cpp b/src/cancel.cpp index 3a01ac2..b3e52b6 100644 --- a/src/cancel.cpp +++ b/src/cancel.cpp @@ -213,7 +213,7 @@ CancelOp which_cancel_op(char const* op_string_) lua_remove(L, idx_); // argument is processed, remove it if (op == CancelOp::Invalid) { - std::ignore = luaL_error(L, "invalid hook option %s", str); // doesn't return + luaL_error(L, "invalid hook option %s", str); // doesn't return } return op; } diff --git a/src/deep.cpp b/src/deep.cpp index 418724e..d0b8123 100644 --- a/src/deep.cpp +++ b/src/deep.cpp @@ -488,7 +488,7 @@ bool copydeep(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode { // raise the error in the proper state (not the keeper) lua_State* const errL{ (mode_ == LookupMode::FromKeeper) ? L2 : L }; - std::ignore = luaL_error(errL, errmsg); // doesn't return + luaL_error(errL, errmsg); // doesn't return } return true; } \ No newline at end of file diff --git a/src/keeper.cpp b/src/keeper.cpp index 37fcba5..f56c50c 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp @@ -656,7 +656,7 @@ void init_keepers(Universe* U, lua_State* L) lua_pop(L, 1); // if (nb_keepers < 1) { - std::ignore = luaL_error(L, "Bad number of keepers (%d)", nb_keepers); // doesn't return + luaL_error(L, "Bad number of keepers (%d)", nb_keepers); // doesn't return } STACK_CHECK(L, 0); @@ -671,7 +671,7 @@ void init_keepers(Universe* U, lua_State* L) U->keepers = static_cast(U->internal_allocator.alloc(bytes)); if (U->keepers == nullptr) { - std::ignore = luaL_error(L, "init_keepers() failed while creating keeper array; out of memory"); // doesn't return + luaL_error(L, "init_keepers() failed while creating keeper array; out of memory"); // doesn't return } U->keepers->Keepers::Keepers(); U->keepers->gc_threshold = keepers_gc_threshold; @@ -688,7 +688,7 @@ void init_keepers(Universe* U, lua_State* L) lua_State* const K{ create_state(U, L) }; if (K == nullptr) { - std::ignore = luaL_error(L, "init_keepers() failed while creating keeper states; out of memory"); // doesn't return + luaL_error(L, "init_keepers() failed while creating keeper states; out of memory"); // doesn't return } U->keepers->keeper_array[i].L = K; @@ -717,7 +717,7 @@ void init_keepers(Universe* U, lua_State* L) if (!lua_isnil(L, -1)) { // when copying with mode LookupMode::ToKeeper, error message is pushed at the top of the stack, not raised immediately - if (luaG_inter_copy_package(U, Source{ L }, Dest{ K }, -1, LookupMode::ToKeeper)) + if (luaG_inter_copy_package(U, Source{ L }, Dest{ K }, -1, LookupMode::ToKeeper) != InterCopyResult::Success) { // if something went wrong, the error message is at the top of the stack lua_remove(L, -2); // error_msg @@ -840,7 +840,7 @@ int keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, voi lua_pushlightuserdata(K, linda); - if ((args == 0) || luaG_inter_copy(U, Source{ L }, Dest{ K }, args, LookupMode::ToKeeper) == 0) // L->K + if ((args == 0) || luaG_inter_copy(U, Source{ L }, Dest{ K }, args, LookupMode::ToKeeper) == InterCopyResult::Success) // L->K { lua_call(K, 1 + args, LUA_MULTRET); retvals = lua_gettop(K) - Ktos; @@ -848,7 +848,7 @@ int keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, voi // 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, Source{ K }, Dest{ L }, retvals, LookupMode::FromKeeper) != 0) // K->L + if ((retvals > 0) && luaG_inter_move(U, Source{ K }, Dest{ L }, retvals, LookupMode::FromKeeper) != InterCopyResult::Success) // K->L { retvals = -1; } diff --git a/src/lanes.cpp b/src/lanes.cpp index f549359..462de0f 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -540,7 +540,7 @@ static void selfdestruct_add(Lane* lane_) if (lane != SELFDESTRUCT_END) { // this causes a leak because we don't call U's destructor (which could be bad if the still running lanes are accessing it) - std::ignore = luaL_error(L, "Zombie thread %s refuses to die!", lane->debug_name); // doesn't return + luaL_error(L, "Zombie thread %s refuses to die!", lane->debug_name); // doesn't return } } @@ -1134,7 +1134,8 @@ LUAG_FUNC(lane_new) { DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: update 'package'\n" INDENT_END)); // when copying with mode LookupMode::LaneBody, should raise an error in case of problem, not leave it one the stack - std::ignore = luaG_inter_copy_package(U, Source{ L }, Dest{ L2 }, package_idx, LookupMode::LaneBody); + [[maybe_unused]] InterCopyResult const ret{ luaG_inter_copy_package(U, Source{ L }, Dest{ L2 }, package_idx, LookupMode::LaneBody) }; + ASSERT_L(ret == InterCopyResult::Success); // either all went well, or we should not even get here } // modules to require in the target lane *before* the function is transfered! @@ -1146,7 +1147,7 @@ LUAG_FUNC(lane_new) // should not happen, was checked in lanes.lua before calling lane_new() if (lua_type(L, required_idx) != LUA_TTABLE) { - return luaL_error(L, "expected required module list as a table, got %s", luaL_typename(L, required_idx)); + luaL_error(L, "expected required module list as a table, got %s", luaL_typename(L, required_idx)); // doesn't return } lua_pushnil(L); // func libs priority globals package required gc_cb [... args ...] nil @@ -1154,7 +1155,7 @@ LUAG_FUNC(lane_new) { if (lua_type(L, -1) != LUA_TSTRING || lua_type(L, -2) != LUA_TNUMBER || lua_tonumber(L, -2) != nbRequired) { - return luaL_error(L, "required module list should be a list of strings"); + luaL_error(L, "required module list should be a list of strings"); // doesn't return } else { @@ -1168,7 +1169,7 @@ LUAG_FUNC(lane_new) if (lua_isnil( L2, -1)) { lua_pop( L2, 1); // - return luaL_error(L, "cannot pre-require modules without loading 'package' library first"); + luaL_error(L, "cannot pre-require modules without loading 'package' library first"); // doesn't return } else { @@ -1203,7 +1204,7 @@ LUAG_FUNC(lane_new) DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: transfer globals\n" INDENT_END)); if (!lua_istable(L, globals_idx)) { - return luaL_error(L, "Expected table, got %s", luaL_typename(L, globals_idx)); + luaL_error(L, "Expected table, got %s", luaL_typename(L, globals_idx)); // doesn't return } DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); @@ -1230,11 +1231,11 @@ LUAG_FUNC(lane_new) DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane body\n" INDENT_END)); DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); lua_pushvalue(L, 1); // func libs priority globals package required gc_cb [... args ...] func - int const res{ luaG_inter_move(U, Source{ L }, Dest{ L2 }, 1, LookupMode::LaneBody) }; // func libs priority globals package required gc_cb [... args ...] // func + InterCopyResult const res{ luaG_inter_move(U, Source{ L }, Dest{ L2 }, 1, LookupMode::LaneBody) }; // func libs priority globals package required gc_cb [... args ...] // func DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); - if (res != 0) + if (res != InterCopyResult::Success) { - return luaL_error(L, "tried to copy unsupported types"); + luaL_error(L, "tried to copy unsupported types"); // doesn't return } } else if (lua_type(L, 1) == LUA_TSTRING) @@ -1243,7 +1244,7 @@ LUAG_FUNC(lane_new) // compile the string if (luaL_loadstring(L2, lua_tostring(L, 1)) != 0) // func { - return luaL_error(L, "error when parsing lane function code"); + luaL_error(L, "error when parsing lane function code"); // doesn't return } } STACK_CHECK(L, 0); @@ -1253,14 +1254,13 @@ LUAG_FUNC(lane_new) // revive arguments if (nargs > 0) { - int res; DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane arguments\n" INDENT_END)); DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); - res = luaG_inter_move(U, Source{ L }, Dest{ L2 }, nargs, LookupMode::LaneBody); // func libs priority globals package required gc_cb // func [... args ...] + InterCopyResult const res{ luaG_inter_move(U, Source{ L }, Dest{ L2 }, nargs, LookupMode::LaneBody) }; // func libs priority globals package required gc_cb // func [... args ...] DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); - if (res != 0) + if (res != InterCopyResult::Success) { - return luaL_error(L, "tried to copy unsupported types"); + luaL_error(L, "tried to copy unsupported types"); // doesn't return } } STACK_CHECK(L, -nargs); @@ -1420,9 +1420,9 @@ LUAG_FUNC(thread_join) case Lane::Done: { int const n{ lua_gettop(L2) }; // whole L2 stack - if ((n > 0) && (luaG_inter_move(U, Source{ L2 }, Dest{ L }, n, LookupMode::LaneBody) != 0)) + if ((n > 0) && (luaG_inter_move(U, Source{ L2 }, Dest{ L }, n, LookupMode::LaneBody) != InterCopyResult::Success)) { - return luaL_error(L, "tried to copy unsupported types"); + luaL_error(L, "tried to copy unsupported types"); // doesn't return } ret = n; } @@ -1434,9 +1434,9 @@ 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, Source{ L2 }, Dest{ L }, n, LookupMode::LaneBody) != 0) // nil "err" [trace] + if (luaG_inter_move(U, Source{ L2 }, Dest{ L }, n, LookupMode::LaneBody) != InterCopyResult::Success) // nil "err" [trace] { - return luaL_error(L, "tried to copy unsupported types: %s", lua_tostring(L, -n)); + luaL_error(L, "tried to copy unsupported types: %s", lua_tostring(L, -n)); // doesn't return } ret = 1 + n; } diff --git a/src/linda.cpp b/src/linda.cpp index 50964ad..e749f52 100644 --- a/src/linda.cpp +++ b/src/linda.cpp @@ -162,7 +162,7 @@ static void check_key_types(lua_State* L, int start_, int end_) { continue; } - std::ignore = luaL_error(L, "argument #%d: invalid key type (not a boolean, string, number or light userdata)", i); + luaL_error(L, "argument #%d: invalid key type (not a boolean, string, number or light userdata)", i); // doesn't return } } diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h index 31ae8bd..99e49f9 100644 --- a/src/macros_and_utils.h +++ b/src/macros_and_utils.h @@ -77,7 +77,7 @@ class StackChecker if ((offset_ < 0) || (m_oldtop < 0)) { assert(false); - std::ignore = luaL_error(m_L, "STACK INIT ASSERT failed (%d not %d): %s:%d", lua_gettop(m_L), offset_, file_, line_); + luaL_error(m_L, "STACK INIT ASSERT failed (%d not %d): %s:%d", lua_gettop(m_L), offset_, file_, line_); // doesn't return } } @@ -88,7 +88,7 @@ class StackChecker if (lua_gettop(m_L) != pos_) { assert(false); - std::ignore = luaL_error(m_L, "STACK INIT ASSERT failed (%d not %d): %s:%d", lua_gettop(m_L), pos_, file_, line_); + luaL_error(m_L, "STACK INIT ASSERT failed (%d not %d): %s:%d", lua_gettop(m_L), pos_, file_, line_); // doesn't return } } @@ -108,7 +108,7 @@ class StackChecker if (actual != expected_) { assert(false); - std::ignore = luaL_error(m_L, "STACK ASSERT failed (%d not %d): %s:%d", actual, expected_, file_, line_); + luaL_error(m_L, "STACK ASSERT failed (%d not %d): %s:%d", actual, expected_, file_, line_); // doesn't return } } } @@ -128,7 +128,7 @@ inline void STACK_GROW(lua_State* L, int n_) { if (!lua_checkstack(L, n_)) { - std::ignore = luaL_error(L, "Cannot grow stack!"); + luaL_error(L, "Cannot grow stack!"); // doesn't return } } diff --git a/src/state.cpp b/src/state.cpp index 496e21e..4a5f995 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -8,7 +8,7 @@ =============================================================================== Copyright (C) 2002-10 Asko Kauppi -2011-21 benoit Germain +2011-24 benoit Germain Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -79,6 +79,8 @@ THE SOFTWARE. return lua_gettop(L); // result(s) } +// ################################################################################################# + /* * Serialize calls to 'require', if it exists */ @@ -117,6 +119,7 @@ void serialize_require(DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* L) return 1; } +// ################################################################################################# static luaL_Reg const libs[] = { @@ -154,7 +157,9 @@ static luaL_Reg const libs[] = { nullptr, nullptr } }; -static void open1lib( DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* L, char const* name_, size_t len_) +// ################################################################################################# + +static void open1lib(DEBUGSPEW_PARAM_COMMA(Universe* U) lua_State* L, char const* name_, size_t len_) { int i; for( i = 0; libs[i].name; ++ i) @@ -183,6 +188,7 @@ static void open1lib( DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* L, char con } } +// ################################################################################################# // just like lua_xmove, args are (from, to) static void copy_one_time_settings(Universe* U, Source L, Dest L2) @@ -194,19 +200,21 @@ static void copy_one_time_settings(Universe* U, Source L, Dest L2) DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "copy_one_time_settings()\n" INDENT_END)); DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); - CONFIG_REGKEY.pushValue(L); // config + CONFIG_REGKEY.pushValue(L); // config // copy settings from from source to destination registry - if( luaG_inter_move( U, L, L2, 1, LookupMode::LaneBody) < 0) // // config + if (luaG_inter_move(U, L, L2, 1, LookupMode::LaneBody) != InterCopyResult::Success) // // config { - (void) luaL_error( L, "failed to copy settings when loading lanes.core"); + luaL_error( L, "failed to copy settings when loading lanes.core"); // doesn't return } // set L2:_R[CONFIG_REGKEY] = settings - CONFIG_REGKEY.setValue(L2, [](lua_State* L) { lua_insert(L, -2); }); // config - STACK_CHECK( L2, 0); - STACK_CHECK( L, 0); + CONFIG_REGKEY.setValue(L2, [](lua_State* L) { lua_insert(L, -2); }); // config + STACK_CHECK(L2, 0); + STACK_CHECK(L, 0); DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); } +// ################################################################################################# + void initialize_on_state_create( Universe* U, lua_State* L) { STACK_CHECK_START_REL(L, 1); // settings @@ -238,7 +246,9 @@ void initialize_on_state_create( Universe* U, lua_State* L) STACK_CHECK(L, 1); } -lua_State* create_state( Universe* U, lua_State* from_) +// ################################################################################################# + +lua_State* create_state(Universe* U, lua_State* from_) { lua_State* L; #if LUAJIT_FLAVOR() == 64 @@ -264,11 +274,13 @@ lua_State* create_state( Universe* U, lua_State* from_) if (L == nullptr) { - std::ignore = luaL_error( from_, "luaG_newstate() failed while creating state; out of memory"); + luaL_error(from_, "luaG_newstate() failed while creating state; out of memory"); // doesn't return } return L; } +// ################################################################################################# + void call_on_state_create(Universe* U, lua_State* L, lua_State* from_, LookupMode mode_) { if (U->on_state_create_func != nullptr) @@ -304,6 +316,8 @@ void call_on_state_create(Universe* U, lua_State* L, lua_State* from_, LookupMod } } +// ################################################################################################# + /* * Like 'luaL_openlibs()' but allows the set of libraries be selected * diff --git a/src/tools.cpp b/src/tools.cpp index 9207df6..aa95f04 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -1415,7 +1415,7 @@ static void copy_cached_func(Universe* U, Dest L2, int L2_cache_i, Source L, int lua_pop(L2, 1); // _R[REG_MTID] if (!inter_copy_one(U, L2, L2_cache_i, L, lua_gettop(L), VT::METATABLE, mode_, upName_)) // _R[REG_MTID] mt? { - std::ignore = luaL_error(L, "Error copying a metatable"); // doesn't return + luaL_error(L, "Error copying a metatable"); // doesn't return } STACK_CHECK(L2, 2); // _R[REG_MTID] mt @@ -1600,7 +1600,7 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; { if (!inter_copy_one(U, L2, L2_cache_i, L, lua_absindex(L, -1), VT::NORMAL, mode_, upName_)) // ... u uv { - std::ignore = luaL_error(L, "Cannot copy upvalue type '%s'", luaL_typename(L, -1)); // doesn't return + luaL_error(L, "Cannot copy upvalue type '%s'", luaL_typename(L, -1)); // doesn't return } lua_pop( L, 1); // ... mt __lanesclone [uv]* // this pops the value from the stack @@ -1670,7 +1670,7 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; } else // raise an error { - std::ignore = luaL_error(L, "can't copy non-deep full userdata across lanes"); // doesn't return + luaL_error(L, "can't copy non-deep full userdata across lanes"); // doesn't return } STACK_CHECK(L2, 1); @@ -1739,7 +1739,7 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; { if (!inter_copy_one(U, L2, L2_cache_i, L, lua_absindex(L, -1), vt_, mode_, upName_)) // ... mt u uv { - std::ignore = luaL_error(L, "Cannot copy upvalue type '%s'", luaL_typename(L, -1)); // doesn't return + luaL_error(L, "Cannot copy upvalue type '%s'", luaL_typename(L, -1)); // doesn't return } lua_pop(L, 1); // ... u [uv]* // this pops the value from the stack @@ -1973,7 +1973,7 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; * * Note: Parameters are in this order ('L' = from first) to be same as 'lua_xmove'. */ -[[nodiscard]] int luaG_inter_copy(Universe* U, Source L, Dest L2, int n, LookupMode mode_) +[[nodiscard]] InterCopyResult luaG_inter_copy(Universe* U, Source L, Dest L2, int n, LookupMode mode_) { int const top_L{ lua_gettop(L) }; // ... {}n int const top_L2{ lua_gettop(L2) }; // ... @@ -1988,7 +1988,7 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; // 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.fetch_sub(1, std::memory_order_relaxed)); - return -1; + return InterCopyResult::NotEnoughValues; } STACK_CHECK_START_REL(L2, 0); @@ -2025,25 +2025,31 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; // 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 InterCopyResult::Success; } // error -> pop everything from the target state stack lua_settop(L2, top_L2); STACK_CHECK(L2, 0); - return -2; + return InterCopyResult::Error; } // ################################################################################################# -[[nodiscard]] int luaG_inter_move(Universe* U, Source L, Dest L2, int n, LookupMode mode_) +[[nodiscard]] InterCopyResult luaG_inter_move(Universe* U, Source L, Dest L2, int n_, LookupMode mode_) { - int const ret{ luaG_inter_copy(U, L, L2, n, mode_) }; - lua_pop( L, n); + InterCopyResult const ret{ luaG_inter_copy(U, L, L2, n_, mode_) }; + lua_pop( L, n_); return ret; } -[[nodiscard]] int luaG_inter_copy_package(Universe* U, Source L, Dest L2, int package_idx_, LookupMode mode_) +// ################################################################################################# + +// transfers stuff from L->_G["package"] to L2->_G["package"] +// returns InterCopyResult::Success if everything is fine +// returns InterCopyResult::Error if pushed an error message in L +// else raise an error in L +[[nodiscard]] InterCopyResult luaG_inter_copy_package(Universe* U, Source L, Dest 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.fetch_add(1, std::memory_order_relaxed)); @@ -2056,7 +2062,11 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; lua_pushfstring(L, "expected package as table, got %s", luaL_typename(L, package_idx_)); STACK_CHECK(L, 1); // raise the error when copying from lane to lane, else just leave it on the stack to be raised later - return (mode_ == LookupMode::LaneBody) ? lua_error(L) : 1; + if (mode_ == LookupMode::LaneBody) + { + lua_error(L); // doesn't return + } + return InterCopyResult::Error; } lua_getglobal(L2, "package"); if (!lua_isnil(L2, -1)) // package library not loaded: do nothing @@ -2095,5 +2105,5 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; STACK_CHECK(L2, 0); STACK_CHECK(L, 0); DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); - return 0; + return InterCopyResult::Success; } diff --git a/src/tools.h b/src/tools.h index f0de7ec..dce7378 100644 --- a/src/tools.h +++ b/src/tools.h @@ -1,8 +1,6 @@ #pragma once -#include "threading.h" #include "deep.h" - #include "macros_and_utils.h" // forwards @@ -11,13 +9,13 @@ class Universe; // ################################################################################################ #ifdef _DEBUG -void luaG_dump( lua_State* L); +void luaG_dump(lua_State* L); #endif // _DEBUG // ################################################################################################ -void push_registry_subtable_mode( lua_State* L, UniqueKey key_, const char* mode_); -void push_registry_subtable( lua_State* L, UniqueKey key_); +void push_registry_subtable_mode(lua_State* L, UniqueKey key_, const char* mode_); +void push_registry_subtable(lua_State* L, UniqueKey key_); enum class VT { @@ -25,14 +23,21 @@ enum class VT KEY, METATABLE }; + +enum class InterCopyResult +{ + Success, + NotEnoughValues, + Error +}; + [[nodiscard]] bool inter_copy_one(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_); // ################################################################################################ -[[nodiscard]] int luaG_inter_copy_package(Universe* U, Source L, Dest L2, int package_idx_, LookupMode mode_); - -[[nodiscard]] int luaG_inter_copy(Universe* U, Source L, Dest L2, int n, LookupMode mode_); -[[nodiscard]] int luaG_inter_move(Universe* U, Source L, Dest L2, int n, LookupMode mode_); +[[nodiscard]] InterCopyResult luaG_inter_copy_package(Universe* U, Source L, Dest L2, int package_idx_, LookupMode mode_); +[[nodiscard]] InterCopyResult luaG_inter_copy(Universe* U, Source L, Dest L2, int n, LookupMode mode_); +[[nodiscard]] InterCopyResult luaG_inter_move(Universe* U, Source L, Dest L2, int n, LookupMode mode_); [[nodiscard]] int luaG_nameof(lua_State* L); -- cgit v1.2.3-55-g6feb From 21e881fd6c085e615c438ceb6eb438712f5c5075 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Wed, 10 Apr 2024 16:21:57 +0200 Subject: C++ migration: wrap lua type values in an enum class for type safety and debugging purposes --- src/compat.h | 27 +++++++++++++++++++++++++++ src/lanes.cpp | 13 +++++++++---- src/macros_and_utils.h | 10 ++++++---- src/tools.cpp | 30 +++++++++++++++--------------- 4 files changed, 57 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/compat.h b/src/compat.h index 8ef1b6c..8d10e78 100644 --- a/src/compat.h +++ b/src/compat.h @@ -98,3 +98,30 @@ int lua_setiuservalue( lua_State* L, int idx, int n); #define LUA_ERRGCMM 666 // doesn't exist in Lua 5.4, we don't care about the actual value #endif // LUA_VERSION_NUM == 504 + +// ################################################################################################# + +// a wrapper over lua types to see them easier in a debugger +enum class LuaType +{ + NONE = LUA_TNONE, + NIL = LUA_TNIL, + BOOLEAN = LUA_TBOOLEAN, + LIGHTUSERDATA = LUA_TLIGHTUSERDATA, + NUMBER = LUA_TNUMBER, + STRING = LUA_TSTRING, + TABLE = LUA_TTABLE, + FUNCTION = LUA_TFUNCTION, + USERDATA = LUA_TUSERDATA, + THREAD = LUA_TTHREAD, + CDATA = 10 // LuaJIT CDATA +}; + +inline LuaType lua_type_as_enum(lua_State* L, int idx_) +{ + return static_cast(lua_type(L, idx_)); +} +inline char const* lua_typename(lua_State* L, LuaType t_) +{ + return lua_typename(L, static_cast(t_)); +} diff --git a/src/lanes.cpp b/src/lanes.cpp index 462de0f..1f795cc 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -967,10 +967,10 @@ LUAG_FUNC(require) LUAG_FUNC(register) { char const* name = luaL_checkstring(L, 1); - int const mod_type = lua_type(L, 2); + LuaType const mod_type{ lua_type_as_enum(L, 2) }; // ignore extra parameters, just in case lua_settop(L, 2); - luaL_argcheck(L, (mod_type == LUA_TTABLE) || (mod_type == LUA_TFUNCTION), 2, "unexpected module type"); + luaL_argcheck(L, (mod_type == LuaType::TABLE) || (mod_type == LuaType::FUNCTION), 2, "unexpected module type"); DEBUGSPEW_CODE(Universe* U = universe_get(L)); STACK_CHECK_START_REL(L, 0); // "name" mod_table DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lanes.register %s BEGIN\n" INDENT_END, name)); @@ -1226,7 +1226,8 @@ LUAG_FUNC(lane_new) STACK_CHECK(L2, 0); // Lane main function - if (lua_type(L, 1) == LUA_TFUNCTION) + LuaType const func_type{ lua_type_as_enum(L, 1) }; + if (func_type == LuaType::FUNCTION) { DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane body\n" INDENT_END)); DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); @@ -1238,7 +1239,7 @@ LUAG_FUNC(lane_new) luaL_error(L, "tried to copy unsupported types"); // doesn't return } } - else if (lua_type(L, 1) == LUA_TSTRING) + else if (func_type == LuaType::STRING) { DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: compile lane body\n" INDENT_END)); // compile the string @@ -1247,6 +1248,10 @@ LUAG_FUNC(lane_new) luaL_error(L, "error when parsing lane function code"); // doesn't return } } + else + { + luaL_error(L, "Expected function, got %s", lua_typename(L, func_type)); // doesn't return + } STACK_CHECK(L, 0); STACK_CHECK(L2, 1); ASSERT_L(lua_isfunction(L2, 1)); diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h index 99e49f9..e8d5ab5 100644 --- a/src/macros_and_utils.h +++ b/src/macros_and_utils.h @@ -182,10 +182,12 @@ template struct Unique { T m_val; - Unique() = default; - operator T() const { return m_val; } - explicit Unique(T b_) : m_val{ b_ } {} + constexpr Unique() = default; + constexpr operator T() const { return m_val; } + constexpr explicit Unique(T b_) : m_val{ b_ } {} }; +// ################################################################################################# + using Source = Unique; -using Dest = Unique; +using Dest = Unique; \ No newline at end of file diff --git a/src/tools.cpp b/src/tools.cpp index aa95f04..4a6f2a2 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -104,7 +104,7 @@ void luaG_dump( lua_State* L) for( i = 1; i <= top; ++ i) { - int type = lua_type( L, i); + LuaType type{ lua_type_as_enum(L, i) }; fprintf( stderr, "\t[%d]= (%s) ", i, lua_typename( L, type)); @@ -1851,8 +1851,8 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; [[nodiscard]] bool inter_copy_one(Universe* U, Dest L2, int L2_cache_i, Source L, int i, VT vt_, LookupMode mode_, char const* upName_) { bool ret{ true }; - int val_type = lua_type( L, i); - static int const pod_mask = (1 << LUA_TNIL) | (1 << LUA_TBOOLEAN) | (1 << LUA_TLIGHTUSERDATA) | (1 << LUA_TNUMBER) | (1 << LUA_TSTRING); + LuaType val_type{ lua_type_as_enum(L, i) }; + static constexpr int pod_mask = (1 << LUA_TNIL) | (1 << LUA_TBOOLEAN) | (1 << LUA_TLIGHTUSERDATA) | (1 << LUA_TNUMBER) | (1 << LUA_TSTRING); STACK_GROW( L2, 1); STACK_CHECK_START_REL(L, 0); // L // L2 STACK_CHECK_START_REL(L2, 0); // L // L2 @@ -1862,7 +1862,7 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "%s %s: " INDENT_END, lua_type_names[val_type], vt_names[static_cast(vt_)])); // Non-POD can be skipped if its metatable contains { __lanesignore = true } - if( ((1 << val_type) & pod_mask) == 0) + if( ((1 << static_cast(val_type)) & pod_mask) == 0) { if( lua_getmetatable( L, i)) // ... mt { @@ -1870,7 +1870,7 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; if( lua_isboolean( L, -1) && lua_toboolean( L, -1)) { DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "__lanesignore -> LUA_TNIL\n" INDENT_END)); - val_type = LUA_TNIL; + val_type = LuaType::NIL; } lua_pop( L, 2); // ... } @@ -1882,7 +1882,7 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; { /* Basic types allowed both as values, and as table keys */ - case LUA_TBOOLEAN: + case LuaType::BOOLEAN: { int const v{ lua_toboolean(L, i) }; DEBUGSPEW_CODE( fprintf( stderr, "%s\n", v ? "true" : "false")); @@ -1890,7 +1890,7 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; } break; - case LUA_TNUMBER: + case LuaType::NUMBER: /* LNUM patch support (keeping integer accuracy) */ #if defined LUA_LNUM || LUA_VERSION_NUM >= 503 if( lua_isinteger( L, i)) @@ -1909,7 +1909,7 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; } break; - case LUA_TSTRING: + case LuaType::STRING: { size_t len; char const* s = lua_tolstring( L, i, &len); @@ -1918,7 +1918,7 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; } break; - case LUA_TLIGHTUSERDATA: + case LuaType::LIGHTUSERDATA: { void* p = lua_touserdata( L, i); DEBUGSPEW_CODE( fprintf( stderr, "%p\n", p)); @@ -1928,11 +1928,11 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; /* The following types are not allowed as table keys */ - case LUA_TUSERDATA: + case LuaType::USERDATA: ret = inter_copy_userdata(U, L2, L2_cache_i, L, i, vt_, mode_, upName_); break; - case LUA_TNIL: + case LuaType::NIL: if (vt_ == VT::KEY) { ret = false; @@ -1941,18 +1941,18 @@ static constexpr UniqueKey CLONABLES_CACHE_KEY{ 0xD04EE018B3DEE8F5ull }; lua_pushnil( L2); break; - case LUA_TFUNCTION: + case LuaType::FUNCTION: ret = inter_copy_function(U, L2, L2_cache_i, L, i, vt_, mode_, upName_); break; - case LUA_TTABLE: + case LuaType::TABLE: ret = inter_copy_table(U, L2, L2_cache_i, L, i, vt_, mode_, upName_); break; /* The following types cannot be copied */ - case 10: // LuaJIT CDATA - case LUA_TTHREAD: + case LuaType::CDATA: + case LuaType::THREAD: ret = false; break; } -- cgit v1.2.3-55-g6feb 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 --- CHANGES | 7 + Makefile | 32 ++-- docs/index.html | 24 ++- lanes-3.16.3-0.rockspec | 78 -------- lanes-3.17.0-0.rockspec | 78 ++++++++ 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 +- tests/basic.lua | 14 +- tests/cancel.lua | 212 ++++++++++++--------- tests/errhangtest.lua | 15 +- tests/fifo.lua | 42 ++++- tests/keeper.lua | 63 ++++++- tests/linda_perf.lua | 165 ++++++++-------- tests/protect_allocator.lua | 3 + tests/timer.lua | 12 +- 30 files changed, 956 insertions(+), 691 deletions(-) delete mode 100644 lanes-3.16.3-0.rockspec create mode 100644 lanes-3.17.0-0.rockspec (limited to 'src') diff --git a/CHANGES b/CHANGES index d93f5b4..34bd2d9 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,12 @@ CHANGES: +CHANGE 160: BGe 11-Apr-24 + * add manual control over GC behavior in keeper states + * update a bunch of test scripts + * minor internal fixes + * probably the last C implementation update, unless a critical bug creeps up + * internal version bumped to 3.17.0 + CHANGE 159: BGe 19-Mar-24 * fix small internal issue with when hitting timeout on thread kill during thread_cancel() in pthread implementation diff --git a/Makefile b/Makefile index 1e9f70e..08888e4 100644 --- a/Makefile +++ b/Makefile @@ -72,25 +72,25 @@ rock: #--- Testing --- # test: - $(MAKE) errhangtest - $(MAKE) irayo_recursive - $(MAKE) irayo_closure + $(MAKE) atexit + $(MAKE) atomic $(MAKE) basic $(MAKE) cancel - $(MAKE) fifo - $(MAKE) keeper - $(MAKE) timer - $(MAKE) atomic $(MAKE) cyclic - $(MAKE) objects + $(MAKE) errhangtest $(MAKE) fibonacci - $(MAKE) recursive + $(MAKE) fifo $(MAKE) func_is_string - $(MAKE) atexit + $(MAKE) irayo_closure + $(MAKE) irayo_recursive + $(MAKE) keeper $(MAKE) linda_perf - $(MAKE) rupval + $(MAKE) objects $(MAKE) package $(MAKE) pingpong + $(MAKE) recursive + $(MAKE) rupval + $(MAKE) timer basic: tests/basic.lua $(_TARGET_SO) $(_PREFIX) $(LUA) $< @@ -104,8 +104,8 @@ cancel: tests/cancel.lua $(_TARGET_SO) REP_ARGS=-llanes -e "print'say aaa'; for i=1,10 do print(i) end" repetitive: $(_TARGET_SO) for i in 1 2 3 4 5 6 7 8 9 10 a b c d e f g h i j k l m n o p q r s t u v w x y z; \ - do $(_PREFIX) $(LUA) $(REP_ARGS); \ - done + do $(_PREFIX) $(LUA) $(REP_ARGS); \ + done repetitive1: $(_TARGET_SO) $(_PREFIX) $(LUA) $(REP_ARGS) @@ -236,9 +236,9 @@ else -rm -rf $(MODULE)-$(VERSION) mkdir $(MODULE)-$(VERSION) tar c * --exclude=.svn --exclude=.DS_Store --exclude="_*" \ - --exclude="*.tgz" --exclude="*.rockspec" \ - --exclude=lanes.dev --exclude="$(MODULE)-*" --exclude=xcode \ - --exclude="*.obj" --exclude="*.dll" --exclude=timeit.dat \ + --exclude="*.tgz" --exclude="*.rockspec" \ + --exclude=lanes.dev --exclude="$(MODULE)-*" --exclude=xcode \ + --exclude="*.obj" --exclude="*.dll" --exclude=timeit.dat \ | (cd $(MODULE)-$(VERSION) && tar x) tar czvf $(MODULE)-$(VERSION).tgz $(MODULE)-$(VERSION) rm -rf $(MODULE)-$(VERSION) diff --git a/docs/index.html b/docs/index.html index aed022a..da94898 100644 --- a/docs/index.html +++ b/docs/index.html @@ -70,7 +70,7 @@

    - This document was revised on 23-Feb-24, and applies to version 3.16.3. + This document was revised on 11-Apr-24, and applies to version 3.17.0.

    @@ -290,6 +290,19 @@
    + .keepers_gc_threshold + integer + (Since v3.17.0)
    + If <0, GC runs automatically. This is the default.
    + If 0, GC runs after *every* keeper operation.
    + If >0, Keepers run GC manually with lua_gc(LUA_GCCOLLECT) whenever memory usage reported by lua_gc(LUA_GCCOUNT) reaches this threshold. Check is made after every keeper operation (see below). If memory usage remains above threshold after the GC cycle, an error is raised. +
    .with_timers @@ -351,7 +364,6 @@
    The contents will be used to create the state with lua_newstate( allocF, allocUD). This option is mostly useful for embedders that want to provide different allocators to each lane, for example to have each one work in a different memory pool thus preventing the need for the allocator itself to be threadsafe. - Note however that linda deep proxy are allocated with the allocator from the master state, because they are not tied to a particular state.
    (Since v3.16.1)
    - Controls which allocator is used for Lanest internal allocations (for keeper and deep userdata management). + Controls which allocator is used for Lanes internal allocations (for keeper, linda and lane management). If "libc", Lanes uses realloc and free.
    If "allocator", Lanes uses whatever was obtained from the "allocator" setting.
    This option is mostly useful for embedders that want control all memory allocations, but have issues when Lanes tries to use the Lua State allocator for internal purposes (especially with LuaJIT). @@ -911,7 +923,7 @@
    -
    	{{name = "name", status = "status", ...}|nil = lanes.threads()
    +
    	{name = "name", status = "status", ...}|nil = lanes.threads()
    @@ -1617,8 +1629,8 @@ events to a common Linda, but... :).

    Clonable full userdata in your own apps

    Starting with version 3.13.0, a new way of passing full userdata across lanes uses a new __lanesclone metamethod. - When a deep userdata is cloned, Lanes calls __lanesclone once, in the context of the source lane.
    - The call receives the clone and original as light userdata, plus the actual userdata size, as in clone:__lanesclone(original,size), and should perform the actual cloning.
    + When a deep userdata is cloned, Lanes calls __lanesclone once, in the context of the source lane.
    + The call receives the clone and original as light userdata, plus the actual userdata size, as in clone:__lanesclone(original,size), and should perform the actual cloning.
    A typical implementation would look like (BEWARE, THIS CHANGED WITH VERSION 3.16.0):
     static int clonable_lanesclone( lua_State* L)
    diff --git a/lanes-3.16.3-0.rockspec b/lanes-3.16.3-0.rockspec
    deleted file mode 100644
    index e223a19..0000000
    --- a/lanes-3.16.3-0.rockspec
    +++ /dev/null
    @@ -1,78 +0,0 @@
    ---
    --- Lanes rockspec
    ---
    --- Ref:
    ---      
    ---
    -
    -package = "Lanes"
    -
    -version = "3.16.3-0"
    -
    -source= {
    -	url= "git+https://github.com/LuaLanes/lanes.git",
    -	branch= "v3.16.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/LuaJIT, 5.2, 5.3 and 5.4
    -}
    -
    -build = {
    -	type = "builtin",
    -	platforms =
    -	{
    -		linux =
    -		{
    -			modules =
    -			{
    -				["lanes.core"] =
    -				{
    -					libraries = "pthread"
    -				},
    -			}
    -		}
    -	},
    -	modules =
    -	{
    -		["lanes.core"] =
    -		{
    -			sources =
    -			{
    -				"src/cancel.c",
    -				"src/compat.c",
    -				"src/deep.c",
    -				"src/keeper.c",
    -				"src/lanes.c",
    -				"src/linda.c",
    -				"src/tools.c",
    -				"src/state.c",
    -				"src/threading.c",
    -				"src/universe.c"
    -			},
    -			incdirs = { "src"},
    -		},
    -		lanes = "src/lanes.lua"
    -	}
    -}
    diff --git a/lanes-3.17.0-0.rockspec b/lanes-3.17.0-0.rockspec
    new file mode 100644
    index 0000000..69ee515
    --- /dev/null
    +++ b/lanes-3.17.0-0.rockspec
    @@ -0,0 +1,78 @@
    +--
    +-- Lanes rockspec
    +--
    +-- Ref:
    +--      
    +--
    +
    +package = "Lanes"
    +
    +version = "3.17.0-0"
    +
    +source= {
    +	url= "git+https://github.com/LuaLanes/lanes.git",
    +	branch= "v3.17.0"
    +}
    +
    +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/LuaJIT, 5.2, 5.3 and 5.4
    +}
    +
    +build = {
    +	type = "builtin",
    +	platforms =
    +	{
    +		linux =
    +		{
    +			modules =
    +			{
    +				["lanes.core"] =
    +				{
    +					libraries = "pthread"
    +				},
    +			}
    +		}
    +	},
    +	modules =
    +	{
    +		["lanes.core"] =
    +		{
    +			sources =
    +			{
    +				"src/cancel.c",
    +				"src/compat.c",
    +				"src/deep.c",
    +				"src/keeper.c",
    +				"src/lanes.c",
    +				"src/linda.c",
    +				"src/tools.c",
    +				"src/state.c",
    +				"src/threading.c",
    +				"src/universe.c"
    +			},
    +			incdirs = { "src"},
    +		},
    +		lanes = "src/lanes.lua"
    +	}
    +}
    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_
    diff --git a/tests/basic.lua b/tests/basic.lua
    index 385e22f..4b4fae6 100644
    --- a/tests/basic.lua
    +++ b/tests/basic.lua
    @@ -154,7 +154,7 @@ PRINT(" "..st)
     assert( st == "cancelled" )
     
     -- cancellation of lanes waiting on a linda
    -local limited = lanes.linda()
    +local limited = lanes.linda("limited")
     limited:limit( "key", 1)
     -- [[################################################
     limited:send( "key", "hello") -- saturate linda
    @@ -234,7 +234,7 @@ local chunk= function( linda )
         WR( "Lane ends!\n" )
     end
     
    -local linda= lanes_linda()
    +local linda= lanes_linda("communications")
     assert( type(linda) == "userdata" )
         --
         -- ["->"] master -> slave
    @@ -278,10 +278,12 @@ local complex_table = RECEIVE(); WR( type(complex_table).." received\n" )
     assert( complex_table[1] == complex_table[3] and complex_table[2] == complex_table[4])
     WR( table.concat( {complex_table[1][1],complex_table[2][1],complex_table[3][1],complex_table[4][1]},", "))
     
    +WR("collectgarbage")
     t = nil
     collectgarbage()
     -- wait
    -linda: receive( 1, "wait")
    +WR("waiting 1s")
    +linda:receive( 1, "wait")
     
     --##############################################################
     --##############################################################
    @@ -336,6 +338,7 @@ for _, t in ipairs( stdlib_naming_tests) do
         assert( f(t[1])[1] )
     end
     
    +WR("collectgarbage")
     collectgarbage()
     
     --##############################################################
    @@ -361,12 +364,13 @@ local tc= lanes_gen( "io", {gc_cb = gc_cb},
       end
     )
     
    -local linda= lanes_linda()
    +local linda= lanes_linda("criss cross")
     
     local a,b= tc(linda, "A","B"), tc(linda, "B","A")   -- launching two lanes, twisted comms
     
     local _= a[1],b[1]  -- waits until they are both ready
     
    +WR("collectgarbage")
     a, b = nil
     collectgarbage()
     
    @@ -408,7 +412,7 @@ local function chunk2( linda )
         linda:send( "up", function() return ":)" end, "ok2" )
     end
     
    -local linda= lanes.linda()
    +local linda= lanes.linda("linda")
     local t2= lanes_gen( "debug,string,io", {gc_cb = gc_cb}, chunk2 )(linda)     -- prepare & launch
     linda:send( "down", function(linda) linda:send( "up", "ready!" ) end,
                         "ok" )
    diff --git a/tests/cancel.lua b/tests/cancel.lua
    index 0d9d143..4e57184 100644
    --- a/tests/cancel.lua
    +++ b/tests/cancel.lua
    @@ -1,36 +1,47 @@
    -local lanes = require "lanes" .configure{ with_timers = false}
    -
    -local linda = lanes.linda()
    +local which_tests, remaining_tests = {}, {}
    +for k,v in ipairs{...} do
    +	print("got arg:", type(v), tostring(v))
    +	which_tests[v] = true
    +	remaining_tests[v] = true
    +end
     
     --####################################################################
    -print "\n\n####################################################################\nbegin genlock & genatomic cancel test\n"
    -
    --- get a lock and a atomic operator
    -local lock = lanes.genlock( linda, "lock", 1)
    -local atomic = lanes.genatomic( linda, "atomic")
    -
    --- check that cancelled lindas give cancel_error as they should
    -linda:cancel()
    -assert( linda:get( "empty") == lanes.cancel_error)
    -assert( lanes.genlock( linda, "any", 1) == lanes.cancel_error)
    -assert( lanes.genatomic( linda, "any") == lanes.cancel_error)
    -
    --- check that lock and atomic functions return cancel_error if the linda was cancelled
    -assert( lock( 1) == lanes.cancel_error)
    -assert( lock( -1) == lanes.cancel_error)
    -assert( atomic( 1) == lanes.cancel_error)
    -
    --- reset the linda so that the other tests work
    -linda:cancel( "none")
    -linda:limit( "lock", -1)
    -linda:set( "lock")
    -linda:limit( "atomic", -1)
    -linda:set( "atomic")
     
    +local lanes = require "lanes" .configure{ with_timers = false}
    +
    +local linda = lanes.linda()
     -- a numeric value to read
     linda:set( "val", 33.0)
     
    -print "test OK"
    +--####################################################################
    +if not next(which_tests) or which_tests.genlock then
    +	remaining_tests.genlock = nil
    +	print "\n\n####################################################################\nbegin genlock & genatomic cancel test\n"
    +
    +	-- get a lock and a atomic operator
    +	local lock = lanes.genlock( linda, "lock", 1)
    +	local atomic = lanes.genatomic( linda, "atomic")
    +
    +	-- check that cancelled lindas give cancel_error as they should
    +	linda:cancel()
    +	assert( linda:get( "empty") == lanes.cancel_error)
    +	assert( lanes.genlock( linda, "any", 1) == lanes.cancel_error)
    +	assert( lanes.genatomic( linda, "any") == lanes.cancel_error)
    +
    +	-- check that lock and atomic functions return cancel_error if the linda was cancelled
    +	assert( lock( 1) == lanes.cancel_error)
    +	assert( lock( -1) == lanes.cancel_error)
    +	assert( atomic( 1) == lanes.cancel_error)
    +
    +	-- reset the linda so that the other tests work
    +	linda:cancel( "none")
    +	linda:limit( "lock", -1)
    +	linda:set( "lock")
    +	linda:limit( "atomic", -1)
    +	linda:set( "atomic")
    +
    +	print "test OK"
    +end
     --####################################################################
     
     local waitCancellation = function( h, expected_status)
    @@ -119,92 +130,115 @@ end
     --####################################################################
     --####################################################################
     
    -print "\n\n####################################################################\nbegin linda cancel test\n"
    -h = lanes.gen( "*", laneBody)( "receive", nil) -- start an infinite wait on the linda
    -
    -print "wait 1s"
    -linda:receive( 1, "yeah")
    -
    --- linda cancel: linda:receive() returns cancel_error immediately
    -linda:cancel( "both")
    +if not next(which_tests) or which_tests.linda then
    +	remaining_tests.linda = nil
    +	print "\n\n####################################################################\nbegin linda cancel test\n"
    +	h = lanes.gen( "*", laneBody)( "receive", nil) -- start an infinite wait on the linda
     
    --- wait until cancellation is effective.
    -waitCancellation( h, "done")
    +	print "wait 1s"
    +	linda:receive( 1, "yeah")
     
    --- reset the linda so that the other tests work
    -linda:cancel( "none")
    +	-- linda cancel: linda:receive() returns cancel_error immediately
    +	print "cancelling"
    +	linda:cancel( "both")
     
    -print "\n\n####################################################################\nbegin soft cancel test\n"
    -h = lanes.gen( "*", protectedBody)( "receive") -- start an infinite wait on the linda
    +	-- wait until cancellation is effective.
    +	waitCancellation( h, "done")
     
    -print "wait 1s"
    -linda:receive( 1, "yeah")
    +	-- reset the linda so that the other tests work
    +	linda:cancel( "none")
    +end
     
    --- soft cancel, no awakening of waiting linda operations, should timeout
    -local a, b = h:cancel( "soft", 1, false)
    --- cancellation should fail as the lane is still waiting on its linda
    -assert( a == false and b == "timeout")
    -waitCancellation( h, "waiting")
    +if not next(which_tests) or which_tests.soft then
    +	remaining_tests.soft = nil
    +	print "\n\n####################################################################\nbegin soft cancel test\n"
    +	h = lanes.gen( "*", protectedBody)( "receive") -- start an infinite wait on the linda
     
    --- soft cancel, this time awakens waiting linda operations, which returns cancel_error immediately, no timeout.
    -h:cancel( "soft", true)
    +	print "wait 1s"
    +	linda:receive( 1, "yeah")
     
    --- wait until cancellation is effective. the lane will interrupt its loop and print the exit message
    -waitCancellation( h, "done")
    +	-- soft cancel, no awakening of waiting linda operations, should timeout
    +	local a, b = h:cancel( "soft", 1, false)
    +	-- cancellation should fail as the lane is still waiting on its linda
    +	assert( a == false and b == "timeout")
    +	waitCancellation( h, "waiting")
     
    --- do return end
    +	-- soft cancel, this time awakens waiting linda operations, which returns cancel_error immediately, no timeout.
    +	print "cancelling"
    +	h:cancel( "soft", true)
     
    -print "\n\n####################################################################\nbegin hook cancel test\n"
    -h = lanes.gen( "*", protectedBody)( "get", 300000)
    -print "wait 2s"
    -linda:receive( 2, "yeah")
    +	-- wait until cancellation is effective. the lane will interrupt its loop and print the exit message
    +	waitCancellation( h, "done")
    +end
     
    --- count hook cancel after 3 instructions
    -h:cancel( "count", 300, 5.0)
    +if not next(which_tests) or which_tests.hook then
    +	remaining_tests.hook = nil
    +	print "\n\n####################################################################\nbegin hook cancel test\n"
    +	h = lanes.gen( "*", protectedBody)( "get", 300000)
    +	print "wait 2s"
    +	linda:receive( 2, "yeah")
     
    --- wait until cancellation is effective. the lane will interrupt its loop and print the exit message
    -waitCancellation( h, "cancelled")
    +	-- count hook cancel after some instruction instructions
    +	print "cancelling"
    +	h:cancel( "line", 300, 5.0)
     
    -print "\n\n####################################################################\nbegin hard cancel test\n"
    -h = lanes.gen( "*", protectedBody)( "receive", nil) -- infinite timeout
    +	-- wait until cancellation is effective. the lane will interrupt its loop and print the exit message
    +	waitCancellation( h, "cancelled")
    +end
     
    --- wait 2s before cancelling the lane
    -print "wait 2s"
    -linda:receive( 2, "yeah")
    +if not next(which_tests) or which_tests.hard then
    +	remaining_tests.hard = nil
    +	print "\n\n####################################################################\nbegin hard cancel test\n"
    +	h = lanes.gen( "*", protectedBody)( "receive", nil) -- infinite timeout
     
    --- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it
    -h:cancel()
    +	-- wait 2s before cancelling the lane
    +	print "wait 2s"
    +	linda:receive( 2, "yeah")
     
    --- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error
    -waitCancellation( h, "cancelled")
    +	-- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it
    +	print "cancelling"
    +	h:cancel()
     
    -print "\n\n####################################################################\nbegin hard cancel test with unprotected lane body\n"
    -h = lanes.gen( "*", laneBody)( "receive", nil)
    +	-- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error
    +	waitCancellation( h, "cancelled")
    +end
     
    --- wait 2s before cancelling the lane
    -print "wait 2s"
    -linda:receive( 2, "yeah")
    +if not next(which_tests) or which_tests.hard_unprotected then
    +	remaining_tests.hard_unprotected = nil
    +	print "\n\n####################################################################\nbegin hard cancel test with unprotected lane body\n"
    +	h = lanes.gen( "*", laneBody)( "receive", nil)
     
    --- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it
    -h:cancel()
    +	-- wait 2s before cancelling the lane
    +	print "wait 2s"
    +	linda:receive( 2, "yeah")
     
    --- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error
    -waitCancellation( h, "cancelled")
    +	-- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it
    +	print "cancelling"
    +	h:cancel()
     
    -print "\n\n####################################################################\nbegin kill cancel test\n"
    -h = lanes.gen( "*", laneBody)( "busy", 50000000) -- start a pure Lua busy loop lane
    +	-- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error
    +	waitCancellation( h, "cancelled")
    +end
     
    --- wait 1/3s before cancelling the lane, before the busy loop can finish
    -print "wait 0.3s"
    -linda:receive( 0.3, "yeah")
    +if not next(which_tests) or which_tests.kill then
    +	remaining_tests.kill = nil
    +	print "\n\n####################################################################\nbegin kill cancel test\n"
    +	h = lanes.gen( "*", laneBody)( "busy", 50000000) -- start a pure Lua busy loop lane
     
    --- hard cancel with kill: the lane thread will be forcefully terminated
    -h:cancel( true)
    +	-- wait 1/3s before cancelling the lane, before the busy loop can finish
    +	print "wait 0.3s"
    +	linda:receive( 0.3, "yeah")
     
    --- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error
    -waitCancellation( h, "killed")
    +	-- hard cancel with kill: the lane thread will be forcefully terminated. kill timeout is pthread-specific
    +	print "cancelling"
    +	h:cancel( true, 1.0)
     
    +	-- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error
    +	waitCancellation( h, "killed")
    +end
     --####################################################################
     
    -print "\ndone"
    +local unknown_test, val = next(remaining_tests)
    +assert(not unknown_test, tostring(unknown_test) .. " test is unknown")
     
    +print "\nTHE END"
    diff --git a/tests/errhangtest.lua b/tests/errhangtest.lua
    index 7286fa5..d26dcef 100644
    --- a/tests/errhangtest.lua
    +++ b/tests/errhangtest.lua
    @@ -4,10 +4,19 @@ local linda = lanes.linda()
     
     local coro = coroutine.create(function() end)
     
    +local fun = function() print "fun" end
    +local t_in = { [fun] = fun, fun = fun }
    +
    +-- send a string
    +print( pcall(linda.send,linda, 'test', "oh boy"))
    +-- send a table that contains a function
    +print( pcall(linda.send,linda, 'test', t_in))
     -- we are not allowed to send coroutines through a lanes
     -- however, this should raise an error, not hang the program...
    -print( pcall(linda.send,linda, 'test', "oh boy"))
     print( pcall(linda.send,linda, 'test', coro))
    -k,res = linda:receive('test')
    -print( res)
    +k,str = linda:receive('test') -- read the contents successfully sent
    +print( str) -- "oh boy"
    +k,t_out = linda:receive('test') -- read the contents successfully sent
    +t_out.fun() -- "fun"
     -- linda:send( 'test', coro)
    +print "SUCCESS"
    \ No newline at end of file
    diff --git a/tests/fifo.lua b/tests/fifo.lua
    index bef60d5..498f540 100644
    --- a/tests/fifo.lua
    +++ b/tests/fifo.lua
    @@ -6,24 +6,27 @@
     
     local lanes = require "lanes".configure{shutdown_timeout=3,with_timers=true}
     
    -local linda = lanes.linda( "atom")
    -local atomic_inc= lanes.genatomic( linda, "FIFO_n")
    +local atomic_linda = lanes.linda( "atom")
    +local atomic_inc= lanes.genatomic( atomic_linda, "FIFO_n")
    +
    +local fifo_linda = lanes.linda( "fifo")
     
     assert( atomic_inc()==1)
     assert( atomic_inc()==2)
     
     local function FIFO()
    -    local my_channel= "FIFO"..atomic_inc()
    +    local my_channel= "FIFO_"..atomic_inc()
     
         return {
             -- Giving explicit 'nil' timeout allows numbers to be used as 'my_channel'
             --
             send = function(self, ...)
    -            linda:send( nil, my_channel, ...)
    +            fifo_linda:send( nil, my_channel, ...)
             end,
             receive = function(self, timeout)
    -            return linda:receive( timeout, my_channel)
    -        end
    +            return fifo_linda:receive( timeout, my_channel)
    +        end,
    +        channel = my_channel
         }
     end
     
    @@ -36,11 +39,38 @@ A:send( 1,2,3,4,5)
     print "Sending to B.."
     B:send( 'a','b','c')
     
    +print "Dumping linda stats.. [1]" -- count everything
    +for key,count in pairs(fifo_linda:count()) do
    +    print("channel " .. key .. " contains " .. count .. " entries.")
    +    -- print(i, key_count[1], key_count[2])
    +end
    +print "Dumping linda stats.. [2]" -- query count for known channels one at a time
    +print("channel " .. A.channel .. " contains " .. fifo_linda:count(A.channel) .. " entries.")
    +print("channel " .. B.channel .. " contains " .. fifo_linda:count(B.channel) .. " entries.")
    +print "Dumping linda stats.. [3]" -- query counts for a predefined list of keys
    +for key,count in pairs(fifo_linda:count(A.channel, B.channel)) do
    +    print("channel " .. key .. " contains " .. count .. " entries.")
    +    -- print(i, key_count[1], key_count[2])
    +end
    +print "Dumping linda stats.. [4]" -- count everything
    +for key,contents in pairs(fifo_linda:dump()) do
    +    print("channel " .. key .. ": limit=".. contents.limit, " first=" .. contents.first, " count=" .. contents.count)
    +    for k,v in pairs(contents.fifo) do
    +        print("[".. k.."] = " .. v)
    +    end
    +end
    +
     print "Reading A.."
     print( A:receive( 1.0))
    +print( A:receive( 1.0))
    +print( A:receive( 1.0))
    +print( A:receive( 1.0))
    +print( A:receive( 1.0))
     
     print "Reading B.."
     print( B:receive( 2.0))
    +print( B:receive( 2.0))
    +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'
    diff --git a/tests/keeper.lua b/tests/keeper.lua
    index f8c915d..3333938 100644
    --- a/tests/keeper.lua
    +++ b/tests/keeper.lua
    @@ -4,7 +4,34 @@
     -- Test program for Lua Lanes
     --
     
    -local lanes = require "lanes".configure{ with_timers = false, nb_keepers = 200}
    +local lanes = require "lanes".configure{ with_timers = false, nb_keepers = 1, keepers_gc_threshold = 500}
    +
    +do
    +    print "Linda names test:"
    +    local unnamedLinda = lanes.linda()
    +    local unnamedLinda2 = lanes.linda("")
    +    local veeeerrrryyyylooongNamedLinda= lanes.linda( "veeeerrrryyyylooongNamedLinda", 1)
    +    print(unnamedLinda, unnamedLinda2, veeeerrrryyyylooongNamedLinda)
    +    print "GC deadlock test start"
    +    -- store a linda in another linda (-> in a keeper)
    +    unnamedLinda:set("here", lanes.linda("temporary linda"))
    +    -- repeatedly add and remove stuff in the linda so that a GC happens during the keeper operation
    +    for i = 1, 1000 do
    +        for j = 1, 1000 do -- send 1000 tables
    +            unnamedLinda:send("here", {"a", "table", "with", "some", "stuff"})
    +        end
    +        unnamedLinda:set("here") -- clear everything
    +    end
    +end
    +print "collecting garbage"
    +collectgarbage()
    +print "GC deadlock test done"
    +
    +local print_id = 0
    +local PRINT = function(...)
    +    print_id = print_id + 1
    +    print("main", print_id .. ".", ...)
    +end
     
     local function keeper(linda)
         local mt= {
    @@ -25,23 +52,49 @@ local A= keeper( lindaA )
     local lindaB= lanes.linda( "B", 2)
     local B= keeper( lindaB )
     
    +local lindaC= lanes.linda( "C", 3)
    +local C= keeper( lindaC )
    +print("Created", lindaA, lindaB, lindaC)
    +
     A.some= 1
    -print( A.some )
    +PRINT("A.some == " .. A.some )
     assert( A.some==1 )
     
     B.some= "hoo"
    +PRINT("B.some == " .. B.some )
     assert( B.some=="hoo" )
     assert( A.some==1 )
    +assert( C.some==nil )
     
     function lane()
    +    local print_id = 0
    +    local PRINT = function(...)
    +        print_id = print_id + 1
    +        print("lane", print_id .. ".", ...)
    +    end
    +    
         local a= keeper(lindaA)
    -    print( a.some )
    +    PRINT("a.some == " .. a.some )
         assert( a.some==1 )
         a.some= 2
    +    assert( a.some==2 )
    +    PRINT("a.some == " .. a.some )
    +
    +    local c = keeper(lindaC)
    +    assert( c.some==nil )
    +    PRINT("c.some == " .. tostring(c.some))
    +    c.some= 3
    +    assert( c.some==3 )
    +    PRINT("c.some == " .. c.some)
     end
     
    +PRINT("lane started")
     local h= lanes.gen( "io", lane )()
    -h:join()
    +PRINT("lane joined:", h:join())
     
    -print( A.some )     -- 2
    +PRINT("A.some = " .. A.some )
     assert( A.some==2 )
    +PRINT("C.some = " .. C.some )
    +assert( C.some==3 )
    +lindaC:set("some")
    +assert( C.some==nil )
    \ No newline at end of file
    diff --git a/tests/linda_perf.lua b/tests/linda_perf.lua
    index a170b01..61b8f05 100644
    --- a/tests/linda_perf.lua
    +++ b/tests/linda_perf.lua
    @@ -1,38 +1,62 @@
     local lanes = require "lanes"
    -lanes.configure{ with_timers = false}
    +lanes.configure{ with_timers = false, keepers_gc_threshold=30000 }
    +
    +-- set TEST1, PREFILL1, FILL1, TEST2, PREFILL2, FILL2 from the command line
     
     -- Lua 5.1/5.2 compatibility
     local table_unpack = unpack or table.unpack
     
    +local finalizer = function(err, stk)
    +	if err == lanes.cancel_error then
    +		-- note that we don't get the cancel_error when running wrapped inside a protected call if it doesn't rethrow it
    +		print("			laneBody after cancel" )
    +	elseif err then
    +		print("			laneBody error: "..tostring(err))
    +	else
    +		print("			laneBody finalized")
    +	end
    +end
    +
    +--##################################################################################################
    +
     -- this lane eats items in the linda one by one
     local eater = function( l, loop)
    +	set_finalizer(finalizer)
     	-- wait for start signal
     	l:receive( "go")
     	-- eat data one by one
     	for i = 1, loop do
    -		local val, key = l:receive( "key")
    -		--print( val)
    +		local key, val = l:receive( "key")
    +		-- print("eater:", val)
     	end
     	-- print "loop is over"
     	key, val = l:receive( "done")
    -	-- print( val)
    +	print("eater: done ("..val..")")
     end
     
    +--##################################################################################################
    +
     -- this lane eats items in the linda in batches
    -local batched = function( l, loop, batch)
    +local gobbler = function( l, loop, batch)
    +	set_finalizer(finalizer)
     	-- wait for start signal
     	l:receive( "go")
     	-- eat data in batches
     	for i = 1, loop/batch do
     		l:receive( l.batched, "key", batch)
    +		-- print("gobbler:", batch)
     	end
     	print "loop is over"
     	key, val = l:receive( "done")
    -	print( val)
    +	print("gobbler: done ("..val..")")
     end
     
    +--##################################################################################################
    +
     local lane_eater_gen = lanes.gen( "*", {priority = 3}, eater)
    -local lane_batched_gen = lanes.gen( "*", {priority = 3}, batched)
    +local lane_gobbler_gen = lanes.gen( "*", {priority = 3}, gobbler)
    +
    +--##################################################################################################
     
     -- main thread writes data while a lane reads it
     local function ziva( preloop, loop, batch)
    @@ -46,7 +70,7 @@ local function ziva( preloop, loop, batch)
     	print( "stored " .. l:count( "key") .. " items in the linda before starting consumer lane")
     	if batch > 0 then
     		if l.batched then
    -			lane = lane_batched_gen( l, top, batch)
    +			lane = lane_gobbler_gen( l, top, batch)
     		else
     			print "no batch support in this version of Lanes"
     			lane = lane_eater_gen( l, top)
    @@ -63,7 +87,9 @@ local function ziva( preloop, loop, batch)
     	for i = 1, batch do
     		table.insert( batch_values, i)
     	end
    +	local batch_send_log = "main: sending "..batch.." values"
     	local batch_send = function()
    +		-- print(batch_send_log)
     		l:send( "key", table_unpack( batch_values))
     	end
     	if loop > preloop then
    @@ -76,57 +102,35 @@ local function ziva( preloop, loop, batch)
     	return lanes.now_secs() - t1
     end
     
    +--##################################################################################################
    +
    +TEST1 = TEST1 or 1000
    +PREFILL1 = PREFILL1 or 10000
    +FILL1 = FILL1 or 2000000
    +
     local tests1 =
     {
    -	{ 10000, 2000000, 0},
    -	{ 10000, 2000000, 1},
    -	{ 10000, 2000000, 2},
    -	{ 10000, 2000000, 3},
    -	{ 10000, 2000000, 5},
    -	{ 10000, 2000000, 8},
    -	{ 10000, 2000000, 13},
    -	{ 10000, 2000000, 21},
    -	{ 10000, 2000000, 44},
    +	{ PREFILL1, FILL1, 0},
    +	{ PREFILL1, FILL1, 1},
    +	{ PREFILL1, FILL1, 2},
    +	{ PREFILL1, FILL1, 3},
    +	{ PREFILL1, FILL1, 5},
    +	{ PREFILL1, FILL1, 8},
    +	{ PREFILL1, FILL1, 13},
    +	{ PREFILL1, FILL1, 21},
    +	{ PREFILL1, FILL1, 44},
    +	{ PREFILL1, FILL1, 65},
     }
    -print "############################################\ntests #1"
    -for k, v in pairs( tests1) do
    +print "############################################ tests #1"
    +for i, v in ipairs( tests1) do
    +	if i > TEST1 then break end
     	local pre, loop, batch = v[1], v[2], v[3]
    -	print( "testing", pre, loop, batch)
    -	print( pre, loop, batch, "duration = " .. ziva( pre, loop, batch) .. "\n")
    +	print("-------------------------------------------------\n")
    +	print("START", "prefill="..pre, "fill="..loop, "batch="..batch)
    +	print("DURATION = " .. ziva( pre, loop, batch) .. "\n")
     end
     
    ---[[
    -	V 2.1.0:
    -	ziva( 20000, 0) -> 4s   	ziva( 10000, 20000) -> 3s
    -	ziva( 30000, 0) -> 8s     ziva( 20000, 30000) -> 7s
    -	ziva( 40000, 0) -> 15s    ziva( 30000, 40000) -> 15s
    -	ziva( 50000, 0) -> 24s    ziva( 40000, 50000) -> 23s
    -	ziva( 60000, 0) -> 34s    ziva( 50000, 60000) -> 33s
    -
    -	SIMPLIFIED:
    -	ziva( 20000, 0) -> 4s   	ziva( 10000, 20000) -> 3s
    -	ziva( 30000, 0) -> 9s     ziva( 20000, 30000) -> 8s
    -	ziva( 40000, 0) -> 15s    ziva( 30000, 40000) -> 15s
    -	ziva( 50000, 0) -> 25s    ziva( 40000, 50000) -> 24s
    -	ziva( 60000, 0) -> 35s    ziva( 50000, 60000) -> 35s
    -
    -	FIFO:
    -	ziva( 2000000, 0) -> 9s   ziva( 1000000, 2000000) -> 33s
    -	ziva( 3000000, 0) -> 14s  ziva( 2000000, 3000000) -> 40s
    -	ziva( 4000000, 0) -> 20s  ziva( 3000000, 4000000) -> 27s
    -	ziva( 5000000, 0) -> 24s  ziva( 4000000, 5000000) -> 42s
    -	ziva( 6000000, 0) -> 29s  ziva( 5000000, 6000000) -> 55s
    -
    -	FIFO BATCHED:
    -	ziva( 4000000, 0, 1)  -> 20s
    -	ziva( 4000000, 0, 2)  -> 11s
    -	ziva( 4000000, 0, 3)  -> 7s
    -	ziva( 4000000, 0, 5)  -> 5s
    -	ziva( 4000000, 0, 8)  -> 3s
    -	ziva( 4000000, 0, 13) -> 3s
    -	ziva( 4000000, 0, 21) -> 3s
    -	ziva( 4000000, 0, 44) -> 2s
    -]]
    +--##################################################################################################
     
     -- sequential write/read (no parallelization involved)
     local function ziva2( preloop, loop, batch)
    @@ -159,7 +163,7 @@ local function ziva2( preloop, loop, batch)
     	for i = 1, preloop, step do
     		batch_send()
     	end
    -	print( "stored " .. (l:count( "key") or 0) .. " items in the linda before starting consumer lane")
    +	print( "stored " .. (l:count( "key") or 0) .. " items in the linda before starting the alternating reads and writes")
     	-- loop that alternatively sends and reads data off the linda
     	if loop > preloop then
     		for i = preloop + 1, loop, step do
    @@ -169,40 +173,39 @@ local function ziva2( preloop, loop, batch)
     	end
     	-- here, we have preloop elements still waiting inside the linda
     	for i = 1, preloop, step do
    -			batch_read()
    +		batch_read()
     	end
     	return lanes.now_secs() - t1
     end
     
    +--##################################################################################################
    +
    +TEST2 = TEST2 or 1000
    +PREFILL2 = PREFILL2 or 0
    +FILL2 = FILL2 or 4000000
    +
     local tests2 =
     {
    -	-- prefill, then consume everything
    -	--[[
    -	{ 4000000, 0},
    -	{ 4000000, 0, 1},
    -	{ 4000000, 0, 2},
    -	{ 4000000, 0, 3},
    -	{ 4000000, 0, 5},
    -	{ 4000000, 0, 8},
    -	{ 4000000, 0, 13},
    -	{ 4000000, 0, 21},
    -	{ 4000000, 0, 44},
    -	--]]
    -	-- alternatively fill and consume
    -	{ 0, 4000000},
    -	{ 0, 4000000, 1},
    -	{ 0, 4000000, 2},
    -	{ 0, 4000000, 3},
    -	{ 0, 4000000, 5},
    -	{ 0, 4000000, 8},
    -	{ 0, 4000000, 13},
    -	{ 0, 4000000, 21},
    -	{ 0, 4000000, 44},
    +	{ PREFILL2, FILL2},
    +	{ PREFILL2, FILL2, 1},
    +	{ PREFILL2, FILL2, 2},
    +	{ PREFILL2, FILL2, 3},
    +	{ PREFILL2, FILL2, 5},
    +	{ PREFILL2, FILL2, 8},
    +	{ PREFILL2, FILL2, 13},
    +	{ PREFILL2, FILL2, 21},
    +	{ PREFILL2, FILL2, 44},
    +	{ PREFILL2, FILL2, 65},
     }
     
    -print "\n############################################\ntests #2"
    -for k, v in pairs( tests2) do
    +print "############################################ tests #2"
    +for i, v in ipairs( tests2) do
    +	if i > TEST2 then break end
     	local pre, loop, batch = v[1], v[2], v[3]
    -	print( "testing", pre, loop, batch)
    -	print( pre, loop, batch, "duration = " .. ziva2( pre, loop, batch) .. "\n")
    +	print("-------------------------------------------------\n")
    +	print("START", "prefill="..pre, "fill="..loop, "batch="..(batch or "no"))
    +	print("DURATION = " .. ziva2( pre, loop, batch) .. "\n")
     end
    +
    +print "############################################"
    +print "THE END"
    \ No newline at end of file
    diff --git a/tests/protect_allocator.lua b/tests/protect_allocator.lua
    index 593261e..5cbb1d8 100644
    --- a/tests/protect_allocator.lua
    +++ b/tests/protect_allocator.lua
    @@ -46,5 +46,8 @@ for i = 1, COUNT do
     end
     
     -- wait for completion
    +print "wait for completion"
     linda:receive( linda.batched, "key", COUNT)
    +print "waiting a bit more ..."
    +linda:receive( 1, "foo")
     print "SUCCESS"
    diff --git a/tests/timer.lua b/tests/timer.lua
    index ec23cee..73ecb93 100644
    --- a/tests/timer.lua
    +++ b/tests/timer.lua
    @@ -18,13 +18,14 @@ end
     
     local T1= "1s"  -- these keys can be anything...
     local T2= "5s"
    +local PING_DURATION = 20
     
     local step= {}
     
     lanes.timer( linda, T1, 1.0, 1.0 )
     step[T1]= 1.0
     
    -PRINT( "\n*** Timers every second (not synced to wall clock) ***\n" )
    +PRINT( "\n*** Starting 1s Timer (not synced to wall clock) ***\n" )
     
     local v_first
     local v_last= {}     -- { [channel]= num }
    @@ -46,14 +47,15 @@ while true do
                 -- do not make measurements, first round is not 5secs due to wall clock adjustment
                 T2_first_round= false
             else
    -            assert( math.abs(v-v_last[channel]- step[channel]) < 0.02 )
    +            local dt = math.abs(v-v_last[channel]- step[channel])
    +            assert( dt < 0.02, "channel " .. channel .. " is late: " .. dt)
             end
         end
         
         if not v_first then
             v_first= v
         elseif v-v_first > 3.0 and (not step[T2]) then
    -        PRINT( "\n*** Adding timers every 5 second (synced to wall clock) ***\n" )
    +        PRINT( "\n*** Starting 5s timer (synced to wall clock) ***\n" )
     
             -- The first event can be in the past (just cut seconds down to 5s)
             --
    @@ -63,7 +65,7 @@ while true do
             lanes.timer( linda, T2, date, 5.0 )
             step[T2]= 5.0
     
    -    elseif v-v_first > 10 then    -- exit condition
    +    elseif v-v_first > PING_DURATION then    -- exit condition
             break
         end
         v_last[channel]= v
    @@ -80,7 +82,7 @@ PRINT( "\n*** Listing timers ***\n" )
     local r = lanes.timers() -- list of {linda, key, {}}
     for _,t in ipairs( r) do
         local linda, key, timer = t[1], t[2], t[3]
    -	print( tostring( linda), key, timer[1], timer[2])
    +    print( tostring( linda), key, timer[1], timer[2])
     end
     
     
    -- 
    cgit v1.2.3-55-g6feb