From 32f8cdfc73ed90dcf88ffcf4bfc1a3e4d5a69e6c Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Mon, 22 Apr 2019 17:17:03 +0200 Subject: Moved cancellation code in separate files --- src/cancel.c | 252 +++++++++++++++++++++++++++++++++++++++++++++++++ src/cancel.h | 57 +++++++++++ src/lanes.c | 203 +-------------------------------------- src/lanes_private.h | 24 +---- src/macros_and_utils.h | 2 + src/tools.c | 4 +- src/tools.h | 2 - 7 files changed, 316 insertions(+), 228 deletions(-) create mode 100644 src/cancel.c create mode 100644 src/cancel.h (limited to 'src') diff --git a/src/cancel.c b/src/cancel.c new file mode 100644 index 0000000..11e100d --- /dev/null +++ b/src/cancel.c @@ -0,0 +1,252 @@ +/* +-- +-- CANCEL.C +-- +-- Lane cancellation support +-- +-- Author: Benoit Germain +-- +--[[ +=============================================================================== + +Copyright (C) 2011-2019 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=============================================================================== +]]-- +*/ + +#include + +#include "threading.h" +#include "cancel.h" +#include "tools.h" +#include "lanes_private.h" + +// ################################################################################################ +// ################################################################################################ + +/* +* Check if the thread in question ('L') has been signalled for cancel. +* +* Called by cancellation hooks and/or pending Linda operations (because then +* the check won't affect performance). +* +* Returns TRUE if any locks are to be exited, and 'cancel_error()' called, +* to make execution of the lane end. +*/ +static inline enum e_cancel_request cancel_test( lua_State* L) +{ + Lane* const s = get_lane_from_registry( L); + // 's' is NULL for the original main state (and no-one can cancel that) + return s ? s->cancel_request : CANCEL_NONE; +} + +// ################################################################################################ + +//--- +// bool = cancel_test() +// +// Available inside the global namespace of lanes +// returns a boolean saying if a cancel request is pending +// +LUAG_FUNC( cancel_test) +{ + enum e_cancel_request test = cancel_test( L); + lua_pushboolean( L, test != CANCEL_NONE); + return 1; +} + +// ################################################################################################ +// ################################################################################################ + +void cancel_hook( lua_State* L, lua_Debug* ar) +{ + (void)ar; + DEBUGSPEW_CODE( fprintf( stderr, "cancel_hook\n")); + if( cancel_test( L) != CANCEL_NONE) + { + cancel_error( L); + } +} + +// ################################################################################################ +// ################################################################################################ + +//--- +// = thread_cancel( lane_ud [,timeout_secs=0.0] [,force_kill_bool=false] ) +// +// The originator thread asking us specifically to cancel the other thread. +// +// 'timeout': <0: wait forever, until the lane is finished +// 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). +// +// Returns: true if the lane was already finished (DONE/ERROR_ST/CANCELLED) or if we +// managed to cancel it. +// false if the cancellation timed out, or a kill was needed. +// + +// ################################################################################################ + +static cancel_result thread_cancel_soft( Lane* s, bool_t wake_lindas_) +{ + s->cancel_request = CANCEL_SOFT; // it's now signaled to stop + // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own + if( wake_lindas_) // wake the thread so that execution returns from any pending linda operation if desired + { + SIGNAL_T *waiting_on = s->waiting_on; + if( s->status == WAITING && waiting_on != NULL) + { + SIGNAL_ALL( waiting_on); + } + } + // say we succeeded though + return CR_Cancelled; +} + +// ################################################################################################ + +static cancel_result thread_cancel_hard( lua_State* L, Lane* s, double secs_, bool_t force_, double waitkill_timeout_) +{ + cancel_result result; + + s->cancel_request = CANCEL_HARD; // it's now signaled to stop + { + SIGNAL_T *waiting_on = s->waiting_on; + if( s->status == WAITING && waiting_on != NULL) + { + SIGNAL_ALL( waiting_on); + } + } + + result = THREAD_WAIT( &s->thread, secs_, &s->done_signal, &s->done_lock, &s->status) ? CR_Cancelled : CR_Timeout; + + if( (result == CR_Timeout) && force_) + { + // Killing is asynchronous; we _will_ wait for it to be done at + // GC, to make sure the data structure can be released (alternative + // would be use of "cancellation cleanup handlers" that at least + // PThread seems to have). + // + THREAD_KILL( &s->thread); +#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( &s->thread, waitkill_timeout_, &s->done_signal, &s->done_lock, &s->status); + if( result == CR_Timeout) + { + return 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 + s->mstatus = KILLED; // mark 'gc' to wait for it + // note that s->status value must remain to whatever it was at the time of the kill + // because we need to know if we can lua_close() the Lua State or not. + result = CR_Killed; + } + return result; +} + +// ################################################################################################ + +cancel_result thread_cancel( lua_State* L, Lane* s, double secs_, bool_t force_, double waitkill_timeout_) +{ + // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here + // We can read 's->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) + if( s->mstatus == KILLED) + { + return CR_Killed; + } + + if( s->status >= DONE) + { + // say "ok" by default, including when lane is already done + return CR_Cancelled; + } + + // signal the linda the wake up the thread so that it can react to the cancel query + // let us hope we never land here with a pointer on a linda that has been destroyed... + if( secs_ < 0.0) + { + return thread_cancel_soft( s, force_); + } + + return thread_cancel_hard( L, s, secs_, force_, waitkill_timeout_); +} + +// ################################################################################################ +// ################################################################################################ + +// lane_h:cancel( [timeout] [, force [, forcekill_timeout]]) +LUAG_FUNC( thread_cancel) +{ + Lane* s = lua_toLane( L, 1); + double secs = 0.0; + int force_i = 2; + int forcekill_timeout_i = 3; + + if( lua_isnumber( L, 2)) + { + secs = lua_tonumber( L, 2); + if( secs < 0.0 && lua_gettop( L) > 3) + { + return luaL_error( L, "can't force_kill a soft cancel"); + } + // negative timeout and force flag means we want to wake linda-waiting threads + ++ force_i; + ++ forcekill_timeout_i; + } + else if( lua_isnil( L, 2)) + { + ++ force_i; + ++ forcekill_timeout_i; + } + + { + bool_t force = lua_toboolean( L, force_i); // FALSE if nothing there + double forcekill_timeout = luaL_optnumber( L, forcekill_timeout_i, 0.0); + + switch( thread_cancel( L, s, secs, force, forcekill_timeout)) + { + case CR_Timeout: + lua_pushboolean( L, 0); + lua_pushstring( L, "timeout"); + return 2; + + case CR_Cancelled: + lua_pushboolean( L, 1); + return 1; + + case CR_Killed: + lua_pushboolean( L, 0); + lua_pushstring( L, "killed"); + return 2; + } + } + // should never happen, only here to prevent the compiler from complaining of "not all control paths returning a value" + return 0; +} diff --git a/src/cancel.h b/src/cancel.h new file mode 100644 index 0000000..3112809 --- /dev/null +++ b/src/cancel.h @@ -0,0 +1,57 @@ +#if !defined( __LANES_CANCEL_H__) +#define __LANES_CANCEL_H__ 1 + +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" + +#include "uniquekey.h" +#include "macros_and_utils.h" + +// ################################################################################################ + +typedef struct s_Lane Lane; // forward + +/* + * Lane cancellation request modes + */ +enum e_cancel_request +{ + CANCEL_NONE, // no pending cancel request + CANCEL_SOFT, // user wants the lane to cancel itself manually on cancel_test() + CANCEL_HARD // user wants the lane to be interrupted (meaning code won't return from those functions) from inside linda:send/receive calls +}; + +typedef enum +{ + CR_Timeout, + CR_Cancelled, + CR_Killed +} cancel_result; + +// 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 + +void cancel_hook( lua_State* L, lua_Debug* ar); + +cancel_result thread_cancel( lua_State* L, Lane* s, double secs_, bool_t force_, double waitkill_timeout_); + +static inline int cancel_error( lua_State* L) +{ + STACK_GROW( L, 1); + push_unique_key( L, CANCEL_ERROR); // special error value + return lua_error( L); // doesn't return +} + +// ################################################################################################ +// ################################################################################################ + +LUAG_FUNC( cancel_test); +LUAG_FUNC( thread_cancel); + +// ################################################################################################ + +#endif // __LANES_CANCEL_H__ diff --git a/src/lanes.c b/src/lanes.c index 8762766..cbac6da 100644 --- a/src/lanes.c +++ b/src/lanes.c @@ -1,6 +1,6 @@ /* * LANES.C Copyright (c) 2007-08, Asko Kauppi - * Copyright (C) 2009-17, Benoit Germain + * Copyright (C) 2009-19, Benoit Germain * * Multithreading in Lua. * @@ -56,7 +56,7 @@ =============================================================================== Copyright (C) 2007-10 Asko Kauppi - 2011-17 Benoit Germain + 2011-19 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 @@ -124,33 +124,6 @@ static void securize_debug_threadname( lua_State* L, Lane* s) STACK_END( L, 0); } -/* -* Check if the thread in question ('L') has been signalled for cancel. -* -* Called by cancellation hooks and/or pending Linda operations (because then -* the check won't affect performance). -* -* Returns TRUE if any locks are to be exited, and 'cancel_error()' called, -* to make execution of the lane end. -*/ -static inline enum e_cancel_request cancel_test( lua_State* L) -{ - Lane* const s = get_lane_from_registry( L); - // 's' is NULL for the original main state (and no-one can cancel that) - return s ? s->cancel_request : CANCEL_NONE; -} - -static void cancel_hook( lua_State* L, lua_Debug* ar) -{ - (void)ar; - DEBUGSPEW_CODE( fprintf( stderr, "cancel_hook\n")); - if( cancel_test( L) != CANCEL_NONE) - { - cancel_error( L); - } -} - - #if ERROR_FULL_STACK static int lane_error( lua_State* L); // crc64/we of string "STACKTRACE_REGKEY" generated at http://www.nitrxgen.net/hashgen/ @@ -404,115 +377,6 @@ static int run_finalizers( lua_State* L, int lua_rc) * ############################################################################################### */ -//--- -// = thread_cancel( lane_ud [,timeout_secs=0.0] [,force_kill_bool=false] ) -// -// The originator thread asking us specifically to cancel the other thread. -// -// 'timeout': <0: wait forever, until the lane is finished -// 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). -// -// Returns: true if the lane was already finished (DONE/ERROR_ST/CANCELLED) or if we -// managed to cancel it. -// false if the cancellation timed out, or a kill was needed. -// - -typedef enum -{ - CR_Timeout, - CR_Cancelled, - CR_Killed -} cancel_result; - -static cancel_result thread_cancel_soft( lua_State* L, Lane* s, bool_t wake_lindas_) -{ - s->cancel_request = CANCEL_SOFT; // it's now signaled to stop - // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own - if( wake_lindas_) // wake the thread so that execution returns from any pending linda operation if desired - { - SIGNAL_T *waiting_on = s->waiting_on; - if( s->status == WAITING && waiting_on != NULL) - { - SIGNAL_ALL( waiting_on); - } - } - // say we succeeded though - return CR_Cancelled; -} - -static cancel_result thread_cancel_hard( lua_State* L, Lane* s, double secs_, bool_t force_, double waitkill_timeout_) -{ - cancel_result result; - - s->cancel_request = CANCEL_HARD; // it's now signaled to stop - { - SIGNAL_T *waiting_on = s->waiting_on; - if( s->status == WAITING && waiting_on != NULL) - { - SIGNAL_ALL( waiting_on); - } - } - - result = THREAD_WAIT( &s->thread, secs_, &s->done_signal, &s->done_lock, &s->status) ? CR_Cancelled : CR_Timeout; - - if( (result == CR_Timeout) && force_) - { - // Killing is asynchronous; we _will_ wait for it to be done at - // GC, to make sure the data structure can be released (alternative - // would be use of "cancellation cleanup handlers" that at least - // PThread seems to have). - // - THREAD_KILL( &s->thread); -#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( &s->thread, waitkill_timeout_, &s->done_signal, &s->done_lock, &s->status); - if( result == CR_Timeout) - { - return 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 - s->mstatus = KILLED; // mark 'gc' to wait for it - // note that s->status value must remain to whatever it was at the time of the kill - // because we need to know if we can lua_close() the Lua State or not. - result = CR_Killed; - } - return result; -} - -static cancel_result thread_cancel( lua_State* L, Lane* s, double secs_, bool_t force_, double waitkill_timeout_) -{ - // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here - // We can read 's->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) - if( s->mstatus == KILLED) - { - return CR_Killed; - } - - if( s->status >= DONE) - { - // say "ok" by default, including when lane is already done - return CR_Cancelled; - } - - // signal the linda the wake up the thread so that it can react to the cancel query - // let us hope we never land here with a pointer on a linda that has been destroyed... - if( secs_ < 0.0) - { - return thread_cancel_soft( L, s, force_); - } - - return thread_cancel_hard( L, s, secs_, force_, waitkill_timeout_); -} - // // Protects modifying the selfdestruct chain @@ -733,19 +597,6 @@ static int selfdestruct_gc( lua_State* L) } -//--- -// bool = cancel_test() -// -// Available inside the global namespace of lanes -// returns a boolean saying if a cancel request is pending -// -LUAG_FUNC( cancel_test) -{ - enum e_cancel_request test = cancel_test( L); - lua_pushboolean( L, test != CANCEL_NONE); - return 1; -} - //--- // = _single( [cores_uint=1] ) // @@ -1527,56 +1378,6 @@ LUAG_FUNC( thread_gc) return 0; } -// lane_h:cancel( [timeout] [, force [, forcekill_timeout]]) -LUAG_FUNC( thread_cancel) -{ - Lane* s = lua_toLane( L, 1); - double secs = 0.0; - int force_i = 2; - int forcekill_timeout_i = 3; - - if( lua_isnumber( L, 2)) - { - secs = lua_tonumber( L, 2); - if( secs < 0.0 && lua_gettop( L) > 3) - { - return luaL_error( L, "can't force_kill a soft cancel"); - } - // negative timeout and force flag means we want to wake linda-waiting threads - ++ force_i; - ++ forcekill_timeout_i; - } - else if( lua_isnil( L, 2)) - { - ++ force_i; - ++ forcekill_timeout_i; - } - - { - bool_t force = lua_toboolean( L, force_i); // FALSE if nothing there - double forcekill_timeout = luaL_optnumber( L, forcekill_timeout_i, 0.0); - - switch( thread_cancel( L, s, secs, force, forcekill_timeout)) - { - case CR_Timeout: - lua_pushboolean( L, 0); - lua_pushstring( L, "timeout"); - return 2; - - case CR_Cancelled: - lua_pushboolean( L, 1); - return 1; - - case CR_Killed: - lua_pushboolean( L, 0); - lua_pushstring( L, "killed"); - return 2; - } - } - // should never happen, only here to prevent the compiler from complaining of "not all control paths returning a value" - return 0; -} - //--- // str= thread_status( lane ) // diff --git a/src/lanes_private.h b/src/lanes_private.h index 16c178d..ac05129 100644 --- a/src/lanes_private.h +++ b/src/lanes_private.h @@ -2,16 +2,7 @@ #define __lanes_private_h__ 1 #include "uniquekey.h" - -/* - * Lane cancellation request modes - */ -enum e_cancel_request -{ - CANCEL_NONE, // no pending cancel request - CANCEL_SOFT, // user wants the lane to cancel itself manually on cancel_test() - CANCEL_HARD // user wants the lane to be interrupted (meaning code won't return from those functions) from inside linda:send/receive calls -}; +#include "cancel.h" // NOTE: values to be changed by either thread, during execution, without // locking, are marked "volatile" @@ -86,12 +77,6 @@ typedef struct s_Lane Lane; // #define lua_toLane( L, i) (*((Lane**) luaL_checkudata( L, i, "Lane"))) -// 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 - static inline Lane* get_lane_from_registry( lua_State* L) { Lane* s; @@ -104,11 +89,4 @@ static inline Lane* get_lane_from_registry( lua_State* L) return s; } -static inline int cancel_error( lua_State* L) -{ - STACK_GROW( L, 1); - push_unique_key( L, CANCEL_ERROR); // special error value - return lua_error( L); // doesn't return -} - #endif // __lanes_private_h__ \ No newline at end of file diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h index 4b0b9d3..ae3a6c9 100644 --- a/src/macros_and_utils.h +++ b/src/macros_and_utils.h @@ -97,4 +97,6 @@ extern char const* debugspew_indent; lua_rawget( L, LUA_REGISTRYINDEX); \ } +#define LUAG_FUNC( func_name) int LG_##func_name( lua_State* L) + #endif // MACROS_AND_UTILS_H diff --git a/src/tools.c b/src/tools.c index 48e904c..02d51ae 100644 --- a/src/tools.c +++ b/src/tools.c @@ -1736,7 +1736,7 @@ static void inter_copy_keyvaluepair( Universe* U, lua_State* L2, uint_t L2_cache } } -bool_t copyclone( 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_) +static bool_t copyclone( Universe* U, lua_State* L2, uint_t L2_cache_i, lua_State* L, uint_t i, LookupMode mode_, char const* upName_) { STACK_CHECK( L, 0); STACK_CHECK( L2, 0); @@ -1849,7 +1849,7 @@ static bool_t inter_copy_userdata( Universe* U, lua_State* L2, uint_t L2_cache_i STACK_MID( L, 0); STACK_MID( L2, 0); - if( copyclone( U, L2, L2_cache_i, L, i, vt, mode_, upName_)) + if( copyclone( U, L2, L2_cache_i, L, i, mode_, upName_)) { STACK_MID( L, 0); STACK_MID( L2, 1); diff --git a/src/tools.h b/src/tools.h index 71460c3..8a8ad7b 100644 --- a/src/tools.h +++ b/src/tools.h @@ -13,8 +13,6 @@ typedef struct s_Universe Universe; // ################################################################################################ -#define LUAG_FUNC( func_name) int LG_##func_name( lua_State* L) - #define luaG_optunsigned(L,i,d) ((uint_t) luaL_optinteger(L,i,d)) #define luaG_tounsigned(L,i) ((uint_t) lua_tointeger(L,i)) -- cgit v1.2.3-55-g6feb