From 11b19bf4d7dfc883e18ea579d978c620865d843f Mon Sep 17 00:00:00 2001
From: Benoit Germain <bnt.germain@gmail.com>
Date: Fri, 14 Dec 2012 15:35:26 +0100
Subject: lanes.threads(): core implementation

---
 src/lanes.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 112 insertions(+), 31 deletions(-)

(limited to 'src/lanes.c')

diff --git a/src/lanes.c b/src/lanes.c
index 6789e4e..161de59 100644
--- a/src/lanes.c
+++ b/src/lanes.c
@@ -103,6 +103,11 @@ THE SOFTWARE.
 # include <sys/types.h>
 #endif
 
+/*
+ * Do we want to activate full lane tracking feature? (EXPERIMENTAL)
+ */
+#define HAVE_LANE_TRACKING 1
+
 /* Do you want full call stacks, or just the line where the error happened?
 *
 * TBD: The full stack feature does not seem to work (try 'make error').
@@ -118,7 +123,9 @@ struct s_lane {
 	// M: sub-thread OS thread
 	// S: not used
 
-	lua_State *L;
+	char const* debug_name;
+
+	lua_State* L;
 	//
 	// M: prepares the state, and reads results
 	// S: while S is running, M must keep out of modifying the state
@@ -164,8 +171,8 @@ struct s_lane {
 	// S: cleans up after itself if non-NULL at lane exit
 };
 
-static bool_t cancel_test( lua_State *L );
-static void cancel_error( lua_State *L );
+static bool_t cancel_test( lua_State*L );
+static void cancel_error( lua_State*L );
 
 #define CANCEL_TEST_KEY ((void*)cancel_test)    // used as registry key
 #define CANCEL_ERROR ((void*)cancel_error)      // 'cancel_error' sentinel
@@ -204,7 +211,7 @@ struct s_Linda;
 * Returns: TRUE if a table was pushed
 *          FALSE if no table found, not created, and nothing pushed
 */
-static bool_t push_registry_table( lua_State *L, void *key, bool_t create ) {
+static bool_t push_registry_table( lua_State*L, void *key, bool_t create ) {
 
     STACK_GROW(L,3);
     
@@ -247,7 +254,7 @@ static void linda_id( lua_State*, char const * const which);
 #define lua_toLinda(L,n) ((struct s_Linda *)luaG_todeep( L, linda_id, n ))
 
 
-static void check_key_types( lua_State *L, int _start, int _end)
+static void check_key_types( lua_State*L, int _start, int _end)
 {
 	int i;
 	for( i = _start; i <= _end; ++ i)
@@ -305,7 +312,7 @@ LUAG_FUNC( linda_send)
 	STACK_GROW(L, 1);
 	{
 		struct s_Keeper *K = keeper_acquire( linda);
-		lua_State *KL = K->L;    // need to do this for 'STACK_CHECK'
+		lua_State*KL = K->L;    // need to do this for 'STACK_CHECK'
 		STACK_CHECK( KL)
 		for( ;;)
 		{
@@ -720,8 +727,6 @@ LUAG_FUNC( linda_deep ) {
 
 static int linda_tostring( lua_State* L, int _idx, bool_t _opt)
 {
-	char text[32];
-	int len;
 	struct s_Linda* linda = lua_toLinda( L, _idx);
 	if( !_opt)
 	{
@@ -729,6 +734,8 @@ static int linda_tostring( lua_State* L, int _idx, bool_t _opt)
 	}
 	if( linda)
 	{
+		char text[32];
+		int len;
 		if( linda->name[0])
 			len = sprintf( text, "Linda: %.*s", (int)sizeof(text) - 8, linda->name);
 		else
@@ -808,7 +815,7 @@ LUAG_FUNC( linda_dump)
 * 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, char const * const which)
+static void linda_id( lua_State*L, char const * const which)
 {
     if (strcmp( which, "new" )==0)
     {
@@ -981,7 +988,7 @@ LUAG_FUNC( set_finalizer )
 //
 // TBD: should we add stack trace on failing finalizer, wouldn't be hard..
 //
-static int run_finalizers( lua_State *L, int lua_rc )
+static int run_finalizers( lua_State*L, int lua_rc )
 {
     unsigned error_index, tbl_index;
     unsigned n;
@@ -1176,7 +1183,7 @@ volatile DEEP_PRELUDE *timer_deep;  // = NULL
 /*
 * Process end; cancel any still free-running threads
 */
-static int selfdestruct_gc( lua_State *L)
+static int selfdestruct_gc( lua_State*L)
 {
     (void)L; // unused
     if (selfdestruct_first == SELFDESTRUCT_END) return 0;    // no free-running threads
@@ -1341,7 +1348,7 @@ static int selfdestruct_gc( lua_State *L)
 * Returns TRUE if any locks are to be exited, and 'cancel_error()' called,
 * to make execution of the lane end.
 */
-static bool_t cancel_test( lua_State *L ) {
+static bool_t cancel_test( lua_State*L ) {
     struct s_lane *s;
 
     STACK_GROW(L,1);
@@ -1358,13 +1365,13 @@ static bool_t cancel_test( lua_State *L ) {
     return s && s->cancel_request;
 }
 
-static void cancel_error( lua_State *L ) {
+static void cancel_error( lua_State*L ) {
     STACK_GROW(L,1);
     lua_pushlightuserdata( L, CANCEL_ERROR );    // special error value
     lua_error(L);   // no return
 }
 
-static void cancel_hook( lua_State *L, lua_Debug *ar ) {
+static void cancel_hook( lua_State*L, lua_Debug *ar ) {
     (void)ar;
     if (cancel_test(L)) cancel_error(L);
 }
@@ -1546,19 +1553,39 @@ static int lane_error( lua_State* L)
 
 LUAG_FUNC( set_debug_threadname)
 {
-	luaL_checktype( L, -1, LUA_TSTRING);
-	THREAD_SETNAME( lua_tostring( L, -1));
+	struct s_lane* s = lua_touserdata( L, lua_upvalueindex( 1));
+	luaL_checktype( L, -1, LUA_TSTRING);                           // "name"
+	// store a hidden reference in the registry to make sure the string is kept around even if a lane decides to manually change the "decoda_name" global...
+	lua_pushlightuserdata( L, LG_set_debug_threadname);            // "name" lud
+	lua_pushvalue( L, -2);                                         // "name" lud "name"
+	lua_rawset( L, LUA_REGISTRYINDEX);                             // "name"
+	s->debug_name = lua_tostring( L, -1);
+	// keep a direct pointer on the string
+	THREAD_SETNAME( s->debug_name);
 	// to see VM name in Decoda debugger Virtual Machine window
-	lua_setglobal( L, "decoda_name");
+	lua_setglobal( L, "decoda_name");                              //
 	return 0;
 }
 
+#if HAVE_LANE_TRACKING
+static bool_t GTrackLanes = FALSE;
+#endif // HAVE_LANE_TRACKING
+
 //---
 static THREAD_RETURN_T THREAD_CALLCONV lane_main( void *vs)
 {
     struct s_lane *s= (struct s_lane *)vs;
     int rc, rc2;
-    lua_State *L= s->L;
+    lua_State*L= s->L;
+
+#if HAVE_LANE_TRACKING
+    if( GTrackLanes)
+    {
+        // If we track lanes, we add them right now to the list so that its traversal hits all known lanes
+        // (else we get only the still running lanes for which GC was called, IOW not accessible anymore from a script)
+        selfdestruct_add( s);
+    }
+#endif // HAVE_LANE_TRACKING
 
    s->status= RUNNING;  // PENDING -> RUNNING
 
@@ -1569,7 +1596,8 @@ static THREAD_RETURN_T THREAD_CALLCONV lane_main( void *vs)
 
     // Tie "set_debug_threadname()" to the state
     //
-    lua_pushcfunction( L, LG_set_debug_threadname);
+    lua_pushlightuserdata( L, s);
+    lua_pushcclosure( L, LG_set_debug_threadname, 1);
     lua_setglobal( L, "set_debug_threadname" );
 
     // Tie "cancel_test()" to the state
@@ -1704,7 +1732,7 @@ static THREAD_RETURN_T THREAD_CALLCONV lane_main( void *vs)
 
 // helper function to require a module in the keeper states and in the target state
 // source state contains module name at the top of the stack
-static void require_one_module( lua_State *L, lua_State *L2, bool_t _fatal)
+static void require_one_module( lua_State*L, lua_State*L2, bool_t _fatal)
 {
 	size_t len;
 	char const *name = lua_tolstring( L, -1, &len);
@@ -1728,7 +1756,7 @@ static void require_one_module( lua_State *L, lua_State *L2, bool_t _fatal)
 
 LUAG_FUNC( thread_new )
 {
-	lua_State *L2;
+	lua_State*L2;
 	struct s_lane *s;
 	struct s_lane **ud;
 
@@ -1918,6 +1946,7 @@ LUAG_FUNC( thread_new )
 	s->L= L2;
 	s->status= PENDING;
 	s->waiting_on = NULL;
+	s->debug_name = NULL;
 	s->cancel_request= FALSE;
 
 #if THREADWAIT_METHOD == THREADWAIT_CONDVAR
@@ -1995,9 +2024,14 @@ LUAG_FUNC( thread_gc)
 	}
 	else if( s->status < DONE)
 	{
-		// still running: will have to be cleaned up later
-		selfdestruct_add( s);
-		assert( s->selfdestruct_next);
+#if HAVE_LANE_TRACKING
+		if( !GTrackLanes)
+#endif // HAVE_LANE_TRACKING
+		{
+			// still running: will have to be cleaned up later
+			selfdestruct_add( s);
+			assert( s->selfdestruct_next);
+		}
 		return 0;
 
 	}
@@ -2008,6 +2042,14 @@ LUAG_FUNC( thread_gc)
 		s->L = 0;
 	}
 
+#if HAVE_LANE_TRACKING
+	if( GTrackLanes)
+	{
+		// Lane was cleaned up, no need to handle at process termination
+		selfdestruct_remove( s);
+	}
+#endif // HAVE_LANE_TRACKING
+
 	// Clean up after a (finished) thread
 	//
 #if THREADWAIT_METHOD == THREADWAIT_CONDVAR
@@ -2091,7 +2133,7 @@ static char const * thread_status_string( struct s_lane *s)
 	return str;
 }
 
-static int push_thread_status( lua_State *L, struct s_lane *s)
+static int push_thread_status( lua_State*L, struct s_lane *s)
 {
 	char const * const str = thread_status_string( s);
 	ASSERT_L( str);
@@ -2113,7 +2155,7 @@ LUAG_FUNC( thread_join)
 {
 	struct s_lane* const s = lua_toLane( L, 1);
 	double wait_secs= luaL_optnumber(L,2,-1.0);
-	lua_State *L2= s->L;
+	lua_State*L2= s->L;
 	int ret;
 	bool_t done;
 
@@ -2329,6 +2371,37 @@ LUAG_FUNC( thread_index)
 	return 0;
 }
 
+#if HAVE_LANE_TRACKING
+//---
+// threads() -> {}|nil
+//
+// Return a list of all known lanes
+LUAG_FUNC( threads)
+{
+	int const top = lua_gettop( L);
+	// List _all_ still running threads
+	//
+	MUTEX_LOCK( &selfdestruct_cs);
+	if( selfdestruct_first != SELFDESTRUCT_END)
+	{
+		struct s_lane* s = selfdestruct_first;
+		lua_newtable( L);                                          // {}
+		while( s != SELFDESTRUCT_END)
+		{
+			if( s->debug_name)
+				lua_pushstring( L, s->debug_name);                     // {} "name"
+			else
+				lua_pushfstring( L, "Lane %p", s);                     // {} "name"
+			push_thread_status( L, s);                               // {} "name" "status"
+			lua_rawset( L, -3);                                      // {}
+			s = s->selfdestruct_next;
+		}
+	}
+	MUTEX_UNLOCK( &selfdestruct_cs);
+	return lua_gettop( L) - top;
+}
+#endif // HAVE_LANE_TRACKING
+
 /*
  * ###############################################################################################
  * ######################################## Timer support ########################################
@@ -2401,6 +2474,9 @@ LUAG_FUNC( wakeup_conv )
 static const struct luaL_Reg lanes_functions [] = {
     {"linda", LG_linda},
     {"now_secs", LG_now_secs},
+#if HAVE_LANE_TRACKING
+    {"threads", LG_threads},
+#endif // HAVE_LANE_TRACKING
     {"wakeup_conv", LG_wakeup_conv},
     {"nameof", luaG_nameof},
     {"set_singlethreaded", LG_set_singlethreaded},
@@ -2410,7 +2486,7 @@ static const struct luaL_Reg lanes_functions [] = {
 /*
 * One-time initializations
 */
-static void init_once_LOCKED( lua_State* L, volatile DEEP_PRELUDE** timer_deep_ref, int const nbKeepers, lua_CFunction _on_state_create, lua_Number _shutdown_timeout)
+static void init_once_LOCKED( lua_State* L, volatile DEEP_PRELUDE** timer_deep_ref, int const nbKeepers, lua_CFunction _on_state_create, lua_Number _shutdown_timeout, bool_t _track_lanes)
 {
     const char *err;
 
@@ -2421,7 +2497,11 @@ static void init_once_LOCKED( lua_State* L, volatile DEEP_PRELUDE** timer_deep_r
 #if (defined PLATFORM_OSX) && (defined _UTILBINDTHREADTOCPU)
         chudInitialize();
 #endif
-    
+
+#if HAVE_LANE_TRACKING
+    GTrackLanes = _track_lanes;
+#endif // HAVE_LANE_TRACKING
+
         // Locks for 'tools.c' inc/dec counters
         //
         MUTEX_INIT( &deep_lock );
@@ -2516,6 +2596,7 @@ LUAG_FUNC( configure )
     int const nbKeepers = (int)lua_tointeger( L, 1);
     lua_CFunction on_state_create = lua_iscfunction( L, 2) ? lua_tocfunction( L, 2) : NULL;
     lua_Number shutdown_timeout = lua_tonumber( L, 3);
+    bool_t track_lanes = lua_toboolean( L, 4);
     /*
     * Making one-time initializations.
     *
@@ -2528,7 +2609,7 @@ LUAG_FUNC( configure )
         static volatile int /*bool*/ go_ahead; // = 0
         if( InterlockedCompareExchange( &s_initCount, 1, 0) == 0)
         {
-            init_once_LOCKED( L, &timer_deep, nbKeepers, on_state_create, shutdown_timeout);
+            init_once_LOCKED( L, &timer_deep, nbKeepers, on_state_create, shutdown_timeout, track_lanes);
             go_ahead= 1;    // let others pass
         }
         else
@@ -2546,7 +2627,7 @@ LUAG_FUNC( configure )
             //
             if( s_initCount == 0)
             {
-                init_once_LOCKED( L, &timer_deep, nbKeepers, on_state_create, shutdown_timeout);
+                init_once_LOCKED( L, &timer_deep, nbKeepers, on_state_create, shutdown_timeout, track_lanes);
                 s_initCount = 1;
             }
         }
@@ -2569,7 +2650,7 @@ LUAG_FUNC( configure )
     lua_newtable( L);
     lua_pushcfunction( L, LG_thread_gc);
     lua_setfield( L, -2, "__gc");
-    lua_pushcfunction( L, LG_thread_index);
+		lua_pushcfunction( L, LG_thread_index);
     lua_setfield( L, -2, "__index");
     lua_getglobal( L, "error");
     ASSERT_L( lua_isfunction( L, -1));
-- 
cgit v1.2.3-55-g6feb