diff options
Diffstat (limited to 'llthreads2/src/llthread.c')
| -rw-r--r-- | llthreads2/src/llthread.c | 746 |
1 files changed, 746 insertions, 0 deletions
diff --git a/llthreads2/src/llthread.c b/llthreads2/src/llthread.c new file mode 100644 index 0000000..0ffa727 --- /dev/null +++ b/llthreads2/src/llthread.c | |||
| @@ -0,0 +1,746 @@ | |||
| 1 | #if !defined(_WIN32) && !defined(USE_PTHREAD) | ||
| 2 | # define USE_PTHREAD | ||
| 3 | #endif | ||
| 4 | |||
| 5 | #define LLTHREAD_VERSION_MAJOR 0 | ||
| 6 | #define LLTHREAD_VERSION_MINOR 1 | ||
| 7 | #define LLTHREAD_VERSION_PATCH 0 | ||
| 8 | #define LLTHREAD_VERSION_COMMENT "" | ||
| 9 | |||
| 10 | #ifndef USE_PTHREAD | ||
| 11 | # include <windows.h> | ||
| 12 | # include <process.h> | ||
| 13 | #else | ||
| 14 | # include <pthread.h> | ||
| 15 | #endif | ||
| 16 | |||
| 17 | #include <stdlib.h> | ||
| 18 | #include <stdio.h> | ||
| 19 | #include <memory.h> | ||
| 20 | #include <assert.h> | ||
| 21 | #include <errno.h> | ||
| 22 | #include <lualib.h> | ||
| 23 | #include "l52util.h" | ||
| 24 | #include "traceback.inc" | ||
| 25 | #include "copy.inc" | ||
| 26 | |||
| 27 | /*export*/ | ||
| 28 | #ifdef _WIN32 | ||
| 29 | # define LLTHREADS_EXPORT_API __declspec(dllexport) | ||
| 30 | #else | ||
| 31 | # define LLTHREADS_EXPORT_API LUALIB_API | ||
| 32 | #endif | ||
| 33 | |||
| 34 | /* wrap strerror_s(). */ | ||
| 35 | #ifdef _WIN32 | ||
| 36 | # ifdef __GNUC__ | ||
| 37 | # ifndef strerror_r | ||
| 38 | # define strerror_r(errno, buf, buflen) do { \ | ||
| 39 | strncpy((buf), strerror(errno), (buflen)-1); \ | ||
| 40 | (buf)[(buflen)-1] = '\0'; \ | ||
| 41 | } while(0) | ||
| 42 | # endif | ||
| 43 | # else | ||
| 44 | # ifndef strerror_r | ||
| 45 | # define strerror_r(errno, buf, buflen) strerror_s((buf), (buflen), (errno)) | ||
| 46 | # endif | ||
| 47 | # endif | ||
| 48 | #endif | ||
| 49 | |||
| 50 | #ifndef USE_PTHREAD | ||
| 51 | # define OS_THREAD_RETURN unsigned int __stdcall | ||
| 52 | # define INVALID_THREAD INVALID_HANDLE_VALUE | ||
| 53 | # define INFINITE_JOIN_TIMEOUT INFINITE | ||
| 54 | # define JOIN_OK 0 | ||
| 55 | # define JOIN_ETIMEDOUT 1 | ||
| 56 | # define JOIN_FAIL 2 | ||
| 57 | typedef DWORD join_timeout_t; | ||
| 58 | typedef HANDLE os_thread_t; | ||
| 59 | #else | ||
| 60 | # define OS_THREAD_RETURN void * | ||
| 61 | # define INFINITE_JOIN_TIMEOUT -1 | ||
| 62 | # define JOIN_OK 0 | ||
| 63 | # define JOIN_ETIMEDOUT ETIMEDOUT | ||
| 64 | typedef int join_timeout_t; | ||
| 65 | typedef pthread_t os_thread_t; | ||
| 66 | #endif | ||
| 67 | |||
| 68 | #define ERROR_LEN 1024 | ||
| 69 | |||
| 70 | #define flags_t unsigned char | ||
| 71 | |||
| 72 | #define FLAG_NONE (flags_t)0 | ||
| 73 | #define FLAG_STARTED (flags_t)1<<0 | ||
| 74 | #define FLAG_DETACHED (flags_t)1<<1 | ||
| 75 | #define FLAG_JOINED (flags_t)1<<2 | ||
| 76 | #define FLAG_JOINABLE (flags_t)1<<3 | ||
| 77 | |||
| 78 | /*At least one flag*/ | ||
| 79 | #define FLAG_IS_SET(O, F) (O->flags & (flags_t)(F)) | ||
| 80 | #define FLAG_SET(O, F) O->flags |= (flags_t)(F) | ||
| 81 | #define FLAG_UNSET(O, F) O->flags &= ~((flags_t)(F)) | ||
| 82 | #define IS(O, F) FLAG_IS_SET(O, FLAG_##F) | ||
| 83 | #define SET(O, F) FLAG_SET(O, FLAG_##F) | ||
| 84 | |||
| 85 | #define ALLOC_STRUCT(S) (S*)calloc(1, sizeof(S)) | ||
| 86 | #define FREE_STRUCT(O) free(O) | ||
| 87 | |||
| 88 | #ifndef LLTHREAD_MODULE_NAME | ||
| 89 | # define LLTHREAD_MODULE_NAME llthreads | ||
| 90 | #endif | ||
| 91 | |||
| 92 | #define CAT(S1,S2) S1##S2 | ||
| 93 | |||
| 94 | #define LLTHREAD_OPEN_NAME_IMPL(NAME) CAT(luaopen_, NAME) | ||
| 95 | |||
| 96 | #define LLTHREAD_OPEN_NAME LLTHREAD_OPEN_NAME_IMPL(LLTHREAD_MODULE_NAME) | ||
| 97 | |||
| 98 | LLTHREADS_EXPORT_API int LLTHREAD_OPEN_NAME(lua_State *L); | ||
| 99 | |||
| 100 | #define LLTHREAD_NAME "LLThread" | ||
| 101 | static const char *LLTHREAD_TAG = LLTHREAD_NAME; | ||
| 102 | static const char *LLTHREAD_LOGGER_HOLDER = LLTHREAD_NAME " logger holder"; | ||
| 103 | |||
| 104 | typedef struct llthread_child_t { | ||
| 105 | lua_State *L; | ||
| 106 | int status; | ||
| 107 | flags_t flags; | ||
| 108 | } llthread_child_t; | ||
| 109 | |||
| 110 | typedef struct llthread_t { | ||
| 111 | llthread_child_t *child; | ||
| 112 | os_thread_t thread; | ||
| 113 | flags_t flags; | ||
| 114 | } llthread_t; | ||
| 115 | |||
| 116 | static int fail(lua_State *L, const char *msg){ | ||
| 117 | lua_pushnil(L); | ||
| 118 | lua_pushstring(L, msg); | ||
| 119 | return 2; | ||
| 120 | } | ||
| 121 | |||
| 122 | //{ logger interface | ||
| 123 | void llthread_log(lua_State *L, const char *hdr, const char *msg){ | ||
| 124 | int top = lua_gettop(L); | ||
| 125 | lua_rawgetp(L, LUA_REGISTRYINDEX, LLTHREAD_LOGGER_HOLDER); | ||
| 126 | if(lua_isnil(L, -1)){ | ||
| 127 | lua_pop(L, 1); | ||
| 128 | fputs(hdr, stderr); | ||
| 129 | fputs(msg, stderr); | ||
| 130 | fputc('\n', stderr); | ||
| 131 | fflush(stderr); | ||
| 132 | return; | ||
| 133 | } | ||
| 134 | lua_pushstring(L, hdr); | ||
| 135 | lua_pushstring(L, msg); | ||
| 136 | lua_concat(L, 2); | ||
| 137 | lua_pcall(L, 1, 0, 0); | ||
| 138 | lua_settop(L, top); | ||
| 139 | } | ||
| 140 | //} | ||
| 141 | |||
| 142 | //{ llthread_child | ||
| 143 | |||
| 144 | static void open_thread_libs(lua_State *L){ | ||
| 145 | #define L_REGLIB(L, name) lua_pushcfunction(L, luaopen_##name); lua_setfield(L, -2) | ||
| 146 | |||
| 147 | int top = lua_gettop(L); | ||
| 148 | |||
| 149 | #ifndef LLTHREAD_REGISTER_STD_LIBRARY | ||
| 150 | |||
| 151 | luaL_openlibs(L); | ||
| 152 | lua_getglobal(L, "package"); lua_getfield(L, -1, "preload"); lua_remove(L, -2); | ||
| 153 | |||
| 154 | #else | ||
| 155 | |||
| 156 | lutil_require(L, "_G", luaopen_base, 1); | ||
| 157 | lutil_require(L, "package", luaopen_package, 1); | ||
| 158 | lua_settop(L, top); | ||
| 159 | |||
| 160 | /* get package.preload */ | ||
| 161 | lua_getglobal(L, "package"); lua_getfield(L, -1, "preload"); lua_remove(L, -2); | ||
| 162 | L_REGLIB(L, io, 1); | ||
| 163 | L_REGLIB(L, os, 1); | ||
| 164 | L_REGLIB(L, math, 1); | ||
| 165 | L_REGLIB(L, table, 1); | ||
| 166 | L_REGLIB(L, string, 1); | ||
| 167 | |||
| 168 | #ifdef LUA_DBLIBNAME | ||
| 169 | L_REGLIB(L, debug, 1); | ||
| 170 | #endif | ||
| 171 | |||
| 172 | /* @fixme find out luaopen_XXX at runtime */ | ||
| 173 | #ifdef LUA_JITLIBNAME | ||
| 174 | L_REGLIB(L, bit, 1); | ||
| 175 | L_REGLIB(L, jit, 1); | ||
| 176 | L_REGLIB(L, ffi, 1); | ||
| 177 | #elif defined LUA_BITLIBNAME | ||
| 178 | L_REGLIB(L, bit32, 1); | ||
| 179 | #endif | ||
| 180 | |||
| 181 | #endif | ||
| 182 | |||
| 183 | #ifdef LLTHREAD_REGISTER_THREAD_LIBRARY | ||
| 184 | L_REGLIB(L, llthreads, 0); | ||
| 185 | #endif | ||
| 186 | |||
| 187 | lua_settop(L, top); | ||
| 188 | |||
| 189 | #undef L_REGLIB | ||
| 190 | } | ||
| 191 | |||
| 192 | static llthread_child_t *llthread_child_new() { | ||
| 193 | llthread_child_t *this = ALLOC_STRUCT(llthread_child_t); | ||
| 194 | if(!this) return NULL; | ||
| 195 | |||
| 196 | memset(this, 0, sizeof(llthread_child_t)); | ||
| 197 | |||
| 198 | /* create new lua_State for the thread. */ | ||
| 199 | /* open standard libraries. */ | ||
| 200 | this->L = luaL_newstate(); | ||
| 201 | open_thread_libs(this->L); | ||
| 202 | |||
| 203 | return this; | ||
| 204 | } | ||
| 205 | |||
| 206 | static void llthread_child_destroy(llthread_child_t *this) { | ||
| 207 | lua_close(this->L); | ||
| 208 | FREE_STRUCT(this); | ||
| 209 | } | ||
| 210 | |||
| 211 | static OS_THREAD_RETURN llthread_child_thread_run(void *arg) { | ||
| 212 | llthread_child_t *this = (llthread_child_t *)arg; | ||
| 213 | lua_State *L = this->L; | ||
| 214 | int nargs = lua_gettop(L) - 1; | ||
| 215 | |||
| 216 | /* push traceback function as first value on stack. */ | ||
| 217 | lua_pushcfunction(this->L, traceback); | ||
| 218 | lua_insert(L, 1); | ||
| 219 | |||
| 220 | this->status = lua_pcall(L, nargs, LUA_MULTRET, 1); | ||
| 221 | |||
| 222 | /* alwasy print errors here, helps with debugging bad code. */ | ||
| 223 | if(this->status != 0) { | ||
| 224 | llthread_log(L, "Error from thread: ", lua_tostring(L, -1)); | ||
| 225 | } | ||
| 226 | |||
| 227 | if(IS(this, DETACHED) || !IS(this, JOINABLE)) { | ||
| 228 | /* thread is detached, so it must clean-up the child state. */ | ||
| 229 | llthread_child_destroy(this); | ||
| 230 | this = NULL; | ||
| 231 | } | ||
| 232 | |||
| 233 | #ifndef USE_PTHREAD | ||
| 234 | if(this) { | ||
| 235 | /* attached thread, don't close thread handle. */ | ||
| 236 | _endthreadex(0); | ||
| 237 | } else { | ||
| 238 | /* detached thread, close thread handle. */ | ||
| 239 | _endthread(); | ||
| 240 | } | ||
| 241 | return 0; | ||
| 242 | #else | ||
| 243 | return this; | ||
| 244 | #endif | ||
| 245 | } | ||
| 246 | |||
| 247 | //} | ||
| 248 | |||
| 249 | //{ llthread | ||
| 250 | |||
| 251 | static void llthread_validate(llthread_t *this){ | ||
| 252 | /* describe valid state of llthread_t object | ||
| 253 | * from after create and before destroy | ||
| 254 | */ | ||
| 255 | if(!IS(this, STARTED)){ | ||
| 256 | assert(!IS(this, DETACHED)); | ||
| 257 | assert(!IS(this, JOINED)); | ||
| 258 | assert(!IS(this, JOINABLE)); | ||
| 259 | return; | ||
| 260 | } | ||
| 261 | |||
| 262 | if(IS(this, DETACHED)){ | ||
| 263 | if(!IS(this, JOINABLE)) assert(this->child == NULL); | ||
| 264 | else assert(this->child != NULL); | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | static int llthread_detach(llthread_t *this); | ||
| 269 | |||
| 270 | static int llthread_join(llthread_t *this, join_timeout_t timeout); | ||
| 271 | |||
| 272 | static llthread_t *llthread_new() { | ||
| 273 | llthread_t *this = ALLOC_STRUCT(llthread_t); | ||
| 274 | if(!this) return NULL; | ||
| 275 | |||
| 276 | this->flags = FLAG_NONE; | ||
| 277 | #ifndef USE_PTHREAD | ||
| 278 | this->thread = INVALID_THREAD; | ||
| 279 | #endif | ||
| 280 | this->child = llthread_child_new(); | ||
| 281 | if(!this->child){ | ||
| 282 | FREE_STRUCT(this); | ||
| 283 | return NULL; | ||
| 284 | } | ||
| 285 | |||
| 286 | return this; | ||
| 287 | } | ||
| 288 | |||
| 289 | static void llthread_cleanup_child(llthread_t *this) { | ||
| 290 | if(this->child) { | ||
| 291 | llthread_child_destroy(this->child); | ||
| 292 | this->child = NULL; | ||
| 293 | } | ||
| 294 | } | ||
| 295 | |||
| 296 | static void llthread_destroy(llthread_t *this) { | ||
| 297 | do{ | ||
| 298 | /* thread not started */ | ||
| 299 | if(!IS(this, STARTED)){ | ||
| 300 | llthread_cleanup_child(this); | ||
| 301 | break; | ||
| 302 | } | ||
| 303 | |||
| 304 | /* DETACHED */ | ||
| 305 | if(IS(this, DETACHED)){ | ||
| 306 | if(IS(this, JOINABLE)){ | ||
| 307 | llthread_detach(this); | ||
| 308 | } | ||
| 309 | break; | ||
| 310 | } | ||
| 311 | |||
| 312 | /* ATTACHED */ | ||
| 313 | if(!IS(this, JOINED)){ | ||
| 314 | llthread_join(this, INFINITE_JOIN_TIMEOUT); | ||
| 315 | if(!IS(this, JOINED)){ | ||
| 316 | /* @todo use current lua state to logging */ | ||
| 317 | /* | ||
| 318 | * char buf[ERROR_LEN]; | ||
| 319 | * strerror_r(errno, buf, ERROR_LEN); | ||
| 320 | * llthread_log(L, "Error can not join thread on gc: ", buf); | ||
| 321 | */ | ||
| 322 | } | ||
| 323 | } | ||
| 324 | if(IS(this, JOINABLE)){ | ||
| 325 | llthread_cleanup_child(this); | ||
| 326 | } | ||
| 327 | |||
| 328 | }while(0); | ||
| 329 | |||
| 330 | FREE_STRUCT(this); | ||
| 331 | } | ||
| 332 | |||
| 333 | static int llthread_push_args(lua_State *L, llthread_child_t *child, int idx, int top) { | ||
| 334 | return llthread_copy_values(L, child->L, idx, top, 1 /* is_arg */); | ||
| 335 | } | ||
| 336 | |||
| 337 | static int llthread_push_results(lua_State *L, llthread_child_t *child, int idx, int top) { | ||
| 338 | return llthread_copy_values(child->L, L, idx, top, 0 /* is_arg */); | ||
| 339 | } | ||
| 340 | |||
| 341 | static int llthread_detach(llthread_t *this){ | ||
| 342 | int rc = 0; | ||
| 343 | |||
| 344 | assert(IS(this, STARTED)); | ||
| 345 | assert(this->child != NULL); | ||
| 346 | |||
| 347 | this->child = NULL; | ||
| 348 | |||
| 349 | /*we can not detach joined thread*/ | ||
| 350 | if(IS(this, JOINED)) | ||
| 351 | return 0; | ||
| 352 | |||
| 353 | #ifdef USE_PTHREAD | ||
| 354 | rc = pthread_detach(this->thread); | ||
| 355 | #else | ||
| 356 | assert(this->thread != INVALID_THREAD); | ||
| 357 | CloseHandle(this->thread); | ||
| 358 | this->thread = INVALID_THREAD; | ||
| 359 | #endif | ||
| 360 | return rc; | ||
| 361 | } | ||
| 362 | |||
| 363 | /* | detached | joinable || join | which thread | gc | detach | | ||
| 364 | * | | || return | destroy child | calls | on | | ||
| 365 | * ------------------------------------------------------------------------ | ||
| 366 | * | false | falas || <NONE> | child | join | <NEVER> | | ||
| 367 | * *| false | true || Lua values | parent | join | <NEVER> | | ||
| 368 | * *| true | false || <ERROR> | child | <NONE> | start | | ||
| 369 | * | true | true || <NONE> | child | detach | gc | | ||
| 370 | * ------------------------------------------------------------------------ | ||
| 371 | * * llthread behavior. | ||
| 372 | */ | ||
| 373 | static int llthread_start(llthread_t *this, int start_detached, int joinable) { | ||
| 374 | llthread_child_t *child = this->child; | ||
| 375 | int rc = 0; | ||
| 376 | |||
| 377 | llthread_validate(this); | ||
| 378 | |||
| 379 | if(joinable) SET(child, JOINABLE); | ||
| 380 | if(start_detached) SET(child, DETACHED); | ||
| 381 | |||
| 382 | #ifndef USE_PTHREAD | ||
| 383 | this->thread = (HANDLE)_beginthreadex(NULL, 0, llthread_child_thread_run, child, 0, NULL); | ||
| 384 | if(INVALID_THREAD == this->thread){ | ||
| 385 | rc = -1; | ||
| 386 | } | ||
| 387 | #else | ||
| 388 | rc = pthread_create(&(this->thread), NULL, llthread_child_thread_run, child); | ||
| 389 | #endif | ||
| 390 | |||
| 391 | if(rc == 0) { | ||
| 392 | SET(this, STARTED); | ||
| 393 | if(joinable) SET(this, JOINABLE); | ||
| 394 | if(start_detached) SET(this, DETACHED); | ||
| 395 | if((start_detached)&&(!joinable)){ | ||
| 396 | rc = llthread_detach(this); | ||
| 397 | } | ||
| 398 | } | ||
| 399 | |||
| 400 | llthread_validate(this); | ||
| 401 | |||
| 402 | return rc; | ||
| 403 | } | ||
| 404 | |||
| 405 | static int llthread_join(llthread_t *this, join_timeout_t timeout) { | ||
| 406 | llthread_validate(this); | ||
| 407 | |||
| 408 | if(IS(this, JOINED)){ | ||
| 409 | return JOIN_OK; | ||
| 410 | } else{ | ||
| 411 | #ifndef USE_PTHREAD | ||
| 412 | DWORD ret = 0; | ||
| 413 | if(INVALID_THREAD == this->thread) return JOIN_OK; | ||
| 414 | ret = WaitForSingleObject( this->thread, timeout ); | ||
| 415 | if( ret == WAIT_OBJECT_0){ /* Destroy the thread object. */ | ||
| 416 | CloseHandle( this->thread ); | ||
| 417 | this->thread = INVALID_THREAD; | ||
| 418 | SET(this, JOINED); | ||
| 419 | |||
| 420 | llthread_validate(this); | ||
| 421 | |||
| 422 | return JOIN_OK; | ||
| 423 | } | ||
| 424 | else if( ret == WAIT_TIMEOUT ){ | ||
| 425 | return JOIN_ETIMEDOUT; | ||
| 426 | } | ||
| 427 | return JOIN_FAIL; | ||
| 428 | #else | ||
| 429 | int rc; | ||
| 430 | if(timeout == 0){ | ||
| 431 | rc = pthread_kill(this->thread, 0); | ||
| 432 | if(rc == 0){ /* still alive */ | ||
| 433 | return JOIN_ETIMEDOUT; | ||
| 434 | } | ||
| 435 | |||
| 436 | if(rc != ESRCH){ | ||
| 437 | /*@fixme what else it can be ?*/ | ||
| 438 | return rc; | ||
| 439 | } | ||
| 440 | |||
| 441 | /*thread dead so we call join to free pthread_t struct */ | ||
| 442 | } | ||
| 443 | |||
| 444 | /* @todo use pthread_tryjoin_np/pthread_timedjoin_np to support timeout */ | ||
| 445 | |||
| 446 | /* then join the thread. */ | ||
| 447 | rc = pthread_join(this->thread, NULL); | ||
| 448 | if((rc == 0) || (rc == ESRCH)) { | ||
| 449 | SET(this, JOINED); | ||
| 450 | rc = JOIN_OK; | ||
| 451 | } | ||
| 452 | |||
| 453 | llthread_validate(this); | ||
| 454 | |||
| 455 | return rc; | ||
| 456 | #endif | ||
| 457 | } | ||
| 458 | } | ||
| 459 | |||
| 460 | static int llthread_alive(llthread_t *this) { | ||
| 461 | llthread_validate(this); | ||
| 462 | |||
| 463 | if(IS(this, JOINED)){ | ||
| 464 | return JOIN_OK; | ||
| 465 | } else{ | ||
| 466 | #ifndef USE_PTHREAD | ||
| 467 | DWORD ret = 0; | ||
| 468 | if(INVALID_THREAD == this->thread) return JOIN_OK; | ||
| 469 | ret = WaitForSingleObject( this->thread, 0 ); | ||
| 470 | if( ret == WAIT_OBJECT_0) return JOIN_OK; | ||
| 471 | if( ret == WAIT_TIMEOUT ) return JOIN_ETIMEDOUT; | ||
| 472 | return JOIN_FAIL; | ||
| 473 | #else | ||
| 474 | int rc = pthread_kill(this->thread, 0); | ||
| 475 | if(rc == 0){ /* still alive */ | ||
| 476 | return JOIN_ETIMEDOUT; | ||
| 477 | } | ||
| 478 | |||
| 479 | if(rc != ESRCH){ | ||
| 480 | /*@fixme what else it can be ?*/ | ||
| 481 | return rc; | ||
| 482 | } | ||
| 483 | |||
| 484 | return JOIN_OK; | ||
| 485 | #endif | ||
| 486 | } | ||
| 487 | } | ||
| 488 | |||
| 489 | static llthread_t *llthread_create(lua_State *L, const char *code, size_t code_len) { | ||
| 490 | llthread_t *this = llthread_new(); | ||
| 491 | llthread_child_t *child = this->child; | ||
| 492 | |||
| 493 | /* load Lua code into child state. */ | ||
| 494 | int rc = luaL_loadbuffer(child->L, code, code_len, code); | ||
| 495 | if(rc != 0) { | ||
| 496 | /* copy error message to parent state. */ | ||
| 497 | size_t len; const char *str = lua_tolstring(child->L, -1, &len); | ||
| 498 | if(str != NULL) { | ||
| 499 | lua_pushlstring(L, str, len); | ||
| 500 | } else { | ||
| 501 | /* non-string error message. */ | ||
| 502 | lua_pushfstring(L, "luaL_loadbuffer() failed to load Lua code: rc=%d", rc); | ||
| 503 | } | ||
| 504 | llthread_destroy(this); | ||
| 505 | lua_error(L); | ||
| 506 | return NULL; | ||
| 507 | } | ||
| 508 | |||
| 509 | /* copy extra args from main state to child state. */ | ||
| 510 | /* Push all args after the Lua code. */ | ||
| 511 | llthread_push_args(L, child, 3, lua_gettop(L)); | ||
| 512 | |||
| 513 | llthread_validate(this); | ||
| 514 | |||
| 515 | return this; | ||
| 516 | } | ||
| 517 | |||
| 518 | //} | ||
| 519 | |||
| 520 | //{ Lua interface to llthread | ||
| 521 | |||
| 522 | static llthread_t *l_llthread_at (lua_State *L, int i) { | ||
| 523 | llthread_t **this = (llthread_t **)lutil_checkudatap (L, i, LLTHREAD_TAG); | ||
| 524 | luaL_argcheck (L, this != NULL, i, "thread expected"); | ||
| 525 | luaL_argcheck (L, *this != NULL, i, "thread expected"); | ||
| 526 | // luaL_argcheck (L, !(counter->flags & FLAG_DESTROYED), 1, "PDH Counter is destroyed"); | ||
| 527 | return *this; | ||
| 528 | } | ||
| 529 | |||
| 530 | static int l_llthread_delete(lua_State *L) { | ||
| 531 | llthread_t **pthis = (llthread_t **)lutil_checkudatap (L, 1, LLTHREAD_TAG); | ||
| 532 | luaL_argcheck (L, pthis != NULL, 1, "thread expected"); | ||
| 533 | if(*pthis == NULL) return 0; | ||
| 534 | llthread_destroy(*pthis); | ||
| 535 | *pthis = NULL; | ||
| 536 | |||
| 537 | return 0; | ||
| 538 | } | ||
| 539 | |||
| 540 | static int l_llthread_start(lua_State *L) { | ||
| 541 | llthread_t *this = l_llthread_at(L, 1); | ||
| 542 | int start_detached = lua_toboolean(L, 2); | ||
| 543 | int joinable, rc; | ||
| 544 | |||
| 545 | if(!lua_isnone(L, 3)) joinable = lua_toboolean(L, 3); | ||
| 546 | else joinable = start_detached ? 0 : 1; | ||
| 547 | |||
| 548 | if(IS(this, STARTED)) { | ||
| 549 | return fail(L, "Thread already started."); | ||
| 550 | } | ||
| 551 | |||
| 552 | rc = llthread_start(this, start_detached, joinable); | ||
| 553 | if(rc != 0) { | ||
| 554 | char buf[ERROR_LEN]; | ||
| 555 | strerror_r(errno, buf, ERROR_LEN); | ||
| 556 | return fail(L, buf); | ||
| 557 | } | ||
| 558 | |||
| 559 | lua_settop(L, 1); // return this | ||
| 560 | return 1; | ||
| 561 | } | ||
| 562 | |||
| 563 | static int l_llthread_join(lua_State *L) { | ||
| 564 | llthread_t *this = l_llthread_at(L, 1); | ||
| 565 | llthread_child_t *child = this->child; | ||
| 566 | int rc; | ||
| 567 | |||
| 568 | if(!IS(this, STARTED )) { | ||
| 569 | return fail(L, "Can't join a thread that hasn't be started."); | ||
| 570 | } | ||
| 571 | if( IS(this, DETACHED) && !IS(this, JOINABLE)) { | ||
| 572 | return fail(L, "Can't join a thread that has been detached."); | ||
| 573 | } | ||
| 574 | if( IS(this, JOINED )) { | ||
| 575 | return fail(L, "Can't join a thread that has already been joined."); | ||
| 576 | } | ||
| 577 | |||
| 578 | /* join the thread. */ | ||
| 579 | rc = llthread_join(this, luaL_optint(L, 2, INFINITE_JOIN_TIMEOUT)); | ||
| 580 | |||
| 581 | if(child && IS(this, JOINED)) { | ||
| 582 | int top; | ||
| 583 | |||
| 584 | if(IS(this, DETACHED) || !IS(this, JOINABLE)){ | ||
| 585 | /*child lua state has been destroyed by child thread*/ | ||
| 586 | /*@todo return thread exit code*/ | ||
| 587 | lua_pushboolean(L, 1); | ||
| 588 | lua_pushnumber(L, 0); | ||
| 589 | return 2; | ||
| 590 | } | ||
| 591 | |||
| 592 | /* copy values from child lua state */ | ||
| 593 | if(child->status != 0) { | ||
| 594 | const char *err_msg = lua_tostring(child->L, -1); | ||
| 595 | lua_pushboolean(L, 0); | ||
| 596 | lua_pushfstring(L, "Error from child thread: %s", err_msg); | ||
| 597 | top = 2; | ||
| 598 | } else { | ||
| 599 | lua_pushboolean(L, 1); | ||
| 600 | top = lua_gettop(child->L); | ||
| 601 | /* return results to parent thread. */ | ||
| 602 | llthread_push_results(L, child, 2, top); | ||
| 603 | } | ||
| 604 | |||
| 605 | llthread_cleanup_child(this); | ||
| 606 | return top; | ||
| 607 | } | ||
| 608 | |||
| 609 | if( rc == JOIN_ETIMEDOUT ){ | ||
| 610 | return fail(L, "timeout"); | ||
| 611 | } | ||
| 612 | |||
| 613 | { | ||
| 614 | char buf[ERROR_LEN]; | ||
| 615 | strerror_r(errno, buf, ERROR_LEN); | ||
| 616 | |||
| 617 | /* llthread_cleanup_child(this); */ | ||
| 618 | |||
| 619 | return fail(L, buf); | ||
| 620 | } | ||
| 621 | |||
| 622 | } | ||
| 623 | |||
| 624 | static int l_llthread_alive(lua_State *L) { | ||
| 625 | llthread_t *this = l_llthread_at(L, 1); | ||
| 626 | llthread_child_t *child = this->child; | ||
| 627 | int rc; | ||
| 628 | |||
| 629 | if(!IS(this, STARTED )) { | ||
| 630 | return fail(L, "Can't join a thread that hasn't be started."); | ||
| 631 | } | ||
| 632 | if( IS(this, DETACHED) && !IS(this, JOINABLE)) { | ||
| 633 | return fail(L, "Can't join a thread that has been detached."); | ||
| 634 | } | ||
| 635 | if( IS(this, JOINED )) { | ||
| 636 | return fail(L, "Can't join a thread that has already been joined."); | ||
| 637 | } | ||
| 638 | |||
| 639 | /* join the thread. */ | ||
| 640 | rc = llthread_alive(this); | ||
| 641 | |||
| 642 | if( rc == JOIN_ETIMEDOUT ){ | ||
| 643 | lua_pushboolean(L, 1); | ||
| 644 | return 1; | ||
| 645 | } | ||
| 646 | |||
| 647 | if(rc == JOIN_OK){ | ||
| 648 | lua_pushboolean(L, 0); | ||
| 649 | return 1; | ||
| 650 | } | ||
| 651 | |||
| 652 | { | ||
| 653 | char buf[ERROR_LEN]; | ||
| 654 | strerror_r(errno, buf, ERROR_LEN); | ||
| 655 | |||
| 656 | /* llthread_cleanup_child(this); */ | ||
| 657 | |||
| 658 | return fail(L, buf); | ||
| 659 | } | ||
| 660 | |||
| 661 | } | ||
| 662 | |||
| 663 | static int l_llthread_new(lua_State *L) { | ||
| 664 | size_t lua_code_len; const char *lua_code = luaL_checklstring(L, 1, &lua_code_len); | ||
| 665 | llthread_t **this = lutil_newudatap(L, llthread_t*, LLTHREAD_TAG); | ||
| 666 | lua_insert(L, 2); /*move self prior args*/ | ||
| 667 | *this = llthread_create(L, lua_code, lua_code_len); | ||
| 668 | |||
| 669 | lua_settop(L, 2); | ||
| 670 | return 1; | ||
| 671 | } | ||
| 672 | |||
| 673 | static const struct luaL_Reg l_llthread_meth[] = { | ||
| 674 | {"start", l_llthread_start }, | ||
| 675 | {"join", l_llthread_join }, | ||
| 676 | {"alive", l_llthread_alive }, | ||
| 677 | {"__gc", l_llthread_delete }, | ||
| 678 | |||
| 679 | {NULL, NULL} | ||
| 680 | }; | ||
| 681 | |||
| 682 | //} | ||
| 683 | |||
| 684 | //{ version | ||
| 685 | |||
| 686 | static int l_llthread_version(lua_State *L){ | ||
| 687 | lua_pushnumber(L, LLTHREAD_VERSION_MAJOR); | ||
| 688 | lua_pushnumber(L, LLTHREAD_VERSION_MINOR); | ||
| 689 | lua_pushnumber(L, LLTHREAD_VERSION_PATCH); | ||
| 690 | #ifdef LLTHREAD_VERSION_COMMENT | ||
| 691 | if(LLTHREAD_VERSION_COMMENT[0]){ | ||
| 692 | lua_pushliteral(L, LLTHREAD_VERSION_COMMENT); | ||
| 693 | return 4; | ||
| 694 | } | ||
| 695 | #endif | ||
| 696 | return 3; | ||
| 697 | } | ||
| 698 | |||
| 699 | static int l_llthread_push_version(lua_State *L){ | ||
| 700 | lua_pushnumber(L, LLTHREAD_VERSION_MAJOR); | ||
| 701 | lua_pushliteral(L, "."); | ||
| 702 | lua_pushnumber(L, LLTHREAD_VERSION_MINOR); | ||
| 703 | lua_pushliteral(L, "."); | ||
| 704 | lua_pushnumber(L, LLTHREAD_VERSION_PATCH); | ||
| 705 | #ifdef LLTHREAD_VERSION_COMMENT | ||
| 706 | if(LLTHREAD_VERSION_COMMENT[0]){ | ||
| 707 | lua_pushliteral(L, "-"LLTHREAD_VERSION_COMMENT); | ||
| 708 | lua_concat(L, 6); | ||
| 709 | } | ||
| 710 | else | ||
| 711 | #endif | ||
| 712 | lua_concat(L, 5); | ||
| 713 | return 1; | ||
| 714 | } | ||
| 715 | |||
| 716 | //} | ||
| 717 | |||
| 718 | static int l_llthread_set_logger(lua_State *L){ | ||
| 719 | lua_settop(L, 1); | ||
| 720 | luaL_argcheck(L, lua_isfunction(L, 1), 1, "function expected"); | ||
| 721 | lua_rawsetp(L, LUA_REGISTRYINDEX, LLTHREAD_LOGGER_HOLDER); | ||
| 722 | return 0; | ||
| 723 | } | ||
| 724 | |||
| 725 | static const struct luaL_Reg l_llthreads_lib[] = { | ||
| 726 | {"new", l_llthread_new }, | ||
| 727 | {"set_logger", l_llthread_set_logger }, | ||
| 728 | {"version", l_llthread_version }, | ||
| 729 | |||
| 730 | {NULL, NULL} | ||
| 731 | }; | ||
| 732 | |||
| 733 | LLTHREADS_EXPORT_API int LLTHREAD_OPEN_NAME(lua_State *L) { | ||
| 734 | int top = lua_gettop(L); | ||
| 735 | lutil_createmetap(L, LLTHREAD_TAG, l_llthread_meth, 0); | ||
| 736 | lua_settop(L, top); | ||
| 737 | |||
| 738 | lua_newtable(L); | ||
| 739 | luaL_setfuncs(L, l_llthreads_lib, 0); | ||
| 740 | |||
| 741 | lua_pushliteral(L, "_VERSION"); | ||
| 742 | l_llthread_push_version(L); | ||
| 743 | lua_rawset(L, -3); | ||
| 744 | |||
| 745 | return 1; | ||
| 746 | } | ||
