From febf9da1af8ba4cf0f8cc64b6af2adb0dcf9b354 Mon Sep 17 00:00:00 2001 From: moteus Date: Thu, 26 Dec 2013 12:00:41 +0400 Subject: First commit. --- .gitignore | 13 + COPYRIGHT.llthreads | 19 ++ README.md | 9 +- msvc/llthreads.sln | 20 ++ msvc/llthreads.vcproj | 208 +++++++++++++++ src/l52util.c | 117 +++++++++ src/l52util.h | 46 ++++ src/llthread.c | 669 +++++++++++++++++++++++++++++++++++++++++++++++ test/test_llthreads.lua | 80 ++++++ test/test_table_copy.lua | 134 ++++++++++ test/test_threads.lua | 74 ++++++ 11 files changed, 1388 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 COPYRIGHT.llthreads create mode 100644 msvc/llthreads.sln create mode 100644 msvc/llthreads.vcproj create mode 100644 src/l52util.c create mode 100644 src/l52util.h create mode 100644 src/llthread.c create mode 100644 test/test_llthreads.lua create mode 100644 test/test_table_copy.lua create mode 100644 test/test_threads.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c19188 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.d +*.spec +*.obj +*.dll +*.o +*.lib +*.exp +*.suo +*.ncb +*.user +*.pdb +msvc/Debug/ +msvc/Release/ diff --git a/COPYRIGHT.llthreads b/COPYRIGHT.llthreads new file mode 100644 index 0000000..4826006 --- /dev/null +++ b/COPYRIGHT.llthreads @@ -0,0 +1,19 @@ +Copyright (c) 2011 by Robert G. Jakabosky + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 072348b..2547cbc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ lua-llthreads2 ============== -[llthreads](https://github.com/Neopallium/lua-llthreads) rewrited without [LuaNativeObjects](https://github.com/Neopallium/LuaNativeObjects) +This is full dropin replacement for [llthreads](https://github.com/Neopallium/lua-llthreads) library. + +##Incompatibility list with origin llthreads library +* does not support ffi interface (use Lua C API for LuaJIT) +* returns nil instead of false on error +* start method returns self instead of true on success +* does not open all standart libraries (set LLTHREAD_REGISTER_STD_LIBRARY to on this feature) +* register loaders for llthreads library itself diff --git a/msvc/llthreads.sln b/msvc/llthreads.sln new file mode 100644 index 0000000..1277174 --- /dev/null +++ b/msvc/llthreads.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "llthreads", "llthreads.vcproj", "{60F3B657-C1F7-47F7-B159-3EEEA6B1220D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {60F3B657-C1F7-47F7-B159-3EEEA6B1220D}.Debug|Win32.ActiveCfg = Debug|Win32 + {60F3B657-C1F7-47F7-B159-3EEEA6B1220D}.Debug|Win32.Build.0 = Debug|Win32 + {60F3B657-C1F7-47F7-B159-3EEEA6B1220D}.Release|Win32.ActiveCfg = Release|Win32 + {60F3B657-C1F7-47F7-B159-3EEEA6B1220D}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/msvc/llthreads.vcproj b/msvc/llthreads.vcproj new file mode 100644 index 0000000..7b2a0e2 --- /dev/null +++ b/msvc/llthreads.vcproj @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/l52util.c b/src/l52util.c new file mode 100644 index 0000000..592c1d1 --- /dev/null +++ b/src/l52util.c @@ -0,0 +1,117 @@ +#include "l52util.h" + +#include +#include + +#if LUA_VERSION_NUM >= 502 + +int luaL_typerror (lua_State *L, int narg, const char *tname) { + const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, + luaL_typename(L, narg)); + return luaL_argerror(L, narg, msg); +} + +void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l){ + if(libname) lua_newtable(L); + luaL_setfuncs(L, l, 0); +} + +#else + +void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup){ + luaL_checkstack(L, nup, "too many upvalues"); + for (; l->name != NULL; l++) { /* fill the table with given functions */ + int i; + for (i = 0; i < nup; i++) /* copy upvalues to the top */ + lua_pushvalue(L, -nup); + lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ + lua_setfield(L, -(nup + 2), l->name); + } + lua_pop(L, nup); /* remove upvalues */ +} + +void lua_rawgetp(lua_State *L, int index, const void *p){ + index = lua_absindex(L, index); + lua_pushlightuserdata(L, (void *)p); + lua_rawget(L, index); +} + +void lua_rawsetp (lua_State *L, int index, const void *p){ + index = lua_absindex(L, index); + lua_pushlightuserdata(L, (void *)p); + lua_insert(L, -2); + lua_rawset(L, index); +} + +#endif + +int lutil_newmetatablep (lua_State *L, const void *p) { + lua_rawgetp(L, LUA_REGISTRYINDEX, p); + if (!lua_isnil(L, -1)) /* name already in use? */ + return 0; /* leave previous value on top, but return 0 */ + lua_pop(L, 1); + + lua_newtable(L); /* create metatable */ + lua_pushvalue(L, -1); /* duplicate metatable to set*/ + lua_rawsetp(L, LUA_REGISTRYINDEX, p); + + return 1; +} + +void lutil_getmetatablep (lua_State *L, const void *p) { + lua_rawgetp(L, LUA_REGISTRYINDEX, p); +} + +void lutil_setmetatablep (lua_State *L, const void *p) { + lutil_getmetatablep(L, p); + assert(lua_istable(L,-1)); + lua_setmetatable (L, -2); +} + +int lutil_isudatap (lua_State *L, int ud, const void *p) { + if (lua_isuserdata(L, ud)){ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + int res; + lutil_getmetatablep(L,p); /* get correct metatable */ + res = lua_rawequal(L, -1, -2); /* does it have the correct mt? */ + lua_pop(L, 2); /* remove both metatables */ + return res; + } + } + return 0; +} + +void *lutil_checkudatap (lua_State *L, int ud, const void *p) { + void *up = lua_touserdata(L, ud); + if (up != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + lutil_getmetatablep(L,p); /* get correct metatable */ + if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ + lua_pop(L, 2); /* remove both metatables */ + return up; + } + } + } + luaL_typerror(L, ud, p); /* else error */ + return NULL; /* to avoid warnings */ +} + +int lutil_createmetap (lua_State *L, const void *p, const luaL_Reg *methods, int nup) { + if (!lutil_newmetatablep(L, p)) + return 0; + + lua_insert(L, -1 - nup); /* move mt prior upvalues */ + luaL_setfuncs (L, methods, nup); /* define methods */ + lua_pushliteral (L, "__index"); /* define metamethods */ + lua_pushvalue (L, -2); + lua_settable (L, -3); + + return 1; +} + +void *lutil_newudatap_impl(lua_State *L, size_t size, const void *p){ + void *obj = lua_newuserdata (L, size); + memset(obj, 0, size); + lutil_setmetatablep(L, p); + return obj; +} diff --git a/src/l52util.h b/src/l52util.h new file mode 100644 index 0000000..f4f0497 --- /dev/null +++ b/src/l52util.h @@ -0,0 +1,46 @@ +#ifndef _LZUTILS_H_9B43D914_9652_4E22_9A43_8073502BF3F4_ +#define _LZUTILS_H_9B43D914_9652_4E22_9A43_8073502BF3F4_ + +#include "lua.h" +#include "lauxlib.h" + +#if LUA_VERSION_NUM >= 502 // lua 5.2 + +// lua_rawgetp +// lua_rawsetp +// luaL_setfuncs +// lua_absindex + + +#define lua_objlen lua_rawlen + +int luaL_typerror (lua_State *L, int narg, const char *tname); + +void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l); + +#else // lua 5.1 + +// functions form lua 5.2 + +# define lua_absindex(L, i) (((i)>0)?(i):((i)<=LUA_REGISTRYINDEX?(i):(lua_gettop(L)+(i)+1))) +# define lua_rawlen lua_objlen + +void lua_rawgetp (lua_State *L, int index, const void *p); +void lua_rawsetp (lua_State *L, int index, const void *p); +void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); + +#endif + +int lutil_newmetatablep (lua_State *L, const void *p); +void lutil_getmetatablep (lua_State *L, const void *p); +void lutil_setmetatablep (lua_State *L, const void *p); + +#define lutil_newudatap(L, TTYPE, TNAME) (TTYPE *)lutil_newudatap_impl(L, sizeof(TTYPE), TNAME) +int lutil_isudatap (lua_State *L, int ud, const void *p); +void *lutil_checkudatap (lua_State *L, int ud, const void *p); +int lutil_createmetap (lua_State *L, const void *p, const luaL_Reg *methods, int nup); + +void *lutil_newudatap_impl (lua_State *L, size_t size, const void *p); + +#endif + diff --git a/src/llthread.c b/src/llthread.c new file mode 100644 index 0000000..a11ad18 --- /dev/null +++ b/src/llthread.c @@ -0,0 +1,669 @@ +#if !defined(_WIN32) && !defined(USE_PTHREAD) +# define USE_PTHREAD +#endif + +#ifndef USE_PTHREAD +# include +# include +# include +#else +# include +# include +#endif + +/*export*/ +#ifdef _WIN32 +# define LLTHREADS_EXPORT_API __declspec(dllexport) +#else +# define LLTHREADS_EXPORT_API LUALIB_API +#endif + +/* wrap strerror_s(). */ +#ifdef _WIN32 +# ifdef __GNUC__ +# ifndef strerror_r +# define strerror_r(errno, buf, buflen) do { \ + strncpy((buf), strerror(errno), (buflen)-1); \ + (buf)[(buflen)-1] = '\0'; \ + } while(0) +# endif +# else +# ifndef strerror_r +# define strerror_r(errno, buf, buflen) strerror_s((buf), (buflen), (errno)) +# endif +# endif +#endif + +#ifndef USE_PTHREAD +# define OS_THREAD_RETURT unsigned int __stdcall +# define INVALID_THREAD INVALID_HANDLE_VALUE +# define INFINITE_JOIN_TIMEOUT INFINITE +typedef DWORD join_timeout_t; +typedef HANDLE os_thread_t; +#else +# define OS_THREAD_RETURT void * +# define INVALID_THREAD 0 +# define INFINITE_JOIN_TIMEOUT -1 +typedef int join_timeout_t; +typedef pthread_t os_thread_t; +#endif + +#include "l52util.h" +#include + +LLTHREADS_EXPORT_API int luaopen_llthreads(lua_State *L); + +//{ traceback + +#define ERROR_LEN 1024 + +/****************************************************************************** +* traceback() function from Lua 5.1/5.2 source. +* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************/ +#if !defined(LUA_VERSION_NUM) || (LUA_VERSION_NUM == 501) +/* from Lua 5.1 */ +static int traceback (lua_State *L) { + if (!lua_isstring(L, 1)) /* 'message' not a string? */ + return 1; /* keep it intact */ + lua_getglobal(L, "debug"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return 1; + } + lua_getfield(L, -1, "traceback"); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 2); + return 1; + } + lua_pushvalue(L, 1); /* pass error message */ + lua_pushinteger(L, 2); /* skip this function and traceback */ + lua_call(L, 2, 1); /* call debug.traceback */ + return 1; +} +#else +/* from Lua 5.2 */ +static int traceback (lua_State *L) { + const char *msg = lua_tostring(L, 1); + if (msg) + luaL_traceback(L, L, msg, 1); + else if (!lua_isnoneornil(L, 1)) { /* is there an error object? */ + if (!luaL_callmeta(L, 1, "__tostring")) /* try its 'tostring' metamethod */ + lua_pushliteral(L, "(no error message)"); + } + return 1; +} +#endif + +//} + +//{ copy values + +/* maximum recursive depth of table copies. */ +#define MAX_COPY_DEPTH 30 + +typedef struct { + lua_State *from_L; + lua_State *to_L; + int has_cache; + int cache_idx; + int is_arg; +} llthread_copy_state; + +static int llthread_copy_table_from_cache(llthread_copy_state *state, int idx) { + void *ptr; + + /* convert table to pointer for lookup in cache. */ + ptr = (void *)lua_topointer(state->from_L, idx); + if(ptr == NULL) return 0; /* can't convert to pointer. */ + + /* check if we need to create the cache. */ + if(!state->has_cache) { + lua_newtable(state->to_L); + lua_replace(state->to_L, state->cache_idx); + state->has_cache = 1; + } + + lua_pushlightuserdata(state->to_L, ptr); + lua_rawget(state->to_L, state->cache_idx); + if(lua_isnil(state->to_L, -1)) { + /* not in cache. */ + lua_pop(state->to_L, 1); + /* create new table and add to cache. */ + lua_newtable(state->to_L); + lua_pushlightuserdata(state->to_L, ptr); + lua_pushvalue(state->to_L, -2); + lua_rawset(state->to_L, state->cache_idx); + return 0; + } + /* found table in cache. */ + return 1; +} + +static int llthread_copy_value(llthread_copy_state *state, int depth, int idx) { + const char *str; + size_t str_len; + int kv_pos; + + /* Maximum recursive depth */ + if(++depth > MAX_COPY_DEPTH) { + return luaL_error(state->from_L, "Hit maximum copy depth (%d > %d).", depth, MAX_COPY_DEPTH); + } + + /* only support string/number/boolean/nil/table/lightuserdata. */ + switch(lua_type(state->from_L, idx)) { + case LUA_TNIL: + lua_pushnil(state->to_L); + break; + case LUA_TNUMBER: + lua_pushnumber(state->to_L, lua_tonumber(state->from_L, idx)); + break; + case LUA_TBOOLEAN: + lua_pushboolean(state->to_L, lua_toboolean(state->from_L, idx)); + break; + case LUA_TSTRING: + str = lua_tolstring(state->from_L, idx, &(str_len)); + lua_pushlstring(state->to_L, str, str_len); + break; + case LUA_TLIGHTUSERDATA: + lua_pushlightuserdata(state->to_L, lua_touserdata(state->from_L, idx)); + break; + case LUA_TTABLE: + /* make sure there is room on the new state for 3 values (table,key,value) */ + if(!lua_checkstack(state->to_L, 3)) { + return luaL_error(state->from_L, "To stack overflow!"); + } + /* make room on from stack for key/value pairs. */ + luaL_checkstack(state->from_L, 2, "From stack overflow!"); + + /* check cache for table. */ + if(llthread_copy_table_from_cache(state, idx)) { + /* found in cache don't need to copy table. */ + break; + } + lua_pushnil(state->from_L); + while (lua_next(state->from_L, idx) != 0) { + /* key is at (top - 1), value at (top), but we need to normalize these + * to positive indices */ + kv_pos = lua_gettop(state->from_L); + /* copy key */ + llthread_copy_value(state, depth, kv_pos - 1); + /* copy value */ + llthread_copy_value(state, depth, kv_pos); + /* Copied key and value are now at -2 and -1 in state->to_L. */ + lua_settable(state->to_L, -3); + /* Pop value for next iteration */ + lua_pop(state->from_L, 1); + } + break; + case LUA_TFUNCTION: + case LUA_TUSERDATA: + case LUA_TTHREAD: + default: + if (state->is_arg) { + return luaL_argerror(state->from_L, idx, "function/userdata/thread types un-supported."); + } else { + /* convert un-supported types to an error string. */ + lua_pushfstring(state->to_L, "Un-supported value: %s: %p", + lua_typename(state->from_L, lua_type(state->from_L, idx)), lua_topointer(state->from_L, idx)); + } + } + + return 1; +} + +static int llthread_copy_values(lua_State *from_L, lua_State *to_L, int idx, int top, int is_arg) { + llthread_copy_state state; + int nvalues = 0; + int n; + + nvalues = (top - idx) + 1; + /* make sure there is room on the new state for the values. */ + if(!lua_checkstack(to_L, nvalues + 1)) { + return luaL_error(from_L, "To stack overflow!"); + } + + /* setup copy state. */ + state.from_L = from_L; + state.to_L = to_L; + state.is_arg = is_arg; + state.has_cache = 0; /* don't create cache table unless it is needed. */ + lua_pushnil(to_L); + state.cache_idx = lua_gettop(to_L); + + nvalues = 0; + for(n = idx; n <= top; n++) { + llthread_copy_value(&state, 0, n); + ++nvalues; + } + + /* remove cache table. */ + lua_remove(to_L, state.cache_idx); + + return nvalues; +} + +//} + +static int fail(lua_State *L, const char *msg){ + lua_pushnil(L); + lua_pushstring(L, msg); + return 2; +} + +#define flags_t unsigned char + +#define TSTATE_NONE (flags_t)0 +#define TSTATE_STARTED (flags_t)1<<0 +#define TSTATE_DETACHED (flags_t)1<<1 +#define TSTATE_JOINED (flags_t)1<<2 + +/*At leas one flag*/ +#define FLAG_IS_SET(O, F) (O->flags & (flags_t)(F)) +/*All flags*/ +#define FLAGS_IS_SET(O, F) ((F) == FLAG_IS_SET(O, F)) +#define FLAG_SET(O, F) O->flags |= (flags_t)(F) +#define FLAG_UNSET(O, F) O->flags &= ~((flags_t)(F)) + +#define ALLOC_STRUCT(S) (S*)calloc(1, sizeof(S)) +#define FREE_STRUCT(O) free(O) + +typedef struct llthread_child_t { + lua_State *L; + int status; + flags_t flags; +} llthread_child_t; + +typedef struct llthread_t { + llthread_child_t *child; + os_thread_t thread; + flags_t flags; +} llthread_t; + +//{ llthread_child + +static void open_thread_libs(lua_State *L){ +#ifdef LLTHREAD_REGISTER_STD_LIBRARY +# define L_REGLIB(L, name) lua_pushcfunction(L, luaopen_##name); lua_setfield(L, -2, #name) +#else +# define L_REGLIB(L, name) lua_cpcall(L, luaopen_##name, 0) +#endif + + int top = lua_gettop(L); + lua_cpcall(L, luaopen_base, 0); + lua_cpcall(L, luaopen_package, 0); + lua_settop(L, top); + + /* get package.preload */ + lua_getglobal(L, "package"); lua_getfield(L, -1, "preload"); lua_remove(L, -2); + + L_REGLIB(L, io ); + L_REGLIB(L, os ); + L_REGLIB(L, math ); + L_REGLIB(L, table ); + L_REGLIB(L, debug ); + L_REGLIB(L, string ); + L_REGLIB(L, llthreads ); + + lua_settop(L, top); +#undef L_REGLIB +} + +static llthread_child_t *llthread_child_new() { + llthread_child_t *this = ALLOC_STRUCT(llthread_child_t); + if(!this) return NULL; + + memset(this, 0, sizeof(llthread_child_t)); + + /* create new lua_State for the thread. */ + /* open standard libraries. */ + /* push traceback function as first value on stack. */ + this->L = luaL_newstate(); + open_thread_libs(this->L); + lua_pushcfunction(this->L, traceback); + + return this; +} + +static void llthread_child_destroy(llthread_child_t *this) { + lua_close(this->L); + FREE_STRUCT(this); +} + +static OS_THREAD_RETURT llthread_child_thread_run(void *arg) { + llthread_child_t *this = (llthread_child_t *)arg; + lua_State *L = this->L; + int nargs = lua_gettop(L) - 2; + + this->status = lua_pcall(L, nargs, LUA_MULTRET, 1); + + /* alwasy print errors here, helps with debugging bad code. */ + if(this->status != 0) { + const char *err_msg = lua_tostring(L, -1); + fprintf(stderr, "Error from thread: %s\n", err_msg); + fflush(stderr); + } + + /* if thread is detached, then destroy the child state. */ + if(FLAG_IS_SET(this, TSTATE_DETACHED)) { + /* thread is detached, so it must clean-up the child state. */ + llthread_child_destroy(this); + this = NULL; + } + +#ifndef USE_PTHREAD + if(this) { + /* attached thread, don't close thread handle. */ + _endthreadex(0); + } else { + /* detached thread, close thread handle. */ + _endthread(); + } + return 0; +#else + return this; +#endif +} + +//} + +//{ llthread + +static llthread_t *llthread_new() { + llthread_t *this = ALLOC_STRUCT(llthread_t); + if(!this) return NULL; + + this->flags = TSTATE_NONE; + this->thread = INVALID_THREAD; + this->child = llthread_child_new(); + if(!this->child){ + FREE_STRUCT(this); + return NULL; + } + + return this; +} + +static void llthread_cleanup_child(llthread_t *this) { + if(this->child) { + llthread_child_destroy(this->child); + this->child = NULL; + } +} + +static void llthread_destroy(llthread_t *this) { + /* We still own the child thread object iff the thread was not started or + * we have joined the thread. + */ + if(FLAG_IS_SET(this, TSTATE_JOINED)||(this->flags == TSTATE_NONE)) { + llthread_cleanup_child(this); + } + FREE_STRUCT(this); +} + +static int llthread_push_args(lua_State *L, llthread_child_t *child, int idx, int top) { + return llthread_copy_values(L, child->L, idx, top, 1 /* is_arg */); +} + +static int llthread_push_results(lua_State *L, llthread_child_t *child, int idx, int top) { + return llthread_copy_values(child->L, L, idx, top, 0 /* is_arg */); +} + +static int llthread_start(llthread_t *this, int start_detached) { + llthread_child_t *child = this->child; + int rc = 0; + + if(start_detached){ + FLAG_SET(child, TSTATE_DETACHED); + } + +#ifndef USE_PTHREAD + this->thread = (HANDLE)_beginthreadex(NULL, 0, llthread_child_thread_run, child, 0, NULL); +#else + rc = pthread_create(&(this->thread), NULL, llthread_child_thread_run, child); + if(rc == 0){ + this->thread = INVALID_THREAD + } +#endif + + if(this->thread != INVALID_THREAD) { + 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 + } + } + + return rc; +} + +static int llthread_join(llthread_t *this, join_timeout_t timeout) { +#ifndef USE_PTHREAD + DWORD ret = 0; + if(INVALID_THREAD == this->thread) return 0; + ret = WaitForSingleObject( this->thread, timeout ); + if( ret == WAIT_OBJECT_0){ /* Destroy the thread object. */ + CloseHandle( this->thread ); + this->thread = INVALID_THREAD; + FLAG_SET(this, TSTATE_JOINED); + return 0; + } + else if( ret == WAIT_TIMEOUT ){ + return 1; + } + return 2; +#else + /* then join the thread. */ + int rc = pthread_join(this->thread, NULL); + if(rc == 0) { + FLAG_SET(this, TSTATE_JOINED); + } + return rc; +#endif +} + +static llthread_t *llthread_create(lua_State *L, const char *code, size_t code_len) { + llthread_t *this = llthread_new(); + llthread_child_t *child = this->child; + + /* load Lua code into child state. */ + int rc = luaL_loadbuffer(child->L, code, code_len, code); + if(rc != 0) { + /* copy error message to parent state. */ + size_t len; const char *str = lua_tolstring(child->L, -1, &len); + if(str != NULL) { + lua_pushlstring(L, str, len); + } else { + /* non-string error message. */ + lua_pushfstring(L, "luaL_loadbuffer() failed to load Lua code: rc=%d", rc); + } + llthread_destroy(this); + lua_error(L); + return NULL; + } + + /* copy extra args from main state to child state. */ + /* Push all args after the Lua code. */ + llthread_push_args(L, child, 3, lua_gettop(L)); + + return this; +} + +//} + +//{ Lua interface to llthread + +#define LLTHREAD_T_NAME "LLThread" +static const char *LLTHREAD_T = LLTHREAD_T_NAME; + +static llthread_t *l_llthread_at (lua_State *L, int i) { + llthread_t **this = (llthread_t **)lutil_checkudatap (L, i, LLTHREAD_T); + luaL_argcheck (L, this != NULL, i, "thread expected"); + luaL_argcheck (L, *this != NULL, i, "thread expected"); + // luaL_argcheck (L, !(counter->flags & FLAG_DESTROYED), 1, "PDH Counter is destroyed"); + return *this; +} + +static int l_llthread_delete(lua_State *L) { + llthread_t **pthis = (llthread_t **)lutil_checkudatap (L, 1, LLTHREAD_T); + llthread_t *this; + luaL_argcheck (L, pthis != NULL, 1, "thread expected"); + this = *pthis; + + /*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) { + const char *err_msg = lua_tostring(child->L, -1); + fprintf(stderr, "Error from non-joined thread: %s\n", err_msg); + fflush(stderr); + } + } + + llthread_destroy(this); + *pthis = NULL; + + return 0; +} + +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; + + if(this->flags != TSTATE_NONE) { + return fail(L, "Thread already started."); + } + + rc = llthread_start(this, start_detached); + if(rc != 0) { + char buf[ERROR_LEN]; + strerror_r(errno, buf, ERROR_LEN); + return fail(L, buf); + } + + lua_settop(L, 1); // return this + return 1; +} + +static int l_llthread_join(lua_State *L) { + llthread_t *this = l_llthread_at(L, 1); + llthread_child_t *child = this->child; + int rc; + + 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)) { + return fail(L, "Can't join a thread that has been detached."); + } + if( FLAG_IS_SET(this, TSTATE_JOINED )) { + return fail(L, "Can't join a thread that has already been joined."); + } + + /* join the thread. */ + rc = llthread_join(this, INFINITE_JOIN_TIMEOUT); + + /* Push all results after the Lua code. */ + if(child && FLAG_IS_SET(this, TSTATE_JOINED)) { + int top; + if(child->status != 0) { + const char *err_msg = lua_tostring(child->L, -1); + lua_pushboolean(L, 0); + lua_pushfstring(L, "Error from child thread: %s", err_msg); + top = 2; + } else { + lua_pushboolean(L, 1); + top = lua_gettop(child->L); + /* return results to parent thread. */ + llthread_push_results(L, child, 2, top); + } + llthread_cleanup_child(this); + return top; + } + +#ifndef USE_PTHREAD + if( rc == 1 ){ + lua_pushboolean(L, 0); + lua_pushstring(L, "timeout"); + return 2; + } +#endif + + { + char buf[ERROR_LEN]; + strerror_r(errno, buf, ERROR_LEN); + + llthread_cleanup_child(this); + + lua_pushboolean(L, 0); + lua_pushstring(L, buf); + return 2; + } + +} + +static int l_llthread_new(lua_State *L) { + size_t lua_code_len; const char *lua_code = luaL_checklstring(L, 1, &lua_code_len); + llthread_t **this = lutil_newudatap(L, llthread_t*, LLTHREAD_T); + lua_insert(L, 2); /*move self prior args*/ + *this = llthread_create(L, lua_code, lua_code_len); + + lua_settop(L, 2); + return 1; +} + +static const struct luaL_Reg l_llthread_meth[] = { + {"start", l_llthread_start }, + {"join", l_llthread_join }, + {"__gc", l_llthread_delete }, + + {NULL, NULL} +}; + +//} + +static const struct luaL_Reg l_llthreads_lib[] = { + {"new", l_llthread_new }, + + {NULL, NULL} +}; + +LLTHREADS_EXPORT_API int luaopen_llthreads(lua_State *L) { + int top = lua_gettop(L); + lutil_createmetap(L, LLTHREAD_T, l_llthread_meth, 0); + lua_settop(L, top); + + lua_newtable(L); + luaL_setfuncs(L, l_llthreads_lib, 0); + return 1; +} diff --git a/test/test_llthreads.lua b/test/test_llthreads.lua new file mode 100644 index 0000000..3474b9b --- /dev/null +++ b/test/test_llthreads.lua @@ -0,0 +1,80 @@ +-- Copyright (c) 2011 by Robert G. Jakabosky +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +local llthreads = require"llthreads" + +local sleep +local status, socket = pcall(require,"socket") +if status then + sleep = function(secs) + return socket.sleep(secs) + end +end + +if not sleep then + local status, ztimer = pcall(require, "lzmq.timer") + if status then + sleep = function(secs) + ztimer.sleep(secs * 1000) + end + end +end + +if not sleep then + sleep = function(secs) + os.execute("sleep " .. tonumber(secs)) + end +end + +local function detached_thread(...) + local thread = llthreads.new([[ print("print_detached_thread:", ...) ]], ...) + -- start detached thread + assert(thread:start(true)) + return thread +end + +local function print_thread(...) + local thread = llthreads.new([[ print("print_thread:", ...); ]], ...) + -- start joinable thread + assert(thread:start()) + return thread +end + +local function pass_through_thread(...) + local thread = llthreads.new([[ return "pass_thread:", ... ]], ...) + -- start joinable thread + assert(thread:start()) + return thread +end + +local thread1 = detached_thread("number:", 1234, "nil:", nil, "bool:", true) + +sleep(1) + +local thread2 = print_thread("number:", 1234, "nil:", nil, "bool:", true) +print("thread2:join: results # = ", select('#', thread2:join())) + +sleep(1) + +local thread3 = pass_through_thread("number:", 1234, "nil:", nil, "bool:", true) +print("thread3:join:", thread3:join()) + +sleep(1) + diff --git a/test/test_table_copy.lua b/test/test_table_copy.lua new file mode 100644 index 0000000..0408ad3 --- /dev/null +++ b/test/test_table_copy.lua @@ -0,0 +1,134 @@ +-- Copyright (c) 2011 by Robert G. Jakabosky +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +local llthreads = require"llthreads" + +local sleep +local status, socket = pcall(require,"socket") +if status then + sleep = function(secs) + return socket.sleep(secs) + end +else + sleep = function(secs) + os.execute("sleep " .. tonumber(secs)) + end +end + +local dump_code = [==[ +local function dump_recur(seen, obj, depth) + local out + local t = type(obj) + -- if not a table just convert to string. + if t ~= "table" then + if t == "string" then + return '"' .. obj .. '"' + end + return tostring(obj) + end + -- check if this table has been seen already. + if seen[obj] then + return "Already dumped " .. tostring(obj) + end + seen[obj] = true + -- restrict max depth. + if depth >= 10 then + return "{... max depth reached ...}" + end + depth = depth + 1 + -- output table key/value pairs + local tabs = string.rep(" ",depth) + local out = "{\n" + for k,v in pairs(obj) do + if type(k) ~= "number" then + out = out .. tabs .. '[' .. dump_recur(seen, k, depth) .. '] = ' .. + dump_recur(seen, v, depth) .. ',\n' + else + out = out .. tabs .. '[' .. k .. '] = ' .. dump_recur(seen, v, depth) .. ',\n' + end + end + return out .. tabs:sub(1,-3) .. "}" +end + +local obj = ... +local seen = {} +return dump_recur(seen, obj, 0) +]==] + +local dump = (loadstring or load)(dump_code) + +local child_code = [==[ +local dump = (loadstring or load)[[ +]==] .. dump_code .. [==[ +]] +local args = ... + +print("Child thread args:", dump(args)) + +-- return all values. +return ... +]==] + +local function test_thread_value_copying(...) + local args = {...} + print("Main thread args:", dump(args)) + local thread = llthreads.new(child_code, args) + -- start joinable thread + assert(thread:start()) + + local status, results = thread:join() + print("Main thread results:", dump(results)) +end + +-- create some tables. +local a1 = { "a1" } +local a2 = { "a2" } +local a3 = { "a3" } +local a4 = { "a4" } +local b1 = { a1, a2, a3, a4 } +local b2 = { a1=a1, a2=a2, a3=a3, a4=a4 } + +-- +-- no loops +-- +test_thread_value_copying(b1, b2) + +local top = {} +-- self reference. +top.top = top +top[top] = top +-- nested reference. +top.sub1 = { sub2 = { sub3 = { top } } } + +-- +-- loops +-- +test_thread_value_copying(top) + +-- +-- Test max depth +-- +local outer = {} +for n=1,100 do + outer = {outer} +end +local status, err = pcall(test_thread_value_copying,outer) +assert(not status, "Assertion failed: max depth test failed.") + diff --git a/test/test_threads.lua b/test/test_threads.lua new file mode 100644 index 0000000..467526e --- /dev/null +++ b/test/test_threads.lua @@ -0,0 +1,74 @@ +-- Copyright (c) 2011 by Ross Anderson +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +-- Sub-thread processing example in Lua using llthreads - 1,000 quick sub-thread execution + +-- luajit sub_threads.lua + +local llthreads = require"llthreads" + +local num_threads = tonumber(arg[1] or 1000) + +-- level 0 string literal enclosure [[ ]] of child execution code +local thread_code = [[ + local lua_init = os.getenv("lua_init") + if lua_init and #lua_init > 0 then + if lua_init:sub(1,1) == '@' then + dofile(lua_init:sub(2)) + else + assert((loadstring or load)(lua_init))() + end + end + + local num_threads = ... + print("CHILD: received from ROOT params:", ...) + local llthreads = require"llthreads" -- need to re-declare this under this scope + local t = {} -- thread storage table + + -- create a new child sub-thread execution code - it requires level 1 literal string [=[ ]=] enclosures, level 2 would be [==[ ]==] + local executed_child_code = [=[ + return "Hello from child sub-thread, new input params:", ... + ]=] + + -- create 1000 sub-threads - which creates an incremental 30% / 20% utilization spike on the two AMD cpu cores + print("CHILD: Create sub threads:", num_threads) + for i=1,num_threads do + -- create child sub-thread with code to execute and the input parmeters + local thread = llthreads.new(executed_child_code , "number:", 1000 + i, "nil:", nil, "bool:", true) + assert(thread:start()) -- start new child sub-thread + table.insert(t, thread) -- append the thread at the end of the thread table + end + + -- wait (block) for all child sub-threads to complete before returning to ROOT + while true do + -- always wait on the first element, since order is not important + print("CHILD: sub-thread returned: ", t[1]:join()) + table.remove(t,1) -- always remove the first element + if (#t == 0) then break end + end + return ... -- return the parents' input params back to the root +]] + +-- create child thread. +local thread = llthreads.new(thread_code, num_threads, "number:", 1000, "nil:", nil, "bool:", true) +-- start joinable child thread. +assert(thread:start()) +-- wait for all child and child sub-threads to finish +print("ROOT: child returned: ", thread:join()) -- cgit v1.2.3-55-g6feb