├── .gitmodules ├── CMakeLists.txt ├── LICENSE.txt ├── lj-async ├── callback.lua ├── keeper.lua ├── lua_cdefs.lua ├── mutex.lua └── thread.lua ├── readme.md └── tests ├── basic.lua ├── callback.lua ├── keepertest.lua ├── keepertest2.lua ├── mutex.lua ├── mutex_timeout.lua ├── thread.lua ├── thread_join.lua └── thread_returns.lua /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pthread"] 2 | path = pthread 3 | url = https://github.com/sonoro1234/pthread.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | PROJECT(luajit-async) 2 | #to allow install from subdirectory 3 | cmake_minimum_required(VERSION 3.13) 4 | 5 | 6 | INSTALL(DIRECTORY lj-async/ DESTINATION ${LUAJIT_BIN}/lua/lj-async) 7 | INSTALL(FILES pthread/pthread.lua DESTINATION ${LUAJIT_BIN}/lua) -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /lj-async/callback.lua: -------------------------------------------------------------------------------- 1 | 2 | --- Framework for creating Lua callbacks that can be asynchronously called. 3 | 4 | local ffi = require "ffi" 5 | local C = ffi.C 6 | 7 | -- Setup function ran by the Lua state to create 8 | local callback_setup_func = string.dump(function(cbtype, cbsource, ...) 9 | --print("in callback_setup_func",cbtype, cbsource,...) 10 | local ffi = _G.require("ffi") 11 | local initfunc = cbsource 12 | --local initfunc = _G.loadstring(cbsource) 13 | local ret_fail2 = cbtype:match("%s*(.-)%s*%(.-%*.-%)") 14 | ret_fail2 = (ret_fail2 ~= "void") and ffi.new(ret_fail2) or nil 15 | local xpcall, dtraceback, tostring, error = _G.xpcall, _G.debug.traceback, _G.tostring, _G.error 16 | 17 | local xpcall_hook = function(err) return dtraceback(tostring(err) or "") end 18 | 19 | --local cbfunc = initfunc(...) 20 | local ok, cbfunc = xpcall(initfunc, xpcall_hook, ...) 21 | if not ok then print("initfunc error", cbfunc);error("initfunc") end 22 | local waserror = false 23 | local cb = ffi.cast(cbtype, function(...) 24 | if not waserror then 25 | local ok, val = xpcall(cbfunc, xpcall_hook, ...) 26 | if not ok then 27 | print("error in callback",val) 28 | waserror = true 29 | return ret_fail2 30 | else 31 | return val or ret_fail2 32 | end 33 | else 34 | return ret_fail2 35 | end 36 | end) 37 | local ptr = tonumber(ffi.cast('uintptr_t', ffi.cast('void *', cb))) 38 | return cb, ptr 39 | end) 40 | 41 | local common = require"lj-async.lua_cdefs" 42 | local push = common.push 43 | 44 | -- Maps callback object ctypes to the callback pointer types 45 | local ctype2cbstr = {} 46 | 47 | local Callback = {} 48 | Callback.__index = Callback 49 | 50 | 51 | -- Copies values into a lua state 52 | local function moveValues(L, ...) 53 | local n = select("#", ...) 54 | 55 | if C.lua_checkstack(L, n) == 0 then 56 | error("out of memory") 57 | end 58 | 59 | for i=1,n do 60 | local v = select(i, ...) 61 | push(L, v, true) 62 | end 63 | return n 64 | end 65 | 66 | local function MakeCallback(L, cb2type, cb2, ... ) 67 | if type(cb2) == "function" then 68 | --[[ 69 | local name,val = debug.getupvalue(cb2,1) 70 | if name then 71 | print("init callback function has upvalue ",name) 72 | error("upvalues in init callback") 73 | end 74 | cb2 = string.dump(cb2) 75 | --]] 76 | end 77 | C.lua_settop(L,0) 78 | 79 | if C.lua_checkstack(L, 20) == 0 then 80 | error("out of memory") 81 | end 82 | -- Load the callback setup function 83 | C.lua_getfield(L, C.LUA_GLOBALSINDEX, "loadstring") 84 | C.lua_pushlstring(L, callback_setup_func, #callback_setup_func) 85 | C.lua_call(L,1,1) 86 | -- Load the actual callback 87 | C.lua_pushlstring(L, cb2type, #cb2type) 88 | --C.lua_pushlstring(L, cb2, #cb2) 89 | push(L,cb2,true) 90 | local n = moveValues(L, ...) 91 | local ret = C.lua_pcall(L,2+n,2,0) 92 | if ret > 0 then 93 | print(ffi.string(C.lua_tolstring(L,1,nil))) 94 | error("error making callback",2) 95 | return nil 96 | end 97 | -- Get and pop the callback function pointer 98 | assert(C.lua_isnumber(L,2) ~= 0) 99 | local ptr = C.lua_tointeger(L,2) 100 | assert(ptr ~= 0) 101 | C.lua_settop(L, 1) 102 | local callback = ffi.cast(cb2type, ptr) 103 | assert(callback ~= nil) 104 | 105 | return callback 106 | end 107 | 108 | --- Creates a new callback object. 109 | -- callback_func is either a function compatible with string.dump (i.e. a Lua function without upvalues) 110 | -- or LuaJIT source/bytecode representing such a function (ex. The output of string.dump(func). This is recommended if you 111 | -- plan on making many callbacks). 112 | -- 113 | -- The function is (re)created in a separate Lua state; thus, no Lua values may be shared. 114 | -- The only way to share information between the main Lua state and the callback is by a 115 | -- userdata pointer in the callback function, which you will need to synchronize 116 | -- yourself. 117 | -- 118 | -- The returned object must be kept alive for as long as the callback may still be called. 119 | -- 120 | -- Errors in callbacks are not caught; thus, they will cause its Lua state's panic function 121 | -- to run and terminate the process. 122 | function Callback:__new(callback_func,...) 123 | 124 | local obj = ffi.new(self) 125 | local cbtype = assert(ctype2cbstr[tonumber(self)]) 126 | 127 | local L = C.luaL_newstate() 128 | if L == nil then 129 | error("Could not allocate new state",2) 130 | end 131 | obj.L = L 132 | 133 | C.luaL_openlibs(L) 134 | 135 | obj.callback = MakeCallback(L, cbtype, callback_func, ...) 136 | return obj 137 | end 138 | 139 | ---for getting another callback from the same Lua state 140 | function Callback:additional_cb(cb2,cb2type,...) 141 | return MakeCallback(self.L, cb2type, cb2, ...) 142 | end 143 | --- Gets and returns the callback function pointer. 144 | function Callback:funcptr() 145 | return self.callback 146 | end 147 | 148 | --- Frees the callback object and associated callback. 149 | function Callback:free() 150 | if self.L ~= nil then 151 | -- TODO: Do we need to free the callback, or will lua_close free it for us? 152 | C.lua_close(self.L) 153 | self.L = nil 154 | end 155 | end 156 | Callback.__gc = Callback.free 157 | 158 | --- Returns a newly created callback ctype. 159 | -- cb_type is a string representation of the callback pointer type (ex. what you would pass to ffi.typeof). 160 | -- This must be a string; actual ctype objects cannot be used. 161 | return function(cb_type) 162 | assert(type(cb_type) == "string", "Bad argument #1 to async type creator; string expected") 163 | 164 | local typ = ffi.typeof([[struct { 165 | lua_State* L; 166 | $ callback; 167 | }]], ffi.typeof(cb_type)) 168 | 169 | ctype2cbstr[tonumber(typ)] = cb_type 170 | 171 | return ffi.metatype(typ, Callback) 172 | end 173 | -------------------------------------------------------------------------------- /lj-async/keeper.lua: -------------------------------------------------------------------------------- 1 | 2 | local Mutex = require"lj-async.mutex" 3 | local ffi = require"ffi" 4 | local C = ffi.C 5 | 6 | local common = require"lj-async.lua_cdefs" 7 | local push = common.push 8 | 9 | local M = {} 10 | 11 | local keeper_cdef = [[ 12 | typedef struct keeper{ 13 | lua_State* L; 14 | mutextype *mutex; 15 | } keeper; 16 | ]] 17 | ffi.cdef(keeper_cdef) 18 | 19 | local Keeper_typ = ffi.typeof("keeper") 20 | local Keeper = {} 21 | Keeper.__index = Keeper 22 | local mutex_anchor = {} 23 | function Keeper:__new() 24 | --print"new keeper" 25 | local obj = ffi.new(self) 26 | 27 | local L = C.luaL_newstate() 28 | if L == nil then 29 | error("Could not allocate new state",2) 30 | end 31 | obj.L = L 32 | local mut = Mutex() 33 | obj.mutex = mut 34 | table.insert(mutex_anchor, mut) 35 | C.luaL_openlibs(L) 36 | 37 | C.lua_settop(L,0) -- eliminar pila 38 | C.lua_createtable(L,0,0); 39 | --C.lua_setglobal (L, "DATA"); 40 | C.lua_setfield(L, C.LUA_GLOBALSINDEX, "DATA") 41 | return obj 42 | end 43 | 44 | function Keeper:send(key,val) 45 | --print("send",key,val,self.L) 46 | local L = self.L 47 | self.mutex:lock() 48 | C.lua_settop(L,0) -- eliminar pila 49 | 50 | if C.lua_checkstack(L, 20) == 0 then 51 | error("out of memory") 52 | end 53 | 54 | --C.lua_getglobal (L, "DATA"); 55 | C.lua_getfield(L, C.LUA_GLOBALSINDEX, "DATA") --DATA 56 | assert(C.lua_type(L, -1)==C.LUA_TTABLE) 57 | local top = C.lua_gettop(L) 58 | ---[[ 59 | push(L, key, true) --DATA/key 60 | C.lua_gettable(L, top) --DATA/keyval 61 | local istable = C.lua_type(L, -1)==C.LUA_TTABLE 62 | --print("istable",istable) 63 | if istable then 64 | local leng = C.lua_objlen(L, -1) 65 | C.lua_pushnumber(L, leng + 1) 66 | push(L, val, true) 67 | C.lua_settable(L, -3); 68 | else 69 | assert(C.lua_type(L, -1)==C.LUA_TNIL) 70 | --C.lua_pop(L, 1) -- deletee nil 71 | C.lua_settop(L, -(1)-1) --DATA 72 | assert(C.lua_type(L, -1)==C.LUA_TTABLE) 73 | C.lua_createtable(L,0,0); --DATA/table 74 | local top1 = C.lua_gettop(L) 75 | C.lua_pushnumber(L, 1) --DATA/table/1 76 | push(L, val, true) --DATA/table/1/val 77 | C.lua_settable(L, top1) --DATA/table 78 | push(L, key, true) --DATA/table/key 79 | C.lua_insert(L, 2) --DATA/key/table 80 | C.lua_settable(L, -3); --DATA 81 | end 82 | 83 | self.mutex:unlock() 84 | 85 | end 86 | local function pop_value(L, index) 87 | --print("pop_value",index) 88 | index = index or -1 89 | if C.lua_type(L, index)==C.LUA_TNIL then 90 | return nil 91 | elseif C.lua_type(L, index)==C.LUA_TBOOLEAN then 92 | return C.lua_toboolean(L, index)==1 and true or false 93 | elseif C.lua_type(L, index)==C.LUA_TNUMBER then 94 | return C.lua_tonumber(L, index) 95 | elseif C.lua_type(L, index)==C.LUA_TSTRING then 96 | return ffi.string(C.lua_tolstring(L, index,nil)) 97 | elseif C.lua_type(L, index)==C.LUA_TTABLE then 98 | local tab = {} 99 | C.lua_pushnil(L); 100 | while (C.lua_next(L, index-1) ~= 0) do 101 | --print"next iter" 102 | local k = pop_value(L, -2) 103 | local v = pop_value(L, -1) 104 | tab[k] = v 105 | --C.lua_pop(L,1) 106 | C.lua_settop(L, -(1)-1) 107 | end 108 | return tab 109 | else 110 | error"pop_value bad value" 111 | end 112 | end 113 | 114 | local function pop_DATA(L, key) 115 | 116 | --print("pop_DATA",key,C.lua_gettop(L)) 117 | push(L, key, true)--DATA/key 118 | --C.lua_pushlstring(L,key,#key) 119 | --print("pop_DATA2",key,C.lua_gettop(L)) 120 | ---[[ 121 | C.lua_gettable(L, -2) --DATA/keyval 122 | if not(C.lua_type(L, -1)==C.LUA_TTABLE) then C.lua_settop(L, -(1)-1);return nil end 123 | local n = C.lua_objlen(L,-1) 124 | 125 | C.lua_rawgeti(L, -1, 1)--DATA/table/table[1] 126 | 127 | local val = pop_value(L,-1) 128 | --print("val",val) 129 | C.lua_settop(L, -(1)-1)--DATA/table 130 | --remove from table 131 | for i=1,n-1 do 132 | C.lua_rawgeti(L,-1,i+1)--DATA/table/table[i+1] 133 | C.lua_rawseti(L,-2,i)--DATA/table 134 | end 135 | C.lua_pushnil(L) --DATA/table/nil 136 | C.lua_rawseti(L,-2,n) --DATA/table 137 | C.lua_settop(L, -(1)-1) 138 | --]] 139 | -- C.lua_settop(L, -(1)-1) 140 | -- print("pop_DATA3",key,C.lua_gettop(L)) 141 | return val 142 | 143 | end 144 | function Keeper:receive(...) 145 | local n = select("#", ...) 146 | local L = self.L 147 | local val 148 | self.mutex:lock() 149 | 150 | if C.lua_checkstack(L, n) == 0 then 151 | error("out of memory") 152 | end 153 | 154 | 155 | C.lua_getfield(L, C.LUA_GLOBALSINDEX, "DATA") --DATA 156 | for i=1,n do 157 | local v = select(i, ...) 158 | val = pop_DATA(L, v) 159 | if val~=nil then 160 | C.lua_settop(L, -(1)-1) 161 | self.mutex:unlock(); 162 | return v,val 163 | end 164 | end 165 | C.lua_settop(L, -(1)-1) 166 | 167 | self.mutex:unlock() 168 | return val 169 | end 170 | 171 | 172 | function Keeper:read() 173 | print"read" 174 | local L = self.L 175 | local code = [[print("readsss",DATA,#DATA)]] 176 | local code =[[ 177 | for k,v in pairs(DATA) do 178 | print("key",k,v) 179 | for i,val in ipairs(v) do 180 | print(i,val) 181 | end 182 | end 183 | ]] 184 | 185 | assert(C.luaL_loadstring(L, code)==0) 186 | 187 | --C.lua_call(L,0,0) 188 | local ret = C.lua_pcall(L, 0, C.LUA_MULTRET, 0) 189 | assert(ret==0) 190 | 191 | end 192 | 193 | local M = {} 194 | 195 | M.MakeKeeper = ffi.metatype(Keeper_typ, Keeper) 196 | function M.KeeperCast(v) 197 | return ffi.cast("keeper*",v) 198 | end 199 | 200 | --[[ 201 | require"anima.utils" 202 | local K = M.MakeKeeper() 203 | 204 | 205 | K:send("uno",11) 206 | K:send("uno",false) 207 | K:send("dos",33) 208 | K:send("uno","stringgg") 209 | local t = {1,pedro={hola=false},3} 210 | K:send("uno",t) 211 | K:send("uno",t) 212 | 213 | --K:read() 214 | print(K.mutex) 215 | print"receive--------------------" 216 | for i=1,7 do 217 | print("--------receive",i,K.mutex.mutex) 218 | local key,val = K:receive("uno","dos") 219 | prtable(key,val) 220 | end 221 | --]] 222 | --[[ 223 | local K = M.MakeKeeper() 224 | for i=1,20000 do 225 | --print(i) 226 | K:receive("clace") 227 | --K.mutex:lock() 228 | --K.mutex:unlock() 229 | end 230 | --]] 231 | return M -------------------------------------------------------------------------------- /lj-async/lua_cdefs.lua: -------------------------------------------------------------------------------- 1 | local ffi = require"ffi" 2 | local C = ffi.C 3 | 4 | ffi.cdef[[ 5 | static const int LUA_GLOBALSINDEX = -10002; 6 | static const int LUA_MULTRET = -1; 7 | 8 | static const int LUA_TNIL = 0; 9 | static const int LUA_TBOOLEAN = 1; 10 | static const int LUA_TLIGHTUSERDATA = 2; 11 | static const int LUA_TNUMBER = 3; 12 | static const int LUA_TSTRING = 4; 13 | static const int LUA_TTABLE = 5; 14 | static const int LUA_TFUNCTION = 6; 15 | static const int LUA_TUSERDATA = 7; 16 | static const int LUA_TTHREAD = 8; 17 | 18 | typedef struct lua_State lua_State; 19 | typedef ptrdiff_t lua_Integer; 20 | 21 | lua_State* luaL_newstate(void); 22 | void luaL_openlibs(lua_State *L); 23 | void lua_close (lua_State *L); 24 | void lua_call(lua_State *L, int nargs, int nresults); 25 | int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc); 26 | void lua_checkstack (lua_State *L, int sz); 27 | void lua_settop (lua_State *L, int index); 28 | int lua_gettop (lua_State *L); 29 | void lua_pushlstring (lua_State *L, const char *s, size_t l); 30 | void lua_gettable (lua_State *L, int idx); 31 | void lua_getfield (lua_State *L, int idx, const char *k); 32 | lua_Integer lua_tointeger (lua_State *L, int index); 33 | int lua_isnumber(lua_State*,int); 34 | const char *lua_tostring (lua_State *L, int index); 35 | const char *lua_tolstring (lua_State *L, int index, size_t *len); 36 | void lua_pushnumber (lua_State *L, double n); 37 | void lua_pushnil (lua_State *L); 38 | void lua_pushboolean (lua_State *L, int b); 39 | void lua_pushlightuserdata (lua_State *L, void *p); 40 | void lua_createtable (lua_State *L, int narr, int nrec); 41 | void lua_settable (lua_State *L, int index); 42 | const char *lua_setupvalue (lua_State *L, int funcindex, int n); 43 | //void lua_setglobal (lua_State *L, const char *name); 44 | //void lua_getglobal (lua_State *L, const char *name); 45 | int lua_type (lua_State *L, int index); 46 | void lua_setfield (lua_State *L, int index, const char *k); 47 | size_t lua_objlen (lua_State *L, int index); 48 | //void lua_pop (lua_State *L, int n); 49 | void lua_insert (lua_State *L, int index); 50 | int luaL_loadstring (lua_State *L, const char *s); 51 | int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc); 52 | int lua_next (lua_State *L, int index); 53 | double lua_tonumber (lua_State *L, int index); 54 | void lua_rawgeti (lua_State *L, int index, int n); 55 | void lua_rawseti (lua_State *L, int index, int n); 56 | //const char *lua_tostring (lua_State *L, int index); 57 | const char *lua_tolstring (lua_State *L, int index, size_t *len); 58 | int lua_toboolean (lua_State *L, int index); 59 | ]] 60 | 61 | local M = {} 62 | 63 | local lookup_t = {} --for detectin cicles 64 | local xpcall_hook = function(err) return debug.traceback(tostring(err) or "") end 65 | local function push(L, v, setupvals) 66 | 67 | --local xpcall, dtraceback, tostring, error = _G.xpcall, _G.debug.traceback, _G.tostring, _G.error 68 | --local dtraceback = debug.traceback 69 | --local xpcall_hook = function(err) return debug.traceback(tostring(err) or "") end 70 | --print("push", type(v), v) 71 | if type(v) == 'nil' then 72 | C.lua_pushnil(L) 73 | elseif type(v) == 'boolean' then 74 | C.lua_pushboolean(L,v) 75 | elseif type(v) == 'number' then 76 | C.lua_pushnumber(L, v) 77 | elseif type(v) == 'string' then 78 | C.lua_pushlstring(L,v,#v) 79 | elseif type(v) == 'function' then 80 | 81 | local stfunc = string.dump(v) 82 | C.lua_getfield(L, C.LUA_GLOBALSINDEX, "loadstring") 83 | C.lua_pushlstring(L, stfunc, #stfunc) 84 | C.lua_call(L,1,1) 85 | if setupvals then 86 | local i = 1 87 | while true do 88 | 89 | local uname, uv = debug.getupvalue(v, i) 90 | if not uname then break end 91 | print("push upvalue",v,i,uname,uv) 92 | if v==uv then 93 | error"recurrence in push function upvalues" 94 | end 95 | --push(L, uv, setupvals) 96 | --local ok = true 97 | --local ok,err = pcall(push, L, uv, setupvals) 98 | local ok,err = xpcall(push, xpcall_hook, L, uv, setupvals) 99 | 100 | if not ok then 101 | --error("false error") 102 | local info = debug.getinfo(v) 103 | 104 | print("error pushing upvalue", uname, "of function:", info.name,"defined in",info.source,info.linedefined); 105 | 106 | print(err) 107 | 108 | error("pushing upvalue",2) 109 | else 110 | C.lua_setupvalue(L, -2, i) 111 | i = i + 1 112 | end 113 | end 114 | end 115 | elseif type(v) == 'table' then 116 | if lookup_t[v] then error"push: cicles in table" end 117 | lookup_t[v] = true 118 | --NOTE: doesn't check duplicate refs 119 | --NOTE: doesn't check for cycles 120 | --NOTE: stack-bound on table depth 121 | assert(C.lua_checkstack(L, 3) ~= 0, 'stack overflow') 122 | C.lua_createtable(L, 0, 0) 123 | local top = C.lua_gettop(L) 124 | for k,v in pairs(v) do 125 | push(L, k, setupvals) 126 | push(L, v, setupvals) 127 | C.lua_settable(L, top) 128 | end 129 | assert(C.lua_gettop(L) == top) 130 | lookup_t[v] = nil 131 | elseif type(v) == 'userdata' then 132 | --NOTE: there's no Lua API to get the size or lightness of a userdata, 133 | --so we don't have enough info to duplicate a userdata automatically. 134 | error('Not implemented push userdata', 2) 135 | elseif type(v) == 'thread' then 136 | --NOTE: there's no Lua API to get the 'lua_State*' of a coroutine. 137 | error('Not implemented push thread', 2) 138 | elseif type(v) == 'cdata' then 139 | --NOTE: there's no Lua C API to push a cdata. 140 | --cdata are not shareable anyway because ctypes are not shareable. 141 | --error('Not implemented push cdata '..tostring(v), 2) 142 | --we push it as a pointer 143 | C.lua_pushlightuserdata(L,v) 144 | end 145 | end 146 | 147 | M.push = push 148 | 149 | return M -------------------------------------------------------------------------------- /lj-async/mutex.lua: -------------------------------------------------------------------------------- 1 | -- Mutex objects 2 | 3 | local ffi = require "ffi" 4 | local C = ffi.C 5 | 6 | local abstraction = {} 7 | if ffi.os == "Windows" and not WINUSEPTHREAD then 8 | --abstractions = require "jitthreads._win" 9 | --abstractions = require "jitthreads._pthreads" 10 | ffi.cdef[[ 11 | static const int STILL_ACTIVE = 259; 12 | static const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; 13 | static const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; 14 | static const int WAIT_ABANDONED = 0x00000080; 15 | static const int WAIT_OBJECT_0 = 0x00000000; 16 | static const int WAIT_TIMEOUT = 0x00000102; 17 | static const int WAIT_FAILED = 0xFFFFFFFF; 18 | static const int INFINITE = 0xFFFFFFFF; 19 | 20 | int CloseHandle(void*); 21 | int GetExitCodeThread(void*,uint32_t*); 22 | uint32_t WaitForSingleObject(void*, uint32_t); 23 | 24 | 25 | void* CreateMutexA(void*, int, const char*); 26 | int ReleaseMutex(void*); 27 | 28 | uint32_t GetLastError(); 29 | uint32_t FormatMessageA( 30 | uint32_t dwFlags, 31 | const void* lpSource, 32 | uint32_t dwMessageId, 33 | uint32_t dwLanguageId, 34 | char* lpBuffer, 35 | uint32_t nSize, 36 | va_list *Arguments 37 | ); 38 | ]] 39 | abstraction.mutex_t = ffi.typeof("void*") 40 | -- Some helper functions 41 | local function error_win(lvl) 42 | local errcode = C.GetLastError() 43 | local str = ffi.new("char[?]",1024) 44 | local numout = C.FormatMessageA(bit.bor(C.FORMAT_MESSAGE_FROM_SYSTEM, 45 | C.FORMAT_MESSAGE_IGNORE_INSERTS), nil, errcode, 0, str, 1023, nil) 46 | if numout == 0 then 47 | error("Windows Error: (Error calling FormatMessage)", lvl) 48 | else 49 | error("Windows Error("..tostring(tonumber(errcode)).."): "..ffi.string(str, numout), lvl) 50 | end 51 | end 52 | 53 | local function error_check(result) 54 | if result == 0 then 55 | error_win(4) 56 | end 57 | end 58 | 59 | function abstraction.mutex_create() 60 | return C.CreateMutexA(nil, false, nil) 61 | end 62 | 63 | function abstraction.mutex_destroy(mutex) 64 | if mutex ~= nil then 65 | error_check(C.CloseHandle(mutex)) 66 | mutex = nil 67 | end 68 | end 69 | 70 | function abstraction.mutex_get(mutex, timeout) 71 | if timeout then 72 | timeout = timeout*1000 73 | else 74 | timeout = C.INFINITE 75 | end 76 | 77 | local r = C.WaitForSingleObject(mutex, timeout) 78 | if r == C.WAIT_OBJECT_0 or r == C.WAIT_ABANDONED then 79 | return true 80 | elseif r == C.WAIT_TIMEOUT then 81 | return false 82 | else 83 | error_win(3) 84 | end 85 | end 86 | 87 | function abstraction.mutex_release(mutex) 88 | error_check(C.ReleaseMutex(mutex)) 89 | end 90 | 91 | else 92 | 93 | local pthreads = require"pthread" 94 | abstraction.mutex_t = ffi.typeof("pthread_mutex_t") 95 | local mut_anchor = {} 96 | function abstraction.mutex_create() 97 | local mut = pthreads.mutex() 98 | table.insert(mut_anchor, mut) 99 | return mut 100 | end 101 | 102 | function abstraction.mutex_destroy(mutex) 103 | for i,v in ipairs(mut_anchor) do 104 | if mutex == v then 105 | table.remove(mut_anchor,i) 106 | break 107 | end 108 | end 109 | mutex:free() 110 | end 111 | 112 | ffi.cdef[[ 113 | typedef int clockid_t; 114 | int clock_gettime(clockid_t clk_id, timespec *tp); 115 | int pthread_mutex_timedlock(pthread_mutex_t *m,const timespec *abs_timeout); 116 | ]] 117 | local tsl 118 | local ETIMEDOUT = (ffi.os=="Linux" and 110) or (ffi.os=="Windows" and 138) or 60 119 | function pthread_mutex_timedlock(mutex,timeout) 120 | tsl = tsl or ffi.new'timespec' 121 | pthreads.C.clock_gettime(0,tsl) -- CLOCK_REALTIME 122 | local int, frac = math.modf(timeout) 123 | tsl.s = tsl.s + int 124 | tsl.ns = tsl.ns + frac * 1e9 125 | while (tsl.ns >= 1e9) do 126 | tsl.ns = tsl.ns - 1e9; 127 | tsl.s = tsl.s + 1 128 | end 129 | local ret = pthreads.C.pthread_mutex_timedlock(mutex,tsl) 130 | if ret == 0 then 131 | return true 132 | elseif ret == ETIMEDOUT then 133 | return false 134 | else 135 | error("error on pthread_mutex_timedlock:"..ret) 136 | end 137 | end 138 | function abstraction.mutex_get(mutex, timeout) 139 | if timeout then 140 | return pthread_mutex_timedlock(mutex,timeout) --mutex:timedlock(timeout) 141 | else 142 | mutex:lock() 143 | return true 144 | end 145 | end 146 | 147 | function abstraction.mutex_release(mutex) 148 | mutex:unlock() 149 | end 150 | end 151 | 152 | -- ----------------------------------------------------------------------------- 153 | 154 | ffi.cdef([[typedef struct {$ mutex;} mutextype;]],abstraction.mutex_t) 155 | 156 | local Mutex = {} 157 | Mutex.__index = Mutex 158 | --- Creates a mutex 159 | function Mutex:__new() 160 | return ffi.new(self, abstraction.mutex_create()) 161 | end 162 | 163 | --- Trys to lock the mutex. If the mutex is already locked, it blocks 164 | -- for timeout seconds. 165 | -- @param timeout Time to wait for the mutex to become unlocked. nil = wait forever, 166 | -- 0 = do not block 167 | function Mutex:lock(timeout) 168 | if self.mutex == nil then error("Invalid mutex",2) end 169 | return abstraction.mutex_get(self.mutex, timeout) 170 | end 171 | 172 | --- Unlocks the mutex. If the current thread is not the owner, throws an error 173 | function Mutex:unlock() 174 | if self.mutex == nil then error("Invalid mutex",2) end 175 | abstraction.mutex_release(self.mutex) 176 | end 177 | 178 | --- Destroys the mutex. 179 | function Mutex:destroy() 180 | if self.mutex then 181 | abstraction.mutex_destroy(self.mutex) 182 | --self.mutex = nil 183 | end 184 | end 185 | Mutex.__gc = Mutex.destroy 186 | 187 | local mmutex = ffi.metatype("mutextype", Mutex) 188 | 189 | --[[ 190 | local mm = mmutex() 191 | for i=1,1000000 do 192 | mm:lock() 193 | mm:unlock() 194 | end 195 | print"done" 196 | --]] 197 | 198 | return mmutex 199 | -------------------------------------------------------------------------------- /lj-async/thread.lua: -------------------------------------------------------------------------------- 1 | 2 | --- Thread type for LuaJIT 3 | -- Supports both windows threads and pthreads. 4 | -- 5 | -- Each exposed function is defined twice; one for windows threads, one for pthreads. 6 | -- The exposed functions will only be documented in the windows section; the pthreads 7 | -- API is the same. 8 | 9 | local ffi = require "ffi" 10 | local CallbackFactory = require "lj-async.callback" 11 | local C = ffi.C 12 | 13 | local Thread = {} 14 | Thread.__index = Thread 15 | local callback_t 16 | 17 | setmetatable(Thread, {__call=function(self,...) return self.new(...) end}) 18 | 19 | if ffi.os == "Windows" and not WINUSEPTHREAD then 20 | ffi.cdef[[ 21 | //static const int STILL_ACTIVE = 259; 22 | static const int WAIT_ABANDONED_TH = 0x00000080; 23 | static const int WAIT_OBJECT_0_TH = 0x00000000; 24 | static const int WAIT_TIMEOUT_TH = 0x00000102; 25 | //static const int WAIT_FAILED = 0xFFFFFFFF; 26 | static const int INFINITE_TH = 0xFFFFFFFF; 27 | 28 | int CloseHandle(void*); 29 | int GetExitCodeThread(void*,unsigned long*); 30 | unsigned long WaitForSingleObject(void*, unsigned long); 31 | 32 | typedef unsigned long (__stdcall *ThreadProc)(void*); 33 | void* CreateThread( 34 | void* lpThreadAttributes, 35 | size_t dwStackSize, 36 | ThreadProc lpStartAddress, 37 | void* lpParameter, 38 | unsigned long dwCreationFlags, 39 | unsigned long* lpThreadId 40 | ); 41 | int TerminateThread(void*, unsigned long); 42 | 43 | unsigned long GetLastError(); 44 | unsigned long FormatMessageA( 45 | unsigned long dwFlags, 46 | const void* lpSource, 47 | unsigned long dwMessageId, 48 | unsigned long dwLanguageId, 49 | char* lpBuffer, 50 | unsigned long nSize, 51 | va_list *Arguments 52 | ); 53 | ]] 54 | 55 | callback_t = CallbackFactory("unsigned long (__stdcall *)(void*)") 56 | 57 | local function error_win(lvl) 58 | local errcode = C.GetLastError() 59 | local str = ffi.new("char[?]",1024) 60 | local FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 61 | local FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 62 | local numout = C.FormatMessageA(bit.bor(FORMAT_MESSAGE_FROM_SYSTEM, 63 | FORMAT_MESSAGE_IGNORE_INSERTS), nil, errcode, 0, str, 1023, nil) 64 | if numout == 0 then 65 | error("Windows Error: (Error calling FormatMessage)", lvl) 66 | else 67 | error("Windows Error: "..ffi.string(str, numout), lvl) 68 | end 69 | end 70 | 71 | local function error_check(result) 72 | if result == 0 then 73 | error_win(4) 74 | end 75 | end 76 | 77 | --- Creates and startes a new thread. This can also be called as simply Thread(func,ud) 78 | -- func is a function or source/bytecode (see callback.lua for info and limitations) 79 | -- It takes a void* userdata as a parameter and should always return 0. 80 | -- ud is the userdata to pass into the thread. 81 | function Thread.new(func, ud, ...) 82 | local self = setmetatable({}, Thread) 83 | local cb = callback_t(func, ...) 84 | self.cb = cb 85 | self.ud = ud -- anchor 86 | 87 | local t = C.CreateThread(nil, 0, cb:funcptr(), ud, 0, nil) 88 | if t == nil then 89 | error_win(3) 90 | end 91 | self.thread = t 92 | 93 | return self 94 | end 95 | 96 | --- Waits for the thread to terminate, or after the timeout has passed. 97 | -- Returns true if the thread has terminated or false if the timeout was 98 | -- exceeded. 99 | function Thread:join(timeout) 100 | if self.thread == nil then error("invalid thread",3) end 101 | if timeout then 102 | timeout = timeout*1000 103 | else 104 | timeout = C.INFINITE_TH 105 | end 106 | 107 | local r = C.WaitForSingleObject(self.thread, timeout) 108 | if r == C.WAIT_OBJECT_0_TH or r == C.WAIT_ABANDONED_TH then 109 | local result = ffi.new"unsigned long[1]" 110 | local ret = C.GetExitCodeThread(self.thread,result) 111 | if ret==0 then error_win(2) end 112 | return true,result[0] 113 | elseif r == C.WAIT_TIMEOUT_TH then 114 | 115 | return false 116 | else 117 | 118 | error_win(2) 119 | end 120 | end 121 | 122 | --- Destroys a thread and the associated callback. 123 | -- Be sure to join the thread first! 124 | function Thread:free() 125 | if self.thread ~= nil then 126 | error_check(C.CloseHandle(self.thread)) 127 | --self.thread = nil 128 | end 129 | 130 | if self.cb ~= nil then 131 | self.cb:free() 132 | --self.cb = nil 133 | end 134 | end 135 | else 136 | local pthread = require"pthread" 137 | callback_t = CallbackFactory("void *(*)(void *)") 138 | 139 | ffi.cdef[[ 140 | typedef int clockid_t; 141 | int clock_gettime(clockid_t clk_id, timespec *tp); 142 | ]] 143 | ffi.cdef[[ 144 | int pthread_timedjoin_np(pthread_t thread, void **retval, 145 | const struct timespec *abstime); 146 | ]] 147 | 148 | local has_pthread_timedjoin_np = pcall(function() return ffi.C.pthread_timedjoin_np end) 149 | 150 | local function addr(cdata) 151 | return tonumber(ffi.cast('intptr_t', ffi.cast('void*', cdata))) 152 | end 153 | 154 | local function ptr(ctype, p) 155 | return ffi.cast(ctype, ffi.cast('void*', p)) 156 | end 157 | 158 | function Thread.new(func, ud, ...) 159 | local self = setmetatable({}, Thread) 160 | 161 | if not has_pthread_timedjoin_np then 162 | self.mutex = pthread.mutex() 163 | self.cond = pthread.cond() 164 | self.done = ffi.new"bool[1]" 165 | local oldfunc = func 166 | func = function(cond, mut ,done ,...) 167 | local ffi = require"ffi" 168 | local pthread = require"pthread" 169 | cond = ffi.cast("pthread_cond_t*", ffi.cast('void*', cond)) 170 | mut = ffi.cast("pthread_mutex_t*", ffi.cast('void*', mut)) 171 | done = ffi.cast("bool*", ffi.cast('void*', done)) 172 | local inner_f = oldfunc(...) 173 | return function(ud1) 174 | local ret = inner_f(ud1) 175 | mut:lock() 176 | done[0] = true 177 | cond:signal() 178 | mut:unlock() 179 | return ret 180 | end 181 | end 182 | self.cb = callback_t(func, addr(self.cond), addr(self.mutex), addr(self.done), ...) 183 | else 184 | self.cb = callback_t(func, ...) 185 | end 186 | 187 | self.ud = ud --anchor 188 | local t = pthread.new(self.cb:funcptr(),nil,ud) 189 | self.thread = t 190 | return self 191 | end 192 | 193 | local function prepare_timeout(timeout) 194 | local tsl = ffi.new'timespec' 195 | pthread.C.clock_gettime(0,tsl) 196 | local int, frac = math.modf(timeout) 197 | tsl.s = tsl.s + int 198 | tsl.ns = tsl.ns + frac * 1e9 199 | while (tsl.ns >= 1e9) do 200 | tsl.ns = tsl.ns - 1e9; 201 | tsl.s = tsl.s + 1 202 | end 203 | return tsl 204 | end 205 | 206 | function Thread:join(timeout) 207 | if self.thread == nil then error("invalid thread",3) end 208 | if not timeout then 209 | return true,pthread.join(self.thread) 210 | elseif has_pthread_timedjoin_np then 211 | local tsl = prepare_timeout(timeout) 212 | local status = ffi.new'void*[1]' 213 | local ret = ffi.C.pthread_timedjoin_np(self.thread, status,tsl) 214 | if ret == 0 then 215 | return true, status[0] 216 | elseif ret == ETIMEDOUT then 217 | return false 218 | else 219 | error("error on pthread_mutex_timedlock:"..ret) 220 | end 221 | else 222 | local tsl = prepare_timeout(timeout) 223 | self.mutex:lock() 224 | if not self.done[0] then 225 | 226 | local ret = self.cond:wait(self.mutex, tsl.s + tsl.ns*1e-9) 227 | self.mutex:unlock() 228 | if not ret then 229 | return false -- timeout 230 | else 231 | return true,pthread.join(self.thread) 232 | end 233 | else 234 | self.mutex:unlock() 235 | return true,pthread.join(self.thread) 236 | end 237 | end 238 | end 239 | function Thread:free() 240 | --if self.thread ~= nil then 241 | --end 242 | end 243 | end 244 | 245 | return Thread 246 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | LuaJIT-Async 3 | ============ 4 | 5 | *This is currently a work-in-progress.* 6 | 7 | lj-async is a library for creating LuaJIT callbacks capable of being called asynchronously. It does this by creating the callback in a different Lua state. 8 | 9 | Callback Usage 10 | -------------- 11 | 12 | The core component of lj-async is the `callback` class, exposed as `lj-async.callback`. It handles all the work of creating a Lua state for the callback and setting things up. 13 | 14 | To use it, first `require` it by doing `local CallbackFactory = require "lj-async.callback"`. This will return a type factory function, which will create ctypes from your function pointer types. 15 | 16 | To create a callback ctype, call the function with the string ctype: `local MyCallback_t = CallbackFactory("void(*)(int)")`. Note: You MUST pass a string here; ctypes cannot be transferred between states. 17 | 18 | Now that you've created the ctype, you can now create a callback. 19 | 20 | ```lua 21 | function initcallback(...) 22 | -- can init things here 23 | print("init args are", ...) 24 | return function(n) 25 | print(n) 26 | end 27 | end 28 | local MyCallback = MyCallback_t(initcallback,"some string",33) 29 | local MyCallback_funcptr = MyCallback:funcptr() -- Get the actual callback 30 | MyCallback_funcptr(123) -- Prints 123 31 | MyCallback:free() -- Destroy the callback and the Lua state. 32 | ``` 33 | 34 | The passed function must be compatible with `string.dump` (ie. it can't have upvalues). Thus, this will not work: 35 | 36 | ```lua 37 | 38 | local ffi = require "ffi" 39 | 40 | ... 41 | 42 | local MyCallback = MyCallback_t(function() return function(userdata) 43 | userdata = ffi.cast("int[1]", userdata) -- BAD! ffi is an upvalue and not preserved by string.dump! 44 | ... 45 | end end) 46 | ``` 47 | 48 | You will have to re-require needed libraries in the function. 49 | 50 | ```lua 51 | 52 | local ffi = require "ffi" 53 | 54 | ... 55 | 56 | local MyCallback = MyCallback_t(function() 57 | local ffi = require "ffi" -- Import FFI in the new state 58 | return function(userdata) 59 | 60 | userdata = ffi.cast("int[1]", userdata) -- This will now work 61 | ... 62 | end) 63 | ``` 64 | 65 | Some other notes: 66 | * You can pass in LuaJIT source/bytecode instead of a function. If you will be creating many callbacks from the same function, you can use `string.dump` on the function and pass the results to the callback constructor. 67 | * The callback object must be kept alive as long as the callback may be called. 68 | 69 | Threads 70 | ------- 71 | 72 | lj-async also provides threads, built on top of the callback objects. The module is `lj-async.thread`. 73 | 74 | The API is: 75 | 76 | * `Thread.new(func, ud, ...)` or `Thread(func, ud, ...)`: Creates and starts a new thread. `func` is an async-callback compatible function or source/bytecode that takes a userdata pointer and returns 0. `ud` is the userdata to pass to the function and ... can be any init args. 77 | * `thread:join([timeout])`: Joins with a thread. `timeout` is the time, in seconds, to block. `0` means don't block, while `nil` means block forever. Returns `true` if the thread terminated, or `false` if the timeout was exceeded. 78 | * `thread:destroy()`: Destroys the thread and callback. Don't call this until after you join! 79 | 80 | Synchronization 81 | --------------- 82 | 83 | local Mutex = require "lj-async.mutex" 84 | local mut = Mutex() 85 | mut:lock() 86 | mut:unlock() 87 | -------------------------------------------------------------------------------- /tests/basic.lua: -------------------------------------------------------------------------------- 1 | --WINUSEPTHREAD=true 2 | local ffi = require "ffi" 3 | local Thread = require "lj-async.thread" 4 | 5 | local thread_func = function(f,...) 6 | print("init args",...) 7 | local args = {...} 8 | return function(ud) 9 | local ffi = require "ffi" 10 | ud = ffi.cast("struct { int x; }*", ud) 11 | print("ud.x is:",ud.x) 12 | f(unpack(args)) 13 | end 14 | end 15 | 16 | 17 | local thread_data_t = ffi.typeof("struct { int x; }") 18 | 19 | local function testThread(c, f, ...) 20 | local thread = Thread(thread_func, thread_data_t(c),f,...) 21 | local ok, err = thread:join() 22 | if ok then 23 | print("Thread "..c.." ran successfully") 24 | else 25 | print("Thread "..c.." terminated with error: "..tostring(err)) 26 | end 27 | thread:free() 28 | end 29 | 30 | print("Basic hello world thread") 31 | testThread(1, function() 32 | print("\tThread 1 says hi!") 33 | end) 34 | 35 | print("\nThread error test") 36 | testThread(2, function() 37 | error("Thread 2 has errors.") 38 | end) 39 | 40 | print("\nArguments test") 41 | testThread(3, function(...) 42 | print("\tGot values:",...) 43 | end, 2,nil, "c", true) 44 | 45 | print("\nCdata test") 46 | local vec = ffi.new("struct {int x, y, z;}", 100,200,300) 47 | testThread(4, function(v) 48 | local ffi = require "ffi" 49 | v = ffi.cast("struct {int x,y,z;}*", v) 50 | print("",v.x, v.y, v.z) 51 | end, vec) 52 | -------------------------------------------------------------------------------- /tests/callback.lua: -------------------------------------------------------------------------------- 1 | 2 | local callback = require "lj-async.callback" 3 | local ffi = require "ffi" 4 | 5 | local cb_t = callback("int(*)(int)") 6 | 7 | function initcall(...) 8 | --here we can init things 9 | local ffi = require"ffi" 10 | print(...) 11 | --here is the callback 12 | return function(n) 13 | print(n,ffi); 14 | return n 15 | end 16 | end 17 | 18 | print("init callback") 19 | local cb = cb_t(initcall,"init string",222) 20 | print("run callback") 21 | assert(cb:funcptr()(123) == 123) 22 | cb:free() 23 | -------------------------------------------------------------------------------- /tests/keepertest.lua: -------------------------------------------------------------------------------- 1 | --WINUSEPTHREAD=true 2 | local Thread = require "lj-async.thread" 3 | local Kmaker = require"lj-async.keeper" 4 | local ffi = require "ffi" 5 | 6 | local thread_func = function(K) 7 | print("init args",K) 8 | local Kmaker = require"lj-async.keeper" 9 | K = Kmaker.KeeperCast(K) 10 | print(K) 11 | return function(ud) 12 | for i=1,100 do 13 | K:send("clave",i) 14 | end 15 | end 16 | end 17 | 18 | local thread_func2 = function(K) 19 | print("init args2",K) 20 | local Kmaker = require"lj-async.keeper" 21 | K = Kmaker.KeeperCast(K) 22 | print(K) 23 | return function(ud) 24 | while true do 25 | local key,value = K:receive("clave") 26 | print("received",key,value) 27 | if value == 10 then break end 28 | end 29 | end 30 | end 31 | 32 | local K = Kmaker.MakeKeeper() 33 | 34 | local th2 = Thread(thread_func2,nil,K) 35 | local th = Thread(thread_func,nil,K) 36 | 37 | 38 | th:join() 39 | th2:join() 40 | 41 | print"done" -------------------------------------------------------------------------------- /tests/keepertest2.lua: -------------------------------------------------------------------------------- 1 | --WINUSEPTHREAD=true 2 | local Thread = require "lj-async.thread" 3 | local Kmaker = require"lj-async.keeper" 4 | local ffi = require "ffi" 5 | 6 | local thread_func = function(K) 7 | print("init args",K) 8 | local Kmaker = require"lj-async.keeper" 9 | K = Kmaker.KeeperCast(K) 10 | print(K) 11 | return function(ud) 12 | local i=1 13 | while true do 14 | K:send("clave",i) 15 | local key,val = K:receive("clave2") 16 | if key then print("received1",key,val) end 17 | if val == "end" then 18 | print("sending finish"..i) 19 | K:send("clave","finish") 20 | break 21 | end 22 | i = i + 1 23 | end 24 | return 0 25 | end 26 | end 27 | 28 | local thread_func2 = function(K) 29 | print("init args2",K) 30 | local Kmaker = require"lj-async.keeper" 31 | K = Kmaker.KeeperCast(K) 32 | print(K) 33 | return function(ud) 34 | while true do 35 | --print(counter) 36 | local key,value = K:receive("clave") 37 | if key then print("received2",key,value) end 38 | if value == 10 then 39 | K:send("clave2","end") 40 | elseif value == "finish" then 41 | return 0 42 | end 43 | end 44 | end 45 | end 46 | 47 | local K = Kmaker.MakeKeeper() 48 | 49 | local th2 = Thread(thread_func2,nil,K) 50 | local th = Thread(thread_func,nil,K) 51 | 52 | 53 | print(1,th:join()) 54 | print(2,th2:join()) 55 | 56 | print"done" -------------------------------------------------------------------------------- /tests/mutex.lua: -------------------------------------------------------------------------------- 1 | local ffi = require "ffi" 2 | 3 | --WINUSEPTHREAD = true 4 | local Mutex = require "lj-async.mutex" 5 | local Thread = require "lj-async.thread" 6 | local thread_data_t = ffi.typeof("struct { int x; }") 7 | 8 | local function threadMain(m,...) 9 | print("init thread") 10 | return function(threadid) 11 | local ffi = require "ffi" 12 | --WINUSEPTHREAD = true 13 | local Mutex = require "lj-async.mutex" 14 | m = ffi.cast(ffi.typeof("$*",Mutex), m) 15 | threadid = ffi.cast("struct { int x; }*",threadid) 16 | for i=1,20 do 17 | m:lock() 18 | print("Thread ",tostring(threadid.x)," got mutex, i=",i) 19 | m:unlock() 20 | end 21 | end 22 | end 23 | 24 | print("Each thread will try to aquire the mutex 20 times.") 25 | 26 | local mutex = Mutex() 27 | local threads = {} 28 | for i=1,3 do 29 | threads[i] = Thread(threadMain, thread_data_t(i), mutex) 30 | end 31 | 32 | for i=#threads,1,-1 do 33 | local ok, err = threads[i]:join() 34 | if ok then 35 | print("Thread "..i.." ran successfully") 36 | else 37 | print("Thread "..i.." terminated with error: "..tostring(err)) 38 | end 39 | threads[i]:free() 40 | threads[i] = nil 41 | end 42 | 43 | mutex:destroy() 44 | mutex = nil -------------------------------------------------------------------------------- /tests/mutex_timeout.lua: -------------------------------------------------------------------------------- 1 | local ThreadF = function(m) 2 | local ffi = require "ffi" 3 | --WINUSEPTHREAD = true 4 | local Mutex = require "lj-async.mutex" 5 | m = ffi.cast(ffi.typeof("$*",Mutex),m) 6 | return function() 7 | for i=1,5 do 8 | assert(not m:lock(1), "Thread locked the mutex, somehow.") 9 | print("Timed out, i=",i) 10 | end 11 | end 12 | end 13 | --WINUSEPTHREAD = true 14 | local Thread = require "lj-async.thread" 15 | local Mutex = require "lj-async.mutex" 16 | 17 | local m = Mutex() 18 | assert(m:lock(), "Couldn't lock a new mutex") 19 | 20 | print("Thread will try to aquire locked mutex 5 times with 1 second timeout") 21 | local t = Thread(ThreadF,nil, m) 22 | t:join() 23 | t:free() 24 | m:unlock() 25 | m:destroy() -------------------------------------------------------------------------------- /tests/thread.lua: -------------------------------------------------------------------------------- 1 | --WINUSEPTHREAD=true 2 | local Thread = require "lj-async.thread" 3 | local ffi = require "ffi" 4 | 5 | local thread_func = function(...) 6 | print("init args",...) 7 | return function(ud) 8 | local ffi = require "ffi" 9 | ud = ffi.cast("struct { int x; }*", ud) 10 | print(ud.x) 11 | end 12 | end 13 | 14 | local thread_data_t = ffi.typeof("struct { int x; }") 15 | 16 | print("Creating thread 1") 17 | local thread1 = Thread(thread_func, thread_data_t(123),1,"uno") 18 | print("Creating thread 2") 19 | local thread2 = Thread(thread_func, thread_data_t(456),"dos",2) 20 | print("Creating thread 3") 21 | local thread3 = Thread(thread_func, thread_data_t(789),33) 22 | 23 | print("Joining thread 1") 24 | thread1:join() 25 | print("Joining thread 2") 26 | thread2:join() 27 | print("Joining thread 3") 28 | thread3:join() 29 | 30 | print("Freeing thread 1") 31 | thread1:free() 32 | print("Freeing thread 2") 33 | thread2:free() 34 | print("Freeing thread 3") 35 | thread3:free() 36 | -------------------------------------------------------------------------------- /tests/thread_join.lua: -------------------------------------------------------------------------------- 1 | local ThreadF = function() 2 | print"init thread" 3 | return function(ud) 4 | local ffi = require "ffi" 5 | print("inside thread",ud) 6 | if ffi.os == "Windows" then 7 | ffi.cdef[[void Sleep(uint32_t);]] 8 | ffi.C.Sleep(5000) 9 | else 10 | ffi.cdef[[unsigned int sleep(unsigned int);]] 11 | ffi.C.sleep(5) 12 | end 13 | 14 | end 15 | end 16 | 17 | --WINUSEPTHREAD = true 18 | local Thread = require "lj-async.thread" 19 | local Mutex = require "lj-async.mutex" 20 | local ffi = require"ffi" 21 | local thread_data_t = ffi.typeof("struct { int x; }") 22 | 23 | local t = Thread(ThreadF,thread_data_t(1)) 24 | print("Thread will run for 5 seconds. Joining with 1 second timeouts.") 25 | while true do 26 | local ok, err = t:join(1) 27 | if ok then 28 | print(" Joined") 29 | break 30 | elseif not err then 31 | print(" Timed out") 32 | else 33 | print(" Error:") 34 | print(err) 35 | break 36 | end 37 | end 38 | t:free() -------------------------------------------------------------------------------- /tests/thread_returns.lua: -------------------------------------------------------------------------------- 1 | --WINUSEPTHREAD=true 2 | local Thread = require "lj-async.thread" 3 | local ffi = require "ffi" 4 | 5 | local thread_func = function(...) 6 | --WINUSEPTHREAD=true 7 | return function(ud) 8 | local ffi = require "ffi" 9 | ud = ffi.cast("struct { int x; }*", ud) 10 | if ffi.os=="Windows" and not WINUSEPTHREAD then 11 | return ud.x 12 | else 13 | return ud 14 | end 15 | end 16 | end 17 | 18 | local thread_data_t = ffi.typeof("struct { int x; }") 19 | 20 | local datas = {} 21 | local threads = {} 22 | local expected = 0 23 | for i=1,10000 do 24 | datas[i] = thread_data_t(i) 25 | threads[i] = Thread(thread_func, datas[i]) 26 | expected = expected + i 27 | end 28 | print("expected",expected) 29 | local gotten = 0 30 | for i,thread in ipairs(threads) do 31 | local ok, res = thread:join() 32 | assert(ok) 33 | if ffi.os~="Windows" or WINUSEPTHREAD then 34 | res = ffi.cast("struct { int x; }*", res).x 35 | end 36 | assert(i==res,"i="..i.." res="..res) 37 | gotten = gotten + res 38 | end 39 | print("gotten",gotten) --------------------------------------------------------------------------------