diff options
| author | moteus <mimir@newmail.ru> | 2013-12-26 18:52:58 +0400 |
|---|---|---|
| committer | moteus <mimir@newmail.ru> | 2013-12-26 18:52:58 +0400 |
| commit | e3fe5123a36643634e00de6a1bb940e6e1febade (patch) | |
| tree | 8eecc3aa10e8c6842efe1d798758942f35f555f6 | |
| parent | 5291caae5be68250a4d9aafa54fe2081ab2a6113 (diff) | |
| download | lua-llthreads2-e3fe5123a36643634e00de6a1bb940e6e1febade.tar.gz lua-llthreads2-e3fe5123a36643634e00de6a1bb940e6e1febade.tar.bz2 lua-llthreads2-e3fe5123a36643634e00de6a1bb940e6e1febade.zip | |
Add. `joinable` parameter to `start` method which control in which thread child Lua VM will be destroyed.
| -rw-r--r-- | .travis.yml | 1 | ||||
| -rw-r--r-- | README.md | 41 | ||||
| -rw-r--r-- | src/llthread.c | 95 | ||||
| -rw-r--r-- | test/test_join_detach.lua | 26 | ||||
| -rw-r--r-- | test/test_join_timeout.lua | 2 |
5 files changed, 137 insertions, 28 deletions
diff --git a/.travis.yml b/.travis.yml index 4a28f5e..7162735 100644 --- a/.travis.yml +++ b/.travis.yml | |||
| @@ -49,6 +49,7 @@ script: | |||
| 49 | - lua$LUA_SFX test_llthreads.lua | 49 | - lua$LUA_SFX test_llthreads.lua |
| 50 | - lua$LUA_SFX test_register_llthreads.lua | 50 | - lua$LUA_SFX test_register_llthreads.lua |
| 51 | - lua$LUA_SFX test_join_timeout.lua | 51 | - lua$LUA_SFX test_join_timeout.lua |
| 52 | - lua$LUA_SFX test_join_detach.lua | ||
| 52 | 53 | ||
| 53 | notifications: | 54 | notifications: |
| 54 | email: | 55 | email: |
| @@ -16,6 +16,7 @@ This is full dropin replacement for [llthreads](https://github.com/Neopallium/lu | |||
| 16 | * thread:join() method support zero timeout to check if thread alive | 16 | * thread:join() method support zero timeout to check if thread alive |
| 17 | * thread:join() method support arbitrary timeout on Windows platform | 17 | * thread:join() method support arbitrary timeout on Windows platform |
| 18 | * set_logger function allow logging errors (crash Lua VM) in current llthread's threads | 18 | * set_logger function allow logging errors (crash Lua VM) in current llthread's threads |
| 19 | * thread:start() has additional parameter which control in which thread child Lua VM will be destroyed | ||
| 19 | 20 | ||
| 20 | ##Usage | 21 | ##Usage |
| 21 | 22 | ||
| @@ -33,5 +34,45 @@ llthread.set_logger(function(msg) LOG.error(msg) end) | |||
| 33 | error("SOME ERROR") | 34 | error("SOME ERROR") |
| 34 | ``` | 35 | ``` |
| 35 | 36 | ||
| 37 | ### Start atached thread collectd in child thread | ||
| 38 | ``` Lua | ||
| 39 | -- This is main thread. | ||
| 40 | local thread = require "llthreads".new[[ | ||
| 41 | require "utils".sleep(5) | ||
| 42 | ]] | ||
| 43 | |||
| 44 | -- We tell that we start atached thread | ||
| 45 | -- but child Lua State shuld be close | ||
| 46 | -- in child thread. | ||
| 47 | -- So thread:join() can not return any values. | ||
| 48 | -- If `thread` became garbage in main thread then | ||
| 49 | -- finallizer calls thread:join() and main thread | ||
| 50 | -- may hungup. | ||
| 51 | thread:start(false, false) | ||
| 52 | |||
| 53 | -- we can call join | ||
| 54 | thread:join() | ||
| 55 | ``` | ||
| 56 | |||
| 57 | ### Start detached thread collectd on which we can call join | ||
| 58 | ``` Lua | ||
| 59 | -- This is main thread. | ||
| 60 | local thread = require "llthreads".new[[ | ||
| 61 | require "utils".sleep(5) | ||
| 62 | ]] | ||
| 63 | |||
| 64 | -- We tell that we start detached thread | ||
| 65 | -- but with ability call thread:join() | ||
| 66 | -- and gets lua return values from child thread. | ||
| 67 | -- In fact we start atached thread but if `thread` | ||
| 68 | -- became garbage in main thread then finallizer | ||
| 69 | -- just detach child thread and main thread | ||
| 70 | -- may not hungup. | ||
| 71 | thread:start(true, true) | ||
| 72 | |||
| 73 | -- we can call join | ||
| 74 | thread:join() | ||
| 75 | ``` | ||
| 76 | |||
| 36 | [](https://bitdeli.com/free "Bitdeli Badge") | 77 | [](https://bitdeli.com/free "Bitdeli Badge") |
| 37 | 78 | ||
diff --git a/src/llthread.c b/src/llthread.c index 0596b2a..b61bf11 100644 --- a/src/llthread.c +++ b/src/llthread.c | |||
| @@ -289,6 +289,7 @@ static int fail(lua_State *L, const char *msg){ | |||
| 289 | #define TSTATE_STARTED (flags_t)1<<0 | 289 | #define TSTATE_STARTED (flags_t)1<<0 |
| 290 | #define TSTATE_DETACHED (flags_t)1<<1 | 290 | #define TSTATE_DETACHED (flags_t)1<<1 |
| 291 | #define TSTATE_JOINED (flags_t)1<<2 | 291 | #define TSTATE_JOINED (flags_t)1<<2 |
| 292 | #define FLAG_JOIN_LUA (flags_t)1<<3 | ||
| 292 | 293 | ||
| 293 | /*At leas one flag*/ | 294 | /*At leas one flag*/ |
| 294 | #define FLAG_IS_SET(O, F) (O->flags & (flags_t)(F)) | 295 | #define FLAG_IS_SET(O, F) (O->flags & (flags_t)(F)) |
| @@ -397,7 +398,7 @@ static OS_THREAD_RETURT llthread_child_thread_run(void *arg) { | |||
| 397 | } | 398 | } |
| 398 | 399 | ||
| 399 | /* if thread is detached, then destroy the child state. */ | 400 | /* if thread is detached, then destroy the child state. */ |
| 400 | if(FLAG_IS_SET(this, TSTATE_DETACHED)) { | 401 | if(!FLAG_IS_SET(this, FLAG_JOIN_LUA)) { |
| 401 | /* thread is detached, so it must clean-up the child state. */ | 402 | /* thread is detached, so it must clean-up the child state. */ |
| 402 | llthread_child_destroy(this); | 403 | llthread_child_destroy(this); |
| 403 | this = NULL; | 404 | this = NULL; |
| @@ -450,7 +451,8 @@ static void llthread_destroy(llthread_t *this) { | |||
| 450 | * we have joined the thread. | 451 | * we have joined the thread. |
| 451 | */ | 452 | */ |
| 452 | if(FLAG_IS_SET(this, TSTATE_JOINED)||(this->flags == TSTATE_NONE)) { | 453 | if(FLAG_IS_SET(this, TSTATE_JOINED)||(this->flags == TSTATE_NONE)) { |
| 453 | llthread_cleanup_child(this); | 454 | if(FLAG_IS_SET(this, FLAG_JOIN_LUA)) |
| 455 | llthread_cleanup_child(this); | ||
| 454 | } | 456 | } |
| 455 | FREE_STRUCT(this); | 457 | FREE_STRUCT(this); |
| 456 | } | 458 | } |
| @@ -463,12 +465,33 @@ static int llthread_push_results(lua_State *L, llthread_child_t *child, int idx, | |||
| 463 | return llthread_copy_values(child->L, L, idx, top, 0 /* is_arg */); | 465 | return llthread_copy_values(child->L, L, idx, top, 0 /* is_arg */); |
| 464 | } | 466 | } |
| 465 | 467 | ||
| 466 | static int llthread_start(llthread_t *this, int start_detached) { | 468 | static int llthread_detach(llthread_t *this){ |
| 469 | int rc = 0; | ||
| 470 | this->child = NULL; | ||
| 471 | FLAG_SET(this, TSTATE_DETACHED); | ||
| 472 | FLAG_UNSET(this, FLAG_JOIN_LUA); | ||
| 473 | #ifdef USE_PTHREAD | ||
| 474 | rc = pthread_detach(this->thread); | ||
| 475 | #endif | ||
| 476 | return rc; | ||
| 477 | } | ||
| 478 | |||
| 479 | /* | detached | join_lua || return values | which thread | gc calls | detach on | | ||
| 480 | | | || thread:join() | closes lua state | | | | ||
| 481 | * ------------------------------------------------------------------------------------- | ||
| 482 | * | false | falas || <NONE> | child | <NONE> | <NEVER> | | ||
| 483 | * *| false | true || Lua values | parent | join | <NEVER> | | ||
| 484 | * *| true | false || <ERROR> | child | <NONE> | start | | ||
| 485 | * | true | true || <NONE> | parent | detach | gc | | ||
| 486 | * ------------------------------------------------------------------------------------- | ||
| 487 | * * llthread behavior. | ||
| 488 | */ | ||
| 489 | static int llthread_start(llthread_t *this, int start_detached, int join_lua) { | ||
| 467 | llthread_child_t *child = this->child; | 490 | llthread_child_t *child = this->child; |
| 468 | int rc = 0; | 491 | int rc = 0; |
| 469 | 492 | ||
| 470 | if(start_detached){ | 493 | if(join_lua){ /*child does not close lua_State*/ |
| 471 | FLAG_SET(child, TSTATE_DETACHED); | 494 | FLAG_SET(child, FLAG_JOIN_LUA); |
| 472 | } | 495 | } |
| 473 | 496 | ||
| 474 | #ifndef USE_PTHREAD | 497 | #ifndef USE_PTHREAD |
| @@ -482,12 +505,10 @@ static int llthread_start(llthread_t *this, int start_detached) { | |||
| 482 | 505 | ||
| 483 | if(rc == 0) { | 506 | if(rc == 0) { |
| 484 | FLAG_SET(this, TSTATE_STARTED); | 507 | FLAG_SET(this, TSTATE_STARTED); |
| 485 | if(start_detached) { | 508 | if(join_lua) FLAG_SET(this, FLAG_JOIN_LUA); |
| 486 | FLAG_SET(this, TSTATE_DETACHED); | 509 | if(start_detached) FLAG_SET(this, TSTATE_DETACHED); |
| 487 | this->child = NULL; | 510 | if((start_detached)&&(!join_lua)){ |
| 488 | #ifdef USE_PTHREAD | 511 | rc = llthread_detach(this); |
| 489 | rc = pthread_detach(this->thread); | ||
| 490 | #endif | ||
| 491 | } | 512 | } |
| 492 | } | 513 | } |
| 493 | 514 | ||
| @@ -523,7 +544,7 @@ static int llthread_join(llthread_t *this, join_timeout_t timeout) { | |||
| 523 | return rc; | 544 | return rc; |
| 524 | } | 545 | } |
| 525 | 546 | ||
| 526 | // @todo use pthread_tryjoin_np to support timeout | 547 | /* @todo use pthread_tryjoin_np to support timeout */ |
| 527 | 548 | ||
| 528 | /* then join the thread. */ | 549 | /* then join the thread. */ |
| 529 | rc = pthread_join(this->thread, NULL); | 550 | rc = pthread_join(this->thread, NULL); |
| @@ -582,17 +603,25 @@ static int l_llthread_delete(lua_State *L) { | |||
| 582 | /*already exists*/ | 603 | /*already exists*/ |
| 583 | if(this == NULL) return 0; | 604 | if(this == NULL) return 0; |
| 584 | 605 | ||
| 585 | /* if the thread has been started and has not been detached/joined. */ | 606 | do{/* join the thread. */ |
| 586 | if( FLAG_IS_SET(this, TSTATE_STARTED) && | 607 | if(!FLAG_IS_SET(this, TSTATE_STARTED)) break; |
| 587 | !FLAG_IS_SET(this, (TSTATE_DETACHED|TSTATE_JOINED)) | 608 | if( FLAG_IS_SET(this, TSTATE_JOINED)) break; |
| 588 | ){ | 609 | |
| 589 | /* then join the thread. */ | 610 | if(FLAG_IS_SET(this, TSTATE_DETACHED)){ |
| 590 | llthread_child_t *child = this->child; | 611 | if(FLAG_IS_SET(this, FLAG_JOIN_LUA)){ |
| 591 | llthread_join(this, INFINITE_JOIN_TIMEOUT); | 612 | llthread_detach(this); |
| 592 | if(child && child->status != 0) { | 613 | } |
| 593 | llthread_log(child->L, "Error from non-joined thread: ", lua_tostring(child->L, -1)); | 614 | break; |
| 594 | } | 615 | } |
| 595 | } | 616 | |
| 617 | { /* @todo log warning about lost thread for debug? */ | ||
| 618 | llthread_child_t *child = this->child; | ||
| 619 | llthread_join(this, INFINITE_JOIN_TIMEOUT); | ||
| 620 | if(child && child->status != 0) { | ||
| 621 | llthread_log(child->L, "Error from non-joined thread: ", lua_tostring(child->L, -1)); | ||
| 622 | } | ||
| 623 | } | ||
| 624 | }while(0); | ||
| 596 | 625 | ||
| 597 | llthread_destroy(this); | 626 | llthread_destroy(this); |
| 598 | *pthis = NULL; | 627 | *pthis = NULL; |
| @@ -603,13 +632,16 @@ static int l_llthread_delete(lua_State *L) { | |||
| 603 | static int l_llthread_start(lua_State *L) { | 632 | static int l_llthread_start(lua_State *L) { |
| 604 | llthread_t *this = l_llthread_at(L, 1); | 633 | llthread_t *this = l_llthread_at(L, 1); |
| 605 | int start_detached = lua_toboolean(L, 2); | 634 | int start_detached = lua_toboolean(L, 2); |
| 606 | int rc; | 635 | int join_lua, rc; |
| 636 | |||
| 637 | if(!lua_isnone(L, 3)) join_lua = lua_toboolean(L, 3); | ||
| 638 | else join_lua = start_detached ? 0 : 1; | ||
| 607 | 639 | ||
| 608 | if(this->flags != TSTATE_NONE) { | 640 | if(this->flags != TSTATE_NONE) { |
| 609 | return fail(L, "Thread already started."); | 641 | return fail(L, "Thread already started."); |
| 610 | } | 642 | } |
| 611 | 643 | ||
| 612 | rc = llthread_start(this, start_detached); | 644 | rc = llthread_start(this, start_detached, join_lua); |
| 613 | if(rc != 0) { | 645 | if(rc != 0) { |
| 614 | char buf[ERROR_LEN]; | 646 | char buf[ERROR_LEN]; |
| 615 | strerror_r(errno, buf, ERROR_LEN); | 647 | strerror_r(errno, buf, ERROR_LEN); |
| @@ -628,7 +660,7 @@ static int l_llthread_join(lua_State *L) { | |||
| 628 | if(!FLAG_IS_SET(this, TSTATE_STARTED )) { | 660 | if(!FLAG_IS_SET(this, TSTATE_STARTED )) { |
| 629 | return fail(L, "Can't join a thread that hasn't be started."); | 661 | return fail(L, "Can't join a thread that hasn't be started."); |
| 630 | } | 662 | } |
| 631 | if( FLAG_IS_SET(this, TSTATE_DETACHED)) { | 663 | if( FLAG_IS_SET(this, TSTATE_DETACHED) && !FLAG_IS_SET(this, FLAG_JOIN_LUA)) { |
| 632 | return fail(L, "Can't join a thread that has been detached."); | 664 | return fail(L, "Can't join a thread that has been detached."); |
| 633 | } | 665 | } |
| 634 | if( FLAG_IS_SET(this, TSTATE_JOINED )) { | 666 | if( FLAG_IS_SET(this, TSTATE_JOINED )) { |
| @@ -638,9 +670,17 @@ static int l_llthread_join(lua_State *L) { | |||
| 638 | /* join the thread. */ | 670 | /* join the thread. */ |
| 639 | rc = llthread_join(this, luaL_optint(L, 2, INFINITE_JOIN_TIMEOUT)); | 671 | rc = llthread_join(this, luaL_optint(L, 2, INFINITE_JOIN_TIMEOUT)); |
| 640 | 672 | ||
| 641 | /* Push all results after the Lua code. */ | ||
| 642 | if(child && FLAG_IS_SET(this, TSTATE_JOINED)) { | 673 | if(child && FLAG_IS_SET(this, TSTATE_JOINED)) { |
| 643 | int top; | 674 | int top; |
| 675 | |||
| 676 | if(!FLAG_IS_SET(this, FLAG_JOIN_LUA)){ | ||
| 677 | /*child lua state has been destroyed by child thread*/ | ||
| 678 | /*@todo return thread exit code*/ | ||
| 679 | lua_pushnumber(L, 0); | ||
| 680 | return 1; | ||
| 681 | } | ||
| 682 | |||
| 683 | /* copy values from child lua state */ | ||
| 644 | if(child->status != 0) { | 684 | if(child->status != 0) { |
| 645 | const char *err_msg = lua_tostring(child->L, -1); | 685 | const char *err_msg = lua_tostring(child->L, -1); |
| 646 | lua_pushboolean(L, 0); | 686 | lua_pushboolean(L, 0); |
| @@ -652,12 +692,13 @@ static int l_llthread_join(lua_State *L) { | |||
| 652 | /* return results to parent thread. */ | 692 | /* return results to parent thread. */ |
| 653 | llthread_push_results(L, child, 2, top); | 693 | llthread_push_results(L, child, 2, top); |
| 654 | } | 694 | } |
| 695 | |||
| 655 | llthread_cleanup_child(this); | 696 | llthread_cleanup_child(this); |
| 656 | return top; | 697 | return top; |
| 657 | } | 698 | } |
| 658 | 699 | ||
| 659 | if( rc == JOIN_ETIMEDOUT ){ | 700 | if( rc == JOIN_ETIMEDOUT ){ |
| 660 | lua_pushboolean(L, 0); | 701 | lua_pushnil(L); |
| 661 | lua_pushstring(L, "timeout"); | 702 | lua_pushstring(L, "timeout"); |
| 662 | return 2; | 703 | return 2; |
| 663 | } | 704 | } |
diff --git a/test/test_join_detach.lua b/test/test_join_detach.lua new file mode 100644 index 0000000..dd3ef11 --- /dev/null +++ b/test/test_join_detach.lua | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | local llthreads = require"llthreads" | ||
| 2 | local utils = require "utils" | ||
| 3 | |||
| 4 | do | ||
| 5 | |||
| 6 | local thread = llthreads.new(utils.thread_init .. [[ | ||
| 7 | local sleep = require"utils".sleep | ||
| 8 | while true do sleep(1) end | ||
| 9 | ]]) | ||
| 10 | |||
| 11 | -- detached + joindable | ||
| 12 | thread:start(true, true) | ||
| 13 | |||
| 14 | local ok, err = thread:join(0) | ||
| 15 | print("thread:join(0): ", ok, err) | ||
| 16 | assert(ok == nil) | ||
| 17 | assert(err == "timeout") | ||
| 18 | |||
| 19 | end | ||
| 20 | |||
| 21 | -- enforce collect `thread` object | ||
| 22 | -- we should not hungup | ||
| 23 | for i = 1, 10 do collectgarbage("collect") end | ||
| 24 | |||
| 25 | print("Done!") | ||
| 26 | |||
diff --git a/test/test_join_timeout.lua b/test/test_join_timeout.lua index 085497f..aa8f88d 100644 --- a/test/test_join_timeout.lua +++ b/test/test_join_timeout.lua | |||
| @@ -14,7 +14,7 @@ local thread = llthreads.new(include .. [[ | |||
| 14 | thread:start() | 14 | thread:start() |
| 15 | local ok, err = thread:join(0) | 15 | local ok, err = thread:join(0) |
| 16 | print("thread:join(0): ", ok, err) | 16 | print("thread:join(0): ", ok, err) |
| 17 | assert(ok == false) | 17 | assert(ok == nil) |
| 18 | assert(err == "timeout") | 18 | assert(err == "timeout") |
| 19 | 19 | ||
| 20 | print("thread:join(): ", thread:join()) | 20 | print("thread:join(): ", thread:join()) |
