diff options
author | Benoit Germain <bnt period germain arrobase gmail period com> | 2014-01-22 12:28:45 +0100 |
---|---|---|
committer | Benoit Germain <bnt period germain arrobase gmail period com> | 2014-01-22 12:28:45 +0100 |
commit | 9808ae3e21ac812ef705a7c1a0b10f49825023c5 (patch) | |
tree | e5cebe59b603645fa1fdedd42f56a4d243e8783d /src | |
parent | 57ccc40847716069053a68e9d6079355dd5a1795 (diff) | |
download | lanes-9808ae3e21ac812ef705a7c1a0b10f49825023c5.tar.gz lanes-9808ae3e21ac812ef705a7c1a0b10f49825023c5.tar.bz2 lanes-9808ae3e21ac812ef705a7c1a0b10f49825023c5.zip |
new lane launcher option gc_cb
* bumped version to 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)
Diffstat (limited to 'src')
-rw-r--r-- | src/lanes.c | 111 | ||||
-rw-r--r-- | src/lanes.lua | 14 |
2 files changed, 93 insertions, 32 deletions
diff --git a/src/lanes.c b/src/lanes.c index 604e43d..a806c16 100644 --- a/src/lanes.c +++ b/src/lanes.c | |||
@@ -52,7 +52,7 @@ | |||
52 | * ... | 52 | * ... |
53 | */ | 53 | */ |
54 | 54 | ||
55 | char const* VERSION = "3.8.1"; | 55 | char const* VERSION = "3.8.2"; |
56 | 56 | ||
57 | /* | 57 | /* |
58 | =============================================================================== | 58 | =============================================================================== |
@@ -209,6 +209,19 @@ static inline struct s_lane* get_lane_from_registry( lua_State* L) | |||
209 | return s; | 209 | return s; |
210 | } | 210 | } |
211 | 211 | ||
212 | // intern the debug name in the specified lua state so that the pointer remains valid when the lane's state is closed | ||
213 | static void securize_debug_threadname( lua_State* L, struct s_lane* s) | ||
214 | { | ||
215 | STACK_CHECK( L); | ||
216 | STACK_GROW( L, 3); | ||
217 | lua_getuservalue( L, 1); | ||
218 | lua_newtable( L); | ||
219 | s->debug_name = lua_pushstring( L, s->debug_name); | ||
220 | lua_rawset( L, -3); | ||
221 | lua_pop( L, 1); | ||
222 | STACK_END( L, 0); | ||
223 | } | ||
224 | |||
212 | /* | 225 | /* |
213 | * Check if the thread in question ('L') has been signalled for cancel. | 226 | * Check if the thread in question ('L') has been signalled for cancel. |
214 | * | 227 | * |
@@ -1766,7 +1779,7 @@ LUAG_FUNC( get_debug_threadname) | |||
1766 | { | 1779 | { |
1767 | struct s_lane* const s = lua_toLane( L, 1); | 1780 | struct s_lane* const s = lua_toLane( L, 1); |
1768 | luaL_argcheck( L, lua_gettop( L) == 1, 2, "too many arguments"); | 1781 | luaL_argcheck( L, lua_gettop( L) == 1, 2, "too many arguments"); |
1769 | lua_pushstring( L, s->debug_name ? s->debug_name : "<unnamed>"); | 1782 | lua_pushstring( L, s->debug_name); |
1770 | return 1; | 1783 | return 1; |
1771 | } | 1784 | } |
1772 | 1785 | ||
@@ -1998,6 +2011,8 @@ LUAG_FUNC( require) | |||
1998 | return 1; | 2011 | return 1; |
1999 | } | 2012 | } |
2000 | 2013 | ||
2014 | LUAG_FUNC( thread_gc); | ||
2015 | #define GCCB_KEY (void*)LG_thread_gc | ||
2001 | //--- | 2016 | //--- |
2002 | // lane_ud= thread_new( function, [libs_str], | 2017 | // lane_ud= thread_new( function, [libs_str], |
2003 | // [cancelstep_uint=0], | 2018 | // [cancelstep_uint=0], |
@@ -2005,6 +2020,7 @@ LUAG_FUNC( require) | |||
2005 | // [globals_tbl], | 2020 | // [globals_tbl], |
2006 | // [package_tbl], | 2021 | // [package_tbl], |
2007 | // [required], | 2022 | // [required], |
2023 | // [gc_cb], | ||
2008 | // [... args ...] ) | 2024 | // [... args ...] ) |
2009 | // | 2025 | // |
2010 | // Upvalues: metatable to use for 'lane_ud' | 2026 | // Upvalues: metatable to use for 'lane_ud' |
@@ -2022,8 +2038,9 @@ LUAG_FUNC( thread_new) | |||
2022 | uint_t glob = lua_isnoneornil( L, 5) ? 0 : 5; | 2038 | uint_t glob = lua_isnoneornil( L, 5) ? 0 : 5; |
2023 | uint_t package = lua_isnoneornil( L, 6) ? 0 : 6; | 2039 | uint_t package = lua_isnoneornil( L, 6) ? 0 : 6; |
2024 | uint_t required = lua_isnoneornil( L, 7) ? 0 : 7; | 2040 | uint_t required = lua_isnoneornil( L, 7) ? 0 : 7; |
2041 | uint_t gc_cb = lua_isnoneornil( L, 8) ? 0 : 8; | ||
2025 | 2042 | ||
2026 | #define FIXED_ARGS 7 | 2043 | #define FIXED_ARGS 8 |
2027 | uint_t args = lua_gettop(L) - FIXED_ARGS; | 2044 | uint_t args = lua_gettop(L) - FIXED_ARGS; |
2028 | 2045 | ||
2029 | // public Lanes API accepts a generic range -3/+3 | 2046 | // public Lanes API accepts a generic range -3/+3 |
@@ -2203,7 +2220,7 @@ LUAG_FUNC( thread_new) | |||
2203 | } | 2220 | } |
2204 | STACK_MID( L, 0); | 2221 | STACK_MID( L, 0); |
2205 | 2222 | ||
2206 | ASSERT_L( (uint_t)lua_gettop( L2) == 1+args); | 2223 | ASSERT_L( (uint_t)lua_gettop( L2) == 1 + args); |
2207 | ASSERT_L( lua_isfunction( L2, 1)); | 2224 | ASSERT_L( lua_isfunction( L2, 1)); |
2208 | 2225 | ||
2209 | // 's' is allocated from heap, not Lua, since its life span may surpass | 2226 | // 's' is allocated from heap, not Lua, since its life span may surpass |
@@ -2217,9 +2234,9 @@ LUAG_FUNC( thread_new) | |||
2217 | 2234 | ||
2218 | //memset( s, 0, sizeof(struct s_lane) ); | 2235 | //memset( s, 0, sizeof(struct s_lane) ); |
2219 | s->L = L2; | 2236 | s->L = L2; |
2220 | s->status= PENDING; | 2237 | s->status = PENDING; |
2221 | s->waiting_on = NULL; | 2238 | s->waiting_on = NULL; |
2222 | s->debug_name = NULL; | 2239 | s->debug_name = "<unnamed>"; |
2223 | s->cancel_request = CANCEL_NONE; | 2240 | s->cancel_request = CANCEL_NONE; |
2224 | 2241 | ||
2225 | #if THREADWAIT_METHOD == THREADWAIT_CONDVAR | 2242 | #if THREADWAIT_METHOD == THREADWAIT_CONDVAR |
@@ -2238,25 +2255,32 @@ LUAG_FUNC( thread_new) | |||
2238 | lua_setmetatable( L, -2); | 2255 | lua_setmetatable( L, -2); |
2239 | STACK_MID( L, 1); | 2256 | STACK_MID( L, 1); |
2240 | 2257 | ||
2241 | // Clear environment for the userdata | 2258 | // Create uservalue for the userdata |
2242 | // | 2259 | // (this is where lane body return values will be stored when the handle is indexed by a numeric key) |
2243 | lua_newtable( L); | 2260 | lua_newtable( L); |
2261 | |||
2262 | // Store the gc_cb callback in the uservalue | ||
2263 | if( gc_cb > 0) | ||
2264 | { | ||
2265 | lua_pushlightuserdata( L, GCCB_KEY); | ||
2266 | lua_pushvalue( L, gc_cb); | ||
2267 | lua_rawset( L, -3); | ||
2268 | } | ||
2269 | |||
2244 | lua_setuservalue( L, -2); | 2270 | lua_setuservalue( L, -2); |
2245 | 2271 | ||
2246 | // Place 's' in registry, for 'cancel_test()' (even if 'cs'==0 we still | 2272 | // Store 's' in the lane's registry, for 'cancel_test()' (even if 'cs'==0 we still do cancel tests at pending send/receive). |
2247 | // do cancel tests at pending send/receive). | ||
2248 | // | ||
2249 | lua_pushlightuserdata( L2, CANCEL_TEST_KEY); | 2273 | lua_pushlightuserdata( L2, CANCEL_TEST_KEY); |
2250 | lua_pushlightuserdata( L2, s); | 2274 | lua_pushlightuserdata( L2, s); |
2251 | lua_rawset( L2, LUA_REGISTRYINDEX); | 2275 | lua_rawset( L2, LUA_REGISTRYINDEX); |
2252 | 2276 | ||
2253 | if( cs) | 2277 | if( cs) |
2254 | { | 2278 | { |
2255 | lua_sethook( L2, cancel_hook, LUA_MASKCOUNT, cs ); | 2279 | lua_sethook( L2, cancel_hook, LUA_MASKCOUNT, cs); |
2256 | } | 2280 | } |
2257 | 2281 | ||
2258 | DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "thread_new: launching thread\n" INDENT_END)); | 2282 | DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "thread_new: launching thread\n" INDENT_END)); |
2259 | THREAD_CREATE( &s->thread, lane_main, s, prio ); | 2283 | THREAD_CREATE( &s->thread, lane_main, s, prio); |
2260 | STACK_END( L, 1); | 2284 | STACK_END( L, 1); |
2261 | 2285 | ||
2262 | DEBUGSPEW_CODE( -- debugspew_indent_depth); | 2286 | DEBUGSPEW_CODE( -- debugspew_indent_depth); |
@@ -2279,7 +2303,23 @@ LUAG_FUNC( thread_new) | |||
2279 | // | 2303 | // |
2280 | LUAG_FUNC( thread_gc) | 2304 | LUAG_FUNC( thread_gc) |
2281 | { | 2305 | { |
2282 | struct s_lane* s = lua_toLane( L, 1); | 2306 | bool_t have_gc_cb = FALSE; |
2307 | struct s_lane* s = lua_toLane( L, 1); // ud | ||
2308 | |||
2309 | // if there a gc callback? | ||
2310 | lua_getuservalue( L, 1); // ud uservalue | ||
2311 | lua_pushlightuserdata( L, GCCB_KEY); // ud uservalue __gc | ||
2312 | lua_rawget( L, -2); // ud uservalue gc_cb|nil | ||
2313 | if( !lua_isnil( L, -1)) | ||
2314 | { | ||
2315 | lua_remove( L, -2); // ud gc_cb|nil | ||
2316 | lua_pushstring( L, s->debug_name); // ud gc_cb name | ||
2317 | have_gc_cb = TRUE; | ||
2318 | } | ||
2319 | else | ||
2320 | { | ||
2321 | lua_pop( L, 2); // ud | ||
2322 | } | ||
2283 | 2323 | ||
2284 | // We can read 's->status' without locks, but not wait for it | 2324 | // We can read 's->status' without locks, but not wait for it |
2285 | // test KILLED state first, as it doesn't need to enter the selfdestruct chain | 2325 | // test KILLED state first, as it doesn't need to enter the selfdestruct chain |
@@ -2291,13 +2331,17 @@ LUAG_FUNC( thread_gc) | |||
2291 | DEBUGSPEW_CODE( fprintf( stderr, "** Joining with a killed thread (needs testing) **")); | 2331 | DEBUGSPEW_CODE( fprintf( stderr, "** Joining with a killed thread (needs testing) **")); |
2292 | // make sure the thread is no longer running, just like thread_join() | 2332 | // make sure the thread is no longer running, just like thread_join() |
2293 | if(! THREAD_ISNULL( s->thread)) | 2333 | if(! THREAD_ISNULL( s->thread)) |
2334 | { | ||
2294 | THREAD_WAIT( &s->thread, -1, &s->done_signal, &s->done_lock, &s->status); | 2335 | THREAD_WAIT( &s->thread, -1, &s->done_signal, &s->done_lock, &s->status); |
2295 | // we know the thread was killed while the Lua VM was not doing anything: we should be able to close it without crashing | 2336 | } |
2296 | // now, thread_cancel() will not forcefully kill a lane with s->status >= DONE, so I am not sure it can ever happen | ||
2297 | if( s->status >= DONE && s->L) | 2337 | if( s->status >= DONE && s->L) |
2298 | { | 2338 | { |
2339 | // we know the thread was killed while the Lua VM was not doing anything: we should be able to close it without crashing | ||
2340 | // now, thread_cancel() will not forcefully kill a lane with s->status >= DONE, so I am not sure it can ever happen | ||
2299 | lua_close( s->L); | 2341 | lua_close( s->L); |
2300 | s->L = 0; | 2342 | s->L = 0; |
2343 | // just in case, but s will be freed soon so... | ||
2344 | s->debug_name = "<gc>"; | ||
2301 | } | 2345 | } |
2302 | DEBUGSPEW_CODE( fprintf( stderr, "** Joined ok **")); | 2346 | DEBUGSPEW_CODE( fprintf( stderr, "** Joined ok **")); |
2303 | } | 2347 | } |
@@ -2306,18 +2350,31 @@ LUAG_FUNC( thread_gc) | |||
2306 | // still running: will have to be cleaned up later | 2350 | // still running: will have to be cleaned up later |
2307 | selfdestruct_add( s); | 2351 | selfdestruct_add( s); |
2308 | assert( s->selfdestruct_next); | 2352 | assert( s->selfdestruct_next); |
2353 | if( have_gc_cb) | ||
2354 | { | ||
2355 | lua_pushliteral( L, "selfdestruct"); // ud gc_cb name status | ||
2356 | lua_call( L, 2, 0); // ud | ||
2357 | } | ||
2309 | return 0; | 2358 | return 0; |
2310 | |||
2311 | } | 2359 | } |
2312 | else if( s->L) | 2360 | else if( s->L) |
2313 | { | 2361 | { |
2314 | // no longer accessing the Lua VM: we can close right now | 2362 | // no longer accessing the Lua VM: we can close right now |
2315 | lua_close( s->L); | 2363 | lua_close( s->L); |
2316 | s->L = 0; | 2364 | s->L = 0; |
2365 | // just in case, but s will be freed soon so... | ||
2366 | s->debug_name = "<gc>"; | ||
2317 | } | 2367 | } |
2318 | 2368 | ||
2319 | // Clean up after a (finished) thread | 2369 | // Clean up after a (finished) thread |
2320 | lane_cleanup( s); | 2370 | lane_cleanup( s); |
2371 | |||
2372 | // do this after lane cleanup in case the callback triggers an error | ||
2373 | if( have_gc_cb) | ||
2374 | { | ||
2375 | lua_pushliteral( L, "closed"); // ud gc_cb name status | ||
2376 | lua_call( L, 2, 0); // ud | ||
2377 | } | ||
2321 | return 0; | 2378 | return 0; |
2322 | } | 2379 | } |
2323 | 2380 | ||
@@ -2422,23 +2479,26 @@ LUAG_FUNC( thread_join) | |||
2422 | bool_t done; | 2479 | bool_t done; |
2423 | 2480 | ||
2424 | done = THREAD_ISNULL( s->thread) || THREAD_WAIT( &s->thread, wait_secs, &s->done_signal, &s->done_lock, &s->status); | 2481 | done = THREAD_ISNULL( s->thread) || THREAD_WAIT( &s->thread, wait_secs, &s->done_signal, &s->done_lock, &s->status); |
2425 | if (!done || !L2) | 2482 | if( !done || !L2) |
2483 | { | ||
2426 | return 0; // timeout: pushes none, leaves 'L2' alive | 2484 | return 0; // timeout: pushes none, leaves 'L2' alive |
2485 | } | ||
2427 | 2486 | ||
2428 | // Thread is DONE/ERROR_ST/CANCELLED; all ours now | 2487 | // Thread is DONE/ERROR_ST/CANCELLED; all ours now |
2429 | 2488 | ||
2430 | STACK_GROW( L, 1); | ||
2431 | |||
2432 | if( s->mstatus == KILLED) // OS thread was killed if thread_cancel was forced | 2489 | if( s->mstatus == KILLED) // OS thread was killed if thread_cancel was forced |
2433 | { | 2490 | { |
2434 | // in that case, even if the thread was killed while DONE/ERROR_ST/CANCELLED, ignore regular return values | 2491 | // in that case, even if the thread was killed while DONE/ERROR_ST/CANCELLED, ignore regular return values |
2435 | 2492 | STACK_GROW( L, 1); | |
2436 | lua_pushnil( L); | 2493 | lua_pushnil( L); |
2437 | lua_pushliteral( L, "killed"); | 2494 | lua_pushliteral( L, "killed"); |
2438 | ret = 2; | 2495 | ret = 2; |
2439 | } | 2496 | } |
2440 | else | 2497 | else |
2441 | { | 2498 | { |
2499 | // debug_name is a pointer to string possibly interned in the lane's state, that no longer exists when the state is closed | ||
2500 | // so store it in the userdata uservalue at a key that can't possibly collide | ||
2501 | securize_debug_threadname( L, s); | ||
2442 | switch( s->status) | 2502 | switch( s->status) |
2443 | { | 2503 | { |
2444 | case DONE: | 2504 | case DONE: |
@@ -2467,11 +2527,9 @@ LUAG_FUNC( thread_join) | |||
2467 | 2527 | ||
2468 | default: | 2528 | default: |
2469 | DEBUGSPEW_CODE( fprintf( stderr, "Status: %d\n", s->status)); | 2529 | DEBUGSPEW_CODE( fprintf( stderr, "Status: %d\n", s->status)); |
2470 | ASSERT_L( FALSE ); ret= 0; | 2530 | ASSERT_L( FALSE); ret = 0; |
2471 | } | 2531 | } |
2472 | lua_close( L2); | 2532 | lua_close( L2); |
2473 | // debug_name is a pointer to an interned string, that no longer exists when the state is closed | ||
2474 | s->debug_name = "<closed>"; | ||
2475 | } | 2533 | } |
2476 | s->L = 0; | 2534 | s->L = 0; |
2477 | 2535 | ||
@@ -2652,10 +2710,7 @@ LUAG_FUNC( threads) | |||
2652 | lua_newtable( L); // {} | 2710 | lua_newtable( L); // {} |
2653 | while( s != TRACKING_END) | 2711 | while( s != TRACKING_END) |
2654 | { | 2712 | { |
2655 | if( s->debug_name) | 2713 | lua_pushstring( L, s->debug_name); // {} "name" |
2656 | lua_pushstring( L, s->debug_name); // {} "name" | ||
2657 | else | ||
2658 | lua_pushfstring( L, "Lane %p", s); // {} "name" | ||
2659 | push_thread_status( L, s); // {} "name" "status" | 2714 | push_thread_status( L, s); // {} "name" "status" |
2660 | lua_rawset( L, -3); // {} | 2715 | lua_rawset( L, -3); // {} |
2661 | s = s->tracking_next; | 2716 | s = s->tracking_next; |
diff --git a/src/lanes.lua b/src/lanes.lua index 9a0287d..1286099 100644 --- a/src/lanes.lua +++ b/src/lanes.lua | |||
@@ -196,7 +196,10 @@ end | |||
196 | -- | 196 | -- |
197 | -- .globals: table of globals to set for a new thread (passed by value) | 197 | -- .globals: table of globals to set for a new thread (passed by value) |
198 | -- | 198 | -- |
199 | -- .required: table of packages to require | 199 | -- .required: table of packages to require |
200 | -- | ||
201 | -- .gc_cb: function called when the lane handle is collected | ||
202 | -- | ||
200 | -- ... (more options may be introduced later) ... | 203 | -- ... (more options may be introduced later) ... |
201 | -- | 204 | -- |
202 | -- Calling with a function parameter ('lane_func') ends the string/table | 205 | -- Calling with a function parameter ('lane_func') ends the string/table |
@@ -272,10 +275,11 @@ local function gen( ... ) | |||
272 | end | 275 | end |
273 | end | 276 | end |
274 | 277 | ||
275 | local prio, cs, g_tbl, package_tbl, required | 278 | local prio, cs, g_tbl, package_tbl, required, gc_cb |
276 | 279 | ||
277 | for k,v in pairs(opt) do | 280 | for k,v in pairs(opt) do |
278 | if k=="priority" then prio= v | 281 | if k == "priority" then |
282 | prio = (type( v) == "number") and v or error( "Bad 'prio' option: expecting number, got " .. type( v), lev) | ||
279 | elseif k=="cancelstep" then | 283 | elseif k=="cancelstep" then |
280 | cs = (v==true) and 100 or | 284 | cs = (v==true) and 100 or |
281 | (v==false) and 0 or | 285 | (v==false) and 0 or |
@@ -286,6 +290,8 @@ local function gen( ... ) | |||
286 | package_tbl = (type( v) == "table") and v or error( "Bad package: " .. tostring( v), lev) | 290 | package_tbl = (type( v) == "table") and v or error( "Bad package: " .. tostring( v), lev) |
287 | elseif k=="required" then | 291 | elseif k=="required" then |
288 | required= (type( v) == "table") and v or error( "Bad 'required' option: expecting table, got " .. type( v), lev) | 292 | required= (type( v) == "table") and v or error( "Bad 'required' option: expecting table, got " .. type( v), lev) |
293 | elseif k == "gc_cb" then | ||
294 | gc_cb = (type( v) == "function") and v or error( "Bad 'gc_cb' option: expecting function, got " .. type( v), lev) | ||
289 | --.. | 295 | --.. |
290 | elseif k==1 then error( "unkeyed option: ".. tostring(v), lev ) | 296 | elseif k==1 then error( "unkeyed option: ".. tostring(v), lev ) |
291 | else error( "Bad option: ".. tostring(k), lev ) | 297 | else error( "Bad option: ".. tostring(k), lev ) |
@@ -296,7 +302,7 @@ local function gen( ... ) | |||
296 | -- Lane generator | 302 | -- Lane generator |
297 | -- | 303 | -- |
298 | return function(...) | 304 | return function(...) |
299 | return thread_new( func, libs, cs, prio, g_tbl, package_tbl, required, ...) -- args | 305 | return thread_new( func, libs, cs, prio, g_tbl, package_tbl, required, gc_cb, ...) -- args |
300 | end | 306 | end |
301 | end | 307 | end |
302 | 308 | ||