diff options
author | Benoit Germain <bnt.germain@gmail.com> | 2019-04-19 14:58:25 +0200 |
---|---|---|
committer | Benoit Germain <bnt.germain@gmail.com> | 2019-04-19 14:58:25 +0200 |
commit | be1e9d37d9809ee55f26d811208fa64ea9b3785a (patch) | |
tree | 1b0122ed7e7f501c64814cdbbf03e5941dbab51c | |
parent | 52934d46eed850c23a9b21125be73e987f34e772 (diff) | |
download | lanes-be1e9d37d9809ee55f26d811208fa64ea9b3785a.tar.gz lanes-be1e9d37d9809ee55f26d811208fa64ea9b3785a.tar.bz2 lanes-be1e9d37d9809ee55f26d811208fa64ea9b3785a.zip |
lane:cancel internal code refactorization
Diffstat (limited to '')
-rw-r--r-- | src/lanes.c | 151 | ||||
-rw-r--r-- | src/linda.c | 4 | ||||
-rw-r--r-- | tests/basic.lua | 112 |
3 files changed, 130 insertions, 137 deletions
diff --git a/src/lanes.c b/src/lanes.c index 90da9bf..abd4171 100644 --- a/src/lanes.c +++ b/src/lanes.c | |||
@@ -169,20 +169,6 @@ static DECLARE_CONST_UNIQUE_KEY( FINALIZER_REGKEY, 0x188fccb8bf348e09); | |||
169 | 169 | ||
170 | struct s_Linda; | 170 | struct s_Linda; |
171 | 171 | ||
172 | #if 1 | ||
173 | # define DEBUG_SIGNAL( msg, signal_ref ) /* */ | ||
174 | #else | ||
175 | # define DEBUG_SIGNAL( msg, signal_ref ) \ | ||
176 | { int i; unsigned char *ptr; char buf[999]; \ | ||
177 | sprintf( buf, ">>> " msg ": %p\t", (signal_ref) ); \ | ||
178 | ptr= (unsigned char *)signal_ref; \ | ||
179 | for( i=0; i<sizeof(*signal_ref); i++ ) { \ | ||
180 | sprintf( strchr(buf,'\0'), "%02x %c ", ptr[i], ptr[i] ); \ | ||
181 | } \ | ||
182 | fprintf( stderr, "%s\n", buf ); \ | ||
183 | } | ||
184 | #endif | ||
185 | |||
186 | /* | 172 | /* |
187 | * Push a table stored in registry onto Lua stack. | 173 | * Push a table stored in registry onto Lua stack. |
188 | * | 174 | * |
@@ -442,90 +428,97 @@ typedef enum | |||
442 | CR_Killed | 428 | CR_Killed |
443 | } cancel_result; | 429 | } cancel_result; |
444 | 430 | ||
445 | static cancel_result thread_cancel( lua_State* L, Lane* s, double secs, bool_t force, double waitkill_timeout_) | 431 | static cancel_result thread_cancel_soft( lua_State* L, Lane* s, bool_t wake_lindas_) |
446 | { | 432 | { |
447 | cancel_result result; | 433 | s->cancel_request = CANCEL_SOFT; // it's now signaled to stop |
448 | 434 | // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own | |
449 | // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here | 435 | if( wake_lindas_) // wake the thread so that execution returns from any pending linda operation if desired |
450 | // We can read 's->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) | ||
451 | if( s->mstatus == KILLED) | ||
452 | { | 436 | { |
453 | result = CR_Killed; | 437 | SIGNAL_T *waiting_on = s->waiting_on; |
438 | if( s->status == WAITING && waiting_on != NULL) | ||
439 | { | ||
440 | SIGNAL_ALL( waiting_on); | ||
441 | } | ||
454 | } | 442 | } |
455 | else if( s->status < DONE) | 443 | // say we succeeded though |
444 | return CR_Cancelled; | ||
445 | } | ||
446 | |||
447 | static cancel_result thread_cancel_hard( lua_State* L, Lane* s, double secs_, bool_t force_, double waitkill_timeout_) | ||
448 | { | ||
449 | cancel_result result; | ||
450 | |||
451 | s->cancel_request = CANCEL_HARD; // it's now signaled to stop | ||
456 | { | 452 | { |
457 | // signal the linda the wake up the thread so that it can react to the cancel query | 453 | SIGNAL_T *waiting_on = s->waiting_on; |
458 | // let us hope we never land here with a pointer on a linda that has been destroyed... | 454 | if( s->status == WAITING && waiting_on != NULL) |
459 | if( secs < 0.0) | ||
460 | { | 455 | { |
461 | s->cancel_request = CANCEL_SOFT; // it's now signaled to stop | 456 | SIGNAL_ALL( waiting_on); |
462 | // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own | ||
463 | if( force) // wake the thread so that execution returns from any pending linda operation if desired | ||
464 | { | ||
465 | SIGNAL_T *waiting_on = s->waiting_on; | ||
466 | if( s->status == WAITING && waiting_on != NULL) | ||
467 | { | ||
468 | SIGNAL_ALL( waiting_on); | ||
469 | } | ||
470 | } | ||
471 | // say we succeeded though | ||
472 | result = CR_Cancelled; | ||
473 | } | 457 | } |
474 | else | 458 | } |
475 | { | ||
476 | s->cancel_request = CANCEL_HARD; // it's now signaled to stop | ||
477 | { | ||
478 | SIGNAL_T *waiting_on = s->waiting_on; | ||
479 | if( s->status == WAITING && waiting_on != NULL) | ||
480 | { | ||
481 | SIGNAL_ALL( waiting_on); | ||
482 | } | ||
483 | } | ||
484 | 459 | ||
485 | result = THREAD_WAIT( &s->thread, secs, &s->done_signal, &s->done_lock, &s->status) ? CR_Cancelled : CR_Timeout; | 460 | result = THREAD_WAIT( &s->thread, secs_, &s->done_signal, &s->done_lock, &s->status) ? CR_Cancelled : CR_Timeout; |
486 | 461 | ||
487 | if( (result == CR_Timeout) && force) | 462 | if( (result == CR_Timeout) && force_) |
488 | { | 463 | { |
489 | // Killing is asynchronous; we _will_ wait for it to be done at | 464 | // Killing is asynchronous; we _will_ wait for it to be done at |
490 | // GC, to make sure the data structure can be released (alternative | 465 | // GC, to make sure the data structure can be released (alternative |
491 | // would be use of "cancellation cleanup handlers" that at least | 466 | // would be use of "cancellation cleanup handlers" that at least |
492 | // PThread seems to have). | 467 | // PThread seems to have). |
493 | // | 468 | // |
494 | THREAD_KILL( &s->thread); | 469 | THREAD_KILL( &s->thread); |
495 | #if THREADAPI == THREADAPI_PTHREAD | 470 | #if THREADAPI == THREADAPI_PTHREAD |
496 | // pthread: make sure the thread is really stopped! | 471 | // pthread: make sure the thread is really stopped! |
497 | // note that this may block forever if the lane doesn't call a cancellation point and pthread doesn't honor PTHREAD_CANCEL_ASYNCHRONOUS | 472 | // note that this may block forever if the lane doesn't call a cancellation point and pthread doesn't honor PTHREAD_CANCEL_ASYNCHRONOUS |
498 | result = THREAD_WAIT( &s->thread, waitkill_timeout_, &s->done_signal, &s->done_lock, &s->status); | 473 | result = THREAD_WAIT( &s->thread, waitkill_timeout_, &s->done_signal, &s->done_lock, &s->status); |
499 | if( result == CR_Timeout) | 474 | if( result == CR_Timeout) |
500 | { | 475 | { |
501 | return luaL_error( L, "force-killed lane failed to terminate within %f second%s", waitkill_timeout_, waitkill_timeout_ > 1 ? "s" : ""); | 476 | return luaL_error( L, "force-killed lane failed to terminate within %f second%s", waitkill_timeout_, waitkill_timeout_ > 1 ? "s" : ""); |
502 | } | 477 | } |
503 | #else | 478 | #else |
504 | (void) waitkill_timeout_; // unused | 479 | (void) waitkill_timeout_; // unused |
505 | (void) L; // unused | 480 | (void) L; // unused |
506 | #endif // THREADAPI == THREADAPI_PTHREAD | 481 | #endif // THREADAPI == THREADAPI_PTHREAD |
507 | s->mstatus = KILLED; // mark 'gc' to wait for it | 482 | s->mstatus = KILLED; // mark 'gc' to wait for it |
508 | // note that s->status value must remain to whatever it was at the time of the kill | 483 | // note that s->status value must remain to whatever it was at the time of the kill |
509 | // because we need to know if we can lua_close() the Lua State or not. | 484 | // because we need to know if we can lua_close() the Lua State or not. |
510 | result = CR_Killed; | 485 | result = CR_Killed; |
511 | } | ||
512 | } | ||
513 | } | 486 | } |
514 | else | 487 | return result; |
488 | } | ||
489 | |||
490 | static cancel_result thread_cancel( lua_State* L, Lane* s, double secs_, bool_t force_, double waitkill_timeout_) | ||
491 | { | ||
492 | // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here | ||
493 | // We can read 's->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) | ||
494 | if( s->mstatus == KILLED) | ||
495 | { | ||
496 | return CR_Killed; | ||
497 | } | ||
498 | |||
499 | if( s->status >= DONE) | ||
515 | { | 500 | { |
516 | // say "ok" by default, including when lane is already done | 501 | // say "ok" by default, including when lane is already done |
517 | result = CR_Cancelled; | 502 | return CR_Cancelled; |
518 | } | 503 | } |
519 | return result; | 504 | |
505 | // signal the linda the wake up the thread so that it can react to the cancel query | ||
506 | // let us hope we never land here with a pointer on a linda that has been destroyed... | ||
507 | if( secs_ < 0.0) | ||
508 | { | ||
509 | return thread_cancel_soft( L, s, force_); | ||
510 | } | ||
511 | |||
512 | return thread_cancel_hard( L, s, secs_, force_, waitkill_timeout_); | ||
520 | } | 513 | } |
521 | 514 | ||
522 | // | 515 | // |
523 | // Protects modifying the selfdestruct chain | 516 | // Protects modifying the selfdestruct chain |
524 | 517 | ||
525 | #define SELFDESTRUCT_END ((Lane*)(-1)) | 518 | #define SELFDESTRUCT_END ((Lane*)(-1)) |
526 | // | 519 | // |
527 | // The chain is ended by '(Lane*)(-1)', not NULL: | 520 | // The chain is ended by '(Lane*)(-1)', not NULL: |
528 | // 'selfdestruct_first -> ... -> ... -> (-1)' | 521 | // 'selfdestruct_first -> ... -> ... -> (-1)' |
529 | 522 | ||
530 | /* | 523 | /* |
531 | * Add the lane to selfdestruct chain; the ones still running at the end of the | 524 | * Add the lane to selfdestruct chain; the ones still running at the end of the |
diff --git a/src/linda.c b/src/linda.c index 69f41b6..150649d 100644 --- a/src/linda.c +++ b/src/linda.c | |||
@@ -245,7 +245,7 @@ LUAG_FUNC( linda_send) | |||
245 | { | 245 | { |
246 | case CANCEL_SOFT: | 246 | case CANCEL_SOFT: |
247 | // if user wants to soft-cancel, the call returns lanes.cancel_error | 247 | // if user wants to soft-cancel, the call returns lanes.cancel_error |
248 | push_unique_key( L, CANCEL_ERROR); | 248 | push_unique_key( L, CANCEL_ERROR); |
249 | return 1; | 249 | return 1; |
250 | 250 | ||
251 | case CANCEL_HARD: | 251 | case CANCEL_HARD: |
@@ -400,7 +400,7 @@ LUAG_FUNC( linda_receive) | |||
400 | { | 400 | { |
401 | case CANCEL_SOFT: | 401 | case CANCEL_SOFT: |
402 | // if user wants to soft-cancel, the call returns CANCEL_ERROR | 402 | // if user wants to soft-cancel, the call returns CANCEL_ERROR |
403 | push_unique_key( L, CANCEL_ERROR); | 403 | push_unique_key( L, CANCEL_ERROR); |
404 | return 1; | 404 | return 1; |
405 | 405 | ||
406 | case CANCEL_HARD: | 406 | case CANCEL_HARD: |
diff --git a/tests/basic.lua b/tests/basic.lua index 7d42ad5..020fe78 100644 --- a/tests/basic.lua +++ b/tests/basic.lua | |||
@@ -26,7 +26,7 @@ local function PRINT(...) | |||
26 | end | 26 | end |
27 | 27 | ||
28 | local gc_cb = function( name_, status_) | 28 | local gc_cb = function( name_, status_) |
29 | PRINT( " ---> lane '" .. name_ .. "' collected with status " .. status_) | 29 | PRINT( " ---> lane '" .. name_ .. "' collected with status " .. status_) |
30 | end | 30 | end |
31 | --gc_cb = nil | 31 | --gc_cb = nil |
32 | 32 | ||
@@ -77,7 +77,7 @@ local function task( a, b, c ) | |||
77 | end | 77 | end |
78 | 78 | ||
79 | local task_launch= lanes_gen( "", { globals={hey=true}, gc_cb = gc_cb}, task ) | 79 | local task_launch= lanes_gen( "", { globals={hey=true}, gc_cb = gc_cb}, task ) |
80 | -- base stdlibs, normal priority | 80 | -- base stdlibs, normal priority |
81 | 81 | ||
82 | -- 'task_launch' is a factory of multithreaded tasks, we can launch several: | 82 | -- 'task_launch' is a factory of multithreaded tasks, we can launch several: |
83 | 83 | ||
@@ -155,12 +155,12 @@ limited:limit( "key", 1) | |||
155 | -- [[################################################ | 155 | -- [[################################################ |
156 | limited:send( "key", "hello") -- saturate linda | 156 | limited:send( "key", "hello") -- saturate linda |
157 | for k, v in pairs( limited:dump()) do | 157 | for k, v in pairs( limited:dump()) do |
158 | PRINT("limited[" .. tostring( k) .. "] = " .. tostring( v)) | 158 | PRINT("limited[" .. tostring( k) .. "] = " .. tostring( v)) |
159 | end | 159 | end |
160 | local wait_send = function() | 160 | local wait_send = function() |
161 | local a,b | 161 | local a,b |
162 | set_finalizer( function() print( "wait_send", a, b) end) | 162 | set_finalizer( function() print( "wait_send", a, b) end) |
163 | a,b = limited:send( "key", "bybye") -- infinite timeout, returns only when lane is cancelled | 163 | a,b = limited:send( "key", "bybye") -- infinite timeout, returns only when lane is cancelled |
164 | end | 164 | end |
165 | 165 | ||
166 | local wait_send_lane = lanes.gen( "*", wait_send)() | 166 | local wait_send_lane = lanes.gen( "*", wait_send)() |
@@ -171,9 +171,9 @@ repeat until wait_send_lane.status == "cancelled" | |||
171 | print "wait_send_lane is cancelled" | 171 | print "wait_send_lane is cancelled" |
172 | --################################################]] | 172 | --################################################]] |
173 | local wait_receive = function() | 173 | local wait_receive = function() |
174 | local k, v | 174 | local k, v |
175 | set_finalizer( function() print( "wait_receive", k, v) end) | 175 | set_finalizer( function() print( "wait_receive", k, v) end) |
176 | k, v = limited:receive( "dummy") -- infinite timeout, returns only when lane is cancelled | 176 | k, v = limited:receive( "dummy") -- infinite timeout, returns only when lane is cancelled |
177 | end | 177 | end |
178 | 178 | ||
179 | local wait_receive_lane = lanes.gen( "*", wait_receive)() | 179 | local wait_receive_lane = lanes.gen( "*", wait_receive)() |
@@ -184,9 +184,9 @@ repeat until wait_receive_lane.status == "cancelled" | |||
184 | print "wait_receive_lane is cancelled" | 184 | print "wait_receive_lane is cancelled" |
185 | --################################################]] | 185 | --################################################]] |
186 | local wait_receive_batched = function() | 186 | local wait_receive_batched = function() |
187 | local k, v1, v2 | 187 | local k, v1, v2 |
188 | set_finalizer( function() print( "wait_receive_batched", k, v1, v2) end) | 188 | set_finalizer( function() print( "wait_receive_batched", k, v1, v2) end) |
189 | k, v1, v2 = limited:receive( limited.batched, "dummy", 2) -- infinite timeout, returns only when lane is cancelled | 189 | k, v1, v2 = limited:receive( limited.batched, "dummy", 2) -- infinite timeout, returns only when lane is cancelled |
190 | end | 190 | end |
191 | 191 | ||
192 | local wait_receive_batched_lane = lanes.gen( "*", wait_receive_batched)() | 192 | local wait_receive_batched_lane = lanes.gen( "*", wait_receive_batched)() |
@@ -206,7 +206,7 @@ PRINT( "\n\n", "---=== Communications ===---", "\n\n") | |||
206 | local function WR(...) io.stderr:write(...) end | 206 | local function WR(...) io.stderr:write(...) end |
207 | 207 | ||
208 | local chunk= function( linda ) | 208 | local chunk= function( linda ) |
209 | set_debug_threadname "chunk" | 209 | set_debug_threadname "chunk" |
210 | local function receive() return linda:receive( "->" ) end | 210 | local function receive() return linda:receive( "->" ) end |
211 | local function send(...) linda:send( "<-", ... ) end | 211 | local function send(...) linda:send( "<-", ... ) end |
212 | 212 | ||
@@ -254,9 +254,9 @@ local a,b,c= RECEIVE(), RECEIVE(), RECEIVE() | |||
254 | 254 | ||
255 | print( "lane status: " .. t.status) | 255 | print( "lane status: " .. t.status) |
256 | if t.status == "error" then | 256 | if t.status == "error" then |
257 | print( t:join()) | 257 | print( t:join()) |
258 | else | 258 | else |
259 | WR( a..", "..b..", "..c.." received\n" ) | 259 | WR( a..", "..b..", "..c.." received\n" ) |
260 | end | 260 | end |
261 | 261 | ||
262 | assert( a==1 and b==2 and c==3 ) | 262 | assert( a==1 and b==2 and c==3 ) |
@@ -286,50 +286,50 @@ linda: receive( 1, "wait") | |||
286 | PRINT( "\n\n", "---=== Stdlib naming ===---", "\n\n") | 286 | PRINT( "\n\n", "---=== Stdlib naming ===---", "\n\n") |
287 | 287 | ||
288 | local function dump_g( _x) | 288 | local function dump_g( _x) |
289 | set_debug_threadname "dump_g" | 289 | set_debug_threadname "dump_g" |
290 | assert(print) | 290 | assert(print) |
291 | print( "### dumping _G for '" .. _x .. "'") | 291 | print( "### dumping _G for '" .. _x .. "'") |
292 | for k, v in pairs( _G) do | 292 | for k, v in pairs( _G) do |
293 | print( "\t" .. k .. ": " .. type( v)) | 293 | print( "\t" .. k .. ": " .. type( v)) |
294 | end | 294 | end |
295 | return true | 295 | return true |
296 | end | 296 | end |
297 | 297 | ||
298 | local function io_os_f( _x) | 298 | local function io_os_f( _x) |
299 | set_debug_threadname "io_os_f" | 299 | set_debug_threadname "io_os_f" |
300 | assert(print) | 300 | assert(print) |
301 | print( "### checking io and os libs existence for '" .. _x .. "'") | 301 | print( "### checking io and os libs existence for '" .. _x .. "'") |
302 | assert(io) | 302 | assert(io) |
303 | assert(os) | 303 | assert(os) |
304 | return true | 304 | return true |
305 | end | 305 | end |
306 | 306 | ||
307 | local function coro_f( _x) | 307 | local function coro_f( _x) |
308 | set_debug_threadname "coro_f" | 308 | set_debug_threadname "coro_f" |
309 | assert(print) | 309 | assert(print) |
310 | print( "### checking coroutine lib existence for '" .. _x .. "'") | 310 | print( "### checking coroutine lib existence for '" .. _x .. "'") |
311 | assert(coroutine) | 311 | assert(coroutine) |
312 | return true | 312 | return true |
313 | end | 313 | end |
314 | 314 | ||
315 | assert.fails( function() lanes_gen( "xxx", {gc_cb = gc_cb}, io_os_f ) end ) | 315 | assert.fails( function() lanes_gen( "xxx", {gc_cb = gc_cb}, io_os_f ) end ) |
316 | 316 | ||
317 | local stdlib_naming_tests = | 317 | local stdlib_naming_tests = |
318 | { | 318 | { |
319 | -- { "", dump_g}, | 319 | -- { "", dump_g}, |
320 | -- { "coroutine", dump_g}, | 320 | -- { "coroutine", dump_g}, |
321 | -- { "io", dump_g}, | 321 | -- { "io", dump_g}, |
322 | -- { "bit32", dump_g}, | 322 | -- { "bit32", dump_g}, |
323 | { "coroutine", coro_f}, | 323 | { "coroutine", coro_f}, |
324 | { "*", io_os_f}, | 324 | { "*", io_os_f}, |
325 | { "io,os", io_os_f}, | 325 | { "io,os", io_os_f}, |
326 | { "io+os", io_os_f}, | 326 | { "io+os", io_os_f}, |
327 | { "io,os,base", io_os_f}, | 327 | { "io,os,base", io_os_f}, |
328 | } | 328 | } |
329 | 329 | ||
330 | for _, t in ipairs( stdlib_naming_tests) do | 330 | for _, t in ipairs( stdlib_naming_tests) do |
331 | local f= lanes_gen( t[1], {gc_cb = gc_cb}, t[2]) -- any delimiter will do | 331 | local f= lanes_gen( t[1], {gc_cb = gc_cb}, t[2]) -- any delimiter will do |
332 | assert( f(t[1])[1] ) | 332 | assert( f(t[1])[1] ) |
333 | end | 333 | end |
334 | 334 | ||
335 | collectgarbage() | 335 | collectgarbage() |
@@ -344,7 +344,7 @@ PRINT( "\n\n", "---=== Comms criss cross ===---", "\n\n") | |||
344 | -- | 344 | -- |
345 | local tc= lanes_gen( "io", {gc_cb = gc_cb}, | 345 | local tc= lanes_gen( "io", {gc_cb = gc_cb}, |
346 | function( linda, ch_in, ch_out ) | 346 | function( linda, ch_in, ch_out ) |
347 | set_debug_threadname( "criss cross " .. ch_in .. " -> " .. ch_out) | 347 | set_debug_threadname( "criss cross " .. ch_in .. " -> " .. ch_out) |
348 | local function STAGE(str) | 348 | local function STAGE(str) |
349 | io.stderr:write( ch_in..": "..str.."\n" ) | 349 | io.stderr:write( ch_in..": "..str.."\n" ) |
350 | linda:send( nil, ch_out, str ) | 350 | linda:send( nil, ch_out, str ) |
@@ -412,7 +412,7 @@ linda:send( "down", function(linda) linda:send( "up", "ready!" ) end, | |||
412 | -- | 412 | -- |
413 | local k,s= linda:receive( 1, "up" ) | 413 | local k,s= linda:receive( 1, "up" ) |
414 | if t2.status == "error" then | 414 | if t2.status == "error" then |
415 | print( "t2 error: " , t2:join()) | 415 | print( "t2 error: " , t2:join()) |
416 | end | 416 | end |
417 | PRINT(s) | 417 | PRINT(s) |
418 | assert( s=="ready!" ) | 418 | assert( s=="ready!" ) |
@@ -440,13 +440,13 @@ PRINT( "\n\n", "---=== :join test ===---", "\n\n") | |||
440 | 440 | ||
441 | local S= lanes_gen( "table", {gc_cb = gc_cb}, | 441 | local S= lanes_gen( "table", {gc_cb = gc_cb}, |
442 | function(arg) | 442 | function(arg) |
443 | set_debug_threadname "join test lane" | 443 | set_debug_threadname "join test lane" |
444 | set_finalizer( function() end) | 444 | set_finalizer( function() end) |
445 | aux= {} | 445 | aux= {} |
446 | for i, v in ipairs(arg) do | 446 | for i, v in ipairs(arg) do |
447 | table.insert (aux, 1, v) | 447 | table.insert (aux, 1, v) |
448 | end | 448 | end |
449 | -- unpack was renamed table.unpack in Lua 5.2: cater for both! | 449 | -- unpack was renamed table.unpack in Lua 5.2: cater for both! |
450 | return (unpack or table.unpack)(aux) | 450 | return (unpack or table.unpack)(aux) |
451 | end ) | 451 | end ) |
452 | 452 | ||
@@ -456,13 +456,13 @@ linda:receive(0.5, "gloupti") | |||
456 | print( "joining with '" .. h:get_debug_threadname() .. "'") | 456 | print( "joining with '" .. h:get_debug_threadname() .. "'") |
457 | local a,b,c,d= h:join() | 457 | local a,b,c,d= h:join() |
458 | if h.status == "error" then | 458 | if h.status == "error" then |
459 | print( h:get_debug_threadname(), "error: " , a, b, c, d) | 459 | print( h:get_debug_threadname(), "error: " , a, b, c, d) |
460 | else | 460 | else |
461 | print( h:get_debug_threadname(), a,b,c,d) | 461 | print( h:get_debug_threadname(), a,b,c,d) |
462 | assert(a==14) | 462 | assert(a==14) |
463 | assert(b==13) | 463 | assert(b==13) |
464 | assert(c==12) | 464 | assert(c==12) |
465 | assert(d==nil) | 465 | assert(d==nil) |
466 | end | 466 | end |
467 | 467 | ||
468 | local nameof_type, nameof_name = lanes.nameof( print) | 468 | local nameof_type, nameof_name = lanes.nameof( print) |