From e97adefde985e30fe31ffa036c74ffb0ce10ca26 Mon Sep 17 00:00:00 2001
From: Benoit Germain
Date: Tue, 1 Mar 2011 21:12:56 +0100
Subject: * 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().
---
CHANGES | 14 ++--
Makefile | 4 +
docs/index.html | 72 ++++++++---------
src/keeper.c | 6 ++
src/lanes.c | 238 +++++++++++++++++++++++++++++++++++--------------------
src/lanes.lua | 4 +-
tests/atexit.lua | 45 +++++++++++
7 files changed, 250 insertions(+), 133 deletions(-)
create mode 100644 tests/atexit.lua
diff --git a/CHANGES b/CHANGES
index bc0368a..58b6061 100644
--- a/CHANGES
+++ b/CHANGES
@@ -3,12 +3,14 @@ CHANGES:
CHANGE X:
-CHANGE 29 BGe 21-Feb-2011
- Make the number of internal keeper states selectable by an optional parameter passed to require.
+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
+ - 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
@@ -29,7 +31,7 @@ CHANGE 26 BGe 14-Feb-2011:
inter-state data copy for unsupported types
CHANGE 25 BGe 12-Feb-2011:
- Changed idfunc signature and contract to clarify the fact it is not lua-callable
+ 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:
@@ -59,7 +61,7 @@ CHANGE 19 BGe 2-Dec-2010:
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
+ A bit of code cleanup
CHANGE 17 BGe 21-Sept-2010:
Fixed stupid compilation errors.
diff --git a/Makefile b/Makefile
index f27404a..ddd8675 100644
--- a/Makefile
+++ b/Makefile
@@ -84,6 +84,7 @@ test:
$(MAKE) fibonacci
$(MAKE) recursive
$(MAKE) func_is_string
+ $(MAKE) atexit
basic: tests/basic.lua $(_TARGET_SO)
$(_PREFIX) $(LUA) $<
@@ -154,6 +155,9 @@ appendud: tests/appendud.lua $(_TARGET_SO)
func_is_string: tests/func_is_string.lua $(_TARGET_SO)
$(_PREFIX) $(LUA) $<
+atexit: tests/atexit.lua $(_TARGET_SO)
+ $(_PREFIX) $(LUA) $<
+
#---
perftest-plain: tests/perftest.lua $(_TARGET_SO)
$(MAKE) _perftest ARGS="$< $(N) -plain"
diff --git a/docs/index.html b/docs/index.html
index 3d2ecf2..ba25515 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -56,7 +56,7 @@
Copyright © 2007-11 Asko Kauppi. All rights reserved.
Lua Lanes is published under the same MIT license as Lua 5.1.
-
This document was revised on 21-Feb-11, and applies to version 2.1.0.
+
This document was revised on 1-Mar-11, and applies to version 2.1.0.
@@ -243,6 +243,7 @@ also in the new lanes.
:receive() or :send() call.
With this option, one can set cancellation check to occur every N
Lua statements. The value true uses a default value (100).
+ It is also possible to manually test for cancel requests with cancel_test().
|
@@ -250,20 +251,21 @@ also in the new lanes.
|
Sets the globals table for the launched threads. This can be used for giving
them constants.
-
+
The global values of different lanes are in no manner connected;
- modifying one will only affect the particular lane. Settings the variable 'threadName' in this table makes VS display the sent name instead of the normal thread name while debugging.
+ modifying one will only affect the particular lane.
|
|
.priority -2..+2 |
The priority of lanes generated. -2 is lowest, +2 is highest.
-
+
Implementation and dependability of priorities varies
by platform. Especially Linux kernel 2.6 is not supporting priorities in user mode.
|
-
+ Each lane also gets a function set_debug_threadname() that it can use anytime to do as the name says.
+Supported debuggers are Microsoft Visual Studio (for the C side) and Decoda (for the Lua side).
Free running lanes
@@ -374,10 +376,15 @@ that id over a Linda once that thread is done (as the last thing you do).
Cancelling
- bool= lane_h:cancel( [timeout_secs=0.0,] [force_kill_bool=false] )
+ bool= lane_h:cancel( [timeout_secs=0.0,] [force_kill_bool=false] )
+
+
+
+ bool= cancel_test()
+
|
-Sends a cancellation request to the lane. If timeout_secs is non-zero, waits
+
cancel()sends a cancellation request to the lane. If timeout_secs is non-zero, waits
for the request to be processed, or a timeout to occur.
Returns true if the lane was already done (in "done", "error" or "cancelled" status)
or if the cancellation was fruitful within timeout period.
@@ -387,8 +394,9 @@ OS thread running the lane is forcefully killed. This means no GC, and should
generally be the last resort.
Cancellation is tested before going to sleep in receive() or send() calls
-and after executing cancelstep Lua statements. A currently pending receive
-or send call is currently not awakened, and may be a reason for a non-detected cancel.
+and after executing cancelstep Lua statements. A currently pending receive()
+or send() call is currently not awakened, and may be a reason for a non-detected cancel.
+It is also possible to manually test for cancel requests with cancel_test().
@@ -799,7 +807,7 @@ can be used for custom userdata as well. Here's what to do.
- Provide an identity function for your userdata, in C. This function is
- used for creation and deletion of your deep userdata (the shared resource),
+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
@@ -817,12 +825,12 @@ can be used for custom userdata as well. Here's what to do.
"delete": receives this same pointer on the stack, and should cleanup the object.
-
"metatable": should build a metatable for the object. Don't cache the metatable
- yourself, Lanes takes care of it ("metatable" should only be invoked once).
+yourself, Lanes takes care of it ("metatable" should only be invoked once).
-
"module": is the name of the module that exports the idfunc,
- to be pushed on the stack as a string. It is necessary so that Lanes can require it in
- any Lane and keeper state that receives a userdata. This is to prevent crashes in situations
- where the module could be unloaded while the idfunc pointer is still held.
+to be pushed on the stack as a string. It is necessary so that Lanes can require it in
+any Lane and keeper state that receives a userdata. This is to prevent crashes in situations
+where the module could be unloaded while the idfunc pointer is still held.
Take a look at linda_id in lanes.c.
@@ -917,10 +925,10 @@ Here are some things one should consider, if best performance is vital:
merged into one main timer state (see timer.lua); no OS side
timers are utilized.
- - Lindas are hashed to a number of "keeper states", which are a locking entity.
- If you are using a lot of Linda objects, it may be useful to try having more of
- these keeper states. By default, only one is used but this is an implementation detail.
- It is possible to require( "lanes", N) to use more keeper states.
+
- Lindas are hashed to a fixed number of "keeper states", which are a locking entity.
+ If you are using a lot of Linda objects,
+ it may be useful to try having more of these keeper states. By default,
+ only one is used (see KEEPER_STATES_N), but this is an implementation detail.
@@ -952,31 +960,15 @@ its actual value.
Change log
-Feb-2011 (2.1.0)
+
+Mar-2011 (2.1.0)
- - Added an auto-require mechanism to ensure any deep userdata transiting in a lane causes its parent module to be required in that lane
-
- - Changed idfunc signature and contract to clarify the fact it is not lua-callable and to be able to require the module it was exported from in the target lanes
- - 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
- - 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
-
-
- - Fixed application hang-up because keeper state was not released in case of errors thrown by inter-state data copy for unsupported types
- - 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
-
-
- - Moved keeper-related code in a separate source file
- - keeper.lua is now embedded in text form instead of bytecode to improve LuaJIT2-compatibility
- - Make the number of internal keeper states selctable by an optional parameter passed to require.
+ - 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().
-
Feb-2011 (2.0.11):
-
+
- 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.
- Changed lanes.c to export functions as a module rather than writing them directly to the globals table.
diff --git a/src/keeper.c b/src/keeper.c
index f89c638..01e8880 100644
--- a/src/keeper.c
+++ b/src/keeper.c
@@ -94,6 +94,12 @@ char const *init_keepers( int const _nbKeepers)
if (!L)
return "out of memory";
+ // to see VM name in Decoda debugger
+ lua_pushliteral( L, "Keeper #");
+ lua_pushinteger( L, i + 1);
+ lua_concat( L, 2);
+ lua_setglobal( L, "decoda_name");
+
luaG_openlibs( L, "io,table,package" ); // 'io' for debugging messages, package because we need to require modules exporting idfuncs
serialize_require( L);
diff --git a/src/lanes.c b/src/lanes.c
index 8b62532..e5a2987 100644
--- a/src/lanes.c
+++ b/src/lanes.c
@@ -119,8 +119,6 @@ struct s_lane {
//
// M: sub-thread OS thread
// S: not used
-
- char threadName[64]; //Optional, for debugging and such. owerflowable by a strcpy.
lua_State *L;
//
@@ -132,6 +130,10 @@ struct s_lane {
// M: sets to PENDING (before launching)
// S: updates -> RUNNING/WAITING -> DONE/ERROR_ST/CANCELLED
+ volatile SIGNAL_T waiting_on;
+ //
+ // When status is WAITING, points on the linda's signal the thread waits on, else NULL
+
volatile bool_t cancel_request;
//
// M: sets to FALSE, flags TRUE for cancel request
@@ -333,17 +335,21 @@ LUAG_FUNC( linda_send)
{
prev_status = s->status;
s->status = WAITING;
+ ASSERT_L( s->waiting_on == NULL);
+ s->waiting_on = &linda->read_happened;
}
if( !SIGNAL_WAIT( &linda->read_happened, &K->lock_, timeout))
{
if( s)
{
+ s->waiting_on = NULL;
s->status = prev_status;
}
break;
}
if( s)
{
+ s->waiting_on = NULL;
s->status = prev_status;
}
}
@@ -450,17 +456,21 @@ LUAG_FUNC( linda_receive)
{
prev_status = s->status;
s->status = WAITING;
+ ASSERT_L( s->waiting_on == NULL);
+ s->waiting_on = &linda->write_happened;
}
if( !SIGNAL_WAIT( &linda->write_happened, &K->lock_, timeout))
{
if( s)
{
+ s->waiting_on = NULL;
s->status = prev_status;
}
break;
}
if( s)
{
+ s->waiting_on = NULL;
s->status = prev_status;
}
}
@@ -870,28 +880,33 @@ volatile DEEP_PRELUDE *timer_deep; // = NULL
/*
* Process end; cancel any still free-running threads
*/
-static void selfdestruct_atexit( void ) {
-
+static void selfdestruct_atexit( void )
+{
if (selfdestruct_first == SELFDESTRUCT_END) return; // no free-running threads
- // Signal _all_ still running threads to exit
+ // Signal _all_ still running threads to exit (including the timer thread)
//
MUTEX_LOCK( &selfdestruct_cs );
{
struct s_lane *s= selfdestruct_first;
- while( s != SELFDESTRUCT_END ) {
- s->cancel_request= TRUE;
- s= s->selfdestruct_next;
+ while( s != SELFDESTRUCT_END )
+ {
+ // attempt a regular unforced cancel with a small timeout
+ bool_t cancelled = thread_cancel( s, 0.0001, FALSE);
+ // if we failed, and we know the thread is waiting on a linda
+ if( cancelled == FALSE && s->status == WAITING && s->waiting_on != NULL)
+ {
+ // 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...
+ SIGNAL_T waiting_on = s->waiting_on;
+ s->waiting_on = NULL;
+ SIGNAL_ALL( waiting_on);
+ }
+ s = s->selfdestruct_next;
}
}
MUTEX_UNLOCK( &selfdestruct_cs );
- // Tell the timer thread to check it's cancel request
- {
- struct s_Linda *td = timer_deep->deep;
- SIGNAL_ALL( &td->write_happened);
- }
-
// When noticing their cancel, the lanes will remove themselves from
// the selfdestruct chain.
@@ -914,15 +929,37 @@ static void selfdestruct_atexit( void ) {
// -- AKa 25-Oct-2008
//
#ifndef ATEXIT_WAIT_SECS
- # define ATEXIT_WAIT_SECS (0.1)
+ # define ATEXIT_WAIT_SECS (0.25)
#endif
{
double t_until= now_secs() + ATEXIT_WAIT_SECS;
-
- while( selfdestruct_first != SELFDESTRUCT_END ) {
+
+ while( selfdestruct_first != SELFDESTRUCT_END )
+ {
YIELD(); // give threads time to act on their cancel
-
- if (now_secs() >= t_until) break;
+ {
+ // count the number of cancelled thread that didn't have the time to act yet
+ int n = 0;
+ double t_now = 0.0;
+ MUTEX_LOCK( &selfdestruct_cs );
+ {
+ struct s_lane *s = selfdestruct_first;
+ while( s != SELFDESTRUCT_END)
+ {
+ if( s->cancel_request)
+ ++ n;
+ s = s->selfdestruct_next;
+ }
+ }
+ MUTEX_UNLOCK( &selfdestruct_cs );
+ // if timeout elapsed, or we know all threads have acted, stop waiting
+ t_now = now_secs();
+ if( n == 0 || ( t_now >= t_until))
+ {
+ DEBUGEXEC(fprintf( stderr, "%d uncancelled lane(s) remain after waiting %fs at process end.\n", n, ATEXIT_WAIT_SECS - (t_until - t_now)));
+ break;
+ }
+ }
}
}
#endif
@@ -951,18 +988,21 @@ static void selfdestruct_atexit( void ) {
//
DEBUGEXEC(fprintf( stderr, "Left %d lane(s) with cancel request at process end.\n", n ));
#else
+ // 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
n=0;
MUTEX_LOCK( &selfdestruct_cs );
{
struct s_lane *s= selfdestruct_first;
- while( s != SELFDESTRUCT_END ) {
+ while( s != SELFDESTRUCT_END)
+ {
struct s_lane *next_s= s->selfdestruct_next;
s->selfdestruct_next= NULL; // detach from selfdestruct chain
-
- THREAD_KILL( &s->thread );
- lua_close(s->L);
- free(s);
- s= next_s;
+ THREAD_KILL( &s->thread);
+ // NO lua_close() in this case because we don't know where execution of the state was interrupted
+ free( s);
+ s = next_s;
n++;
}
selfdestruct_first= SELFDESTRUCT_END;
@@ -1021,6 +1061,19 @@ static void cancel_hook( lua_State *L, lua_Debug *ar ) {
}
+//---
+// bool= cancel_test()
+//
+// Available inside the global namespace of lanes
+// returns a boolean saying if a cancel request is pending
+//
+LUAG_FUNC( cancel_test)
+{
+ bool_t test = cancel_test( L);
+ lua_pushboolean( L, test);
+ return 1;
+}
+
//---
// = _single( [cores_uint=1] )
//
@@ -1131,12 +1184,12 @@ typedef struct tagTHREADNAME_INFO
} THREADNAME_INFO;
#pragma pack(pop)
-void SetThreadName( DWORD dwThreadID, char* threadName)
+void SetThreadName( DWORD dwThreadID, char const *_threadName)
{
THREADNAME_INFO info;
Sleep(10);
info.dwType = 0x1000;
- info.szName = threadName;
+ info.szName = _threadName;
info.dwThreadID = dwThreadID;
info.dwFlags = 0;
@@ -1150,6 +1203,22 @@ void SetThreadName( DWORD dwThreadID, char* threadName)
}
#endif
+LUAG_FUNC( set_debug_threadname)
+{
+ char const *threadName;
+ luaL_checktype( L, -1, LUA_TSTRING);
+ threadName = lua_tostring( L, -1);
+
+#if defined PLATFORM_WIN32 && !defined __GNUC__
+ // to see thead name in Visual Studio C debugger
+ SetThreadName(-1, threadName);
+#endif
+
+ // to see VM name in Decoda debugger Virtual Machine window
+ lua_setglobal( L, "decoda_name");
+
+ return 0;
+}
//---
#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC)
@@ -1162,11 +1231,6 @@ void SetThreadName( DWORD dwThreadID, char* threadName)
int rc, rc2;
lua_State *L= s->L;
-
-#if defined PLATFORM_WIN32 && !defined __GNUC__
- SetThreadName(-1, s->threadName);
-#endif
-
s->status= RUNNING; // PENDING -> RUNNING
// Tie "set_finalizer()" to the state
@@ -1174,6 +1238,16 @@ void SetThreadName( DWORD dwThreadID, char* threadName)
lua_pushcfunction( L, LG_set_finalizer );
lua_setglobal( L, "set_finalizer" );
+ // Tie "set_debug_threadname()" to the state
+ //
+ lua_pushcfunction( L, LG_set_debug_threadname);
+ lua_setglobal( L, "set_debug_threadname" );
+
+ // Tie "cancel_test()" to the state
+ //
+ lua_pushcfunction( L, LG_cancel_test);
+ lua_setglobal( L, "cancel_test" );
+
#ifdef ERROR_FULL_STACK
STACK_GROW( L, 1 );
lua_pushcfunction( L, lane_error );
@@ -1240,7 +1314,7 @@ void SetThreadName( DWORD dwThreadID, char* threadName)
//
lua_newtable(L);
}
-
+ s->waiting_on = NULL; // just in case
if (s->selfdestruct_next != NULL) {
// We're a free-running thread and no-one's there to clean us up.
//
@@ -1276,7 +1350,6 @@ void SetThreadName( DWORD dwThreadID, char* threadName)
MUTEX_UNLOCK( &s->done_lock_ );
#endif
}
-
return 0; // ignored
}
@@ -1295,7 +1368,6 @@ LUAG_FUNC( thread_new )
lua_State *L2;
struct s_lane *s;
struct s_lane **ud;
- const char *threadName = 0;
const char *libs= lua_tostring( L, 2 );
uint_t cs= luaG_optunsigned( L, 3,0);
@@ -1330,10 +1402,7 @@ LUAG_FUNC( thread_new )
luaL_error( L, "Expected table, got %s", luaG_typename(L,glob) );
lua_pushvalue( L, glob );
- lua_pushstring( L, "threadName");
- lua_gettable( L, -2);
- threadName = lua_tostring( L, -1);
- lua_pop( L, 1);
+
luaG_inter_move( L, L2, 1); // moves the table to L2
// L2 [-1]: table of globals
@@ -1398,11 +1467,9 @@ LUAG_FUNC( thread_new )
//memset( s, 0, sizeof(struct s_lane) );
s->L= L2;
s->status= PENDING;
+ s->waiting_on = NULL;
s->cancel_request= FALSE;
- threadName = threadName ? threadName : "";
- strcpy(s->threadName, threadName);
-
#if !( (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) )
MUTEX_INIT( &s->done_lock_ );
SIGNAL_INIT( &s->done_signal_ );
@@ -1484,7 +1551,7 @@ LUAG_FUNC( thread_gc )
#if 0
lua_close( s->L );
s->L = 0;
-#else
+#else // 0
DEBUGEXEC(fprintf( stderr, "** Joining with a killed thread (needs testing) **" ));
#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN)
THREAD_WAIT( &s->thread, -1 );
@@ -1492,7 +1559,7 @@ LUAG_FUNC( thread_gc )
THREAD_WAIT( &s->thread, &s->done_signal_, &s->done_lock_, &s->status, -1 );
#endif
DEBUGEXEC(fprintf( stderr, "** Joined ok **" ));
-#endif
+#endif // 0
}
else if( s->L)
{
@@ -1529,54 +1596,55 @@ LUAG_FUNC( thread_gc )
// managed to cancel it.
// false if the cancellation timed out, or a kill was needed.
//
-LUAG_FUNC( thread_cancel )
+static bool_t thread_cancel( struct s_lane *s, double secs, bool_t force)
{
- struct s_lane *s= lua_toLane(L,1);
- double secs= 0.0;
- uint_t force_i=2;
- bool_t force, done= TRUE;
-
- if (lua_isnumber(L,2)) {
- secs= lua_tonumber(L,2);
- force_i++;
- } else if (lua_isnil(L,2))
- force_i++;
-
- force= lua_toboolean(L,force_i); // FALSE if nothing there
-
- // We can read 's->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN)
- //
- if (s->status < DONE) {
- s->cancel_request= TRUE; // it's now signalled to stop
-
- done= thread_cancel( s, secs, force );
- }
-
- lua_pushboolean( L, done );
- return 1;
-}
-
-static bool_t thread_cancel( struct s_lane *s, double secs, bool_t force )
-{
- bool_t done=
+ bool_t done= TRUE;
+ // We can read 's->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN)
+ //
+ if( s->status < DONE)
+ {
+ s->cancel_request = TRUE; // it's now signalled to stop
+ done=
#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN)
- THREAD_WAIT( &s->thread, secs );
+ THREAD_WAIT( &s->thread, secs);
#else
- THREAD_WAIT( &s->thread, &s->done_signal_, &s->done_lock_, &s->status, secs );
+ THREAD_WAIT( &s->thread, &s->done_signal_, &s->done_lock_, &s->status, secs);
#endif
- if ((!done) && force) {
- // Killing is asynchronous; we _will_ wait for it to be done at
- // GC, to make sure the data structure can be released (alternative
- // would be use of "cancellation cleanup handlers" that at least
- // PThread seems to have).
- //
- THREAD_KILL( &s->thread );
- s->mstatus= KILLED; // mark 'gc' to wait for it
- }
- return done;
+ if ((!done) && force)
+ {
+ // Killing is asynchronous; we _will_ wait for it to be done at
+ // GC, to make sure the data structure can be released (alternative
+ // would be use of "cancellation cleanup handlers" that at least
+ // PThread seems to have).
+ //
+ THREAD_KILL( &s->thread);
+ s->mstatus= KILLED; // mark 'gc' to wait for it
+ }
+ }
+ return done;
}
+LUAG_FUNC( thread_cancel)
+{
+ struct s_lane *s= lua_toLane(L,1);
+ double secs= 0.0;
+ uint_t force_i=2;
+ bool_t force, done= TRUE;
+
+ if (lua_isnumber(L,2)) {
+ secs= lua_tonumber(L,2);
+ force_i++;
+ } else if (lua_isnil(L,2))
+ force_i++;
+
+ force= lua_toboolean(L,force_i); // FALSE if nothing there
+
+ done = thread_cancel( s, secs, force);
+
+ lua_pushboolean( L, done);
+ return 1;
+}
//---
// str= thread_status( lane )
diff --git a/src/lanes.lua b/src/lanes.lua
index 78582f9..704559a 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -399,8 +399,8 @@ if first_time then
-- We let the timer lane be a "free running" thread; no handle to it
-- remains.
--
- gen( "io,package", { priority=max_prio, globals={threadName="LanesTimer"} }, function()
-
+ gen( "io,package", { priority=max_prio}, function()
+ set_debugger_threadname( "LanesTimer")
while true do
local next_wakeup= check_timers()
diff --git a/tests/atexit.lua b/tests/atexit.lua
new file mode 100644
index 0000000..fb4f34a
--- /dev/null
+++ b/tests/atexit.lua
@@ -0,0 +1,45 @@
+require "lanes"
+
+-- create a free-running lane
+
+local linda = lanes.linda()
+
+local f = function( _linda)
+ _linda:receive("oy")
+end
+
+local g = function()
+ local cancelled
+ repeat
+ cancelled = cancel_test()
+ until cancelled
+ print "User cancellation detected!"
+end
+
+local genF = lanes.gen( "", {globals = {threadName = "mylane"}}, f)
+local genG = lanes.gen( "", g)
+
+
+-- launch a good batch of free running lanes
+for i = 1, 10 do
+ -- if i%50 == 0 then print( i) end
+ local h = genF( linda)
+ local status
+ repeat
+ status = h.status
+ --print( status)
+ until status == "waiting"
+
+ -- [[
+ local h = genG()
+ local status
+ repeat
+ status = h.status
+ --print( status)
+ until status == "running"
+ --]]
+end
+
+print "exiting"
+
+-- let process end terminate them and see what happens
\ No newline at end of file
--
cgit v1.2.3-55-g6feb