From e3fe5123a36643634e00de6a1bb940e6e1febade Mon Sep 17 00:00:00 2001 From: moteus Date: Thu, 26 Dec 2013 18:52:58 +0400 Subject: Add. `joinable` parameter to `start` method which control in which thread child Lua VM will be destroyed. --- .travis.yml | 1 + README.md | 41 ++++++++++++++++++++ src/llthread.c | 95 +++++++++++++++++++++++++++++++++------------- test/test_join_detach.lua | 26 +++++++++++++ test/test_join_timeout.lua | 2 +- 5 files changed, 137 insertions(+), 28 deletions(-) create mode 100644 test/test_join_detach.lua diff --git a/.travis.yml b/.travis.yml index 4a28f5e..7162735 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,6 +49,7 @@ script: - lua$LUA_SFX test_llthreads.lua - lua$LUA_SFX test_register_llthreads.lua - lua$LUA_SFX test_join_timeout.lua + - lua$LUA_SFX test_join_detach.lua notifications: email: diff --git a/README.md b/README.md index b14d44d..a5dc46e 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ This is full dropin replacement for [llthreads](https://github.com/Neopallium/lu * thread:join() method support zero timeout to check if thread alive * thread:join() method support arbitrary timeout on Windows platform * set_logger function allow logging errors (crash Lua VM) in current llthread's threads +* thread:start() has additional parameter which control in which thread child Lua VM will be destroyed ##Usage @@ -33,5 +34,45 @@ llthread.set_logger(function(msg) LOG.error(msg) end) error("SOME ERROR") ``` +### Start atached thread collectd in child thread +``` Lua +-- This is main thread. +local thread = require "llthreads".new[[ + require "utils".sleep(5) +]] + +-- We tell that we start atached thread +-- but child Lua State shuld be close +-- in child thread. +-- So thread:join() can not return any values. +-- If `thread` became garbage in main thread then +-- finallizer calls thread:join() and main thread +-- may hungup. +thread:start(false, false) + +-- we can call join +thread:join() +``` + +### Start detached thread collectd on which we can call join +``` Lua +-- This is main thread. +local thread = require "llthreads".new[[ + require "utils".sleep(5) +]] + +-- We tell that we start detached thread +-- but with ability call thread:join() +-- and gets lua return values from child thread. +-- In fact we start atached thread but if `thread` +-- became garbage in main thread then finallizer +-- just detach child thread and main thread +-- may not hungup. +thread:start(true, true) + +-- we can call join +thread:join() +``` + [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/moteus/lua-llthreads2/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 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){ #define TSTATE_STARTED (flags_t)1<<0 #define TSTATE_DETACHED (flags_t)1<<1 #define TSTATE_JOINED (flags_t)1<<2 +#define FLAG_JOIN_LUA (flags_t)1<<3 /*At leas one flag*/ #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) { } /* if thread is detached, then destroy the child state. */ - if(FLAG_IS_SET(this, TSTATE_DETACHED)) { + if(!FLAG_IS_SET(this, FLAG_JOIN_LUA)) { /* thread is detached, so it must clean-up the child state. */ llthread_child_destroy(this); this = NULL; @@ -450,7 +451,8 @@ static void llthread_destroy(llthread_t *this) { * we have joined the thread. */ if(FLAG_IS_SET(this, TSTATE_JOINED)||(this->flags == TSTATE_NONE)) { - llthread_cleanup_child(this); + if(FLAG_IS_SET(this, FLAG_JOIN_LUA)) + llthread_cleanup_child(this); } FREE_STRUCT(this); } @@ -463,12 +465,33 @@ static int llthread_push_results(lua_State *L, llthread_child_t *child, int idx, return llthread_copy_values(child->L, L, idx, top, 0 /* is_arg */); } -static int llthread_start(llthread_t *this, int start_detached) { +static int llthread_detach(llthread_t *this){ + int rc = 0; + this->child = NULL; + FLAG_SET(this, TSTATE_DETACHED); + FLAG_UNSET(this, FLAG_JOIN_LUA); +#ifdef USE_PTHREAD + rc = pthread_detach(this->thread); +#endif + return rc; +} + +/* | detached | join_lua || return values | which thread | gc calls | detach on | + | | || thread:join() | closes lua state | | | +* ------------------------------------------------------------------------------------- +* | false | falas || | child | | | +* *| false | true || Lua values | parent | join | | +* *| true | false || | child | | start | +* | true | true || | parent | detach | gc | +* ------------------------------------------------------------------------------------- +* * llthread behavior. +*/ +static int llthread_start(llthread_t *this, int start_detached, int join_lua) { llthread_child_t *child = this->child; int rc = 0; - if(start_detached){ - FLAG_SET(child, TSTATE_DETACHED); + if(join_lua){ /*child does not close lua_State*/ + FLAG_SET(child, FLAG_JOIN_LUA); } #ifndef USE_PTHREAD @@ -482,12 +505,10 @@ static int llthread_start(llthread_t *this, int start_detached) { if(rc == 0) { FLAG_SET(this, TSTATE_STARTED); - if(start_detached) { - FLAG_SET(this, TSTATE_DETACHED); - this->child = NULL; -#ifdef USE_PTHREAD - rc = pthread_detach(this->thread); -#endif + if(join_lua) FLAG_SET(this, FLAG_JOIN_LUA); + if(start_detached) FLAG_SET(this, TSTATE_DETACHED); + if((start_detached)&&(!join_lua)){ + rc = llthread_detach(this); } } @@ -523,7 +544,7 @@ static int llthread_join(llthread_t *this, join_timeout_t timeout) { return rc; } - // @todo use pthread_tryjoin_np to support timeout + /* @todo use pthread_tryjoin_np to support timeout */ /* then join the thread. */ rc = pthread_join(this->thread, NULL); @@ -582,17 +603,25 @@ static int l_llthread_delete(lua_State *L) { /*already exists*/ if(this == NULL) return 0; - /* if the thread has been started and has not been detached/joined. */ - if( FLAG_IS_SET(this, TSTATE_STARTED) && - !FLAG_IS_SET(this, (TSTATE_DETACHED|TSTATE_JOINED)) - ){ - /* then join the thread. */ - llthread_child_t *child = this->child; - llthread_join(this, INFINITE_JOIN_TIMEOUT); - if(child && child->status != 0) { - llthread_log(child->L, "Error from non-joined thread: ", lua_tostring(child->L, -1)); + do{/* join the thread. */ + if(!FLAG_IS_SET(this, TSTATE_STARTED)) break; + if( FLAG_IS_SET(this, TSTATE_JOINED)) break; + + if(FLAG_IS_SET(this, TSTATE_DETACHED)){ + if(FLAG_IS_SET(this, FLAG_JOIN_LUA)){ + llthread_detach(this); + } + break; } - } + + { /* @todo log warning about lost thread for debug? */ + llthread_child_t *child = this->child; + llthread_join(this, INFINITE_JOIN_TIMEOUT); + if(child && child->status != 0) { + llthread_log(child->L, "Error from non-joined thread: ", lua_tostring(child->L, -1)); + } + } + }while(0); llthread_destroy(this); *pthis = NULL; @@ -603,13 +632,16 @@ static int l_llthread_delete(lua_State *L) { static int l_llthread_start(lua_State *L) { llthread_t *this = l_llthread_at(L, 1); int start_detached = lua_toboolean(L, 2); - int rc; + int join_lua, rc; + + if(!lua_isnone(L, 3)) join_lua = lua_toboolean(L, 3); + else join_lua = start_detached ? 0 : 1; if(this->flags != TSTATE_NONE) { return fail(L, "Thread already started."); } - rc = llthread_start(this, start_detached); + rc = llthread_start(this, start_detached, join_lua); if(rc != 0) { char buf[ERROR_LEN]; strerror_r(errno, buf, ERROR_LEN); @@ -628,7 +660,7 @@ static int l_llthread_join(lua_State *L) { if(!FLAG_IS_SET(this, TSTATE_STARTED )) { return fail(L, "Can't join a thread that hasn't be started."); } - if( FLAG_IS_SET(this, TSTATE_DETACHED)) { + if( FLAG_IS_SET(this, TSTATE_DETACHED) && !FLAG_IS_SET(this, FLAG_JOIN_LUA)) { return fail(L, "Can't join a thread that has been detached."); } if( FLAG_IS_SET(this, TSTATE_JOINED )) { @@ -638,9 +670,17 @@ static int l_llthread_join(lua_State *L) { /* join the thread. */ rc = llthread_join(this, luaL_optint(L, 2, INFINITE_JOIN_TIMEOUT)); - /* Push all results after the Lua code. */ if(child && FLAG_IS_SET(this, TSTATE_JOINED)) { int top; + + if(!FLAG_IS_SET(this, FLAG_JOIN_LUA)){ + /*child lua state has been destroyed by child thread*/ + /*@todo return thread exit code*/ + lua_pushnumber(L, 0); + return 1; + } + + /* copy values from child lua state */ if(child->status != 0) { const char *err_msg = lua_tostring(child->L, -1); lua_pushboolean(L, 0); @@ -652,12 +692,13 @@ static int l_llthread_join(lua_State *L) { /* return results to parent thread. */ llthread_push_results(L, child, 2, top); } + llthread_cleanup_child(this); return top; } if( rc == JOIN_ETIMEDOUT ){ - lua_pushboolean(L, 0); + lua_pushnil(L); lua_pushstring(L, "timeout"); return 2; } 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 @@ +local llthreads = require"llthreads" +local utils = require "utils" + +do + +local thread = llthreads.new(utils.thread_init .. [[ + local sleep = require"utils".sleep + while true do sleep(1) end +]]) + +-- detached + joindable +thread:start(true, true) + +local ok, err = thread:join(0) +print("thread:join(0): ", ok, err) +assert(ok == nil) +assert(err == "timeout") + +end + +-- enforce collect `thread` object +-- we should not hungup +for i = 1, 10 do collectgarbage("collect") end + +print("Done!") + 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 .. [[ thread:start() local ok, err = thread:join(0) print("thread:join(0): ", ok, err) -assert(ok == false) +assert(ok == nil) assert(err == "timeout") print("thread:join(): ", thread:join()) -- cgit v1.2.3-55-g6feb