1 #if !defined(_WIN32) && !defined(USE_PTHREAD)
22 # define LLTHREADS_EXPORT_API __declspec(dllexport)
24 # define LLTHREADS_EXPORT_API LUALIB_API
27 /* wrap strerror_s(). */
31 # define strerror_r(errno, buf, buflen) do { \
32 strncpy((buf), strerror(errno), (buflen)-1); \
33 (buf)[(buflen)-1] = '\0'; \
38 # define strerror_r(errno, buf, buflen) strerror_s((buf), (buflen), (errno))
44 # define OS_THREAD_RETURN unsigned int __stdcall
45 # define INVALID_THREAD INVALID_HANDLE_VALUE
46 # define INFINITE_JOIN_TIMEOUT INFINITE
48 # define JOIN_ETIMEDOUT 1
50 typedef DWORD join_timeout_t;
51 typedef HANDLE os_thread_t;
53 # define OS_THREAD_RETURN void *
54 # define INFINITE_JOIN_TIMEOUT -1
56 # define JOIN_ETIMEDOUT ETIMEDOUT
57 typedef int join_timeout_t;
58 typedef pthread_t os_thread_t;
61 LLTHREADS_EXPORT_API int luaopen_llthreads(lua_State *L);
63 #define LLTHREAD_T_NAME "LLThread"
64 static const char *LLTHREAD_T = LLTHREAD_T_NAME;
65 static const char *LLTHREAD_LOGGER_HOLDER = LLTHREAD_T_NAME " logger holder";
69 #include "traceback.inc"
79 static int fail(lua_State *L, const char *msg){
81 lua_pushstring(L, msg);
85 #define ERROR_LEN 1024
87 #define flags_t unsigned char
89 #define FLAG_NONE (flags_t)0
90 #define FLAG_STARTED (flags_t)1<<0
91 #define FLAG_DETACHED (flags_t)1<<1
92 #define FLAG_JOINED (flags_t)1<<2
93 #define FLAG_JOINABLE (flags_t)1<<3
96 #define FLAG_IS_SET(O, F) (O->flags & (flags_t)(F))
97 #define FLAG_SET(O, F) O->flags |= (flags_t)(F)
98 #define FLAG_UNSET(O, F) O->flags &= ~((flags_t)(F))
99 #define IS(O, F) FLAG_IS_SET(O, FLAG_##F)
100 #define SET(O, F) FLAG_SET(O, FLAG_##F)
102 #define ALLOC_STRUCT(S) (S*)calloc(1, sizeof(S))
103 #define FREE_STRUCT(O) free(O)
105 typedef struct llthread_child_t {
111 typedef struct llthread_t {
112 llthread_child_t *child;
118 void llthread_log(lua_State *L, const char *hdr, const char *msg){
119 int top = lua_gettop(L);
120 lua_rawgetp(L, LUA_REGISTRYINDEX, LLTHREAD_LOGGER_HOLDER);
121 if(lua_isnil(L, -1)){
129 lua_pushstring(L, hdr);
130 lua_pushstring(L, msg);
132 lua_pcall(L, 1, 0, 0);
139 static void open_thread_libs(lua_State *L){
140 #define L_REGLIB(L, name) lua_pushcfunction(L, luaopen_##name); lua_setfield(L, -2)
142 int top = lua_gettop(L);
144 #ifndef LLTHREAD_REGISTER_STD_LIBRARY
147 lua_getglobal(L, "package"); lua_getfield(L, -1, "preload"); lua_remove(L, -2);
151 lutil_require(L, "_G", luaopen_base, 1);
152 lutil_require(L, "package", luaopen_package, 1);
155 /* get package.preload */
156 lua_getglobal(L, "package"); lua_getfield(L, -1, "preload"); lua_remove(L, -2);
159 L_REGLIB(L, math, 1);
160 L_REGLIB(L, table, 1);
161 L_REGLIB(L, string, 1);
164 L_REGLIB(L, debug, 1);
167 /* @fixme find out luaopen_XXX at runtime */
168 #ifdef LUA_JITLIBNAME
172 #elif defined LUA_BITLIBNAME
173 L_REGLIB(L, bit32, 1);
178 #ifdef LLTHREAD_REGISTER_THREAD_LIBRARY
179 L_REGLIB(L, llthreads, 0);
187 static llthread_child_t *llthread_child_new() {
188 llthread_child_t *this = ALLOC_STRUCT(llthread_child_t);
189 if(!this) return NULL;
191 memset(this, 0, sizeof(llthread_child_t));
193 /* create new lua_State for the thread. */
194 /* open standard libraries. */
195 this->L = luaL_newstate();
196 open_thread_libs(this->L);
201 static void llthread_child_destroy(llthread_child_t *this) {
206 static OS_THREAD_RETURN llthread_child_thread_run(void *arg) {
207 llthread_child_t *this = (llthread_child_t *)arg;
208 lua_State *L = this->L;
209 int nargs = lua_gettop(L) - 1;
211 /* push traceback function as first value on stack. */
212 lua_pushcfunction(this->L, traceback);
215 this->status = lua_pcall(L, nargs, LUA_MULTRET, 1);
217 /* alwasy print errors here, helps with debugging bad code. */
218 if(this->status != 0) {
219 llthread_log(L, "Error from thread: ", lua_tostring(L, -1));
222 if(IS(this, DETACHED) || !IS(this, JOINABLE)) {
223 /* thread is detached, so it must clean-up the child state. */
224 llthread_child_destroy(this);
230 /* attached thread, don't close thread handle. */
233 /* detached thread, close thread handle. */
246 static void llthread_validate(llthread_t *this){
247 /* describe valid state of llthread_t object
248 * from after create and before destroy
250 if(!IS(this, STARTED)){
251 assert(!IS(this, DETACHED));
252 assert(!IS(this, JOINED));
253 assert(!IS(this, JOINABLE));
257 if(IS(this, DETACHED)){
258 if(!IS(this, JOINABLE)) assert(this->child == NULL);
259 else assert(this->child != NULL);
263 static int llthread_detach(llthread_t *this);
265 static int llthread_join(llthread_t *this, join_timeout_t timeout);
267 static llthread_t *llthread_new() {
268 llthread_t *this = ALLOC_STRUCT(llthread_t);
269 if(!this) return NULL;
271 this->flags = FLAG_NONE;
273 this->thread = INVALID_THREAD;
275 this->child = llthread_child_new();
284 static void llthread_cleanup_child(llthread_t *this) {
286 llthread_child_destroy(this->child);
291 static void llthread_destroy(llthread_t *this) {
293 /* thread not started */
294 if(!IS(this, STARTED)){
295 llthread_cleanup_child(this);
300 if(IS(this, DETACHED)){
301 if(IS(this, JOINABLE)){
302 llthread_detach(this);
308 if(!IS(this, JOINED)){
309 llthread_join(this, INFINITE_JOIN_TIMEOUT);
310 if(!IS(this, JOINED)){
311 /* @todo use current lua state to logging */
313 * char buf[ERROR_LEN];
314 * strerror_r(errno, buf, ERROR_LEN);
315 * llthread_log(L, "Error can not join thread on gc: ", buf);
319 if(IS(this, JOINABLE)){
320 llthread_cleanup_child(this);
328 static int llthread_push_args(lua_State *L, llthread_child_t *child, int idx, int top) {
329 return llthread_copy_values(L, child->L, idx, top, 1 /* is_arg */);
332 static int llthread_push_results(lua_State *L, llthread_child_t *child, int idx, int top) {
333 return llthread_copy_values(child->L, L, idx, top, 0 /* is_arg */);
336 static int llthread_detach(llthread_t *this){
339 assert(IS(this, STARTED));
340 assert(this->child != NULL);
342 /*we can not detach joined thread*/
348 rc = pthread_detach(this->thread);
350 assert(this->thread != INVALID_THREAD);
351 CloseHandle(this->thread);
352 this->thread = INVALID_THREAD;
357 /* | detached | joinable || join | which thread | gc | detach |
358 * | | || return | destroy child | calls | on |
359 * ------------------------------------------------------------------------
360 * | false | falas || <NONE> | child | join | <NEVER> |
361 * *| false | true || Lua values | parent | join | <NEVER> |
362 * *| true | false || <ERROR> | child | <NONE> | start |
363 * | true | true || <NONE> | child | detach | gc |
364 * ------------------------------------------------------------------------
365 * * llthread behavior.
367 static int llthread_start(llthread_t *this, int start_detached, int joinable) {
368 llthread_child_t *child = this->child;
371 llthread_validate(this);
373 if(joinable) SET(child, JOINABLE);
374 if(start_detached) SET(child, DETACHED);
377 this->thread = (HANDLE)_beginthreadex(NULL, 0, llthread_child_thread_run, child, 0, NULL);
378 if(INVALID_THREAD == this->thread){
382 rc = pthread_create(&(this->thread), NULL, llthread_child_thread_run, child);
387 if(joinable) SET(this, JOINABLE);
388 if(start_detached) SET(this, DETACHED);
389 if((start_detached)&&(!joinable)){
390 rc = llthread_detach(this);
394 llthread_validate(this);
399 static int llthread_join(llthread_t *this, join_timeout_t timeout) {
400 llthread_validate(this);
402 if(IS(this, JOINED)){
407 if(INVALID_THREAD == this->thread) return JOIN_OK;
408 ret = WaitForSingleObject( this->thread, timeout );
409 if( ret == WAIT_OBJECT_0){ /* Destroy the thread object. */
410 CloseHandle( this->thread );
411 this->thread = INVALID_THREAD;
414 llthread_validate(this);
418 else if( ret == WAIT_TIMEOUT ){
419 return JOIN_ETIMEDOUT;
425 rc = pthread_kill(this->thread, 0);
426 if(rc == 0){ /* still alive */
427 return JOIN_ETIMEDOUT;
431 /*@fixme what else it can be ?*/
435 /*thread dead so we call join to free pthread_t struct */
438 /* @todo use pthread_tryjoin_np/pthread_timedjoin_np to support timeout */
440 /* then join the thread. */
441 rc = pthread_join(this->thread, NULL);
442 if((rc == 0) || (rc == ESRCH)) {
447 llthread_validate(this);
454 static llthread_t *llthread_create(lua_State *L, const char *code, size_t code_len) {
455 llthread_t *this = llthread_new();
456 llthread_child_t *child = this->child;
458 /* load Lua code into child state. */
459 int rc = luaL_loadbuffer(child->L, code, code_len, code);
461 /* copy error message to parent state. */
462 size_t len; const char *str = lua_tolstring(child->L, -1, &len);
464 lua_pushlstring(L, str, len);
466 /* non-string error message. */
467 lua_pushfstring(L, "luaL_loadbuffer() failed to load Lua code: rc=%d", rc);
469 llthread_destroy(this);
474 /* copy extra args from main state to child state. */
475 /* Push all args after the Lua code. */
476 llthread_push_args(L, child, 3, lua_gettop(L));
478 llthread_validate(this);
485 //{ Lua interface to llthread
487 static llthread_t *l_llthread_at (lua_State *L, int i) {
488 llthread_t **this = (llthread_t **)lutil_checkudatap (L, i, LLTHREAD_T);
489 luaL_argcheck (L, this != NULL, i, "thread expected");
490 luaL_argcheck (L, *this != NULL, i, "thread expected");
491 // luaL_argcheck (L, !(counter->flags & FLAG_DESTROYED), 1, "PDH Counter is destroyed");
495 static int l_llthread_delete(lua_State *L) {
496 llthread_t **pthis = (llthread_t **)lutil_checkudatap (L, 1, LLTHREAD_T);
497 luaL_argcheck (L, pthis != NULL, 1, "thread expected");
498 if(*pthis == NULL) return 0;
499 llthread_destroy(*pthis);
505 static int l_llthread_start(lua_State *L) {
506 llthread_t *this = l_llthread_at(L, 1);
507 int start_detached = lua_toboolean(L, 2);
510 if(!lua_isnone(L, 3)) joinable = lua_toboolean(L, 3);
511 else joinable = start_detached ? 0 : 1;
513 if(IS(this, STARTED)) {
514 return fail(L, "Thread already started.");
517 rc = llthread_start(this, start_detached, joinable);
520 strerror_r(errno, buf, ERROR_LEN);
524 lua_settop(L, 1); // return this
528 static int l_llthread_join(lua_State *L) {
529 llthread_t *this = l_llthread_at(L, 1);
530 llthread_child_t *child = this->child;
533 if(!IS(this, STARTED )) {
534 return fail(L, "Can't join a thread that hasn't be started.");
536 if( IS(this, DETACHED) && !IS(this, JOINABLE)) {
537 return fail(L, "Can't join a thread that has been detached.");
539 if( IS(this, JOINED )) {
540 return fail(L, "Can't join a thread that has already been joined.");
543 /* join the thread. */
544 rc = llthread_join(this, luaL_optint(L, 2, INFINITE_JOIN_TIMEOUT));
546 if(child && IS(this, JOINED)) {
549 if(IS(this, DETACHED) || !IS(this, JOINABLE)){
550 /*child lua state has been destroyed by child thread*/
551 /*@todo return thread exit code*/
552 lua_pushboolean(L, 1);
553 lua_pushnumber(L, 0);
557 /* copy values from child lua state */
558 if(child->status != 0) {
559 const char *err_msg = lua_tostring(child->L, -1);
560 lua_pushboolean(L, 0);
561 lua_pushfstring(L, "Error from child thread: %s", err_msg);
564 lua_pushboolean(L, 1);
565 top = lua_gettop(child->L);
566 /* return results to parent thread. */
567 llthread_push_results(L, child, 2, top);
570 llthread_cleanup_child(this);
574 if( rc == JOIN_ETIMEDOUT ){
575 return fail(L, "timeout");
580 strerror_r(errno, buf, ERROR_LEN);
582 /* llthread_cleanup_child(this); */
589 static int l_llthread_new(lua_State *L) {
590 size_t lua_code_len; const char *lua_code = luaL_checklstring(L, 1, &lua_code_len);
591 llthread_t **this = lutil_newudatap(L, llthread_t*, LLTHREAD_T);
592 lua_insert(L, 2); /*move self prior args*/
593 *this = llthread_create(L, lua_code, lua_code_len);
599 static const struct luaL_Reg l_llthread_meth[] = {
600 {"start", l_llthread_start },
601 {"join", l_llthread_join },
602 {"__gc", l_llthread_delete },
609 static int l_llthread_set_logger(lua_State *L){
611 luaL_argcheck(L, lua_isfunction(L, 1), 1, "function expected");
612 lua_rawsetp(L, LUA_REGISTRYINDEX, LLTHREAD_LOGGER_HOLDER);
616 static const struct luaL_Reg l_llthreads_lib[] = {
617 {"new", l_llthread_new },
618 {"set_logger", l_llthread_set_logger },
623 LLTHREADS_EXPORT_API int luaopen_llthreads(lua_State *L) {
624 int top = lua_gettop(L);
625 lutil_createmetap(L, LLTHREAD_T, l_llthread_meth, 0);
629 luaL_setfuncs(L, l_llthreads_lib, 0);