├── .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)
--------------------------------------------------------------------------------