diff options
47 files changed, 9195 insertions, 0 deletions
@@ -0,0 +1,17 @@ | |||
1 | |||
2 | Lua Lanes | ||
3 | --------- | ||
4 | |||
5 | Lanes is a lightweight, native, lazy evaluating multithreading library for | ||
6 | Lua 5.1. It allows efficient use of multicore processors in Lua, by passing | ||
7 | function calls into separate OS threads, and separate Lua states. | ||
8 | |||
9 | No locking of the threads is needed, only launching and waiting for (with an | ||
10 | optional timeout) a thread to get ready. Efficient communications between the | ||
11 | running threads are possible either using message passing or shared state | ||
12 | models. Values passed can be anything but coroutines (see detailed limitations | ||
13 | in the manual). | ||
14 | |||
15 | Lua Lanes has been optimized for performance, and provides around 50-60% | ||
16 | speed increase when running heavily threaded applications on dual core | ||
17 | processors (compared to running a non-threaded plain Lua implementation). | ||
@@ -0,0 +1,6 @@ | |||
1 | |||
2 | BUGS: | ||
3 | |||
4 | - tests/irayo_closure.lua fails (trouble with setting globals right | ||
5 | for functions carried over to another Lua state) | ||
6 | |||
@@ -0,0 +1,72 @@ | |||
1 | |||
2 | CHANGES: | ||
3 | |||
4 | CHANGE X: | ||
5 | |||
6 | CHANGE 12 (bug fix on Windows, 2.0.3) AKa 25-Jan-2009: | ||
7 | Did CHANGE 9 the way it should be done. | ||
8 | |||
9 | CHANGE 11 (new feature, 2.0.3) AKa 23-Jan-2009: | ||
10 | Finalizers ('set_finalizer()') for being able to do cleanup of a lane's | ||
11 | resources, whether it returned succesfully or via an error. | ||
12 | |||
13 | CHANGE 10 (new feature, 2.0.3) AKa 23-Jan-2009: | ||
14 | Call stack showing where an error occurred is not merged with the error | ||
15 | message, but delivered as a separate stack table ({ "filename:line" [, ...] }). | ||
16 | Getting call stacks of errorred lanes is now possible. | ||
17 | |||
18 | CHANGE 9 (bug fix on Windows) AKa 10-Dec-2008 (> 2.0.2): | ||
19 | Applied patch from Kriss Daniels to avoid issues on 'now_time()' in Win32 | ||
20 | (http://luaforge.net/forum/forum.php?thread_id=22704&forum_id=1781). | ||
21 | |||
22 | CHANGE 8 (bug fix) AKa 26-Oct-2008: | ||
23 | Avoids occasional segfault at process exit (on multicore CPUs). Does this | ||
24 | by keeping track of "free running" threads (s.a. the time thread) and | ||
25 | cancelling them at process exit. | ||
26 | |||
27 | Tested (2.0.2) on Linux 64,x86, OS X, WinXP. | ||
28 | |||
29 | CHANGE 7 (bug fix) AKa 15-Oct-2008: | ||
30 | Recursive functions that use themselves as direct upvalue can now be | ||
31 | passed to other lanes, and used as a lane function. | ||
32 | |||
33 | CHANGE 6 (bug fix) AKa 15-Oct-2008: | ||
34 | Added local caches of the following to src/lanes.lua (was otherwise getting | ||
35 | errors at least in 'tests/irayo_recursive.lua'). | ||
36 | |||
37 | local assert= assert | ||
38 | local string_gmatch= assert( string.gmatch ) | ||
39 | local select= assert( select ) | ||
40 | local type= assert( type ) | ||
41 | local pairs= assert( pairs ) | ||
42 | local tostring= assert( tostring ) | ||
43 | local error= assert( error ) | ||
44 | local setmetatable= assert( setmetatable ) | ||
45 | local rawget= assert( rawget ) | ||
46 | |||
47 | Thanks to Irayo for detecting and reporting this. | ||
48 | |||
49 | CHANGE 5 (new feature): | ||
50 | Modifying Makefile so it's better suited to LuaRocks. | ||
51 | |||
52 | CHANGE 4 (new feature): | ||
53 | Metatable copying, allowing Lua objects to be copied across lanes. | ||
54 | |||
55 | CHANGE 3 (bug fix) AKa 5-Aug-2008: | ||
56 | The '__gc' method was not tied to thread userdata, at all. Caused memory | ||
57 | lifespan problems at least on OS X when threads were cancelled (EINVAL). | ||
58 | |||
59 | CHANGE 2 (bug fix) AKa 5-Aug-2008: | ||
60 | Better calculation of timeouts, always making them absolute (even in Win32) | ||
61 | to allow for events that wake the lane up but don't read/write the Linda | ||
62 | key that it was observing. | ||
63 | |||
64 | CHANGE 1 (bug fix) AKa 4-Aug-2008: | ||
65 | Signalling woke up only one waiting thread, not all. This caused i.e. | ||
66 | receive to not wake up if there was another thread waiting on the same | ||
67 | Linda object. | ||
68 | |||
69 | PThread fix: using 'pthread_cond_broadcast()' instead of 'pthread_cond_signal()' | ||
70 | Win32 fix: using manual events and 'PulseEvent()' | ||
71 | |||
72 | (end) | ||
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1799c01 --- /dev/null +++ b/CMakeLists.txt | |||
@@ -0,0 +1,86 @@ | |||
1 | # Copyright (C) 2007-2009 LuaDist. | ||
2 | # Created by Peter Kapec | ||
3 | # Redistribution and use of this file is allowed according to the terms of the MIT license. | ||
4 | # For details see the COPYRIGHT file distributed with LuaDist. | ||
5 | # Please note that the package source code is licensed under its own license. | ||
6 | |||
7 | PROJECT(lanes C) | ||
8 | CMAKE_MINIMUM_REQUIRED(VERSION 2.8) | ||
9 | INCLUDE(dist.cmake) | ||
10 | |||
11 | #2DO - patch threading.c to suppot cygwin. | ||
12 | # The following values are just a guess. | ||
13 | # WARNING: test segfault under Cygwin | ||
14 | IF(CYGWIN) | ||
15 | ADD_DEFINITIONS(-D_PRIO_MODE=SCHED_FIFO) | ||
16 | ADD_DEFINITIONS(-D_PRIO_HI=15) # maximum that doesn't crash | ||
17 | ADD_DEFINITIONS(-D_PRIO_0=0) | ||
18 | ADD_DEFINITIONS(-D_PRIO_LO=-15) # ??? | ||
19 | ADD_DEFINITIONS(-Dpthread_yield=sched_yield) | ||
20 | ENDIF(CYGWIN) | ||
21 | |||
22 | #2DO - use provided bin2c | ||
23 | # Compile Lua bytecode to C | ||
24 | ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/keeper.lch | ||
25 | DEPENDS src/keeper.lua | ||
26 | COMMAND "${LUAC}" "-o" "${CMAKE_CURRENT_BINARY_DIR}/keeper.lo" | ||
27 | "${CMAKE_CURRENT_SOURCE_DIR}/src/keeper.lua" | ||
28 | COMMAND "${LUA}" "${CMAKE_CURRENT_SOURCE_DIR}/tools/bin2c.lua" | ||
29 | "${CMAKE_CURRENT_BINARY_DIR}/keeper.lo" | ||
30 | "-o" "${CMAKE_CURRENT_BINARY_DIR}/keeper.lch") | ||
31 | SET_SOURCE_FILES_PROPERTIES(src/lanes.c PROPERTIES OBJECT_DEPENDS | ||
32 | ${CMAKE_CURRENT_BINARY_DIR}/keeper.lch) | ||
33 | INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) | ||
34 | |||
35 | |||
36 | # Build | ||
37 | INCLUDE_DIRECTORIES(src) | ||
38 | ADD_LIBRARY(lua51-lanes MODULE src/lanes.c src/threading.c src/tools.c) | ||
39 | |||
40 | IF(UNIX AND NOT CYGWIN) | ||
41 | SET(LIBS pthread) | ||
42 | ENDIF(UNIX AND NOT CYGWIN) | ||
43 | |||
44 | IF(MINGW) | ||
45 | #~ FIND_FILE(MSVCR80 NAMES msvcr80.dll msvcr90.dll) | ||
46 | #~ SET(LIBS gcc ${MSVCR80}) | ||
47 | #~ IF(MSVC90) | ||
48 | |||
49 | #~ from InstallRequiredSystemLibraries.cmake | ||
50 | |||
51 | IF(CMAKE_CL_64) | ||
52 | SET(CMAKE_MSVC_ARCH amd64) | ||
53 | ELSE(CMAKE_CL_64) | ||
54 | SET(CMAKE_MSVC_ARCH x86) | ||
55 | ENDIF(CMAKE_CL_64) | ||
56 | |||
57 | GET_FILENAME_COMPONENT(devenv_dir "${CMAKE_MAKE_PROGRAM}" PATH) | ||
58 | GET_FILENAME_COMPONENT(base_dir "${devenv_dir}/../.." ABSOLUTE) | ||
59 | |||
60 | # Find the runtime library redistribution directory. | ||
61 | FIND_PATH(MSVC90_REDIST_DIR NAMES ${CMAKE_MSVC_ARCH}/Microsoft.VC90.CRT/Microsoft.VC90.CRT.manifest | ||
62 | PATHS | ||
63 | "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\9.0;InstallDir]/../../VC/redist" | ||
64 | "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VCExpress\\9.0;InstallDir]/../../VC/redist" | ||
65 | "${base_dir}/VC/redist" | ||
66 | ) | ||
67 | SET(MSVC90_CRT_DIR "${MSVC90_REDIST_DIR}/${CMAKE_MSVC_ARCH}/Microsoft.VC90.CRT") | ||
68 | SET(LIBS gcc msvcr90 "${MSVC90_CRT_DIR}/msvcr90.dll") | ||
69 | |||
70 | #~ ENDIF() | ||
71 | |||
72 | ENDIF() | ||
73 | |||
74 | |||
75 | |||
76 | |||
77 | TARGET_LINK_LIBRARIES(lua51-lanes ${LUA_LIBRARY} ${LIBS}) | ||
78 | SET_TARGET_PROPERTIES(lua51-lanes PROPERTIES PREFIX "") | ||
79 | |||
80 | # Install all files and documentation | ||
81 | INSTALL (TARGETS lua51-lanes DESTINATION ${INSTALL_CMOD}) | ||
82 | INSTALL (FILES src/lanes.lua DESTINATION ${INSTALL_LMOD}) | ||
83 | |||
84 | INSTALL (FILES ABOUT BUGS COPYRIGHT CHANGES README TODO DESTINATION ${INSTALL_DATA}) | ||
85 | INSTALL (DIRECTORY docs/ DESTINATION ${INSTALL_DOC}) | ||
86 | INSTALL (DIRECTORY tests/ DESTINATION ${INSTALL_TEST}) | ||
diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..2930f19 --- /dev/null +++ b/COPYRIGHT | |||
@@ -0,0 +1,31 @@ | |||
1 | |||
2 | Lua Lanes is licensed under the same MIT license as the Lua 5.1 source code, | ||
3 | reproduced below. | ||
4 | |||
5 | For details and rationale, see http://www.lua.org/license.html | ||
6 | |||
7 | =============================================================================== | ||
8 | |||
9 | Copyright (C) 2007-09 Asko Kauppi, <akauppi@gmail.com> | ||
10 | |||
11 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
12 | of this software and associated documentation files (the "Software"), to deal | ||
13 | in the Software without restriction, including without limitation the rights | ||
14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
15 | copies of the Software, and to permit persons to whom the Software is | ||
16 | furnished to do so, subject to the following conditions: | ||
17 | |||
18 | The above copyright notice and this permission notice shall be included in | ||
19 | all copies or substantial portions of the Software. | ||
20 | |||
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
27 | THE SOFTWARE. | ||
28 | |||
29 | =============================================================================== | ||
30 | |||
31 | (end of COPYRIGHT) | ||
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4c0ff4b --- /dev/null +++ b/Makefile | |||
@@ -0,0 +1,229 @@ | |||
1 | # | ||
2 | # Lanes/Makefile | ||
3 | # | ||
4 | # make | ||
5 | # make test | ||
6 | # make basic|fifo|keeper|... | ||
7 | # | ||
8 | # make perftest[-odd|-even|-plain] | ||
9 | # make launchtest | ||
10 | # | ||
11 | # make install DESTDIR=path | ||
12 | # make tar|tgz VERSION=x.x | ||
13 | # make clean | ||
14 | # | ||
15 | |||
16 | MODULE = lanes | ||
17 | |||
18 | N=1000 | ||
19 | |||
20 | _SO=.so | ||
21 | _TARGET_SO=src/lua51-lanes.so | ||
22 | TIME=time | ||
23 | |||
24 | ifeq "$(findstring MINGW32,$(shell uname -s))" "MINGW32" | ||
25 | # MinGW MSYS on XP | ||
26 | # | ||
27 | LUA=lua | ||
28 | LUAC=luac | ||
29 | _SO=.dll | ||
30 | _TARGET_SO=./lua51-lanes.dll | ||
31 | TIME=timeit.exe | ||
32 | else | ||
33 | # Autodetect LUA & LUAC | ||
34 | # | ||
35 | LUA=$(word 1,$(shell which lua5.1) $(shell which lua51) lua) | ||
36 | LUAC=$(word 1,$(shell which luac5.1) $(shell which luac51) luac) | ||
37 | endif | ||
38 | |||
39 | _PREFIX=LUA_CPATH=./src/?$(_SO) LUA_PATH="src/?.lua;./tests/?.lua" | ||
40 | |||
41 | #--- | ||
42 | all: $(_TARGET_SO) | ||
43 | |||
44 | $(_TARGET_SO): src/*.lua src/*.c src/*.h | ||
45 | cd src && $(MAKE) LUA=$(LUA) LUAC=$(LUAC) | ||
46 | |||
47 | clean: | ||
48 | cd src && $(MAKE) clean | ||
49 | |||
50 | debug: | ||
51 | $(MAKE) clean | ||
52 | cd src && $(MAKE) LUA=$(LUA) LUAC=$(LUAC) OPT_FLAGS="-O0 -g" | ||
53 | @echo "" | ||
54 | @echo "** Now, try 'make repetitive' or something and if it crashes, 'gdb $(LUA) ...core file...'" | ||
55 | @echo " Then 'bt' for a backtrace." | ||
56 | @echo "" | ||
57 | @echo " You have enabled core, no? 'ulimit -c unlimited'" | ||
58 | @echo " On OS X, core files are under '/cores/'" | ||
59 | @echo "" | ||
60 | |||
61 | gdb: | ||
62 | @echo "echo *** To start debugging: 'run tests/basic.lua' ***\n\n" > .gdb.cmd | ||
63 | $(_PREFIX) gdb -x .gdb.cmd $(LUA) | ||
64 | |||
65 | #--- LuaRocks automated build --- | ||
66 | # | ||
67 | rock: | ||
68 | cd src && $(MAKE) LUAROCKS=1 CFLAGS="$(CFLAGS)" LIBFLAG="$(LIBFLAG)" LUA=$(LUA) LUAC=$(LUAC) | ||
69 | |||
70 | |||
71 | #--- Testing --- | ||
72 | # | ||
73 | test: | ||
74 | $(MAKE) irayo_recursive | ||
75 | # $(MAKE) irayo_closure | ||
76 | $(MAKE) basic | ||
77 | $(MAKE) fifo | ||
78 | $(MAKE) keeper | ||
79 | $(MAKE) timer | ||
80 | $(MAKE) atomic | ||
81 | $(MAKE) cyclic | ||
82 | $(MAKE) objects | ||
83 | $(MAKE) fibonacci | ||
84 | $(MAKE) recursive | ||
85 | |||
86 | basic: tests/basic.lua $(_TARGET_SO) | ||
87 | $(_PREFIX) $(LUA) $< | ||
88 | |||
89 | # | ||
90 | # This tries to show out a bug which happens in lane cleanup (multicore CPU's only) | ||
91 | # | ||
92 | REP_ARGS=-llanes -e "print'say aaa'; for i=1,10 do print(i) end" | ||
93 | repetitive: $(_TARGET_SO) | ||
94 | for i in 1 2 3 4 5 6 7 8 9 10 a b c d e f g h i j k l m n o p q r s t u v w x y z; \ | ||
95 | do $(_PREFIX) $(LUA) $(REP_ARGS); \ | ||
96 | done | ||
97 | |||
98 | repetitive1: $(_TARGET_SO) | ||
99 | $(_PREFIX) $(LUA) $(REP_ARGS) | ||
100 | |||
101 | fifo: tests/fifo.lua $(_TARGET_SO) | ||
102 | $(_PREFIX) $(LUA) $< | ||
103 | |||
104 | keeper: tests/keeper.lua $(_TARGET_SO) | ||
105 | $(_PREFIX) $(LUA) $< | ||
106 | |||
107 | fibonacci: tests/fibonacci.lua $(_TARGET_SO) | ||
108 | $(_PREFIX) $(LUA) $< | ||
109 | |||
110 | timer: tests/timer.lua $(_TARGET_SO) | ||
111 | $(_PREFIX) $(LUA) $< | ||
112 | |||
113 | atomic: tests/atomic.lua $(_TARGET_SO) | ||
114 | $(_PREFIX) $(LUA) $< | ||
115 | |||
116 | cyclic: tests/cyclic.lua $(_TARGET_SO) | ||
117 | $(_PREFIX) $(LUA) $< | ||
118 | |||
119 | recursive: tests/recursive.lua $(_TARGET_SO) | ||
120 | $(_PREFIX) $(LUA) $< | ||
121 | |||
122 | hangtest: tests/hangtest.lua $(_TARGET_SO) | ||
123 | $(_PREFIX) $(LUA) $< | ||
124 | |||
125 | ehynes: tests/ehynes.lua $(_TARGET_SO) | ||
126 | $(_PREFIX) $(LUA) $< | ||
127 | |||
128 | #require: tests/require.lua $(_TARGET_SO) | ||
129 | # $(_PREFIX) $(LUA) $< | ||
130 | |||
131 | objects: tests/objects.lua $(_TARGET_SO) | ||
132 | $(_PREFIX) $(LUA) $< | ||
133 | |||
134 | irayo_recursive: tests/irayo_recursive.lua $(_TARGET_SO) | ||
135 | $(_PREFIX) $(LUA) $< | ||
136 | |||
137 | irayo_closure: tests/irayo_closure.lua $(_TARGET_SO) | ||
138 | $(_PREFIX) $(LUA) $< | ||
139 | |||
140 | finalizer: tests/finalizer.lua $(_TARGET_SO) | ||
141 | $(_PREFIX) $(LUA) $< | ||
142 | |||
143 | error-test: tests/error.lua $(_TARGET_SO) | ||
144 | $(_PREFIX) $(LUA) $< | ||
145 | |||
146 | #--- | ||
147 | perftest-plain: tests/perftest.lua $(_TARGET_SO) | ||
148 | $(MAKE) _perftest ARGS="$< $(N) -plain" | ||
149 | |||
150 | perftest: tests/perftest.lua $(_TARGET_SO) | ||
151 | $(MAKE) _perftest ARGS="$< $(N)" | ||
152 | |||
153 | perftest-odd: tests/perftest.lua $(_TARGET_SO) | ||
154 | $(MAKE) _perftest ARGS="$< $(N) -prio=+2" | ||
155 | |||
156 | perftest-even: tests/perftest.lua $(_TARGET_SO) | ||
157 | $(MAKE) _perftest ARGS="$< $(N) -prio=-2" | ||
158 | |||
159 | #--- | ||
160 | launchtest: tests/launchtest.lua $(_TARGET_SO) | ||
161 | $(MAKE) _perftest ARGS="$< $(N)" | ||
162 | |||
163 | _perftest: | ||
164 | $(_PREFIX) $(TIME) $(LUA) $(ARGS) | ||
165 | |||
166 | |||
167 | #--- Installing --- | ||
168 | # | ||
169 | # This is for LuaRocks automatic install, mainly | ||
170 | # | ||
171 | # LUA_LIBDIR and LUA_SHAREDIR are used by the .rockspec (don't change the names!) | ||
172 | # | ||
173 | DESTDIR=/usr/local | ||
174 | LUA_LIBDIR=$(DESTDIR)/lib/lua/5.1 | ||
175 | LUA_SHAREDIR=$(DESTDIR)/share/lua/5.1 | ||
176 | |||
177 | # | ||
178 | # AKa 17-Oct: changed to use 'install -m 644' and 'cp -p' | ||
179 | # | ||
180 | install: $(_TARGET_SO) src/lanes.lua | ||
181 | mkdir -p $(LUA_LIBDIR) $(LUA_SHAREDIR) | ||
182 | install -m 644 $(_TARGET_SO) $(LUA_LIBDIR) | ||
183 | cp -p src/lanes.lua $(LUA_SHAREDIR) | ||
184 | |||
185 | |||
186 | #--- Packaging --- | ||
187 | # | ||
188 | # Make a folder of the same name as tgz, good manners (for the manual | ||
189 | # expander) | ||
190 | # | ||
191 | # "make tgz VERSION=yyyymmdd" | ||
192 | # | ||
193 | VERSION= | ||
194 | |||
195 | tar tgz: | ||
196 | ifeq "$(VERSION)" "" | ||
197 | echo "Usage: make tar VERSION=x.x"; false | ||
198 | else | ||
199 | $(MAKE) clean | ||
200 | -rm -rf $(MODULE)-$(VERSION) | ||
201 | mkdir $(MODULE)-$(VERSION) | ||
202 | tar c * --exclude=.svn --exclude=.DS_Store --exclude="_*" \ | ||
203 | --exclude="*.tgz" --exclude="*.rockspec" \ | ||
204 | --exclude=lanes.dev --exclude="$(MODULE)-*" --exclude=xcode \ | ||
205 | --exclude="*.obj" --exclude="*.dll" --exclude=timeit.dat \ | ||
206 | | (cd $(MODULE)-$(VERSION) && tar x) | ||
207 | tar czvf $(MODULE)-$(VERSION).tgz $(MODULE)-$(VERSION) | ||
208 | rm -rf $(MODULE)-$(VERSION) | ||
209 | md5sum $(MODULE)-$(VERSION).tgz | ||
210 | endif | ||
211 | |||
212 | |||
213 | #--- Undocumented --- | ||
214 | # | ||
215 | |||
216 | # 2.0.1: Running this (instant exit of the main Lua state) occasionally | ||
217 | # segfaults (1:15 or so on OS X PowerPC G4). | ||
218 | # | ||
219 | require: $(_TARGET_SO) | ||
220 | $(_PREFIX) $(LUA) -e "require '$(MODULE)'" | ||
221 | |||
222 | run: $(_TARGET_SO) | ||
223 | $(_PREFIX) $(LUA) -e "require '$(MODULE)'" -i | ||
224 | |||
225 | echo: | ||
226 | @echo $(PROGRAMFILES:C=X) | ||
227 | |||
228 | .PROXY: all clean test require debug _nodemo _notest | ||
229 | |||
@@ -0,0 +1,106 @@ | |||
1 | |||
2 | ===================== | ||
3 | Usage on Windows: | ||
4 | ===================== | ||
5 | |||
6 | For once, Win32 thread prioritazion scheme is actually a good one, and | ||
7 | it works. :) Windows users, feel yourself as VIP citizens!! | ||
8 | |||
9 | ------------------- | ||
10 | Windows / MSYS: | ||
11 | ------------------- | ||
12 | |||
13 | On MSYS, 'stderr' output seems to be buffered. You might want to make | ||
14 | it auto-flush, to help you track & debug your scripts. Like this: | ||
15 | |||
16 | io.stderr:setvbuf "no" | ||
17 | |||
18 | Even after this, MSYS insists on linewise buffering; it will flush at | ||
19 | each newline only. | ||
20 | |||
21 | |||
22 | =================== | ||
23 | Usage on Linux: | ||
24 | =================== | ||
25 | |||
26 | Linux NTPL 2.5 (Ubuntu 7.04) was used in the testing of Lua Lanes. | ||
27 | |||
28 | This document (http://www.net.in.tum.de/~gregor/docs/pthread-scheduling.html) | ||
29 | describes fairly well, what (all) is wrong with Linux threading, even today. | ||
30 | |||
31 | For other worthy links: | ||
32 | http://kerneltrap.org/node/6080 | ||
33 | http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library | ||
34 | |||
35 | In short, you cannot use thread prioritation in Linux. Unless you run as | ||
36 | root, and I _truly_ would not recommend that. Lobby for yet-another thread | ||
37 | implementation for Linux, and mail -> akauppi@gmail.com about it. :) | ||
38 | |||
39 | |||
40 | ====================== | ||
41 | Usage on Mac OS X: | ||
42 | ====================== | ||
43 | |||
44 | No real problems in OS X, _once_ everything is set up right... | ||
45 | |||
46 | In short, have your Lua core compiled with LUA_USE_DLOPEN and LUA_USE_POSIX | ||
47 | instead of the (default as of 5.1) LUA_DL_DYLD and LUA_USE_MACOSX. This is | ||
48 | crucial to have each module loaded only once (even if initialized separately | ||
49 | for each thread) and for the static & global variables within the modules to | ||
50 | actually be process-wide. Lua Lanes cannot live without (and hopefully, | ||
51 | LUA_DL_DYLD is long gone by Lua 5.2)... | ||
52 | |||
53 | Another issue is making sure you only have _one_ Lua core. Your 'lua' binary | ||
54 | must link dynamically to a .dylib, it must _not_ carry a personal copy of Lua | ||
55 | core with itself. If it does, you will gain many mysterious malloc errors | ||
56 | when entering multithreading realm. | ||
57 | |||
58 | << | ||
59 | lua-xcode2(9473,0xa000ed88) malloc: *** Deallocation of a pointer not malloced: 0xe9fc0; This could be a double free(), or free() called with the middle of an allocated block; Try setting environment variable MallocHelp to see tools to help debug | ||
60 | << | ||
61 | |||
62 | rm lua.o luac.o | ||
63 | gcc -dynamiclib -install_name /usr/local/lib/liblua.5.1.dylib \ | ||
64 | -compatibility_version 5.1 -current_version 5.1.2 \ | ||
65 | -o liblua.5.1.2.dylib *.o | ||
66 | |||
67 | gcc -fno-common -DLUA_USE_POSIX -DLUA_USE_DLOPEN -DLUA_USE_READLINE -lreadline -L. -llua.5.1.2 lua.c -o lua | ||
68 | |||
69 | That should be it. :) | ||
70 | |||
71 | Fink 'lua51' packaging has the necessary changes since 5.1.2-3. | ||
72 | |||
73 | |||
74 | ===================== | ||
75 | Usage on FreeBSD: | ||
76 | ===================== | ||
77 | |||
78 | Unlike in Linux, also the Lua engine used with Lanes needs to be compiled with | ||
79 | '-lpthread'. Otherwise, the following malloc errors are received: | ||
80 | |||
81 | << | ||
82 | lua in free(): warning: recursive call | ||
83 | PANIC: unprotected error in call to Lua API (not enough memory) | ||
84 | << | ||
85 | |||
86 | Here are the Lua compilation steps that proved to work (FreeBSD 6.2 i386): | ||
87 | |||
88 | gmake freebsd | ||
89 | rm lua.o luac.o liblua.a | ||
90 | gcc -shared -lm -Wl,-E -o liblua.5.1.2.so *.o | ||
91 | gcc -O2 -Wall -DLUA_USE_LINUX -lm -Wl,-E -lpthread -lreadline -L. -llua.5.1.2 lua.c -o lua | ||
92 | |||
93 | To compile Lanes, use 'gmake' or simply: | ||
94 | |||
95 | cc -O2 -Wall -llua.5.1.2 -lpthread -shared -o out/bsd-x86/lua51-lanes.so \ | ||
96 | -DGLUA_LUA51 gluax.c lanes.c | ||
97 | |||
98 | To place Lua into ~/local, set the following environment variables (this is | ||
99 | just a reminder for myself...): | ||
100 | |||
101 | export CPATH=.../local/include | ||
102 | export LIBRARY_PATH=.../local/lib | ||
103 | export LD_LIBRARY_PATH=.../local/lib | ||
104 | |||
105 | |||
106 | (end) | ||
@@ -0,0 +1,4 @@ | |||
1 | |||
2 | TODO: | ||
3 | |||
4 | - Testing Lane killing (not cancellation, but actual killing) | ||
diff --git a/dist.cmake b/dist.cmake new file mode 100644 index 0000000..95928b2 --- /dev/null +++ b/dist.cmake | |||
@@ -0,0 +1,130 @@ | |||
1 | # LuaDist CMake utility library. | ||
2 | # Provides variables and utility functions common to LuaDist CMake builds. | ||
3 | # | ||
4 | # Copyright (C) 2007-2010 LuaDist. | ||
5 | # by David Manura, Peter Drahos | ||
6 | # Redistribution and use of this file is allowed according to the terms of the MIT license. | ||
7 | # For details see the COPYRIGHT file distributed with LuaDist. | ||
8 | # Please note that the package source code is licensed under its own license. | ||
9 | |||
10 | # Few convinence settings | ||
11 | SET (CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true) | ||
12 | SET (CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_MODULE_PATH}) | ||
13 | |||
14 | # Where to install module parts: | ||
15 | set(INSTALL_BIN bin CACHE PATH "Where to install binaries to.") | ||
16 | set(INSTALL_LIB lib CACHE PATH "Where to install libraries to.") | ||
17 | set(INSTALL_INC include CACHE PATH "Where to install headers to.") | ||
18 | set(INSTALL_ETC etc CACHE PATH "Where to store configuration files") | ||
19 | set(INSTALL_LMOD share/lua/lmod CACHE PATH "Directory to install Lua modules.") | ||
20 | set(INSTALL_CMOD share/lua/cmod CACHE PATH "Directory to install Lua binary modules.") | ||
21 | set(INSTALL_DATA share/${PROJECT_NAME} CACHE PATH "Directory the package can store documentation, tests or other data in.") | ||
22 | set(INSTALL_DOC ${INSTALL_DATA}/doc CACHE PATH "Recommended directory to install documentation into.") | ||
23 | set(INSTALL_EXAMPLE ${INSTALL_DATA}/example CACHE PATH "Recommended directory to install examples into.") | ||
24 | set(INSTALL_TEST ${INSTALL_DATA}/test CACHE PATH "Recommended directory to install tests into.") | ||
25 | set(INSTALL_FOO ${INSTALL_DATA}/etc CACHE PATH "Where to install additional files") | ||
26 | |||
27 | |||
28 | # In MSVC, prevent warnings that can occur when using standard libraries. | ||
29 | if(MSVC) | ||
30 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) | ||
31 | endif(MSVC) | ||
32 | |||
33 | # Adds Lua shared library module target `_target`. | ||
34 | # Additional sources to build the module are listed after `_target`. | ||
35 | macro(add_lua_module _target) | ||
36 | find_package(Lua51 REQUIRED) | ||
37 | include_directories(${LUA_INCLUDE_DIR}) #2DO: somehow apply only to _target? | ||
38 | |||
39 | add_library(${_target} MODULE ${ARGN}) | ||
40 | set_target_properties(${_target} PROPERTIES PREFIX "") | ||
41 | target_link_libraries(${_target} ${LUA_LIBRARY}) | ||
42 | |||
43 | IF(WIN32) | ||
44 | set_target_properties(${_target} PROPERTIES LINK_FLAGS "-Wl,--enable-auto-import") | ||
45 | ENDIF() | ||
46 | |||
47 | endmacro(add_lua_module) | ||
48 | |||
49 | # Runs Lua script `_testfile` under CTest tester. | ||
50 | # Optional argument `_testcurrentdir` is current working directory to run test under | ||
51 | # (defaults to ${CMAKE_CURRENT_BINARY_DIR}). | ||
52 | # Both paths, if relative, are relative to ${CMAKE_CURRENT_SOURCE_DIR}. | ||
53 | # Under LuaDist, set test=true in config.lua to enable testing. | ||
54 | macro(add_lua_test _testfile) | ||
55 | include(CTest) | ||
56 | if(BUILD_TESTING) | ||
57 | find_program(LUA NAMES lua lua.bat) | ||
58 | get_filename_component(TESTFILEABS ${_testfile} ABSOLUTE) | ||
59 | get_filename_component(TESTFILENAME ${_testfile} NAME) | ||
60 | get_filename_component(TESTFILEBASE ${_testfile} NAME_WE) | ||
61 | |||
62 | # Write wrapper script. | ||
63 | set(TESTWRAPPER ${CMAKE_CURRENT_BINARY_DIR}/${TESTFILENAME}) | ||
64 | set(TESTWRAPPERSOURCE | ||
65 | "package.path = '${CMAKE_CURRENT_BINARY_DIR}/?.lua\;${CMAKE_CURRENT_SOURCE_DIR}/?.lua\;' .. package.path | ||
66 | package.cpath = '${CMAKE_CURRENT_BINARY_DIR}/?.so\;${CMAKE_CURRENT_BINARY_DIR}/?.dll\;' .. package.cpath | ||
67 | return dofile '${TESTFILEABS}' | ||
68 | " ) | ||
69 | if(${ARGC} GREATER 1) | ||
70 | set(_testcurrentdir ${ARGV1}) | ||
71 | get_filename_component(TESTCURRENTDIRABS ${_testcurrentdir} ABSOLUTE) | ||
72 | set(TESTWRAPPERSOURCE | ||
73 | "require 'lfs' | ||
74 | lfs.chdir('${TESTCURRENTDIRABS}') | ||
75 | ${TESTWRAPPERSOURCE}") | ||
76 | endif() | ||
77 | FILE(WRITE ${TESTWRAPPER} ${TESTWRAPPERSOURCE}) | ||
78 | |||
79 | add_test(${TESTFILEBASE} ${LUA} ${TESTWRAPPER}) | ||
80 | endif(BUILD_TESTING) | ||
81 | |||
82 | # see also http://gdcm.svn.sourceforge.net/viewvc/gdcm/Sandbox/CMakeModules/UsePythonTest.cmake | ||
83 | endmacro(add_lua_test) | ||
84 | |||
85 | # Converts Lua source file `_source` to binary string embedded in C source | ||
86 | # file `_target`. Optionally compiles Lua source to byte code (not available | ||
87 | # under LuaJIT2, which doesn't have a bytecode loader). Additionally, Lua | ||
88 | # versions of bin2c [1] and luac [2] may be passed respectively as additional | ||
89 | # arguments. | ||
90 | # | ||
91 | # [1] http://lua-users.org/wiki/BinToCee | ||
92 | # [2] http://lua-users.org/wiki/LuaCompilerInLua | ||
93 | function(add_lua_bin2c _target _source) | ||
94 | find_program(LUA NAMES lua lua.bat) | ||
95 | execute_process(COMMAND ${LUA} -e "string.dump(function()end)" RESULT_VARIABLE _LUA_DUMP_RESULT ERROR_QUIET) | ||
96 | if (NOT ${_LUA_DUMP_RESULT}) | ||
97 | SET(HAVE_LUA_DUMP true) | ||
98 | endif() | ||
99 | message("-- string.dump=${HAVE_LUA_DUMP}") | ||
100 | |||
101 | if (ARGV2) | ||
102 | get_filename_component(BIN2C ${ARGV2} ABSOLUTE) | ||
103 | set(BIN2C ${LUA} ${BIN2C}) | ||
104 | else() | ||
105 | find_program(BIN2C NAMES bin2c bin2c.bat) | ||
106 | endif() | ||
107 | if (HAVE_LUA_DUMP) | ||
108 | if (ARGV3) | ||
109 | get_filename_component(LUAC ${ARGV3} ABSOLUTE) | ||
110 | set(LUAC ${LUA} ${LUAC}) | ||
111 | else() | ||
112 | find_program(LUAC NAMES luac luac.bat) | ||
113 | endif() | ||
114 | endif (HAVE_LUA_DUMP) | ||
115 | message("-- bin2c=${BIN2C}") | ||
116 | message("-- luac=${LUAC}") | ||
117 | |||
118 | get_filename_component(SOURCEABS ${_source} ABSOLUTE) | ||
119 | if (HAVE_LUA_DUMP) | ||
120 | get_filename_component(SOURCEBASE ${_source} NAME_WE) | ||
121 | add_custom_command( | ||
122 | OUTPUT ${_target} DEPENDS ${_source} | ||
123 | COMMAND ${LUAC} -o ${CMAKE_CURRENT_BINARY_DIR}/${SOURCEBASE}.lo ${SOURCEABS} | ||
124 | COMMAND ${BIN2C} ${CMAKE_CURRENT_BINARY_DIR}/${SOURCEBASE}.lo ">${_target}" ) | ||
125 | else() | ||
126 | add_custom_command( | ||
127 | OUTPUT ${_target} DEPENDS ${SOURCEABS} | ||
128 | COMMAND ${BIN2C} ${_source} ">${_target}" ) | ||
129 | endif() | ||
130 | endfunction(add_lua_bin2c) | ||
diff --git a/dist.info b/dist.info new file mode 100644 index 0000000..a6553b0 --- /dev/null +++ b/dist.info | |||
@@ -0,0 +1,14 @@ | |||
1 | --- This file is part of LuaDist project | ||
2 | |||
3 | name = "lanes" | ||
4 | version = "2.0.3" | ||
5 | |||
6 | desc = "Lanes is a lightweight, native, lazy evaluating multithreading library for Lua 5.1." | ||
7 | author = "Asko Kauppi" | ||
8 | license = "MIT" | ||
9 | url = "http://luaforge.net/projects/lanes" | ||
10 | maintainer = "Peter Kapec" | ||
11 | |||
12 | depends = { | ||
13 | "lua ~> 5.1" | ||
14 | } | ||
diff --git a/docs/Lua multithreading choices.graffle b/docs/Lua multithreading choices.graffle new file mode 100644 index 0000000..2bd4cb4 --- /dev/null +++ b/docs/Lua multithreading choices.graffle | |||
Binary files differ | |||
diff --git a/docs/Lua multithreading choices.svg b/docs/Lua multithreading choices.svg new file mode 100644 index 0000000..8a09698 --- /dev/null +++ b/docs/Lua multithreading choices.svg | |||
@@ -0,0 +1,15 @@ | |||
1 | <?xml version="1.0"?> | ||
2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | ||
3 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 813.60004 566.95" width="813.60004pt" height="566.95pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2008-07-25 19:33Z</dc:date><!-- Produced by OmniGraffle Professional 4.2.1 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><font-face font-family="Helvetica" font-size="18" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="555.55554" cap-height="722.22223" ascent="770.01953" descent="-229.98047" font-weight="500"><!--NSCTFontDescriptor <0x7988130> = { | ||
4 | NSFontNameAttribute = Helvetica; | ||
5 | NSFontSizeAttribute = 18; | ||
6 | }--><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledArrow_Marker" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="black"><g><path d="M 8 0 L 0 -3 L 0 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Courier" font-size="18" units-per-em="1000" underline-position="-178.22266" underline-thickness="57.617188" slope="0" x-height="444.44446" cap-height="611.11115" ascent="753.90625" descent="-246.09375" font-weight="500"><!--NSCTFontDescriptor <0x79dbbf0> = { | ||
7 | NSFontNameAttribute = Courier; | ||
8 | NSFontSizeAttribute = 18; | ||
9 | }--><font-face-src><font-face-name name="Courier"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="16" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="531.25" cap-height="718.75" ascent="770.01953" descent="-229.98047" font-weight="500"><!--NSCTFontDescriptor <0x79dde00> = { | ||
10 | NSFontNameAttribute = Helvetica; | ||
11 | NSFontSizeAttribute = 16; | ||
12 | }--><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><font-face font-family="Times" font-size="24" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="458.33334" cap-height="666.6667" ascent="750" descent="-250" font-weight="500"><!--NSCTFontDescriptor <0x79df960> = { | ||
13 | NSFontNameAttribute = "Times-Roman"; | ||
14 | NSFontSizeAttribute = 24; | ||
15 | }--><font-face-src><font-face-name name="Times-Roman"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><rect fill="white" width="813.60004" height="566.95"/><g><title>Layer 1</title><g><use xl:href="#id4_Graphic" filter="url(#Shadow)"/><use xl:href="#id2_Graphic" filter="url(#Shadow)"/><use xl:href="#id33_Graphic" filter="url(#Shadow)"/><use xl:href="#id36_Graphic" filter="url(#Shadow)"/></g><g id="id4_Graphic"><path d="M 162.15001 63.999992 L 484.84998 63.999992 C 523.0208 63.999992 554 94.02719 554 131.02499 C 554 168.0228 523.0208 198.04999 484.84998 198.04999 L 162.15001 198.04999 C 123.9792 198.04999 93 168.0228 93 131.02499 C 93 94.02719 123.9792 63.999992 162.15001 63.999992" fill="#ffff51"/><path d="M 162.15001 63.999992 L 484.84998 63.999992 C 523.0208 63.999992 554 94.02719 554 131.02499 C 554 168.0228 523.0208 198.04999 484.84998 198.04999 L 162.15001 198.04999 C 123.9792 198.04999 93 168.0228 93 131.02499 C 93 94.02719 123.9792 63.999992 162.15001 63.999992" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(144.10001 120.024994)" fill="black"><tspan font-family="Helvetica" font-size="18" font-weight="500" x="154.878525" y="18" textLength="49.04297">Lanes</tspan></text></g><line x1="67" y1="508" x2="733.09998" y2="508" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="67" y1="508" x2="67" y2="68.90001" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(613 516)" fill="black"><tspan font-family="Courier" font-size="18" font-weight="500" x="0" y="18" textLength="118.819336">distributed</tspan></text><text transform="translate(104.9992 516)" fill="black"><tspan font-family="Courier" font-size="18" font-weight="500" x="0" y="18" textLength="118.819336">shared data</tspan></text><text transform="translate(323.5 516)" fill="black"><tspan font-family="Courier" font-size="18" font-weight="500" x="0" y="18" textLength="140.42285">isolated data</tspan></text><text transform="translate(21.501301 355) rotate(-90)" fill="black"><tspan font-family="Courier" font-size="18" font-weight="500" x="0" y="18" textLength="108.01758">coroutines</tspan></text><text transform="translate(21.501297 187.5) rotate(-90)" fill="black"><tspan font-family="Courier" font-size="18" font-weight="500" x="0" y="18" textLength="108.01758">OS threads</tspan></text><g id="id2_Graphic"><ellipse cx="164.4995" cy="312.5" rx="71.49962" ry="67.025116" fill="white"/><ellipse cx="164.4995" cy="312.5" rx="71.49962" ry="67.025116" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(112.299896 293.50003)" fill="black"><tspan font-family="Helvetica" font-size="16" font-weight="500" x="38.85194" y="15" textLength="26.695312">Lua</tspan><tspan font-family="Helvetica" font-size="16" font-weight="500" x="15.28944" y="34" textLength="73.820312">coroutines</tspan></text></g><text transform="translate(21.501301 504) rotate(-90)" fill="black"><tspan font-family="Courier" font-size="18" font-weight="500" x="0" y="18" textLength="97.21582">core mods</tspan></text><g id="id33_Graphic"><ellipse cx="164.49921" cy="441.975" rx="71.49961" ry="67.02514" fill="#ff7695"/><ellipse cx="164.49921" cy="441.975" rx="71.49961" ry="67.02514" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(112.2996 432.47504)" fill="black"><tspan font-family="Helvetica" font-size="16" font-weight="500" x="13.504284" y="15" textLength="77.390625">LuaThread</tspan></text></g><text transform="translate(518 26)" fill="black"><tspan font-family="Times" font-size="24" font-weight="500" x="0" y="23" textLength="262.58203">Lua multithreading choices</tspan></text><g id="id36_Graphic"><path d="M 345.15002 245.475 L 667.84998 245.475 C 706.0208 245.475 737 275.5022 737 312.5 C 737 349.4978 706.0208 379.525 667.84998 379.525 L 345.15002 379.525 C 306.97919 379.525 276 349.4978 276 312.5 C 276 275.5022 306.97919 245.475 345.15002 245.475" fill="#69b1ff"/><path d="M 345.15002 245.475 L 667.84998 245.475 C 706.0208 245.475 737 275.5022 737 312.5 C 737 349.4978 706.0208 379.525 667.84998 379.525 L 345.15002 379.525 C 306.97919 379.525 276 349.4978 276 312.5 C 276 275.5022 306.97919 245.475 345.15002 245.475" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(327.1 301.5)" fill="black"><tspan font-family="Helvetica" font-size="18" font-weight="500" x="119.8629" y="18" textLength="119.07422">ConcurrentLua</tspan></text></g></g></g></svg> | ||
diff --git a/docs/comparison.html b/docs/comparison.html new file mode 100644 index 0000000..84ef9ca --- /dev/null +++ b/docs/comparison.html | |||
@@ -0,0 +1,297 @@ | |||
1 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||
2 | <!-- | ||
3 | Comparison of Lua Lanes with other approaches | ||
4 | --> | ||
5 | |||
6 | <html> | ||
7 | <head> | ||
8 | <meta name="description" content="Lua Lanes - Comparison" /> | ||
9 | <meta name="keywords" content="Lua, Library, Multithreading, Threads, Rocks" /> | ||
10 | |||
11 | <title>Lua Lanes - Comparison</title> | ||
12 | </head> | ||
13 | |||
14 | <body> | ||
15 | |||
16 | <!-- comparisons +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
17 | <hr/> | ||
18 | <h2 id="comparisons">Comparisons to other threading kits</h2> | ||
19 | |||
20 | <p> | ||
21 | A short comparison of Lanes with other existing Lua multithreading kits. | ||
22 | </p> | ||
23 | |||
24 | <table><tr><td width=40> | ||
25 | <td bgcolor="#ffffe0"> | ||
26 | <pre> | ||
27 | ============= | ||
28 | Lua Lanes | ||
29 | ============= | ||
30 | |||
31 | With the introduction of Lindas (Jun-2008), Lua Lanes simplifies its API while | ||
32 | simultaneously adding more power and speed. | ||
33 | |||
34 | Pros: | ||
35 | - regular Lua 5.1 module | ||
36 | - completely separate Lua states, one per OS thread | ||
37 | - message passing, or shared data using Lindas | ||
38 | - no application level locking, ever | ||
39 | - scales well, up to 1000's of threads | ||
40 | - priorities (-2..+2) for launched threads | ||
41 | - threads are cancellable (with complete cleanup) | ||
42 | - timeouts on all pending operations | ||
43 | - thread contents are given as regular Lua functions; syntax checked early on, | ||
44 | syntax highlighting works | ||
45 | - standard libraries opened to subthreads can be granually selected | ||
46 | - fast stack-to-stack copies, via hidden "keeper states". No serialization needed. | ||
47 | - protects calls to 'require', allowing wide compatibility with existing | ||
48 | modules (and all, with minor changes) | ||
49 | |||
50 | Cons: | ||
51 | - requires OS threads | ||
52 | - not utilizing network parallelism (all threads on one CPU) | ||
53 | |||
54 | Sample: | ||
55 | << | ||
56 | require "lanes" | ||
57 | |||
58 | local function calculate(a,b,c) | ||
59 | if not a then | ||
60 | error "sample error; propagated to main lane when reading results" | ||
61 | end | ||
62 | return a+b+c | ||
63 | end | ||
64 | |||
65 | local h1= lanes.new(calculate)(1,2,3) | ||
66 | local h2= lanes.new(calculate)(10,20,30) | ||
67 | local h3= lanes.new(calculate)(100,200,300) | ||
68 | |||
69 | print( h1[1], h2[1], h3[1] ) -- pends for the results, or propagates error | ||
70 | << | ||
71 | |||
72 | |||
73 | ================== | ||
74 | Lua coroutines (by Lua authors) | ||
75 | ================== | ||
76 | |||
77 | <A HREF="http://www.lua.org/manual/5.1/manual.html#2.11">http://www.lua.org/manual/5.1/manual.html#2.11</A> | ||
78 | <A HREF="http://lua-users.org/wiki/CoroutinesTutorial">http://lua-users.org/wiki/CoroutinesTutorial</A> | ||
79 | |||
80 | Lua coroutines is an integral part of Lua 5 itself. It is listed here, since | ||
81 | it should be the _first_ multitasking mechanism to consider. It can also be | ||
82 | used together with Lanes, or the other mechanisms listed below. | ||
83 | |||
84 | Coroutines are very usable in provider/consumer situations, allowing you to | ||
85 | queue Lua functions on an as-needed dataflow basis with each other. | ||
86 | |||
87 | Pros: | ||
88 | - works with plain Lua (no extensions) | ||
89 | - works on any platform | ||
90 | - lightweight (no OS level threading or locking involved) | ||
91 | |||
92 | Cons: | ||
93 | - co-operative, meaning your code will need to decide, who gets to run | ||
94 | - does not utilize multiple CPUs/cores | ||
95 | |||
96 | Sample: | ||
97 | |||
98 | ..TBD: sample code doing the "child" "parent" output here (see below).. | ||
99 | |||
100 | |||
101 | ============= | ||
102 | LuaThread (by Diego Nehab) | ||
103 | ============= | ||
104 | |||
105 | <A HREF="http://www.cs.princeton.edu/~diego/professional/luathread/">http://www.cs.princeton.edu/~diego/professional/luathread/</A> | ||
106 | |||
107 | LuaThread provides thread creation, mutexes, condition variables, and inter- | ||
108 | thread queues to the Lua scripts. It takes a C-kind of approach, where Lua | ||
109 | globals are shared by the threads running, and need therefore to be guarded | ||
110 | against multithreading conflicts. | ||
111 | |||
112 | Whether this is exactly what you want, or whether a more loosely implemented | ||
113 | multithreading (s.a. Lanes) would be better, is up to you. One can argue that | ||
114 | a loose implementation is easier for the developer, since no application level | ||
115 | lockings need to be considered. | ||
116 | |||
117 | Pros: | ||
118 | - no marshalling overhead, since threads share the same Lua state | ||
119 | |||
120 | Cons: | ||
121 | - requires a modified Lua core | ||
122 | - application level locking required | ||
123 | |||
124 | Sample: | ||
125 | << | ||
126 | local function flood(output, word) | ||
127 | while 1 do | ||
128 | output:lock() | ||
129 | io.write(word, ", ") | ||
130 | output:unlock() | ||
131 | end | ||
132 | end | ||
133 | |||
134 | local output = thread.newmutex() | ||
135 | thread.newthread(flood, {output, "child"}) | ||
136 | flood(output, "parent") | ||
137 | << | ||
138 | |||
139 | |||
140 | ============= | ||
141 | Lua Rings (by Roberto Ierusalimschy & Tomás Guisasola) | ||
142 | ============= | ||
143 | |||
144 | <A HREF="http://www.keplerproject.org/rings/">http://www.keplerproject.org/rings/</A> | ||
145 | |||
146 | ".. library which provides a way to create new Lua states from within Lua. | ||
147 | It also offers a simple way to communicate between the creator (master) and | ||
148 | the created (slave) states." | ||
149 | |||
150 | ".. master can execute code in any of its slaves but each slave only has | ||
151 | access to its master (or its own slaves)." | ||
152 | |||
153 | Rings offers separate Lua states, but no multithreading. This makes it simple, | ||
154 | but it won't use more than one CPU core. Other differences include: | ||
155 | |||
156 | - opens all Lua standard libraries for subthreads | ||
157 | (Lanes opens the needed ones) | ||
158 | |||
159 | - marshalls numbers, strings, booleans, userdata | ||
160 | (Lanes marshalls also non-cyclic tables) | ||
161 | |||
162 | - "remotedostring" allows executing code in the master state | ||
163 | (Lanes does _not_ allow subthreads to trouble/modify master automatically, | ||
164 | to allow effective sandboxing. The same can be achieved by sending code | ||
165 | between the threads, but master needs to explicitly allow this = receive | ||
166 | a function and execute it) | ||
167 | |||
168 | - offers "Stable, a very simple API to manage a shared table at the master | ||
169 | state" | ||
170 | (Lanes 2008 offers keeper tables) | ||
171 | |||
172 | Pros: | ||
173 | - "offers Stable, a very simple API to manage a shared table at the master | ||
174 | state" | ||
175 | |||
176 | Cons: | ||
177 | - thread contents defined as strings, not Lua source as such; does not | ||
178 | give syntax check at file parsing, does not allow syntax highlight | ||
179 | |||
180 | Sample: | ||
181 | << | ||
182 | require"rings" | ||
183 | S = rings.new () | ||
184 | |||
185 | data = { 12, 13, 14, } | ||
186 | print (S:dostring ([[ | ||
187 | aux = {} | ||
188 | for i, v in ipairs (arg) do | ||
189 | table.insert (aux, 1, v) | ||
190 | end | ||
191 | return unpack (aux)]], unpack (data))) -- true, 14, 13, 12 | ||
192 | |||
193 | S:close () | ||
194 | << | ||
195 | |||
196 | |||
197 | ========================== | ||
198 | Helper Threads Toolkit (by Javier Guerra G.) | ||
199 | ========================== | ||
200 | |||
201 | <A HREF="http://luaforge.net/projects/helper-threads/">http://luaforge.net/projects/helper-threads/</A> | ||
202 | |||
203 | "Provides a consistent framework to write non-blocking C libraries, with a Lua | ||
204 | interface for starting tasks and managing the Futures, Queues and Threads." | ||
205 | |||
206 | This seems like a companion of the "occasional threading" model (see below); | ||
207 | Lua side is kept clear of multithreading, while C side can be "spawn" off to | ||
208 | do things on the background. | ||
209 | |||
210 | Pros: | ||
211 | - provides an "update" mechanism, allowing the (C) thread and controlling | ||
212 | Lua to interact during execution of the thread | ||
213 | - ... | ||
214 | |||
215 | Cons: | ||
216 | - thread is "only for C code and it can't touch or access the Lua state", | ||
217 | in other words there is no Lua-side multithreading concept (by design) | ||
218 | |||
219 | |||
220 | ======================== | ||
221 | Occasional threading (by Russ Cox) | ||
222 | ======================== | ||
223 | |||
224 | <A HREF="http://lua-users.org/lists/lua-l/2006-11/msg00368.html">http://lua-users.org/lists/lua-l/2006-11/msg00368.html</A> | ||
225 | |||
226 | ".. able to have certain C calls run in parallel with the [Lua] VM, but | ||
227 | otherwise keep the VM single-threaded." | ||
228 | |||
229 | That pretty much says it all. | ||
230 | |||
231 | Pros: | ||
232 | - simple, yet providing for the "occasional" need to run really multithreaded | ||
233 | - can be made into a loadable module (says the message) | ||
234 | |||
235 | Cons: | ||
236 | - only for occasional usage, the programming paradigm is still essentially | ||
237 | singlethreaded (by definition) | ||
238 | - not a real project, just a message on the Lua list (but a good one!) | ||
239 | |||
240 | |||
241 | ================= | ||
242 | ConcurrentLua | ||
243 | ================= | ||
244 | |||
245 | <A TARGET="_blank" HREF="http://concurrentlua.luaforge.net/index.html" | ||
246 | >http://concurrentlua.luaforge.net/index.html</A> | ||
247 | |||
248 | ConcurrentLua is based on the Lua model for concurrency, namely coroutines, and | ||
249 | extends this model by providing message-passing primitives. | ||
250 | |||
251 | ".. implementation of the share-nothing asynchronous message-passing model" | ||
252 | |||
253 | ".. process can check its mailbox for new messages at any time, and if there | ||
254 | are any, they can be read in the order of arrival." | ||
255 | |||
256 | ".. processes in the system are implemented with Lua coroutines" | ||
257 | |||
258 | ".. still based on the cooperative multithreading model that Lua uses" | ||
259 | |||
260 | Recent, released on 21 June 2008. | ||
261 | |||
262 | Pros: | ||
263 | - From ground up designed for distributed computing (multiple computers, | ||
264 | not only CPU cores) | ||
265 | - Does not require a pre-emptive operating system | ||
266 | |||
267 | Cons: | ||
268 | - Serialization must degrade raw performance in one-computer scenarios | ||
269 | (vs. stack-to-stack copying ala Lanes) | ||
270 | - Depends on LuaSocket and Copas modules. | ||
271 | - Each thread has a single mailbox tied to it (vs. separating threads and | ||
272 | connection resources) | ||
273 | |||
274 | </pre> | ||
275 | </td></tr></table> | ||
276 | |||
277 | |||
278 | <!-- footnotes +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
279 | <hr/> | ||
280 | |||
281 | <p>For feedback, questions and suggestions: | ||
282 | <UL> | ||
283 | <li><A HREF="http://luaforge.net/projects/lanes">Lanes @ LuaForge</A></li> | ||
284 | <li><A HREF="mailto:akauppi@gmail.com">the author</A></li> | ||
285 | </UL> | ||
286 | </p> | ||
287 | |||
288 | <!-- | ||
289 | <font size="-1"> | ||
290 | <p> | ||
291 | 1) ... | ||
292 | </p> | ||
293 | </font> | ||
294 | --> | ||
295 | |||
296 | </body> | ||
297 | </html> | ||
diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..956e691 --- /dev/null +++ b/docs/index.html | |||
@@ -0,0 +1,951 @@ | |||
1 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||
2 | <!-- | ||
3 | Documentation for Lua Lanes | ||
4 | --> | ||
5 | |||
6 | <html> | ||
7 | <head> | ||
8 | <meta name="description" content="Lua Lanes - multithreading in Lua" /> | ||
9 | <meta name="keywords" content="Lua, Library, Multithreading, Threads, Rocks" /> | ||
10 | |||
11 | <title>Lua Lanes - multithreading in Lua</title> | ||
12 | </head> | ||
13 | |||
14 | <body> | ||
15 | <div class="header"> | ||
16 | <hr /> | ||
17 | |||
18 | <center> | ||
19 | <table summary="Lua logo"> | ||
20 | <tbody> | ||
21 | <tr> | ||
22 | <td align="center"> | ||
23 | <a href="http://www.lua.org"> | ||
24 | <img src="http://akauppi.googlepages.com/multi.png" alt="Lua" align="middle" border="0" height="120" width="128" /> | ||
25 | <img src="http://akauppi.googlepages.com/multi.png" alt="Lua" align="middle" border="0" height="120" width="128" /> | ||
26 | <img src="http://akauppi.googlepages.com/multi.png" alt="Lua" align="middle" border="0" height="120" width="128" /> | ||
27 | <img src="http://akauppi.googlepages.com/multi.png" alt="Lua" align="middle" border="0" height="120" width="128" /> | ||
28 | <img src="http://akauppi.googlepages.com/multi.png" alt="Lua" align="middle" border="0" height="120" width="128" /> | ||
29 | </a></td> | ||
30 | </tr> | ||
31 | <tr> | ||
32 | <td align="center" valign="top"><h1>Lua Lanes - multithreading in Lua</h1> | ||
33 | </td> | ||
34 | </tr> | ||
35 | </tbody> | ||
36 | </table> | ||
37 | |||
38 | <p class="bar"> | ||
39 | <a href="#description">Description</a> · | ||
40 | <a href="#systems">Supported systems</a> · | ||
41 | <a href="#installing">Building and Installing</a> | ||
42 | </p><p class="bar"> | ||
43 | <a href="#creation">Creation</a> · | ||
44 | <a href="#status">Status</a> · | ||
45 | <a href="#results">Results and errors</a> | ||
46 | </p><p class="bar"> | ||
47 | <a href="#cancelling">Cancelling</a> · | ||
48 | <a href="#finalizers">Finalizers</a> · | ||
49 | <a href="#lindas">Lindas</a> · | ||
50 | <a href="#timers">Timers</a> · | ||
51 | <a href="#locks">Locks etc.</a> | ||
52 | </p><p class="bar"> | ||
53 | <a href="#other">Other issues</a> · | ||
54 | <a href="#changes">Change log</a> | ||
55 | <!-- ... --> | ||
56 | |||
57 | <p><br/><font size="-1"><i>Copyright © 2007-08 Asko Kauppi. All rights reserved.</i> | ||
58 | <br>Lua Lanes is published under the same <A HREF="http://en.wikipedia.org/wiki/MIT_License">MIT license</A> as Lua 5.1. | ||
59 | </p><p>This document was revised on 23-Jan-09, and applies to version 2.0.3. | ||
60 | </font></p> | ||
61 | |||
62 | </center> | ||
63 | </div> | ||
64 | |||
65 | |||
66 | <!-- description +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
67 | <hr/> | ||
68 | <h2 id="description">Description</h2> | ||
69 | |||
70 | <p>Lua Lanes is a Lua extension library providing | ||
71 | the possibility to run multiple Lua states in parallel. It is intended to | ||
72 | be used for optimizing performance on multicore CPU's and to study ways to make Lua programs naturally parallel to begin with. | ||
73 | </p><p> | ||
74 | Lanes is included into your software by the regular | ||
75 | <tt>require "lanes"</tt> method. No C side programming is needed; all APIs are Lua side, and most existing extension modules should | ||
76 | work seamlessly together with the multiple lanes. | ||
77 | </p><p> | ||
78 | See <A HREF="comparison.html">comparison</A> of Lua Lanes with other Lua multithreading solutions. | ||
79 | </p><p> | ||
80 | <h3>Features:</h3> | ||
81 | |||
82 | <ul> | ||
83 | <li>Lanes have separated data, by default. Shared data is possible with Linda objects. | ||
84 | </li> | ||
85 | <li>Communications is separate of threads, using Linda objects. | ||
86 | </li> | ||
87 | <li>Data passing uses fast inter-state copies (no serialization required)</li> | ||
88 | </li> | ||
89 | <li>"Deep userdata" concept, for sharing userdata over multiple lanes | ||
90 | </li> | ||
91 | <li>Millisecond level timers, integrated with the Linda system. | ||
92 | </li> | ||
93 | <li>Threads can be given priorities -2..+2 (default is 0). | ||
94 | </li> | ||
95 | <li>Lanes are cancellable, with proper cleanup. | ||
96 | </li> | ||
97 | <li>No application level locking - ever! | ||
98 | </li> | ||
99 | </ul> | ||
100 | |||
101 | |||
102 | <h3>Limitations:</h3> | ||
103 | |||
104 | <ul><li>coroutines are not passed between states | ||
105 | </li> | ||
106 | <li>sharing full userdata between states needs special C side | ||
107 | preparations (-> <A HREF="#deep_userdata">deep userdata</A>) | ||
108 | </li> | ||
109 | <li>network level parallelism not included | ||
110 | </li> | ||
111 | </ul> | ||
112 | </p> | ||
113 | |||
114 | |||
115 | <!-- systems +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
116 | <hr/> | ||
117 | <h2 id="systems">Supported systems</h2> | ||
118 | |||
119 | <p>Lua Lanes supports the following operating systems: | ||
120 | |||
121 | <ul> | ||
122 | <li>Mac OS X PowerPC / Intel (10.4 and later)</li> | ||
123 | <li>Linux x86</li> | ||
124 | <li>Windows 2000/XP and later <font size="-1">(MinGW or Visual C++ 2005/2008)</font></li> | ||
125 | <!-- | ||
126 | Other OS'es here once people help test them. (and the tester's name) | ||
127 | |||
128 | Win64, BSD, Linux x64, Linux embedded, QNX, Solaris, ... | ||
129 | --> | ||
130 | </ul> | ||
131 | |||
132 | <p>The underlying threading code can be compiled either towards Win32 API | ||
133 | or <a TARGET="_blank" HREF="http://en.wikipedia.org/wiki/POSIX_Threads">Pthreads</a>. Unfortunately, thread prioritation under Pthreads is a JOKE, | ||
134 | requiring OS specific tweaks and guessing undocumented behaviour. Other | ||
135 | features should be portable to any modern platform. | ||
136 | </p> | ||
137 | </p> | ||
138 | |||
139 | |||
140 | <!-- installing +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
141 | <hr/> | ||
142 | <h2 id="installing">Building and Installing</h2> | ||
143 | |||
144 | <p>Lua Lanes is built simply by <tt>make</tt> on the supported platforms | ||
145 | (<tt>make-vc</tt> for Visual C++). See <tt>README</tt> for system specific | ||
146 | details and limitations. | ||
147 | </p> | ||
148 | |||
149 | <p>To install Lanes, all you need are the <tt>lanes.lua</tt> and <tt>lua51-lanes.so|dll</tt> | ||
150 | files to be reachable by Lua (see LUA_PATH, LUA_CPATH). | ||
151 | |||
152 | Or use <A HREF="http://www.luarocks.org" TARGET="_blank">Lua Rocks</A> package management. | ||
153 | </p> | ||
154 | |||
155 | <pre> | ||
156 | > luarocks search lanes | ||
157 | ... output listing Lua Lanes is there ... | ||
158 | |||
159 | > luarocks install lanes | ||
160 | ... output ... | ||
161 | </pre> | ||
162 | |||
163 | |||
164 | <!-- launching +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
165 | <hr/> | ||
166 | <h2 id="creation">Creation</h2> | ||
167 | |||
168 | <p>The following sample shows preparing a function for parallel calling, and | ||
169 | calling it with varying arguments. Each of the two results is calculated in | ||
170 | a separate OS thread, parallel to the calling one. Reading the results | ||
171 | joins the threads, waiting for any results not already there. | ||
172 | </p> | ||
173 | |||
174 | <table border=1 bgcolor="#FFFFE0" width=500><tr><td> | ||
175 | <pre> | ||
176 | require "lanes" | ||
177 | |||
178 | f= lanes.gen( function(n) return 2*n end ) | ||
179 | a= f(1) | ||
180 | b= f(2) | ||
181 | |||
182 | print( a[1], b[1] ) -- 2 4 | ||
183 | </pre> | ||
184 | </table> | ||
185 | |||
186 | <p> | ||
187 | <table border=1 bgcolor="#E0E0FF" cellpadding=10><tr><td> | ||
188 | <code>func= lanes.gen( [libs_str | opt_tbl [, ...],] lane_func ) | ||
189 | <br/><br/> | ||
190 | lane_h= func( ... )</code> | ||
191 | </table> | ||
192 | </p> | ||
193 | </p><p> | ||
194 | The function returned by <tt>lanes.gen()</tt> is a "generator" for | ||
195 | launching any number of lanes. They will share code, options, initial globals, | ||
196 | but the particular arguments may vary. Only calling the generator function | ||
197 | actually launches a lane, and provides a handle for controlling it. | ||
198 | <!-- | ||
199 | </p> | ||
200 | <p>This prepares <tt>lane_func</tt> to be called in parallel. It does not yet start | ||
201 | anything, but merely returns a <i>generator function</i> that can be called | ||
202 | any number of times, with varying parameters. Each call will spawn a new lane. | ||
203 | --> | ||
204 | </p><p> | ||
205 | Lanes automatically copies upvalues over to the new lanes, so you | ||
206 | need not wrap all the required elements into one 'wrapper' function. If | ||
207 | <tt>lane_func</tt> uses some local values, or local functions, they will be there | ||
208 | also in the new lanes. | ||
209 | </p><p> | ||
210 | <code>libs_str</code> defines the standard libraries made available to the | ||
211 | new Lua state: | ||
212 | <table> | ||
213 | <tr><td/><td>(nothing)</td><td>no standard libraries (default)</td></tr> | ||
214 | <tr><td width=40><td><tt>"base"</tt> or <tt>""</tt></td> | ||
215 | <td>root level names, <tt>print</tt>, <tt>assert</tt>, <tt>unpack</tt> etc.</td></tr> | ||
216 | <tr><td/><td><tt>"coroutine"</tt></td><td><tt>coroutine.*</tt> namespace <font size="-1">(part of base in Lua 5.1)</font></td></tr> | ||
217 | <tr><td/><td><tt>"debug"</tt></td><td><tt>debug.*</tt> namespace</td></tr> | ||
218 | <tr><td/><td><tt>"io"</tt></td><td><tt>io.*</tt> namespace</td></tr> | ||
219 | <tr><td/><td><tt>"math"</tt></td><td><tt>math.*</tt> namespace</td></tr> | ||
220 | <tr><td/><td><tt>"os"</tt></td><td><tt>os.*</tt> namespace</td></tr> | ||
221 | <tr><td/><td><tt>"package"</tt></td><td><tt>package.*</tt> namespace and <tt>require</tt></td></tr> | ||
222 | <tr><td/><td><tt>"string"</tt></td><td><tt>string.*</tt> namespace</td></tr> | ||
223 | <tr><td/><td><tt>"table"</tt></td><td><tt>table.*</tt> namespace</td></tr> | ||
224 | <br/> | ||
225 | <tr><td/><td><tt>"*"</tt></td><td>all standard libraries</td></tr> | ||
226 | </table> | ||
227 | |||
228 | </p><p> | ||
229 | Initializing the standard libs takes a bit of time at each lane invocation. | ||
230 | This is the main reason why "no libraries" is the default. | ||
231 | </p><p> | ||
232 | |||
233 | <code>opt_tbl</code> is a collection of named options to control the way | ||
234 | lanes are run: | ||
235 | </p><p> | ||
236 | <table> | ||
237 | <tr valign=top><td/><td> | ||
238 | <code>.cancelstep</code> <br/><nobr>N / true</nobr></td> | ||
239 | <td> | ||
240 | By default, lanes are only cancellable when they enter a pending | ||
241 | <tt>:receive()</tt> or <tt>:send()</tt> call. | ||
242 | With this option, one can set cancellation check to occur every <tt>N</tt> | ||
243 | Lua statements. The value <tt>true</tt> uses a default value (100). | ||
244 | </td></tr> | ||
245 | |||
246 | <tr valign=top><td/><td> | ||
247 | <code>.globals</code> <br/>globals_tbl</td> | ||
248 | <td> | ||
249 | Sets the globals table for the launched threads. This can be used for giving | ||
250 | them constants. | ||
251 | </p><p> | ||
252 | The global values of different lanes are in no manner connected; | ||
253 | modifying one will only affect the particular lane. | ||
254 | </td></tr> | ||
255 | |||
256 | <tr valign=top><td width=40><td> | ||
257 | <code>.priority</code> <br/><nobr>-2..+2</nobr></td> | ||
258 | <td>The priority of lanes generated. -2 is lowest, +2 is highest. | ||
259 | <p> | ||
260 | Implementation and dependability of priorities varies | ||
261 | by platform. Especially Linux kernel 2.6 is not supporting priorities in user mode. | ||
262 | </td></tr> | ||
263 | </table> | ||
264 | |||
265 | </p> | ||
266 | |||
267 | <h3>Free running lanes</h3> | ||
268 | |||
269 | <p> | ||
270 | The lane handles are allowed to be 'let loose'; in other words you may execute | ||
271 | a lane simply by: | ||
272 | |||
273 | <pre> | ||
274 | lanes.gen( function() ... end ) () | ||
275 | </pre> | ||
276 | |||
277 | Normally, this kind of lanes will be in an eternal loop handling messages. | ||
278 | Since the lane handle is gone, | ||
279 | there is no way to control such a lane from the outside, nor read its potential | ||
280 | return values. Then again, such a lane does not even normally return. | ||
281 | </p> | ||
282 | |||
283 | |||
284 | <!-- status +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
285 | <hr/> | ||
286 | <h2 id="status">Status</h2> | ||
287 | |||
288 | <table border=1 bgcolor="#E0E0FF" cellpadding=10><tr><td> | ||
289 | <code>str= lane_h.status</code> | ||
290 | </table> | ||
291 | |||
292 | <p>The current execution state of a lane can be read via its <tt>status</tt> | ||
293 | member, providing one of these values: <sup>(<a href="#2">2</a></sup> | ||
294 | |||
295 | <table> | ||
296 | <tr><td width=40><td><tt>"pending"</tt></td><td>not started, yet</td></tr> | ||
297 | <tr><td/><td><tt>"running"</tt></td><td>running</td></tr> | ||
298 | <tr><td/><td><tt>"waiting"</tt></td><td>waiting at a Linda <tt>:receive()</tt> or <tt>:send()</tt></td></tr> | ||
299 | <tr><td/><td><tt>"done"</tt></td><td>finished executing (results are ready)</td></tr> | ||
300 | <tr><td/><td><tt>"error"</tt></td><td>met an error (reading results will propagate it)</td></tr> | ||
301 | <tr><td/><td><tt>"cancelled"</tt></td><td>received cancellation and finished itself</td></tr> | ||
302 | </table> | ||
303 | </p><p> | ||
304 | This is similar to <tt>coroutine.status</tt>, which has: <tt>"running"</tt> / | ||
305 | <tt>"suspended"</tt> / <tt>"normal"</tt> / <tt>"dead"</tt>. Not using the | ||
306 | exact same names is intentional. | ||
307 | </p> | ||
308 | |||
309 | |||
310 | <!-- results +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
311 | <hr/> | ||
312 | <h2 id="results">Results and errors</h2> | ||
313 | |||
314 | <p>A lane can be waited upon by simply reading its results. This can be done | ||
315 | in two ways. | ||
316 | </p><p> | ||
317 | |||
318 | <table border=1 bgcolor="#E0E0FF" cellpadding=10><tr><td> | ||
319 | <code>[val]= lane_h[1]</code> | ||
320 | </table> | ||
321 | <p> | ||
322 | Makes sure lane has finished, and gives its first (maybe only) return value. | ||
323 | Other return values will be available in other <tt>lane_h</tt> indices. | ||
324 | </p><p> | ||
325 | If the lane ended in an error, it is propagated to master state at this place. | ||
326 | </p> | ||
327 | |||
328 | <table border=1 bgcolor="#E0E0FF" cellpadding=10><tr><td> | ||
329 | <code>[...]|[nil,err,stack_tbl]= lane_h:join( [timeout_secs] )</code> | ||
330 | </table> | ||
331 | <p> | ||
332 | Waits until the lane finishes, or <tt>timeout</tt> seconds have passed. | ||
333 | Returns <tt>nil</tt> on timeout, <tt>nil,err,stack_tbl</tt> if the lane hit an error, | ||
334 | or the return values of the lane. Unlike in reading the results in table | ||
335 | fashion, errors are not propagated. | ||
336 | </p><p> | ||
337 | <tt>stack_tbl</tt> is an array of "<filename>:<line>" strings, | ||
338 | describing where the error was thrown. Use <tt>table.concat()</tt> to format | ||
339 | it to your liking (or just ignore it). | ||
340 | </p><p> | ||
341 | If you use <tt>:join</tt>, make sure your lane main function returns | ||
342 | a non-nil value so you can tell timeout and error cases apart from succesful | ||
343 | return (using the <tt>.status</tt> property may be risky, since it might change | ||
344 | between a timed out join and the moment you read it). | ||
345 | </p><p> | ||
346 | |||
347 | <table border=1 bgcolor="#FFFFE0" width=500><tr><td> | ||
348 | <pre> | ||
349 | require "lanes" | ||
350 | |||
351 | f= lanes.gen( function() error "!!!" end ) | ||
352 | a= f(1) | ||
353 | |||
354 | --print( a[1] ) -- propagates error | ||
355 | |||
356 | v,err= a:join() -- no propagation | ||
357 | if v==nil then | ||
358 | error( "'a' faced error"..tostring(err) ) -- manual propagation | ||
359 | end | ||
360 | </pre> | ||
361 | </table> | ||
362 | |||
363 | |||
364 | <!-- cancelling +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
365 | <hr/> | ||
366 | <h2 id="cancelling">Cancelling</h2> | ||
367 | |||
368 | <table border=1 bgcolor="#E0E0FF" cellpadding=10><tr><td> | ||
369 | <code>bool= lane_h:cancel( [timeout_secs=0.0,] [force_kill_bool=false] )</code> | ||
370 | </table> | ||
371 | |||
372 | <p>Sends a cancellation request to the lane. If <tt>timeout_secs</tt> is non-zero, waits | ||
373 | for the request to be processed, or a timeout to occur. | ||
374 | Returns <tt>true</tt> if the lane was already done (in <tt>"done"</tt>, <tt>"error"</tt> or <tt>"cancelled"</tt> status) | ||
375 | or if the cancellation was fruitful within timeout period. | ||
376 | </p><p> | ||
377 | If the lane is still running and <tt>force_kill</tt> is <tt>true</tt>, the | ||
378 | OS thread running the lane is forcefully killed. This means no GC, and should | ||
379 | generally be the last resort. | ||
380 | </p> | ||
381 | <p>Cancellation is tested before going to sleep in <tt>receive()</tt> or <tt>send()</tt> calls | ||
382 | and after executing <tt>cancelstep</tt> Lua statements. A currently pending <tt>receive</tt> | ||
383 | or <tt>send</tt> call is currently not awakened, and may be a reason for a non-detected cancel. | ||
384 | </p> | ||
385 | |||
386 | |||
387 | <!-- finalizers +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
388 | <hr/> | ||
389 | <h2 id="finalizers">Finalizers</h2> | ||
390 | |||
391 | <table border=1 bgcolor="#E0E0FF" cellpadding=10><tr><td> | ||
392 | <code>set_finalizer( finalizer_func )</code> | ||
393 | <br/><br/> | ||
394 | <code>void= finalizer_func( [error] )</code> | ||
395 | </table> | ||
396 | |||
397 | <p>The <tt>error</tt> call is used for throwing exceptions in Lua. What Lua | ||
398 | does not offer, however, is scoped <a href="http://en.wikipedia.org/wiki/Finalizer">finalizers</a> | ||
399 | that would get called when a certain block of instructions gets exited, whether | ||
400 | through peaceful return or abrupt <tt>error</tt>. | ||
401 | </p> | ||
402 | <p>Since 2.0.3, Lanes prepares a function <tt>set_finalizer</tt> for doing this. | ||
403 | Any functions given to it will be called in the lane Lua state, just prior to | ||
404 | closing it. They are not called in any particular order. | ||
405 | </p> | ||
406 | <p>An error in a finalizer itself overrides the state of the regular chunk | ||
407 | (in practise, it would be highly preferable <i>not</i> to have errors in finalizers). | ||
408 | If one finalizer errors, the others may not get called. | ||
409 | </p> | ||
410 | |||
411 | |||
412 | <!-- lindas +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
413 | <hr/> | ||
414 | <h2 id="lindas">Lindas</h2> | ||
415 | |||
416 | <p>Communications between lanes is completely detached from the lane handles | ||
417 | themselves. By itself, a lane can only provide return values once it's finished, | ||
418 | or throw an error. Needs to communicate during runtime are handled by <A HREF="http://en.wikipedia.org/wiki/Linda_%28coordination_language%29" TARGET="_blank">Linda objects</A>, which are | ||
419 | <A HREF="#deep_userdata">deep userdata</A> instances. They can be provided to a lane | ||
420 | as startup parameters, upvalues or in some other Linda's message. | ||
421 | </p><p> | ||
422 | Access to a Linda object means a lane can read or write to any of its data | ||
423 | slots. Multiple lanes can be accessing the same Linda in parallel. No application | ||
424 | level locking is required; each Linda operation is atomic. | ||
425 | </p><p> | ||
426 | |||
427 | <table border=1 bgcolor="#FFFFE0" width=500><tr><td> | ||
428 | <pre> | ||
429 | require "lanes" | ||
430 | |||
431 | local linda= lanes.linda() | ||
432 | |||
433 | local function loop( max ) | ||
434 | for i=1,max do | ||
435 | print( "sending: "..i ) | ||
436 | linda:send( "x", i ) -- linda as upvalue | ||
437 | end | ||
438 | end | ||
439 | |||
440 | a= lanes.gen("",loop)( 10000 ) | ||
441 | |||
442 | while true do | ||
443 | local val= linda:receive( 3.0, "x" ) -- timeout in seconds | ||
444 | if val==nil then | ||
445 | print( "timed out" ) | ||
446 | break | ||
447 | end | ||
448 | print( "received: "..val ) | ||
449 | end | ||
450 | </pre> | ||
451 | </table> | ||
452 | |||
453 | </p> | ||
454 | <p>Characteristics of the Lanes implementation of Lindas are: | ||
455 | |||
456 | <ul> | ||
457 | <li>keys can be of number, string or boolean type | ||
458 | </li> | ||
459 | <li>values can be any type supported by inter-state copying (same limits | ||
460 | as for function parameters and upvalues) | ||
461 | </li> | ||
462 | <li>consuming method is <tt>:receive</tt> (not in) | ||
463 | </li> | ||
464 | <li>non-consuming method is <tt>:get</tt> (not rd) | ||
465 | </li> | ||
466 | <li>two producer-side methods: <tt>:send</tt> and <tt>:set</tt> (not out) | ||
467 | </li> | ||
468 | <li><tt>send</tt> allows for sending multiple values -atomically- to a | ||
469 | given key | ||
470 | </li> | ||
471 | <li><tt>receive</tt> can wait for multiple keys at once | ||
472 | </li> | ||
473 | <li>individual keys' queue length can be limited, balancing speed differences | ||
474 | in a producer/consumer scenario (making <tt>:send</tt> wait) | ||
475 | </li> | ||
476 | </ul> | ||
477 | </p> | ||
478 | |||
479 | <p> | ||
480 | <table border=1 bgcolor="#E0E0FF" cellpadding=10><tr><td> | ||
481 | <code>h= lanes.linda()</code> | ||
482 | <br/><br/> | ||
483 | <code>bool= h:send( [timeout_secs,] key, ... )</code> | ||
484 | <br/> | ||
485 | <code>[val, key]= h:receive( [timeout_secs,] key [, ...] )</code> | ||
486 | <br/><br/> | ||
487 | <code>= h:limit( key, n_uint )</code> | ||
488 | </table> | ||
489 | |||
490 | <p>The <tt>send</tt> and <tt>receive</tt> methods use Linda keys as FIFO stacks | ||
491 | (first in, first out). Timeouts are given in seconds (millisecond accuracy). | ||
492 | If using numbers as the first Linda key, one must explicitly give <tt>nil</tt> | ||
493 | as the timeout parameter to avoid ambiguities. | ||
494 | </p><p> | ||
495 | By default, stack sizes are unlimited but limits can be | ||
496 | enforced using the <tt>limit</tt> method. This can be useful to balance execution | ||
497 | speeds in a producer/consumer scenario. | ||
498 | </p><p> | ||
499 | Note that any number of lanes can be reading or writing a Linda. There can be | ||
500 | many producers, and many consumers. It's up to you. | ||
501 | </p> | ||
502 | <p><tt>send</tt> returns <tt>true</tt> if the sending succeeded, and <tt>false</tt> | ||
503 | if the queue limit was met, and the queue did not empty enough during the given | ||
504 | timeout. | ||
505 | </p><p> | ||
506 | Equally, <tt>receive</tt> returns a value and the key that provided the value, | ||
507 | or nothing for timeout. Note that <tt>nil</tt>s can be sent and received; | ||
508 | the <tt>key</tt> value will tell it apart from a timeout. | ||
509 | </p><p> | ||
510 | Multiple values can be sent to a given key at once, atomically (the send will | ||
511 | fail unless all the values fit within the queue limit). This can be useful for | ||
512 | multiple producer scenarios, if the protocols used are giving data in streams | ||
513 | of multiple units. Atomicity avoids the producers from garbling each others | ||
514 | messages, which could happen if the units were sent individually. | ||
515 | </p><p> | ||
516 | |||
517 | When receiving from multiple slots, the keys are checked in order, which can | ||
518 | be used for making priority queues. | ||
519 | </p><p> | ||
520 | |||
521 | <table border=1 bgcolor="#E0E0FF" cellpadding=10><tr><td> | ||
522 | <code>linda_h:set( key, [val] )</code> | ||
523 | <br/> | ||
524 | <code>[val]= linda_h:get( key )</code> | ||
525 | </table> | ||
526 | |||
527 | </p><p> | ||
528 | The table access methods are for accessing a slot without queuing or consuming. | ||
529 | They can be used for making shared tables of storage among the lanes. | ||
530 | </p><p> | ||
531 | Writing to a slot overwrites existing value, and clears any possible queued | ||
532 | entries. Table access and <tt>send</tt>/<tt>receive</tt> can be used together; | ||
533 | reading a slot essentially peeks the next outcoming value of a queue. | ||
534 | </p> | ||
535 | |||
536 | <!-- | ||
537 | <p> | ||
538 | <table border=1 bgcolor="#E0E0FF" cellpadding=10><tr><td> | ||
539 | <code>lightuserdata= linda_h:deep()</code> | ||
540 | </table> | ||
541 | |||
542 | <p>There is one more method that is not required in applications, but | ||
543 | discussing it is good for a preview of how deep userdata works. | ||
544 | </p><p> | ||
545 | Because proxy objects (<tt>linda_h</tt>) are just pointers to the real, deep | ||
546 | userdata, they cannot be used to identify a certain Linda from the others. | ||
547 | The internal timer system needs to do this, and the <tt>:deep()</tt> method | ||
548 | has been added for its use. It returns a light userdata pointing to the | ||
549 | <i>actual</i> deep object, and thus can be used for seeing, which proxies actually | ||
550 | mean the same underlying object. You might or might not need a similar system | ||
551 | with your own deep userdata. | ||
552 | </p> | ||
553 | --> | ||
554 | |||
555 | |||
556 | <h3>Granularity of using Lindas</h3> | ||
557 | |||
558 | <p>A single Linda object provides an infinite number of slots, so why would | ||
559 | you want to use several? | ||
560 | </p><p>There are some important reasons: | ||
561 | |||
562 | <ul> | ||
563 | <li>Access control. If you don't trust certain code completely, or just | ||
564 | to modularize your design, use one Linda for one usage and another one | ||
565 | for the other. This keeps your code clear and readable. You can pass | ||
566 | multiple Linda handles to a lane with practically no added cost. | ||
567 | </li> | ||
568 | |||
569 | <li>Namespace control. Linda keys have a "flat" namespace, so collisions | ||
570 | are possible if you try to use the same Linda for too many separate uses. | ||
571 | </li> | ||
572 | |||
573 | <li>Performance. Changing any slot in a Linda causes all pending threads | ||
574 | for that Linda to be momentarily awakened (at least in the C level). | ||
575 | This can degrade performance due to unnecessary OS level context switches. | ||
576 | </li> | ||
577 | </ul> | ||
578 | |||
579 | On the other side, you need to use a common Linda for waiting for multiple | ||
580 | keys. You cannot wait for keys from two separate Linda objects at the same | ||
581 | time. | ||
582 | </p><p> | ||
583 | <font size="-1">Actually, you can. Make separate lanes to wait each, and then multiplex those | ||
584 | events to a common Linda, but... :).</font> | ||
585 | </p> | ||
586 | |||
587 | |||
588 | <!-- timers +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
589 | <hr/> | ||
590 | <h2 id="timers">Timers</h2> | ||
591 | |||
592 | <table border=1 bgcolor="#E0E0FF" cellpadding=10><tr><td> | ||
593 | <code>= lanes.timer( linda_h, key, date_tbl|first_secs [,period_secs] )</code> | ||
594 | </table> | ||
595 | |||
596 | <p> | ||
597 | Timers can be run once, or in a reoccurring fashion (<tt>period_secs > 0</tt>). | ||
598 | The first occurrence can be given either as a date or as a relative delay in seconds. | ||
599 | The <tt>date</tt> table is like what <tt>os.date("*t")</tt> returns, in the | ||
600 | local time zone. | ||
601 | </p><p> | ||
602 | Once a timer expires, the <tt>key</tt> is set with the current time | ||
603 | (in seconds, same offset as <tt>os.time()</tt> but with millisecond accuracy). | ||
604 | The key can be waited upon using the regular Linda <tt>:receive()</tt> | ||
605 | method. | ||
606 | </p><p> | ||
607 | A timer can be stopped simply by <tt>first_secs=0</tt> and no period. | ||
608 | </p><p> | ||
609 | |||
610 | <table border=1 bgcolor="#FFFFE0" width=500><tr><td> | ||
611 | <pre> | ||
612 | require "lanes" | ||
613 | |||
614 | local linda= lanes.linda() | ||
615 | |||
616 | -- First timer once a second, not synchronized to wall clock | ||
617 | -- | ||
618 | lanes.timer( linda, "sec", 1, 1 ) | ||
619 | |||
620 | -- Timer to a future event (next even minute); wall clock synchronized | ||
621 | -- | ||
622 | local t= os.date( "*t", os.time()+60 ) -- now + 1min | ||
623 | t.sec= 0 | ||
624 | |||
625 | lanes.timer( linda, "min", t, 60 ) -- reoccur every minute (sharp) | ||
626 | |||
627 | while true do | ||
628 | local v,key= linda:receive( "sec", "min" ) | ||
629 | print( "Timer "..key..": "..v ) | ||
630 | end | ||
631 | </pre> | ||
632 | </table> | ||
633 | |||
634 | </p><p> | ||
635 | NOTE: Timer keys are set, not queued, so missing a beat is possible especially | ||
636 | if the timer cycle is extremely small. The key value can be used to know the | ||
637 | actual time passed. | ||
638 | </p><p> | ||
639 | <table> | ||
640 | <tr><td valign=top><nobr><i>Design note:</i></nobr> </td> | ||
641 | <td> | ||
642 | <font size="-1"> | ||
643 | Having the API as <tt>lanes.timer()</tt> is intentional. Another | ||
644 | alternative would be <tt>linda_h:timer()</tt> but timers are not traditionally | ||
645 | seen to be part of Lindas. Also, it would mean any lane getting a Linda handle | ||
646 | would be able to modify timers on it. A third choice could | ||
647 | be abstracting the timers out of Linda realm altogether (<tt>timer_h= lanes.timer( date|first_secs, period_secs )</tt>) | ||
648 | but that would mean separate waiting functions for timers, and lindas. Even if | ||
649 | a linda object and key was returned, that key couldn't be waited upon simultaneously | ||
650 | with one's general linda events. | ||
651 | The current system gives maximum capabilities with minimum API, and any smoothenings | ||
652 | can easily be crafted in Lua at the application level. | ||
653 | </font> | ||
654 | </td> | ||
655 | </tr> | ||
656 | </table> | ||
657 | </p> | ||
658 | |||
659 | |||
660 | <!-- locks +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
661 | <hr/> | ||
662 | <h2 id="locks">Locks etc.</h2> | ||
663 | |||
664 | <p> | ||
665 | Lanes does not generally require locks or critical sections to be used, at all. | ||
666 | If necessary, a limited queue can be used to emulate them. <tt>lanes.lua</tt> | ||
667 | offers some sugar to make it easy: | ||
668 | </p><p> | ||
669 | |||
670 | <table border=1 bgcolor="#E0E0FF" cellpadding=10><tr><td><pre> | ||
671 | lock_func= lanes.genlock( linda_h, key [,N_uint=1] ) | ||
672 | |||
673 | lock_func( M_uint ) -- acquire | ||
674 | .. | ||
675 | lock_func( -M_uint ) -- release | ||
676 | </table> | ||
677 | </p><p> | ||
678 | |||
679 | The generated function acquires M entries from the N available, or releases | ||
680 | them if the value is negative. The acquiring call will suspend the lane, if necessary. | ||
681 | Use <tt>M=N=1</tt> for a critical section lock (only one lane allowed to enter). | ||
682 | </p><p> | ||
683 | |||
684 | Note: The locks generated are <u>not recursive</u>. That would need another | ||
685 | kind of generator, which is currently not implemented. | ||
686 | </p><p> | ||
687 | |||
688 | Similar sugar exists for atomic counters: | ||
689 | </p><p> | ||
690 | |||
691 | <table border=1 bgcolor="#E0E0FF" cellpadding=10><tr><td><pre> | ||
692 | atomic_func= lanes.genatomic( linda_h, key [,initial_num=0.0] ) | ||
693 | |||
694 | new_num= atomic_func( [diff_num=+1.0] ) | ||
695 | </table> | ||
696 | </p><p> | ||
697 | |||
698 | Each time called, the generated function will change <tt>linda[key]</tt> | ||
699 | atomically, without other lanes being able to interfere. The new value is | ||
700 | returned. You can use either <tt>diff 0.0</tt> or <tt>get</tt> to just read the current | ||
701 | value. | ||
702 | </p><p> | ||
703 | |||
704 | Note that the generated functions can be passed on to other lanes. | ||
705 | </p> | ||
706 | |||
707 | |||
708 | <!-- others +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
709 | <hr/> | ||
710 | <h2 id="other">Other issues</h2> | ||
711 | |||
712 | <h3>Limitations on data passing</h3> | ||
713 | |||
714 | <p>Data passed between lanes (either as starting parameters, return values, upvalues or via Lindas) must conform to the following: | ||
715 | </p> | ||
716 | <p><ul> | ||
717 | <li>Booleans, numbers, strings, light userdata, Lua functions and tables of such can always be passed. | ||
718 | </li> | ||
719 | <li>Cyclic tables and/or duplicate references are allowed and reproduced appropriately, | ||
720 | but only <u>within the same transmission</u>. | ||
721 | <ul> | ||
722 | <li>using the same source table in multiple Linda messages keeps no ties between the tables | ||
723 | </li> | ||
724 | </ul> | ||
725 | </li> | ||
726 | <li>Objects (tables with a metatable) are copyable between lanes. | ||
727 | <ul> | ||
728 | <li>metatables are assumed to be immutable; they are internally indexed and only copied once | ||
729 | per each type of objects per lane | ||
730 | </li> | ||
731 | </ul> | ||
732 | </li> | ||
733 | <li>C functions (<tt>lua_CFunction</tt>) referring to <tt>LUA_ENVIRONINDEX</tt> or <tt>LUA_REGISTRYINDEX</tt> might not | ||
734 | work right in the target | ||
735 | <ul> | ||
736 | <li>rather completely re-initialize a module with <tt>require</tt> in the target lane | ||
737 | </li> | ||
738 | </ul> | ||
739 | </li> | ||
740 | <li>Full userdata can be passed only if it's prepared using the <A HREF="#deep_userdata">deep userdata</A> | ||
741 | system, which handles its lifespan management | ||
742 | <ul> | ||
743 | <li>in particular, lane handles cannot be passed between lanes | ||
744 | </li> | ||
745 | </ul> | ||
746 | </li> | ||
747 | <li>coroutines cannot be passed | ||
748 | </li> | ||
749 | </ul> | ||
750 | </p> | ||
751 | |||
752 | |||
753 | <h3>Required of module makers</h3> | ||
754 | |||
755 | <p> | ||
756 | Most Lua extension modules should work unaltered with Lanes. | ||
757 | If the module simply ties C side features to Lua, everything is fine without | ||
758 | alterations. The <tt>luaopen_...()</tt> entry point will be called separately for each | ||
759 | lane, where the module is <tt>require</tt>'d from. | ||
760 | </p><p> | ||
761 | If it, however, also does one-time C side initializations, these | ||
762 | should be covered into a one-time-only construct such as below. | ||
763 | </p><p> | ||
764 | |||
765 | <table><tr><td width=40> | ||
766 | <td bgcolor="#ffffe0"> | ||
767 | <pre> | ||
768 | int luaopen_module( lua_State *L ) | ||
769 | { | ||
770 | static char been_here; /* 0 by ANSI C */ | ||
771 | |||
772 | /* Calls to 'require' serialized by Lanes; this is safe. | ||
773 | */ | ||
774 | if (!been_here) { | ||
775 | been_here= 1; | ||
776 | ... one time initializations ... | ||
777 | } | ||
778 | |||
779 | ... binding to Lua ... | ||
780 | } | ||
781 | </pre> | ||
782 | </td></tr></table> | ||
783 | </p> | ||
784 | |||
785 | |||
786 | <h3 id="shared_userdata">Deep userdata in your own apps</h3> | ||
787 | |||
788 | <p> | ||
789 | The mechanism Lanes uses for sharing Linda handles between separate Lua states | ||
790 | can be used for custom userdata as well. Here's what to do. | ||
791 | </p> | ||
792 | <ol> | ||
793 | <li>Provide an <i>identity function</i> for your userdata, in C. This function is | ||
794 | used for creation and deletion of your deep userdata (the shared resource), | ||
795 | and for making metatables for the state-specific proxies for accessing it. | ||
796 | Take a look at <tt>linda_id</tt> in <tt>lanes.c</tt>. | ||
797 | </li> | ||
798 | <li>Create your userdata using <tt>luaG_deep_userdata()</tt>, which is | ||
799 | a Lua-callable function. Given an <tt>idfunc</tt>, it sets up the support | ||
800 | structures and returns a state-specific proxy userdata for accessing your | ||
801 | data. This proxy can also be copied over to other lanes. | ||
802 | </li> | ||
803 | <li>Accessing the deep userdata from your C code, use <tt>luaG_todeep()</tt> | ||
804 | instead of the regular <tt>lua_touserdata()</tt>. | ||
805 | </li> | ||
806 | </ol> | ||
807 | |||
808 | <p>Deep userdata management will take care of tying to <tt>__gc</tt> methods, | ||
809 | and doing reference counting to see how many proxies are still there for | ||
810 | accessing the data. Once there are none, the data will be freed through a call | ||
811 | to the <tt>idfunc</tt> you provided. | ||
812 | </p> | ||
813 | <p><b>NOTE</b>: The lifespan of deep userdata may exceed that of the Lua state | ||
814 | that created it. The allocation of the data storage should not be tied to | ||
815 | the Lua state used. In other words, use <tt>malloc</tt>/<tt>free</tt> or | ||
816 | similar memory handling mechanism. | ||
817 | </p> | ||
818 | |||
819 | |||
820 | <h3>Lane handles don't travel</h3> | ||
821 | |||
822 | <p> | ||
823 | Lane handles are not implemented as deep userdata, and cannot thus be | ||
824 | copied across lanes. This is intentional; problems would occur at least when | ||
825 | multiple lanes were to wait upon one to get ready. Also, it is a matter of | ||
826 | design simplicity. | ||
827 | </p><p> | ||
828 | The same benefits can be achieved by having a single worker lane spawn all | ||
829 | the sublanes, and keep track of them. Communications to and from this lane | ||
830 | can be handled via a Linda. | ||
831 | </p> | ||
832 | |||
833 | |||
834 | <h3>Beware with print and file output</h3> | ||
835 | |||
836 | <p> | ||
837 | In multithreaded scenarios, giving multiple parameters to <tt>print()</tt> | ||
838 | or <tt>file:write()</tt> may cause them to be overlapped in the output, | ||
839 | something like this: | ||
840 | |||
841 | <pre> | ||
842 | A: print( 1, 2, 3, 4 ) | ||
843 | B: print( 'a', 'b', 'c', 'd' ) | ||
844 | |||
845 | 1 a b 2 3 c d 4 | ||
846 | </pre> | ||
847 | |||
848 | Lanes does not protect you from this behaviour. The thing to do is either to | ||
849 | concentrate your output to a certain lane per stream, or to concatenate output | ||
850 | into a single string before you call the output function. | ||
851 | </p> | ||
852 | |||
853 | |||
854 | <h3 id="performance">Performance considerations</h3> | ||
855 | |||
856 | <p> | ||
857 | Lanes is about making multithreading easy, and natural in the Lua state of mind. | ||
858 | Expect performance not to be an issue, if your program is logically built. | ||
859 | Here are some things one should consider, if best performance is vital: | ||
860 | </p><p> | ||
861 | <ul> | ||
862 | <li>Data passing (parameters, upvalues, Linda messages) is generally fast, | ||
863 | doing two binary state-to-state copies (from source state to hidden state, | ||
864 | hidden state to target state). Remember that not only the function you | ||
865 | specify but also its upvalues, their upvalues, etc. etc. will get copied. | ||
866 | </li> | ||
867 | <li>Lane startup is fast (1000's of lanes a second), depending on the | ||
868 | number of standard libraries initialized. Initializing all standard libraries | ||
869 | is about 3-4 times slower than having no standard libraries at all. If you | ||
870 | throw in a lot of lanes per second, make sure you give them minimal necessary | ||
871 | set of libraries. | ||
872 | </li> | ||
873 | <li>Waiting Lindas are woken up (and execute some hidden Lua code) each | ||
874 | time <u>any</u> key in the Lindas they are waiting for are changed. This | ||
875 | may give essential slow-down (not measured, just a gut feeling) if a lot | ||
876 | of Linda keys are used. Using separate Linda objects for logically separate | ||
877 | issues will help (which is good practise anyhow). | ||
878 | </li> | ||
879 | <li>Linda objects are light. The memory footprint is two OS-level signalling | ||
880 | objects (<tt>HANDLE</tt> or <tt>pthread_cond_t</tt>) for each, plus one | ||
881 | C pointer for the proxies per each Lua state using the Linda. Barely nothing. | ||
882 | </li> | ||
883 | <li>Timers are light. You can probably expect timers up to 0.01 second | ||
884 | resolution to be useful, but that is very system specific. All timers are | ||
885 | merged into one main timer state (see <tt>timer.lua</tt>); no OS side | ||
886 | timers are utilized. | ||
887 | </li> | ||
888 | <li>Lindas are hashed to a fixed number of "keeper states", which are a locking entity. | ||
889 | If you are using a lot of Linda objects, | ||
890 | it may be useful to try having more of these keeper states. By default, | ||
891 | only one is used (see <tt>KEEPER_STATES_N</tt>), but this is an implementation detail. | ||
892 | </li> | ||
893 | </ul> | ||
894 | </p> | ||
895 | |||
896 | |||
897 | <h3 id="cancelling_cancel">Cancelling cancel</h3> | ||
898 | |||
899 | <p> | ||
900 | Cancellation of lanes uses the Lua error mechanism with a special lightuserdata | ||
901 | error sentinel. | ||
902 | If you use <tt>pcall</tt> in code that needs to be cancellable | ||
903 | from the outside, the special error might not get through to Lanes, thus | ||
904 | preventing the Lane from being cleanly cancelled. You should throw any | ||
905 | lightuserdata error further. | ||
906 | </p><p> | ||
907 | This system can actually be used by application to detect cancel, do your own | ||
908 | cancellation duties, and pass on the error so Lanes will get it. If it does | ||
909 | not get a clean cancellation from a lane in due time, | ||
910 | it may forcefully kill the lane. | ||
911 | </p><p> | ||
912 | The sentinel is exposed as <tt>lanes.cancel_error</tt>, if you wish to use | ||
913 | its actual value. | ||
914 | </p> | ||
915 | |||
916 | |||
917 | |||
918 | <!-- change log +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
919 | <hr/> | ||
920 | <h2 id="changes">Change log</h2> | ||
921 | |||
922 | <p> | ||
923 | Jan-2009 (2.0.3): | ||
924 | <ul> | ||
925 | <li>Added 'finalizer' to lane options. (TBD: not implemented yet!) | ||
926 | </li> | ||
927 | <li>Added call stack to errors coming from a lane. | ||
928 | </li> | ||
929 | </ul> | ||
930 | |||
931 | Jul-2008 (2.0): | ||
932 | <ul> | ||
933 | <li>Too many changes to list (you'll need to re-read this manual) | ||
934 | </li> | ||
935 | </ul> | ||
936 | </p> | ||
937 | |||
938 | <!-- footnotes +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
939 | <hr/> | ||
940 | |||
941 | <p>For feedback, questions and suggestions: | ||
942 | <UL> | ||
943 | <li><A HREF="http://luaforge.net/projects/lanes">Lanes @ LuaForge</A></li> | ||
944 | <li><A HREF="mailto:akauppi@gmail.com">the author</A></li> | ||
945 | </UL> | ||
946 | </p> | ||
947 | |||
948 | <p><br/></p> | ||
949 | |||
950 | </body> | ||
951 | </html> | ||
diff --git a/docs/multi.png b/docs/multi.png new file mode 100644 index 0000000..f527aff --- /dev/null +++ b/docs/multi.png | |||
Binary files differ | |||
diff --git a/docs/performance.ods b/docs/performance.ods new file mode 100644 index 0000000..541cc8e --- /dev/null +++ b/docs/performance.ods | |||
Binary files differ | |||
diff --git a/make-vc.cmd b/make-vc.cmd new file mode 100644 index 0000000..2b4a7f6 --- /dev/null +++ b/make-vc.cmd | |||
@@ -0,0 +1,274 @@ | |||
1 | @REM | ||
2 | @REM make-vc.cmd to build Lanes on Visual C++ 2005/08 | ||
3 | @REM | ||
4 | @REM Requires: Windows XP or later (cmd.exe) | ||
5 | @REM Visual C++ 2005/2008 (Express) | ||
6 | @REM LuaBinaries 5.1.3 or Lua for Windows 5.1.3 | ||
7 | @REM | ||
8 | |||
9 | @setlocal | ||
10 | @set LUA_PATH=src\?.lua;tests\?.lua | ||
11 | |||
12 | @if not "%LUA51%"=="" ( | ||
13 | @goto LUA_OK | ||
14 | ) | ||
15 | |||
16 | @REM *** Lua for Windows >=5.1.3.14 (%LUA_DEV%) *** | ||
17 | @REM | ||
18 | @if exist "%LUA_DEV%\lua.exe" ( | ||
19 | @set LUA51=%LUA_DEV% | ||
20 | @goto LUA_OK | ||
21 | ) | ||
22 | |||
23 | @REM *** Lua for Windows (default path) *** | ||
24 | @REM | ||
25 | @if exist "%ProgramFiles%\Lua\5.1\lua.exe" ( | ||
26 | @set LUA51=%ProgramFiles:~0,2%\Progra~1\Lua\5.1 | ||
27 | @goto LUA_OK | ||
28 | ) | ||
29 | |||
30 | @REM *** LuaBinaries (default path) *** | ||
31 | @REM | ||
32 | @if exist "%ProgramFiles%\Lua5.1\lua5.1.exe" ( | ||
33 | @set LUA51=%ProgramFiles:~0,2%\Progra~1\Lua5.1 | ||
34 | @goto LUA_OK | ||
35 | ) | ||
36 | |||
37 | goto ERR_NOLUA | ||
38 | :LUA_OK | ||
39 | |||
40 | @REM --- | ||
41 | @REM %LUA_EXE% = %LUA51%\lua[5.1].exe | ||
42 | @REM %LUAC_EXE% = %LUA51%\luac[5.1].exe | ||
43 | @REM %LUA_LIB% = %LUA51%[\lib] | ||
44 | @REM --- | ||
45 | |||
46 | @set LUA_EXE=%LUA51%\lua5.1.exe | ||
47 | @if exist "%LUA_EXE%" goto LUA_EXE_OK | ||
48 | @set LUA_EXE=%LUA51%\lua.exe | ||
49 | @if exist "%LUA_EXE%" goto LUA_EXE_OK | ||
50 | @echo "Cannot find %LUA51%\lua[5.1].exe | ||
51 | @goto EXIT | ||
52 | :LUA_EXE_OK | ||
53 | |||
54 | @set LUAC_EXE=%LUA51%\luac5.1.exe | ||
55 | @if exist "%LUAC_EXE%" goto LUAC_EXE_OK | ||
56 | @set LUAC_EXE=%LUA51%\luac.exe | ||
57 | @if exist "%LUAC_EXE%" goto LUAC_EXE_OK | ||
58 | @echo "Cannot find %LUA51%\luac[5.1].exe | ||
59 | @goto EXIT | ||
60 | :LUAC_EXE_OK | ||
61 | |||
62 | |||
63 | @if "%1"=="" goto BUILD | ||
64 | @if "%1"=="clean" goto CLEAN | ||
65 | @if "%1"=="test" goto TEST | ||
66 | @if "%1"=="launchtest" goto LAUNCHTEST | ||
67 | @if "%1"=="perftest" goto PERFTEST | ||
68 | @if "%1"=="perftest-plain" goto PERFTEST-PLAIN | ||
69 | @if "%1"=="stress" goto STRESS | ||
70 | @if "%1"=="basic" goto BASIC | ||
71 | @if "%1"=="fifo" goto FIFO | ||
72 | @if "%1"=="keeper" goto KEEPER | ||
73 | @if "%1"=="atomic" goto ATOMIC | ||
74 | @if "%1"=="cyclic" goto CYCLIC | ||
75 | @if "%1"=="timer" goto TIMER | ||
76 | @if "%1"=="recursive" goto RECURSIVE | ||
77 | @if "%1"=="fibonacci" goto FIBONACCI | ||
78 | @if "%1"=="hangtest" goto HANGTEST | ||
79 | @if "%1"=="require" goto REQUIRE | ||
80 | |||
81 | @echo Unknown target: %1 | ||
82 | @echo. | ||
83 | @goto EXIT | ||
84 | |||
85 | :BUILD | ||
86 | @REM LuaBinaries: | ||
87 | @REM The current build system does not show 'lua51-lanes.dll' to | ||
88 | @REM be dependent on more than 'KERNEL32.DLL'. Good. | ||
89 | @REM | ||
90 | @REM Lua for Windows: | ||
91 | @REM Depends on KERNEL32.DLL and LUA5.1.DLL. Good? | ||
92 | |||
93 | @set LUA_LIB=%LUA51% | ||
94 | @if exist "%LUA_LIB%\lua5.1.lib" ( | ||
95 | @echo. | ||
96 | @echo *** | ||
97 | @echo *** Using Lua from: %LUA51% | ||
98 | @echo *** | ||
99 | @echo. | ||
100 | @goto LUA_LIB_OK | ||
101 | ) | ||
102 | |||
103 | @set LUA_LIB=%LUA51%\lib | ||
104 | @if exist "%LUA_LIB%\lua5.1.lib" ( | ||
105 | @echo. | ||
106 | @echo *** | ||
107 | @echo *** Using Lua from: %LUA51% | ||
108 | @echo *** | ||
109 | @echo. | ||
110 | @goto LUA_LIB_OK | ||
111 | ) | ||
112 | @echo Cannot find %LUA51%\[lib\]lua5.1.lib | ||
113 | @goto EXIT | ||
114 | :LUA_LIB_OK | ||
115 | |||
116 | @REM | ||
117 | @REM Precompile src/.lua -> .lch | ||
118 | @REM | ||
119 | @REM Note: we cannot use piping in Windows since we need binary output. | ||
120 | @REM | ||
121 | "%LUAC_EXE%" -o delme src/keeper.lua | ||
122 | "%LUA_EXE%" tools/bin2c.lua -o src/keeper.lch delme | ||
123 | @del delme | ||
124 | |||
125 | @if "%VCINSTALLDIR%"=="" goto ERR_NOVC | ||
126 | |||
127 | @REM | ||
128 | @REM Win32 (Visual C++ 2005/08 Express) build commands | ||
129 | @REM | ||
130 | @REM MS itself has warnings in stdlib.h (4255), winbase.h (4668), several (4820, 4826) | ||
131 | @REM 4054: "type cast from function pointer to data pointer" | ||
132 | @REM 4127: "conditional expression is constant" | ||
133 | @REM 4711: ".. selected for automatic inline expansion" | ||
134 | @REM | ||
135 | @set WARN=/Wall /wd4054 /wd4127 /wd4255 /wd4668 /wd4711 /wd4820 /wd4826 | ||
136 | |||
137 | @REM /LDd: debug DLL | ||
138 | @REM /O2 /LD: release DLL | ||
139 | @REM | ||
140 | @set FLAGS=/O2 /LD | ||
141 | |||
142 | cl %WARN% %FLAGS% /I "%LUA51%\include" /Felua51-lanes.dll src\*.c "%LUA_LIB%\lua5.1.lib" | ||
143 | @REM cl %WARN% %FLAGS% /I "%LUA51%\include" /Felua51-lanes.dll src\*.c "%LUA_LIB%\lua5.1.lib" /link /NODEFAULTLIB:libcmt | ||
144 | |||
145 | @del lua51-lanes.lib | ||
146 | @del lua51-lanes.exp | ||
147 | @goto EXIT | ||
148 | |||
149 | :CLEAN | ||
150 | if exist *.dll del *.dll | ||
151 | if exist delme del delme | ||
152 | @goto EXIT | ||
153 | |||
154 | :TEST | ||
155 | @REM "make test" does not automatically build/update the dll. We're NOT a makefile. :! | ||
156 | @REM | ||
157 | "%LUA_EXE%" tests\basic.lua | ||
158 | @IF errorlevel 1 goto EXIT | ||
159 | |||
160 | "%LUA_EXE%" tests\fifo.lua | ||
161 | @IF errorlevel 1 goto EXIT | ||
162 | |||
163 | "%LUA_EXE%" tests\keeper.lua | ||
164 | @IF errorlevel 1 goto EXIT | ||
165 | |||
166 | "%LUA_EXE%" tests\fibonacci.lua | ||
167 | @IF errorlevel 1 goto EXIT | ||
168 | |||
169 | "%LUA_EXE%" tests\timer.lua | ||
170 | @IF errorlevel 1 goto EXIT | ||
171 | |||
172 | "%LUA_EXE%" tests\atomic.lua | ||
173 | @IF errorlevel 1 goto EXIT | ||
174 | |||
175 | "%LUA_EXE%" tests\cyclic.lua | ||
176 | @IF errorlevel 1 goto EXIT | ||
177 | |||
178 | "%LUA_EXE%" tests\recursive.lua | ||
179 | @IF errorlevel 1 goto EXIT | ||
180 | |||
181 | @goto EXIT | ||
182 | |||
183 | :BASIC | ||
184 | "%LUA_EXE%" tests\basic.lua | ||
185 | @goto EXIT | ||
186 | |||
187 | :FIFO | ||
188 | "%LUA_EXE%" tests\fifo.lua | ||
189 | @goto EXIT | ||
190 | |||
191 | :KEEPER | ||
192 | "%LUA_EXE%" tests\keeper.lua | ||
193 | @goto EXIT | ||
194 | |||
195 | :ATOMIC | ||
196 | "%LUA_EXE%" tests\atomic.lua | ||
197 | @goto EXIT | ||
198 | |||
199 | :CYCLIC | ||
200 | "%LUA_EXE%" tests\cyclic.lua | ||
201 | @goto EXIT | ||
202 | |||
203 | :TIMER | ||
204 | "%LUA_EXE%" tests\timer.lua | ||
205 | @goto EXIT | ||
206 | |||
207 | :RECURSIVE | ||
208 | "%LUA_EXE%" tests\recursive.lua | ||
209 | @goto EXIT | ||
210 | |||
211 | :FIBONACCI | ||
212 | "%LUA_EXE%" tests\fibonacci.lua | ||
213 | @goto EXIT | ||
214 | |||
215 | :HANGTEST | ||
216 | "%LUA_EXE%" tests\hangtest.lua | ||
217 | @goto EXIT | ||
218 | |||
219 | :REQUIRE | ||
220 | "%LUA_EXE%" -e "require'lanes'" | ||
221 | @goto EXIT | ||
222 | |||
223 | REM --- | ||
224 | REM NOTE: 'timeit' is a funny thing; it does _not_ work with quoted | ||
225 | REM long paths, but it _does_ work without the quotes. I have no idea, | ||
226 | REM how it knows the spaces in paths apart from spaces in between | ||
227 | REM parameters. | ||
228 | |||
229 | :LAUNCHTEST | ||
230 | timeit %LUA_EXE% tests\launchtest.lua %2 %3 %4 | ||
231 | @goto EXIT | ||
232 | |||
233 | :PERFTEST | ||
234 | timeit %LUA_EXE% tests\perftest.lua %2 %3 %4 | ||
235 | @goto EXIT | ||
236 | |||
237 | :PERFTEST-PLAIN | ||
238 | timeit %LUA_EXE% tests\perftest.lua --plain %2 %3 %4 | ||
239 | @goto EXIT | ||
240 | |||
241 | :STRESS | ||
242 | "%LUA_EXE%" tests\test.lua | ||
243 | "%LUA_EXE%" tests\perftest.lua 100 | ||
244 | "%LUA_EXE%" tests\perftest.lua 50 -prio=-1,0 | ||
245 | "%LUA_EXE%" tests\perftest.lua 50 -prio=0,-1 | ||
246 | "%LUA_EXE%" tests\perftest.lua 50 -prio=0,2 | ||
247 | "%LUA_EXE%" tests\perftest.lua 50 -prio=2,0 | ||
248 | |||
249 | @echo All seems okay! | ||
250 | @goto EXIT | ||
251 | |||
252 | REM --- | ||
253 | :ERR_NOLUA | ||
254 | @echo *** | ||
255 | @echo *** Please set LUA51 to point to either LuaBinaries or | ||
256 | @echo *** Lua for Windows directory. | ||
257 | @echo *** | ||
258 | @echo *** http://luabinaries.luaforge.net/download.html | ||
259 | @echo *** lua5_1_2_Win32_dll8_lib | ||
260 | @echo *** lua5_1_2_Win32_bin | ||
261 | @echo *** | ||
262 | @echo *** http://luaforge.net/frs/?group_id=377&release_id=1138 | ||
263 | @echo *** | ||
264 | @echo. | ||
265 | @goto EXIT | ||
266 | |||
267 | :ERR_NOVC | ||
268 | @echo *** | ||
269 | @echo *** VCINSTALLDIR not defined; please run 'setup-vc' | ||
270 | @echo *** | ||
271 | @echo. | ||
272 | @goto EXIT | ||
273 | |||
274 | :EXIT | ||
diff --git a/setup-vc.cmd b/setup-vc.cmd new file mode 100644 index 0000000..e93262e --- /dev/null +++ b/setup-vc.cmd | |||
@@ -0,0 +1,90 @@ | |||
1 | @echo off | ||
2 | REM | ||
3 | REM Setting up command line to use Visual C++ 2005/2008 Express | ||
4 | REM | ||
5 | REM Visual C++ 2005: | ||
6 | REM VCINSTALLDIR=C:\Program Files\Microsoft Visual Studio 8\VC | ||
7 | REM VS80COMNTOOLS=C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\ | ||
8 | REM VSINSTALLDIR=C:\Program Files\Microsoft Visual Studio 8 | ||
9 | REM | ||
10 | REM Visual C++ 2008: | ||
11 | REM VCINSTALLDIR=C:\Program Files\Microsoft Visual Studio 9.0\VC | ||
12 | REM VS90COMNTOOLS=C:\Program Files\Microsoft Visual Studio 9.0\Common7\Tools\ | ||
13 | REM VSINSTALLDIR=C:\Program Files\Microsoft Visual Studio 9.0 | ||
14 | REM | ||
15 | |||
16 | REM Test for VC++2005 FIRST, because it is the norm with Lua 5.1.4 | ||
17 | REM LuaBinaries and LfW. All prebuilt modules and lua.exe are built | ||
18 | REM with it. | ||
19 | REM | ||
20 | set VSINSTALLDIR=C:\Program Files\Microsoft Visual Studio 8 | ||
21 | if not exist "%VSINSTALLDIR%\VC\vcvarsall.bat" goto TRY_VC9 | ||
22 | |||
23 | REM Win32 headers must be separately downloaded for VC++2005 | ||
24 | REM (VC++2008 SP1 carries an SDK with it) | ||
25 | REM | ||
26 | set _SDK=C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\SetEnv.cmd | ||
27 | if not exist "%_SDK%" goto ERR_NOSDK | ||
28 | call "%_SDK%" | ||
29 | goto FOUND_VC | ||
30 | |||
31 | :TRY_VC9 | ||
32 | set VSINSTALLDIR=C:\Program Files\Microsoft Visual Studio 9.0 | ||
33 | if not exist "%VSINSTALLDIR%\VC\vcvarsall.bat" goto ERR_NOVC | ||
34 | |||
35 | echo. | ||
36 | echo *** Warning: Visual C++ 2008 in use *** | ||
37 | echo. | ||
38 | echo Using VC++2005 is recommended for runtime compatibility issues | ||
39 | echo (LuaBinaries and LfW use it; if you compile everything from | ||
40 | echo scratch, ignore this message) | ||
41 | echo. | ||
42 | |||
43 | :FOUND_VC | ||
44 | set VCINSTALLDIR=%VSINSTALLDIR%\vc | ||
45 | |||
46 | REM vcvars.bat sets the following values right: | ||
47 | REM | ||
48 | REM PATH=... | ||
49 | REM INCLUDE=%VCINSTALLDIR%\ATLMFC\INCLUDE;%VCINSTALLDIR%\INCLUDE;%VCINSTALLDIR%\PlatformSDK\include;%FrameworkSDKDir%\include;%INCLUDE% | ||
50 | REM LIB=%VCINSTALLDIR%\ATLMFC\LIB;%VCINSTALLDIR%\LIB;%VCINSTALLDIR%\PlatformSDK\lib;%FrameworkSDKDir%\lib;%LIB% | ||
51 | REM LIBPATH=%FrameworkDir%\%FrameworkVersion%;%VCINSTALLDIR%\ATLMFC\LIB | ||
52 | REM | ||
53 | call "%VSINSTALLDIR%\VC\vcvarsall.bat" | ||
54 | |||
55 | REM 'timeit.exe' is part of the MS Server Res Kit Tools (needed for "make perftest") | ||
56 | REM | ||
57 | set _RESKIT=C:\Program Files\Windows Resource Kits\Tools\ | ||
58 | if not exist "%_RESKIT%\timeit.exe" goto WARN_NOTIMEIT | ||
59 | PATH=%PATH%;%_RESKIT% | ||
60 | goto EXIT | ||
61 | |||
62 | :WARN_NOTIMEIT | ||
63 | echo. | ||
64 | echo ** WARNING: Windows Server 2003 Resource Kit Tools - not detected | ||
65 | echo You will need the 'timeit' utility to run 'make perftest' | ||
66 | echo http://www.microsoft.com/downloads/details.aspx?familyid=9D467A69-57FF-4AE7-96EE-B18C4790CFFD | ||
67 | echo. | ||
68 | goto EXIT | ||
69 | |||
70 | REM --- | ||
71 | :ERR_NOVC | ||
72 | echo. | ||
73 | echo ** ERROR: Visual C++ 2005/08 Express - not detected | ||
74 | echo You can set the environment variables separately, and run 'make-vc.cmd' | ||
75 | echo or download the compiler from: | ||
76 | echo http://msdn.microsoft.com/vstudio/express/downloads/ | ||
77 | echo. | ||
78 | goto EXIT | ||
79 | |||
80 | :ERR_NOSDK | ||
81 | echo. | ||
82 | echo ** ERROR: Windows Server 2003 Platform SDK - not detected | ||
83 | echo You will need the core API's of it to compile Win32 applications. | ||
84 | echo http://www.microsoft.com/downloads/details.aspx?familyid=0BAF2B35-C656-4969-ACE8-E4C0C0716ADB | ||
85 | echo. | ||
86 | goto EXIT | ||
87 | |||
88 | :EXIT | ||
89 | set _SDK= | ||
90 | set _RESKIT= | ||
diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..a17e9cd --- /dev/null +++ b/src/Makefile | |||
@@ -0,0 +1,176 @@ | |||
1 | # | ||
2 | # Lanes/src/Makefile | ||
3 | # | ||
4 | # make [LUA=... LUAC=...] Manual build | ||
5 | # make LUAROCKS=1 CFLAGS=... LIBFLAG=... LUA=... LUAC=... LuaRocks automated build | ||
6 | # | ||
7 | |||
8 | MODULE=lanes | ||
9 | |||
10 | SRC=lanes.c threading.c tools.c | ||
11 | |||
12 | OBJ=$(SRC:.c=.o) | ||
13 | |||
14 | # LuaRocks gives 'LIBFLAG' from the outside | ||
15 | # | ||
16 | LIBFLAG=-shared | ||
17 | |||
18 | OPT_FLAGS=-O2 | ||
19 | # -O0 -g | ||
20 | |||
21 | LUA=lua | ||
22 | LUAC=luac | ||
23 | |||
24 | _SO=.so | ||
25 | ifeq "$(findstring MINGW32,$(shell uname -s))" "MINGW32" | ||
26 | _SO=.dll | ||
27 | endif | ||
28 | |||
29 | ifeq "$(LUAROCKS)" "" | ||
30 | ifeq "$(findstring MINGW32,$(shell uname -s))" "MINGW32" | ||
31 | # MinGW MSYS on Windows | ||
32 | # | ||
33 | # - 'lua' and 'luac' expected to be on the path | ||
34 | # - %LUA_DEV% must lead to include files and libraries (Lua for Windows >= 5.1.3.14) | ||
35 | # - %MSCVR80% must be the full pathname of 'msvcr80.dll' | ||
36 | # | ||
37 | ifeq "$(LUA_DEV)" "" | ||
38 | $(error LUA_DEV not defined - try i.e. 'make LUA_DEV=/c/Program\ Files/Lua/5.1') | ||
39 | endif | ||
40 | ifeq "$(MSVCR80)" "" | ||
41 | MSVCR80:=$(LUA_DEV)/install/support/Microsoft.VC80.CRT.SP1/MSVCR80.DLL | ||
42 | ifneq '$(shell test -f "$(MSVCR80)" && echo found)' 'found' | ||
43 | $(error MSVCR80 not defined - set it to full path of msvcr80.dll') | ||
44 | endif | ||
45 | $(warning MSVCR80=$(MSVCR80)) | ||
46 | endif | ||
47 | LUA_FLAGS:=-I "$(LUA_DEV)/include" | ||
48 | LUA_LIBS:="$(LUA_DEV)/lua5.1.dll" -lgcc -lmsvcr80 "$(MSVCR80)" | ||
49 | LIBFLAG=-shared -Wl,-Map,lanes.map | ||
50 | else | ||
51 | # Autodetect LUA_FLAGS and/or LUA_LIBS | ||
52 | # | ||
53 | ifneq "$(shell which pkg-config)" "" | ||
54 | ifeq "$(shell pkg-config --exists lua5.1 && echo 1)" "1" | ||
55 | LUA_FLAGS:=$(shell pkg-config --cflags lua5.1) | ||
56 | LUA_LIBS:=$(shell pkg-config --libs lua5.1) | ||
57 | # | ||
58 | # Ubuntu: -I/usr/include/lua5.1 | ||
59 | # -llua5.1 | ||
60 | else | ||
61 | ifeq "$(shell pkg-config --exists lua && echo 1)" "1" | ||
62 | LUA_FLAGS:=$(shell pkg-config --cflags lua) | ||
63 | LUA_LIBS:=$(shell pkg-config --libs lua) | ||
64 | # | ||
65 | # OS X fink with pkg-config: | ||
66 | # -I/sw/include | ||
67 | # -L/sw/lib -llua -lm | ||
68 | else | ||
69 | $(warning *** 'pkg-config' existed but did not know of 'lua[5.1]' - Good luck!) | ||
70 | LUA_FLAGS:= | ||
71 | LUA_LIBS:=-llua | ||
72 | endif | ||
73 | endif | ||
74 | else | ||
75 | # No 'pkg-config'; try defaults | ||
76 | # | ||
77 | ifeq "$(shell uname -s)" "Darwin" | ||
78 | $(warning *** Assuming 'fink' at default path) | ||
79 | LUA_FLAGS:=-I/sw/include | ||
80 | LUA_LIBS:=-L/sw/lib -llua | ||
81 | else | ||
82 | $(warning *** Assuming an arbitrary Lua installation; try installing 'pkg-config') | ||
83 | LUA_FLAGS:= | ||
84 | LUA_LIBS:=-llua | ||
85 | endif | ||
86 | endif | ||
87 | endif | ||
88 | |||
89 | ifeq "$(shell uname -s)" "Darwin" | ||
90 | # Some machines need 'MACOSX_DEPLOYMENT_TARGET=10.3' for using '-undefined dynamic_lookup' | ||
91 | # (at least PowerPC running 10.4.11); does not harm the others | ||
92 | # | ||
93 | CC = MACOSX_DEPLOYMENT_TARGET=10.3 gcc | ||
94 | LIBFLAG = -bundle -undefined dynamic_lookup | ||
95 | endif | ||
96 | |||
97 | CFLAGS=-Wall -Werror $(OPT_FLAGS) $(LUA_FLAGS) | ||
98 | LIBS=$(LUA_LIBS) | ||
99 | endif | ||
100 | |||
101 | #--- | ||
102 | # PThread platform specifics | ||
103 | # | ||
104 | ifeq "$(shell uname -s)" "Linux" | ||
105 | # -D_GNU_SOURCE needed for 'pthread_mutexattr_settype' | ||
106 | CFLAGS += -D_GNU_SOURCE -fPIC | ||
107 | |||
108 | # Use of -DUSE_PTHREAD_TIMEDJOIN is possible, but not recommended (slower & keeps threads | ||
109 | # unreleased somewhat longer) | ||
110 | #CFLAGS += -DUSE_PTHREAD_TIMEDJOIN | ||
111 | |||
112 | LIBS += -lpthread | ||
113 | endif | ||
114 | |||
115 | ifeq "$(shell uname -s)" "BSD" | ||
116 | LIBS += -lpthread | ||
117 | endif | ||
118 | |||
119 | #--- | ||
120 | all: lua51-$(MODULE)$(_SO) | ||
121 | |||
122 | %.o: %.c *.h Makefile | ||
123 | |||
124 | # Note: Don't put $(LUA_LIBS) ahead of $^; MSYS will not like that (I think) | ||
125 | # | ||
126 | lua51-$(MODULE)$(_SO): $(OBJ) | ||
127 | $(CC) $(LIBFLAG) $(LIBS) $^ $(LUA_LIBS) -o $@ | ||
128 | |||
129 | clean: | ||
130 | -rm -rf lua51-$(MODULE)$(_SO) *.lch *.o *.tmp *.map | ||
131 | |||
132 | lanes.o: keeper.lch | ||
133 | |||
134 | # Note: 'luac -o -' could be used on systems other than Windows (where pipes | ||
135 | # are binary). We need to support MinGW as well, so a temporary file. | ||
136 | # | ||
137 | %.lch: %.lua | ||
138 | $(LUAC) -o $@.tmp $< | ||
139 | $(LUA) ../tools/bin2c.lua $@.tmp -o $@ | ||
140 | -rm $@.tmp | ||
141 | |||
142 | #--- | ||
143 | # NSLU2 "slug" Linux ARM | ||
144 | # | ||
145 | nslu2: | ||
146 | $(MAKE) all CFLAGS="$(CFLAGS) -I/opt/include -L/opt/lib -D_GNU_SOURCE -lpthread" | ||
147 | |||
148 | #--- | ||
149 | # Cross compiling to Win32 (MinGW on OS X Intel) | ||
150 | # | ||
151 | # Point WIN32_LUA51 to an extraction of LuaBinaries dll8 and dev packages. | ||
152 | # | ||
153 | # Note: Only works on platforms with same endianess (i.e. not from PowerPC OS X, | ||
154 | # since 'luac' uses the host endianess) | ||
155 | # | ||
156 | # EXPERIMENTAL; NOT TESTED OF LATE. | ||
157 | # | ||
158 | MINGW_GCC=mingw32-gcc | ||
159 | # i686-pc-mingw32-gcc | ||
160 | |||
161 | win32: $(WIN32_LUA51)/include/lua.h | ||
162 | $(MAKE) build CC=$(MINGW_GCC) \ | ||
163 | LUA_FLAGS=-I$(WIN32_LUA51)/include \ | ||
164 | LUA_LIBS="-L$(WIN32_LUA51) -llua51" \ | ||
165 | _SO=.dll \ | ||
166 | SO_FLAGS=-shared \ | ||
167 | LUA=lua51 \ | ||
168 | LUAC=luac51 | ||
169 | |||
170 | $(WIN32_LUA51)/include/lua.h: | ||
171 | @echo "Usage: make win32 WIN32_LUA51=<path of extracted LuaBinaries dll8 and dev packages>" | ||
172 | @echo " [MINGW_GCC=...mingw32-gcc]" | ||
173 | @false | ||
174 | |||
175 | .PROXY: all clean nslu2 win32 | ||
176 | |||
diff --git a/src/keeper.lua b/src/keeper.lua new file mode 100644 index 0000000..f76173b --- /dev/null +++ b/src/keeper.lua | |||
@@ -0,0 +1,244 @@ | |||
1 | -- | ||
2 | -- KEEPER.LUA | ||
3 | -- | ||
4 | -- Keeper state logic | ||
5 | -- | ||
6 | -- This code is read in for each "keeper state", which are the hidden, inter- | ||
7 | -- mediate data stores used by Lanes inter-state communication objects. | ||
8 | -- | ||
9 | -- Author: Asko Kauppi <akauppi@gmail.com> | ||
10 | -- | ||
11 | --[[ | ||
12 | =============================================================================== | ||
13 | |||
14 | Copyright (C) 2008 Asko Kauppi <akauppi@gmail.com> | ||
15 | |||
16 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
17 | of this software and associated documentation files (the "Software"), to deal | ||
18 | in the Software without restriction, including without limitation the rights | ||
19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
20 | copies of the Software, and to permit persons to whom the Software is | ||
21 | furnished to do so, subject to the following conditions: | ||
22 | |||
23 | The above copyright notice and this permission notice shall be included in | ||
24 | all copies or substantial portions of the Software. | ||
25 | |||
26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
32 | THE SOFTWARE. | ||
33 | |||
34 | =============================================================================== | ||
35 | ]]-- | ||
36 | |||
37 | -- unique key instead of 'nil' in queues | ||
38 | -- | ||
39 | assert( nil_sentinel ) | ||
40 | |||
41 | -- We only need to have base and table libraries (and io for debugging) | ||
42 | -- | ||
43 | local table_remove= assert( table.remove ) | ||
44 | local table_concat= assert( table.concat ) | ||
45 | |||
46 | local function WR(...) | ||
47 | if io then | ||
48 | io.stderr:write( table_concat({...},'\t').."\n" ) | ||
49 | end | ||
50 | end | ||
51 | |||
52 | ----- | ||
53 | -- Actual data store | ||
54 | -- | ||
55 | -- { [linda_deep_ud]= { key= val [, ...] } | ||
56 | -- ... | ||
57 | -- } | ||
58 | -- | ||
59 | local _data= {} | ||
60 | |||
61 | ----- | ||
62 | -- Entries queued for use when the existing 'data[ud][key]' entry is consumed. | ||
63 | -- | ||
64 | -- { [linda_deep_ud]= { key= { val [, ... } [, ...] } | ||
65 | -- ... | ||
66 | -- } | ||
67 | -- | ||
68 | local _incoming= {} | ||
69 | |||
70 | ----- | ||
71 | -- Length limits (if any) for queues | ||
72 | -- | ||
73 | -- 0: don't queue values at all; ':send()' waits if the slot is not vacant | ||
74 | -- N: allow N values to be queued (slot itself + N-1); wait if full | ||
75 | -- nil: no limits, '_incoming' may grow endlessly | ||
76 | -- | ||
77 | local _limits= {} | ||
78 | |||
79 | ----- | ||
80 | -- data_tbl, incoming_tbl, limits_tbl = tables( linda_deep_ud ) | ||
81 | -- | ||
82 | -- Gives appropriate tables for a certain Linda (creates them if needed) | ||
83 | -- | ||
84 | local function tables( ud ) | ||
85 | -- tables are created either all or nothing | ||
86 | -- | ||
87 | if not _data[ud] then | ||
88 | _data[ud]= {} | ||
89 | _incoming[ud]= {} | ||
90 | _limits[ud]= {} | ||
91 | end | ||
92 | return _data[ud], _incoming[ud], _limits[ud] | ||
93 | end | ||
94 | |||
95 | |||
96 | local function DEBUG(title,ud,key) | ||
97 | assert( title and ud and key ) | ||
98 | |||
99 | local data,incoming,_= tables(ud) | ||
100 | |||
101 | local s= tostring(data[key]) | ||
102 | for _,v in ipairs( incoming[key] or {} ) do | ||
103 | s= s..", "..tostring(v) | ||
104 | end | ||
105 | WR( "*** "..title.." ("..tostring(key).."): ", s ) | ||
106 | end | ||
107 | |||
108 | |||
109 | ----- | ||
110 | -- bool= send( linda_deep_ud, key, ... ) | ||
111 | -- | ||
112 | -- Send new data (1..N) to 'key' slot. This send is atomic; all the values | ||
113 | -- end up one after each other (this is why having possibility for sending | ||
114 | -- multiple values in one call is deemed important). | ||
115 | -- | ||
116 | -- If the queue has a limit, values are sent only if all of them fit in. | ||
117 | -- | ||
118 | -- Returns: 'true' if all the values were placed | ||
119 | -- 'false' if sending would exceed the queue limit (wait & retry) | ||
120 | -- | ||
121 | function send( ud, key, ... ) | ||
122 | |||
123 | local data,incoming,limits= tables(ud) | ||
124 | |||
125 | local n= select('#',...) | ||
126 | if n==0 then return true end -- nothing to send | ||
127 | |||
128 | -- Initialize queue for all keys that have been used with ':send()' | ||
129 | -- | ||
130 | if incoming[key]==nil then | ||
131 | incoming[key]= {} | ||
132 | end | ||
133 | |||
134 | local len= data[key] and 1+#incoming[key] or 0 | ||
135 | local m= limits[key] | ||
136 | |||
137 | if m and len+n > m then | ||
138 | return false -- would exceed the limit; try again later | ||
139 | end | ||
140 | |||
141 | for i=1,n do | ||
142 | local val= select(i,...) | ||
143 | |||
144 | -- 'nil' in the data replaced by sentinel | ||
145 | if val==nil then | ||
146 | val= nil_sentinel | ||
147 | end | ||
148 | |||
149 | if len==0 then | ||
150 | data[key]= val | ||
151 | len= 1 | ||
152 | else | ||
153 | incoming[key][len]= val | ||
154 | len= len+1 | ||
155 | end | ||
156 | end | ||
157 | return true | ||
158 | end | ||
159 | |||
160 | |||
161 | ----- | ||
162 | -- [val, key]= receive( linda_deep_ud, key [, ...] ) | ||
163 | -- | ||
164 | -- Read any of the given keys, consuming the data found. Keys are read in | ||
165 | -- order. | ||
166 | -- | ||
167 | function receive( ud, ... ) | ||
168 | |||
169 | local data,incoming,_= tables(ud) | ||
170 | |||
171 | for i=1,select('#',...) do | ||
172 | local key= select(i,...) | ||
173 | local val= data[key] | ||
174 | |||
175 | if val~=nil then | ||
176 | if incoming[key] and incoming[key][1]~=nil then | ||
177 | -- pop [1] from 'incoming[key]' into the actual slot | ||
178 | data[key]= table_remove( incoming[key], 1 ) | ||
179 | else | ||
180 | data[key]= nil -- empty the slot | ||
181 | end | ||
182 | if val==nil_sentinel then | ||
183 | val= nil | ||
184 | end | ||
185 | return val, key | ||
186 | end | ||
187 | end | ||
188 | --return nil | ||
189 | end | ||
190 | |||
191 | |||
192 | ----- | ||
193 | -- = limit( linda_deep_ud, key, uint ) | ||
194 | -- | ||
195 | function limit( ud, key, n ) | ||
196 | |||
197 | local _,_,limits= tables(ud) | ||
198 | |||
199 | limits[key]= n | ||
200 | end | ||
201 | |||
202 | |||
203 | ----- | ||
204 | -- void= set( linda_deep_ud, key, [val] ) | ||
205 | -- | ||
206 | function set( ud, key, val ) | ||
207 | |||
208 | local data,incoming,_= tables(ud) | ||
209 | |||
210 | -- Setting a key to 'nil' really clears it; only queing uses sentinels. | ||
211 | -- | ||
212 | data[key]= val | ||
213 | incoming[key]= nil | ||
214 | end | ||
215 | |||
216 | |||
217 | ----- | ||
218 | -- [val]= get( linda_deep_ud, key ) | ||
219 | -- | ||
220 | function get( ud, key ) | ||
221 | |||
222 | local data,_,_= tables(ud) | ||
223 | |||
224 | local val= data[key] | ||
225 | if val==nil_sentinel then | ||
226 | val= nil | ||
227 | end | ||
228 | return val | ||
229 | end | ||
230 | |||
231 | |||
232 | ----- | ||
233 | -- void= clear( linda_deep_ud ) | ||
234 | -- | ||
235 | -- Clear the data structures used for a Linda (at its destructor) | ||
236 | -- | ||
237 | function clear( ud ) | ||
238 | |||
239 | _data[ud]= nil | ||
240 | _incoming[ud]= nil | ||
241 | _limits[ud]= nil | ||
242 | end | ||
243 | |||
244 | |||
diff --git a/src/lanes.c b/src/lanes.c new file mode 100644 index 0000000..9b36e4d --- /dev/null +++ b/src/lanes.c | |||
@@ -0,0 +1,1849 @@ | |||
1 | /* | ||
2 | * LANES.C Copyright (c) 2007-08, Asko Kauppi | ||
3 | * | ||
4 | * Multithreading in Lua. | ||
5 | * | ||
6 | * History: | ||
7 | * 20-Oct-08 (2.0.2): Added closing of free-running threads, but it does | ||
8 | * not seem to eliminate the occasional segfaults at process | ||
9 | * exit. | ||
10 | * ... | ||
11 | * 24-Jun-08 .. 14-Aug-08 AKa: Major revise, Lanes 2008 version (2.0 rc1) | ||
12 | * ... | ||
13 | * 18-Sep-06 AKa: Started the module. | ||
14 | * | ||
15 | * Platforms (tested internally): | ||
16 | * OS X (10.5.4 PowerPC/Intel) | ||
17 | * Linux x86 (Ubuntu 8.04) | ||
18 | * Win32 (Windows XP Home SP2, Visual C++ 2005/2008 Express) | ||
19 | * PocketPC (TBD) | ||
20 | * | ||
21 | * Platforms (tested externally): | ||
22 | * Win32 (MSYS) by Ross Berteig. | ||
23 | * | ||
24 | * Platforms (testers appreciated): | ||
25 | * Win64 - should work??? | ||
26 | * Linux x64 - should work | ||
27 | * FreeBSD - should work | ||
28 | * QNX - porting shouldn't be hard | ||
29 | * Sun Solaris - porting shouldn't be hard | ||
30 | * | ||
31 | * References: | ||
32 | * "Porting multithreaded applications from Win32 to Mac OS X": | ||
33 | * <http://developer.apple.com/macosx/multithreadedprogramming.html> | ||
34 | * | ||
35 | * Pthreads: | ||
36 | * <http://vergil.chemistry.gatech.edu/resources/programming/threads.html> | ||
37 | * | ||
38 | * MSDN: <http://msdn2.microsoft.com/en-us/library/ms686679.aspx> | ||
39 | * | ||
40 | * <http://ridiculousfish.com/blog/archives/2007/02/17/barrier> | ||
41 | * | ||
42 | * Defines: | ||
43 | * -DLINUX_SCHED_RR: all threads are lifted to SCHED_RR category, to | ||
44 | * allow negative priorities (-2,-1) be used. Even without this, | ||
45 | * using priorities will require 'sudo' privileges on Linux. | ||
46 | * | ||
47 | * -DUSE_PTHREAD_TIMEDJOIN: use 'pthread_timedjoin_np()' for waiting | ||
48 | * for threads with a timeout. This changes the thread cleanup | ||
49 | * mechanism slightly (cleans up at the join, not once the thread | ||
50 | * has finished). May or may not be a good idea to use it. | ||
51 | * Available only in selected operating systems (Linux). | ||
52 | * | ||
53 | * Bugs: | ||
54 | * | ||
55 | * To-do: | ||
56 | * | ||
57 | * ... | ||
58 | */ | ||
59 | |||
60 | const char *VERSION= "2.0.3"; | ||
61 | |||
62 | /* | ||
63 | =============================================================================== | ||
64 | |||
65 | Copyright (C) 2007-08 Asko Kauppi <akauppi@gmail.com> | ||
66 | |||
67 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
68 | of this software and associated documentation files (the "Software"), to deal | ||
69 | in the Software without restriction, including without limitation the rights | ||
70 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
71 | copies of the Software, and to permit persons to whom the Software is | ||
72 | furnished to do so, subject to the following conditions: | ||
73 | |||
74 | The above copyright notice and this permission notice shall be included in | ||
75 | all copies or substantial portions of the Software. | ||
76 | |||
77 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
78 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
79 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
80 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
81 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
82 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
83 | THE SOFTWARE. | ||
84 | |||
85 | =============================================================================== | ||
86 | */ | ||
87 | #include <string.h> | ||
88 | #include <stdio.h> | ||
89 | #include <ctype.h> | ||
90 | #include <stdlib.h> | ||
91 | |||
92 | #include "lua.h" | ||
93 | #include "lauxlib.h" | ||
94 | |||
95 | #include "threading.h" | ||
96 | #include "tools.h" | ||
97 | |||
98 | #if !((defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC)) | ||
99 | # include <sys/time.h> | ||
100 | #endif | ||
101 | |||
102 | /* geteuid() */ | ||
103 | #ifdef PLATFORM_LINUX | ||
104 | # include <unistd.h> | ||
105 | # include <sys/types.h> | ||
106 | #endif | ||
107 | |||
108 | /* The selected number is not optimal; needs to be tested. Even using just | ||
109 | * one keeper state may be good enough (depends on the number of Lindas used | ||
110 | * in the applications). | ||
111 | */ | ||
112 | #define KEEPER_STATES_N 1 // 6 | ||
113 | |||
114 | /* Do you want full call stacks, or just the line where the error happened? | ||
115 | * | ||
116 | * TBD: The full stack feature does not seem to work (try 'make error'). | ||
117 | */ | ||
118 | #define ERROR_FULL_STACK | ||
119 | |||
120 | #ifdef ERROR_FULL_STACK | ||
121 | # define STACK_TRACE_KEY ((void*)lane_error) // used as registry key | ||
122 | #endif | ||
123 | |||
124 | /* | ||
125 | * Lua code for the keeper states (baked in) | ||
126 | */ | ||
127 | static char keeper_chunk[]= | ||
128 | #include "keeper.lch" | ||
129 | |||
130 | struct s_lane; | ||
131 | static bool_t cancel_test( lua_State *L ); | ||
132 | static void cancel_error( lua_State *L ); | ||
133 | |||
134 | #define CANCEL_TEST_KEY ((void*)cancel_test) // used as registry key | ||
135 | #define CANCEL_ERROR ((void*)cancel_error) // 'cancel_error' sentinel | ||
136 | |||
137 | /* | ||
138 | * registry[FINALIZER_REG_KEY] is either nil (no finalizers) or a table | ||
139 | * of functions that Lanes will call after the executing 'pcall' has ended. | ||
140 | * | ||
141 | * We're NOT using the GC system for finalizer mainly because providing the | ||
142 | * error (and maybe stack trace) parameters to the finalizer functions would | ||
143 | * anyways complicate that approach. | ||
144 | */ | ||
145 | #define FINALIZER_REG_KEY ((void*)LG_set_finalizer) | ||
146 | |||
147 | struct s_Linda; | ||
148 | |||
149 | #if 1 | ||
150 | # define DEBUG_SIGNAL( msg, signal_ref ) /* */ | ||
151 | #else | ||
152 | # define DEBUG_SIGNAL( msg, signal_ref ) \ | ||
153 | { int i; unsigned char *ptr; char buf[999]; \ | ||
154 | sprintf( buf, ">>> " msg ": %p\t", (signal_ref) ); \ | ||
155 | ptr= (unsigned char *)signal_ref; \ | ||
156 | for( i=0; i<sizeof(*signal_ref); i++ ) { \ | ||
157 | sprintf( strchr(buf,'\0'), "%02x %c ", ptr[i], ptr[i] ); \ | ||
158 | } \ | ||
159 | fprintf( stderr, "%s\n", buf ); \ | ||
160 | } | ||
161 | #endif | ||
162 | |||
163 | static bool_t thread_cancel( struct s_lane *s, double secs, bool_t force ); | ||
164 | |||
165 | |||
166 | /* | ||
167 | * Push a table stored in registry onto Lua stack. | ||
168 | * | ||
169 | * If there is no existing table, create one if 'create' is TRUE. | ||
170 | * | ||
171 | * Returns: TRUE if a table was pushed | ||
172 | * FALSE if no table found, not created, and nothing pushed | ||
173 | */ | ||
174 | static bool_t push_registry_table( lua_State *L, void *key, bool_t create ) { | ||
175 | |||
176 | STACK_GROW(L,3); | ||
177 | |||
178 | lua_pushlightuserdata( L, key ); | ||
179 | lua_gettable( L, LUA_REGISTRYINDEX ); | ||
180 | |||
181 | if (lua_isnil(L,-1)) { | ||
182 | lua_pop(L,1); | ||
183 | |||
184 | if (!create) return FALSE; // nothing pushed | ||
185 | |||
186 | lua_newtable(L); | ||
187 | lua_pushlightuserdata( L, key ); | ||
188 | lua_pushvalue(L,-2); // duplicate of the table | ||
189 | lua_settable( L, LUA_REGISTRYINDEX ); | ||
190 | |||
191 | // [-1]: table that's also bound in registry | ||
192 | } | ||
193 | return TRUE; // table pushed | ||
194 | } | ||
195 | |||
196 | |||
197 | /*---=== Serialize require ===--- | ||
198 | */ | ||
199 | |||
200 | static MUTEX_T require_cs; | ||
201 | |||
202 | //--- | ||
203 | // [val]= new_require( ... ) | ||
204 | // | ||
205 | // Call 'old_require' but only one lane at a time. | ||
206 | // | ||
207 | // Upvalues: [1]: original 'require' function | ||
208 | // | ||
209 | static int new_require( lua_State *L ) { | ||
210 | int rc; | ||
211 | int args= lua_gettop(L); | ||
212 | |||
213 | STACK_GROW(L,1); | ||
214 | STACK_CHECK(L) | ||
215 | |||
216 | // Using 'lua_pcall()' to catch errors; otherwise a failing 'require' would | ||
217 | // leave us locked, blocking any future 'require' calls from other lanes. | ||
218 | // | ||
219 | MUTEX_LOCK( &require_cs ); | ||
220 | { | ||
221 | lua_pushvalue( L, lua_upvalueindex(1) ); | ||
222 | lua_insert( L, 1 ); | ||
223 | |||
224 | rc= lua_pcall( L, args, 1 /*retvals*/, 0 /*errfunc*/ ); | ||
225 | // | ||
226 | // LUA_ERRRUN / LUA_ERRMEM | ||
227 | } | ||
228 | MUTEX_UNLOCK( &require_cs ); | ||
229 | |||
230 | if (rc) lua_error(L); // error message already at [-1] | ||
231 | |||
232 | STACK_END(L,0) | ||
233 | return 1; | ||
234 | } | ||
235 | |||
236 | /* | ||
237 | * Serialize calls to 'require', if it exists | ||
238 | */ | ||
239 | static | ||
240 | void serialize_require( lua_State *L ) { | ||
241 | |||
242 | STACK_GROW(L,1); | ||
243 | STACK_CHECK(L) | ||
244 | |||
245 | // Check 'require' is there; if not, do nothing | ||
246 | // | ||
247 | lua_getglobal( L, "require" ); | ||
248 | if (lua_isfunction( L, -1 )) { | ||
249 | // [-1]: original 'require' function | ||
250 | |||
251 | lua_pushcclosure( L, new_require, 1 /*upvalues*/ ); | ||
252 | lua_setglobal( L, "require" ); | ||
253 | |||
254 | } else { | ||
255 | // [-1]: nil | ||
256 | lua_pop(L,1); | ||
257 | } | ||
258 | |||
259 | STACK_END(L,0) | ||
260 | } | ||
261 | |||
262 | |||
263 | /*---=== Keeper states ===--- | ||
264 | */ | ||
265 | |||
266 | /* | ||
267 | * Pool of keeper states | ||
268 | * | ||
269 | * Access to keeper states is locked (only one OS thread at a time) so the | ||
270 | * bigger the pool, the less chances of unnecessary waits. Lindas map to the | ||
271 | * keepers randomly, by a hash. | ||
272 | */ | ||
273 | struct s_Keeper { | ||
274 | MUTEX_T lock_; | ||
275 | lua_State *L; | ||
276 | } keeper[ KEEPER_STATES_N ]; | ||
277 | |||
278 | /* We could use an empty table in 'keeper.lua' as the sentinel, but maybe | ||
279 | * checking for a lightuserdata is faster. | ||
280 | */ | ||
281 | static bool_t nil_sentinel; | ||
282 | |||
283 | /* | ||
284 | * Initialize keeper states | ||
285 | * | ||
286 | * If there is a problem, return an error message (NULL for okay). | ||
287 | * | ||
288 | * Note: Any problems would be design flaws; the created Lua state is left | ||
289 | * unclosed, because it does not really matter. In production code, this | ||
290 | * function never fails. | ||
291 | */ | ||
292 | static const char *init_keepers(void) { | ||
293 | unsigned int i; | ||
294 | for( i=0; i<KEEPER_STATES_N; i++ ) { | ||
295 | |||
296 | // Initialize Keeper states with bare minimum of libs (those required | ||
297 | // by 'keeper.lua') | ||
298 | // | ||
299 | lua_State *L= luaL_newstate(); | ||
300 | if (!L) return "out of memory"; | ||
301 | |||
302 | luaG_openlibs( L, "io,table" ); // 'io' for debugging messages | ||
303 | |||
304 | lua_pushlightuserdata( L, &nil_sentinel ); | ||
305 | lua_setglobal( L, "nil_sentinel" ); | ||
306 | |||
307 | // Read in the preloaded chunk (and run it) | ||
308 | // | ||
309 | if (luaL_loadbuffer( L, keeper_chunk, sizeof(keeper_chunk), "=lanes_keeper" )) | ||
310 | return "luaL_loadbuffer() failed"; // LUA_ERRMEM | ||
311 | |||
312 | if (lua_pcall( L, 0 /*args*/, 0 /*results*/, 0 /*errfunc*/ )) { | ||
313 | // LUA_ERRRUN / LUA_ERRMEM / LUA_ERRERR | ||
314 | // | ||
315 | const char *err= lua_tostring(L,-1); | ||
316 | assert(err); | ||
317 | return err; | ||
318 | } | ||
319 | |||
320 | MUTEX_INIT( &keeper[i].lock_ ); | ||
321 | keeper[i].L= L; | ||
322 | } | ||
323 | return NULL; // ok | ||
324 | } | ||
325 | |||
326 | static | ||
327 | struct s_Keeper *keeper_acquire( const void *ptr ) { | ||
328 | /* | ||
329 | * Any hashing will do that maps pointers to 0..KEEPER_STATES_N-1 | ||
330 | * consistently. | ||
331 | * | ||
332 | * Pointers are often aligned by 8 or so - ignore the low order bits | ||
333 | */ | ||
334 | unsigned int i= ((unsigned long)(ptr) >> 3) % KEEPER_STATES_N; | ||
335 | struct s_Keeper *K= &keeper[i]; | ||
336 | |||
337 | MUTEX_LOCK( &K->lock_ ); | ||
338 | return K; | ||
339 | } | ||
340 | |||
341 | static | ||
342 | void keeper_release( struct s_Keeper *K ) { | ||
343 | MUTEX_UNLOCK( &K->lock_ ); | ||
344 | } | ||
345 | |||
346 | /* | ||
347 | * Call a function ('func_name') in the keeper state, and pass on the returned | ||
348 | * values to 'L'. | ||
349 | * | ||
350 | * 'linda': deep Linda pointer (used only as a unique table key, first parameter) | ||
351 | * 'starting_index': first of the rest of parameters (none if 0) | ||
352 | * | ||
353 | * Returns: number of return values (pushed to 'L') | ||
354 | */ | ||
355 | static | ||
356 | int keeper_call( lua_State* K, const char *func_name, | ||
357 | lua_State *L, struct s_Linda *linda, uint_t starting_index ) { | ||
358 | |||
359 | int args= starting_index ? (lua_gettop(L) - starting_index +1) : 0; | ||
360 | int Ktos= lua_gettop(K); | ||
361 | int retvals; | ||
362 | |||
363 | lua_getglobal( K, func_name ); | ||
364 | ASSERT_L( lua_isfunction(K,-1) ); | ||
365 | |||
366 | STACK_GROW( K, 1 ); | ||
367 | lua_pushlightuserdata( K, linda ); | ||
368 | |||
369 | luaG_inter_copy( L,K, args ); // L->K | ||
370 | lua_call( K, 1+args, LUA_MULTRET ); | ||
371 | |||
372 | retvals= lua_gettop(K) - Ktos; | ||
373 | |||
374 | luaG_inter_move( K,L, retvals ); // K->L | ||
375 | return retvals; | ||
376 | } | ||
377 | |||
378 | |||
379 | /*---=== Linda ===--- | ||
380 | */ | ||
381 | |||
382 | /* | ||
383 | * Actual data is kept within a keeper state, which is hashed by the 's_Linda' | ||
384 | * pointer (which is same to all userdatas pointing to it). | ||
385 | */ | ||
386 | struct s_Linda { | ||
387 | SIGNAL_T read_happened; | ||
388 | SIGNAL_T write_happened; | ||
389 | }; | ||
390 | |||
391 | static int LG_linda_id( lua_State* ); | ||
392 | |||
393 | #define lua_toLinda(L,n) ((struct s_Linda *)luaG_todeep( L, LG_linda_id, n )) | ||
394 | |||
395 | |||
396 | /* | ||
397 | * bool= linda_send( linda_ud, [timeout_secs=-1,] key_num|str|bool|lightuserdata, ... ) | ||
398 | * | ||
399 | * Send one or more values to a Linda. If there is a limit, all values must fit. | ||
400 | * | ||
401 | * Returns: 'true' if the value was queued | ||
402 | * 'false' for timeout (only happens when the queue size is limited) | ||
403 | */ | ||
404 | LUAG_FUNC( linda_send ) { | ||
405 | struct s_Linda *linda= lua_toLinda( L, 1 ); | ||
406 | bool_t ret; | ||
407 | bool_t cancel= FALSE; | ||
408 | struct s_Keeper *K; | ||
409 | time_d timeout= -1.0; | ||
410 | uint_t key_i= 2; // index of first key, if timeout not there | ||
411 | |||
412 | if (lua_isnumber(L,2)) { | ||
413 | timeout= SIGNAL_TIMEOUT_PREPARE( lua_tonumber(L,2) ); | ||
414 | key_i++; | ||
415 | } else if (lua_isnil(L,2)) | ||
416 | key_i++; | ||
417 | |||
418 | if (lua_isnil(L,key_i)) | ||
419 | luaL_error( L, "nil key" ); | ||
420 | |||
421 | STACK_GROW(L,1); | ||
422 | |||
423 | K= keeper_acquire( linda ); | ||
424 | { | ||
425 | lua_State *KL= K->L; // need to do this for 'STACK_CHECK' | ||
426 | STACK_CHECK(KL) | ||
427 | while(TRUE) { | ||
428 | int pushed; | ||
429 | |||
430 | STACK_MID(KL,0) | ||
431 | pushed= keeper_call( K->L, "send", L, linda, key_i ); | ||
432 | ASSERT_L( pushed==1 ); | ||
433 | |||
434 | ret= lua_toboolean(L,-1); | ||
435 | lua_pop(L,1); | ||
436 | |||
437 | if (ret) { | ||
438 | // Wake up ALL waiting threads | ||
439 | // | ||
440 | SIGNAL_ALL( &linda->write_happened ); | ||
441 | break; | ||
442 | |||
443 | } else if (timeout==0.0) { | ||
444 | break; /* no wait; instant timeout */ | ||
445 | |||
446 | } else { | ||
447 | /* limit faced; push until timeout */ | ||
448 | |||
449 | cancel= cancel_test( L ); // testing here causes no delays | ||
450 | if (cancel) break; | ||
451 | |||
452 | // K lock will be released for the duration of wait and re-acquired | ||
453 | // | ||
454 | if (!SIGNAL_WAIT( &linda->read_happened, &K->lock_, timeout )) | ||
455 | break; // timeout | ||
456 | } | ||
457 | } | ||
458 | STACK_END(KL,0) | ||
459 | } | ||
460 | keeper_release(K); | ||
461 | |||
462 | if (cancel) | ||
463 | cancel_error(L); | ||
464 | |||
465 | lua_pushboolean( L, ret ); | ||
466 | return 1; | ||
467 | } | ||
468 | |||
469 | |||
470 | /* | ||
471 | * [val, key]= linda_receive( linda_ud, [timeout_secs_num=-1], key_num|str|bool|lightuserdata [, ...] ) | ||
472 | * | ||
473 | * Receive a value from Linda, consuming it. | ||
474 | * | ||
475 | * Returns: value received (which is consumed from the slot) | ||
476 | * key which had it | ||
477 | */ | ||
478 | LUAG_FUNC( linda_receive ) { | ||
479 | struct s_Linda *linda= lua_toLinda( L, 1 ); | ||
480 | int pushed; | ||
481 | bool_t cancel= FALSE; | ||
482 | struct s_Keeper *K; | ||
483 | time_d timeout= -1.0; | ||
484 | uint_t key_i= 2; | ||
485 | |||
486 | if (lua_isnumber(L,2)) { | ||
487 | timeout= SIGNAL_TIMEOUT_PREPARE( lua_tonumber(L,2) ); | ||
488 | key_i++; | ||
489 | } else if (lua_isnil(L,2)) | ||
490 | key_i++; | ||
491 | |||
492 | K= keeper_acquire( linda ); | ||
493 | { | ||
494 | while(TRUE) { | ||
495 | pushed= keeper_call( K->L, "receive", L, linda, key_i ); | ||
496 | if (pushed) { | ||
497 | ASSERT_L( pushed==2 ); | ||
498 | |||
499 | // To be done from within the 'K' locking area | ||
500 | // | ||
501 | SIGNAL_ALL( &linda->read_happened ); | ||
502 | break; | ||
503 | |||
504 | } else if (timeout==0.0) { | ||
505 | break; /* instant timeout */ | ||
506 | |||
507 | } else { /* nothing received; wait until timeout */ | ||
508 | |||
509 | cancel= cancel_test( L ); // testing here causes no delays | ||
510 | if (cancel) break; | ||
511 | |||
512 | // Release the K lock for the duration of wait, and re-acquire | ||
513 | // | ||
514 | if (!SIGNAL_WAIT( &linda->write_happened, &K->lock_, timeout )) | ||
515 | break; | ||
516 | } | ||
517 | } | ||
518 | } | ||
519 | keeper_release(K); | ||
520 | |||
521 | if (cancel) | ||
522 | cancel_error(L); | ||
523 | |||
524 | return pushed; | ||
525 | } | ||
526 | |||
527 | |||
528 | /* | ||
529 | * = linda_set( linda_ud, key_num|str|bool|lightuserdata [,value] ) | ||
530 | * | ||
531 | * Set a value to Linda. | ||
532 | * | ||
533 | * Existing slot value is replaced, and possible queue entries removed. | ||
534 | */ | ||
535 | LUAG_FUNC( linda_set ) { | ||
536 | struct s_Linda *linda= lua_toLinda( L, 1 ); | ||
537 | bool_t has_value= !lua_isnil(L,3); | ||
538 | |||
539 | struct s_Keeper *K= keeper_acquire( linda ); | ||
540 | { | ||
541 | int pushed= keeper_call( K->L, "set", L, linda, 2 ); | ||
542 | ASSERT_L( pushed==0 ); | ||
543 | |||
544 | /* Set the signal from within 'K' locking. | ||
545 | */ | ||
546 | if (has_value) { | ||
547 | SIGNAL_ALL( &linda->write_happened ); | ||
548 | } | ||
549 | } | ||
550 | keeper_release(K); | ||
551 | |||
552 | return 0; | ||
553 | } | ||
554 | |||
555 | |||
556 | /* | ||
557 | * [val]= linda_get( linda_ud, key_num|str|bool|lightuserdata ) | ||
558 | * | ||
559 | * Get a value from Linda. | ||
560 | */ | ||
561 | LUAG_FUNC( linda_get ) { | ||
562 | struct s_Linda *linda= lua_toLinda( L, 1 ); | ||
563 | int pushed; | ||
564 | |||
565 | struct s_Keeper *K= keeper_acquire( linda ); | ||
566 | { | ||
567 | pushed= keeper_call( K->L, "get", L, linda, 2 ); | ||
568 | ASSERT_L( pushed==0 || pushed==1 ); | ||
569 | } | ||
570 | keeper_release(K); | ||
571 | |||
572 | return pushed; | ||
573 | } | ||
574 | |||
575 | |||
576 | /* | ||
577 | * = linda_limit( linda_ud, key_num|str|bool|lightuserdata, uint [, ...] ) | ||
578 | * | ||
579 | * Set limits to 1 or more Linda keys. | ||
580 | */ | ||
581 | LUAG_FUNC( linda_limit ) { | ||
582 | struct s_Linda *linda= lua_toLinda( L, 1 ); | ||
583 | |||
584 | struct s_Keeper *K= keeper_acquire( linda ); | ||
585 | { | ||
586 | int pushed= keeper_call( K->L, "limit", L, linda, 2 ); | ||
587 | ASSERT_L( pushed==0 ); | ||
588 | } | ||
589 | keeper_release(K); | ||
590 | |||
591 | return 0; | ||
592 | } | ||
593 | |||
594 | |||
595 | /* | ||
596 | * lightuserdata= linda_deep( linda_ud ) | ||
597 | * | ||
598 | * Return the 'deep' userdata pointer, identifying the Linda. | ||
599 | * | ||
600 | * This is needed for using Lindas as key indices (timer system needs it); | ||
601 | * separately created proxies of the same underlying deep object will have | ||
602 | * different userdata and won't be known to be essentially the same deep one | ||
603 | * without this. | ||
604 | */ | ||
605 | LUAG_FUNC( linda_deep ) { | ||
606 | struct s_Linda *linda= lua_toLinda( L, 1 ); | ||
607 | lua_pushlightuserdata( L, linda ); // just the address | ||
608 | return 1; | ||
609 | } | ||
610 | |||
611 | |||
612 | /* | ||
613 | * Identity function of a shared userdata object. | ||
614 | * | ||
615 | * lightuserdata= linda_id( "new" [, ...] ) | ||
616 | * = linda_id( "delete", lightuserdata ) | ||
617 | * | ||
618 | * Creation and cleanup of actual 'deep' objects. 'luaG_...' will wrap them into | ||
619 | * regular userdata proxies, per each state using the deep data. | ||
620 | * | ||
621 | * tbl= linda_id( "metatable" ) | ||
622 | * | ||
623 | * Returns a metatable for the proxy objects ('__gc' method not needed; will | ||
624 | * be added by 'luaG_...') | ||
625 | * | ||
626 | * = linda_id( str, ... ) | ||
627 | * | ||
628 | * For any other strings, the ID function must not react at all. This allows | ||
629 | * future extensions of the system. | ||
630 | */ | ||
631 | LUAG_FUNC( linda_id ) { | ||
632 | const char *which= lua_tostring(L,1); | ||
633 | |||
634 | if (strcmp( which, "new" )==0) { | ||
635 | struct s_Linda *s; | ||
636 | |||
637 | /* We don't use any parameters, but one could (they're at [2..TOS]) | ||
638 | */ | ||
639 | ASSERT_L( lua_gettop(L)==1 ); | ||
640 | |||
641 | /* The deep data is allocated separately of Lua stack; we might no | ||
642 | * longer be around when last reference to it is being released. | ||
643 | * One can use any memory allocation scheme. | ||
644 | */ | ||
645 | s= (struct s_Linda *) malloc( sizeof(struct s_Linda) ); | ||
646 | ASSERT_L(s); | ||
647 | |||
648 | SIGNAL_INIT( &s->read_happened ); | ||
649 | SIGNAL_INIT( &s->write_happened ); | ||
650 | |||
651 | lua_pushlightuserdata( L, s ); | ||
652 | return 1; | ||
653 | |||
654 | } else if (strcmp( which, "delete" )==0) { | ||
655 | struct s_Keeper *K; | ||
656 | struct s_Linda *s= lua_touserdata(L,2); | ||
657 | ASSERT_L(s); | ||
658 | |||
659 | /* Clean associated structures in the keeper state. | ||
660 | */ | ||
661 | K= keeper_acquire(s); | ||
662 | { | ||
663 | keeper_call( K->L, "clear", L, s, 0 ); | ||
664 | } | ||
665 | keeper_release(K); | ||
666 | |||
667 | /* There aren't any lanes waiting on these lindas, since all proxies | ||
668 | * have been gc'ed. Right? | ||
669 | */ | ||
670 | SIGNAL_FREE( &s->read_happened ); | ||
671 | SIGNAL_FREE( &s->write_happened ); | ||
672 | free(s); | ||
673 | |||
674 | return 0; | ||
675 | |||
676 | } else if (strcmp( which, "metatable" )==0) { | ||
677 | |||
678 | STACK_CHECK(L) | ||
679 | lua_newtable(L); | ||
680 | lua_newtable(L); | ||
681 | // | ||
682 | // [-2]: linda metatable | ||
683 | // [-1]: metatable's to-be .__index table | ||
684 | |||
685 | lua_pushcfunction( L, LG_linda_send ); | ||
686 | lua_setfield( L, -2, "send" ); | ||
687 | |||
688 | lua_pushcfunction( L, LG_linda_receive ); | ||
689 | lua_setfield( L, -2, "receive" ); | ||
690 | |||
691 | lua_pushcfunction( L, LG_linda_limit ); | ||
692 | lua_setfield( L, -2, "limit" ); | ||
693 | |||
694 | lua_pushcfunction( L, LG_linda_set ); | ||
695 | lua_setfield( L, -2, "set" ); | ||
696 | |||
697 | lua_pushcfunction( L, LG_linda_get ); | ||
698 | lua_setfield( L, -2, "get" ); | ||
699 | |||
700 | lua_pushcfunction( L, LG_linda_deep ); | ||
701 | lua_setfield( L, -2, "deep" ); | ||
702 | |||
703 | lua_setfield( L, -2, "__index" ); | ||
704 | STACK_END(L,1) | ||
705 | |||
706 | return 1; | ||
707 | } | ||
708 | |||
709 | return 0; // unknown request, be quiet | ||
710 | } | ||
711 | |||
712 | |||
713 | /*---=== Finalizer ===--- | ||
714 | */ | ||
715 | |||
716 | //--- | ||
717 | // void= finalizer( finalizer_func ) | ||
718 | // | ||
719 | // finalizer_func( [err, stack_tbl] ) | ||
720 | // | ||
721 | // Add a function that will be called when exiting the lane, either via | ||
722 | // normal return or an error. | ||
723 | // | ||
724 | LUAG_FUNC( set_finalizer ) | ||
725 | { | ||
726 | STACK_GROW(L,3); | ||
727 | |||
728 | // Get the current finalizer table (if any) | ||
729 | // | ||
730 | push_registry_table( L, FINALIZER_REG_KEY, TRUE /*do create if none*/ ); | ||
731 | |||
732 | lua_pushinteger( L, lua_objlen(L,-1)+1 ); | ||
733 | lua_pushvalue( L, 1 ); // copy of the function | ||
734 | lua_settable( L, -3 ); | ||
735 | |||
736 | lua_pop(L,1); | ||
737 | return 0; | ||
738 | } | ||
739 | |||
740 | |||
741 | //--- | ||
742 | // Run finalizers - if any - with the given parameters | ||
743 | // | ||
744 | // If 'rc' is nonzero, error message and stack index are available as: | ||
745 | // [-1]: stack trace (table) | ||
746 | // [-2]: error message (any type) | ||
747 | // | ||
748 | // Returns: | ||
749 | // 0 if finalizers were run without error (or there were none) | ||
750 | // LUA_ERRxxx return code if any of the finalizers failed | ||
751 | // | ||
752 | // TBD: should we add stack trace on failing finalizer, wouldn't be hard.. | ||
753 | // | ||
754 | static int run_finalizers( lua_State *L, int lua_rc ) | ||
755 | { | ||
756 | unsigned error_index, tbl_index; | ||
757 | unsigned n; | ||
758 | int rc= 0; | ||
759 | |||
760 | if (!push_registry_table(L, FINALIZER_REG_KEY, FALSE /*don't create one*/)) | ||
761 | return 0; // no finalizers | ||
762 | |||
763 | tbl_index= lua_gettop(L); | ||
764 | error_index= (lua_rc!=0) ? tbl_index-1 : 0; // absolute indices | ||
765 | |||
766 | STACK_GROW(L,4); | ||
767 | |||
768 | // [-1]: { func [, ...] } | ||
769 | // | ||
770 | for( n= lua_objlen(L,-1); n>0; n-- ) { | ||
771 | unsigned args= 0; | ||
772 | lua_pushinteger( L,n ); | ||
773 | lua_gettable( L, -2 ); | ||
774 | |||
775 | // [-1]: function | ||
776 | // [-2]: finalizers table | ||
777 | |||
778 | if (error_index) { | ||
779 | lua_pushvalue( L, error_index ); | ||
780 | lua_pushvalue( L, error_index+1 ); // stack trace | ||
781 | args= 2; | ||
782 | } | ||
783 | |||
784 | rc= lua_pcall( L, args, 0 /*retvals*/, 0 /*no errfunc*/ ); | ||
785 | // | ||
786 | // LUA_ERRRUN / LUA_ERRMEM | ||
787 | |||
788 | if (rc!=0) { | ||
789 | // [-1]: error message | ||
790 | // | ||
791 | // If one finalizer fails, don't run the others. Return this | ||
792 | // as the 'real' error, preceding that we could have had (or not) | ||
793 | // from the actual code. | ||
794 | // | ||
795 | break; | ||
796 | } | ||
797 | } | ||
798 | |||
799 | lua_remove(L,tbl_index); // take finalizer table out of stack | ||
800 | |||
801 | return rc; | ||
802 | } | ||
803 | |||
804 | |||
805 | /*---=== Threads ===--- | ||
806 | */ | ||
807 | |||
808 | // NOTE: values to be changed by either thread, during execution, without | ||
809 | // locking, are marked "volatile" | ||
810 | // | ||
811 | struct s_lane { | ||
812 | THREAD_T thread; | ||
813 | // | ||
814 | // M: sub-thread OS thread | ||
815 | // S: not used | ||
816 | |||
817 | lua_State *L; | ||
818 | // | ||
819 | // M: prepares the state, and reads results | ||
820 | // S: while S is running, M must keep out of modifying the state | ||
821 | |||
822 | volatile enum e_status status; | ||
823 | // | ||
824 | // M: sets to PENDING (before launching) | ||
825 | // S: updates -> RUNNING/WAITING -> DONE/ERROR_ST/CANCELLED | ||
826 | |||
827 | volatile bool_t cancel_request; | ||
828 | // | ||
829 | // M: sets to FALSE, flags TRUE for cancel request | ||
830 | // S: reads to see if cancel is requested | ||
831 | |||
832 | #if !( (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) ) | ||
833 | SIGNAL_T done_signal_; | ||
834 | // | ||
835 | // M: Waited upon at lane ending (if Posix with no PTHREAD_TIMEDJOIN) | ||
836 | // S: sets the signal once cancellation is noticed (avoids a kill) | ||
837 | |||
838 | MUTEX_T done_lock_; | ||
839 | // | ||
840 | // Lock required by 'done_signal' condition variable, protecting | ||
841 | // lane status changes to DONE/ERROR_ST/CANCELLED. | ||
842 | #endif | ||
843 | |||
844 | volatile enum { | ||
845 | NORMAL, // normal master side state | ||
846 | KILLED // issued an OS kill | ||
847 | } mstatus; | ||
848 | // | ||
849 | // M: sets to NORMAL, if issued a kill changes to KILLED | ||
850 | // S: not used | ||
851 | |||
852 | struct s_lane * volatile selfdestruct_next; | ||
853 | // | ||
854 | // M: sets to non-NULL if facing lane handle '__gc' cycle but the lane | ||
855 | // is still running | ||
856 | // S: cleans up after itself if non-NULL at lane exit | ||
857 | }; | ||
858 | |||
859 | static MUTEX_T selfdestruct_cs; | ||
860 | // | ||
861 | // Protects modifying the selfdestruct chain | ||
862 | |||
863 | #define SELFDESTRUCT_END ((struct s_lane *)(-1)) | ||
864 | // | ||
865 | // The chain is ended by '(struct s_lane*)(-1)', not NULL: | ||
866 | // 'selfdestruct_first -> ... -> ... -> (-1)' | ||
867 | |||
868 | struct s_lane * volatile selfdestruct_first= SELFDESTRUCT_END; | ||
869 | |||
870 | /* | ||
871 | * Add the lane to selfdestruct chain; the ones still running at the end of the | ||
872 | * whole process will be cancelled. | ||
873 | */ | ||
874 | static void selfdestruct_add( struct s_lane *s ) { | ||
875 | |||
876 | MUTEX_LOCK( &selfdestruct_cs ); | ||
877 | { | ||
878 | assert( s->selfdestruct_next == NULL ); | ||
879 | |||
880 | s->selfdestruct_next= selfdestruct_first; | ||
881 | selfdestruct_first= s; | ||
882 | } | ||
883 | MUTEX_UNLOCK( &selfdestruct_cs ); | ||
884 | } | ||
885 | |||
886 | /* | ||
887 | * A free-running lane has ended; remove it from selfdestruct chain | ||
888 | */ | ||
889 | static void selfdestruct_remove( struct s_lane *s ) { | ||
890 | |||
891 | MUTEX_LOCK( &selfdestruct_cs ); | ||
892 | { | ||
893 | // Make sure (within the MUTEX) that we actually are in the chain | ||
894 | // still (at process exit they will remove us from chain and then | ||
895 | // cancel/kill). | ||
896 | // | ||
897 | if (s->selfdestruct_next != NULL) { | ||
898 | struct s_lane **ref= (struct s_lane **) &selfdestruct_first; | ||
899 | bool_t found= FALSE; | ||
900 | |||
901 | while( *ref != SELFDESTRUCT_END ) { | ||
902 | if (*ref == s) { | ||
903 | *ref= s->selfdestruct_next; | ||
904 | s->selfdestruct_next= NULL; | ||
905 | found= TRUE; | ||
906 | break; | ||
907 | } | ||
908 | ref= (struct s_lane **) &((*ref)->selfdestruct_next); | ||
909 | } | ||
910 | assert( found ); | ||
911 | } | ||
912 | } | ||
913 | MUTEX_UNLOCK( &selfdestruct_cs ); | ||
914 | } | ||
915 | |||
916 | /* | ||
917 | * Process end; cancel any still free-running threads | ||
918 | */ | ||
919 | static void selfdestruct_atexit( void ) { | ||
920 | |||
921 | if (selfdestruct_first == SELFDESTRUCT_END) return; // no free-running threads | ||
922 | |||
923 | // Signal _all_ still running threads to exit | ||
924 | // | ||
925 | MUTEX_LOCK( &selfdestruct_cs ); | ||
926 | { | ||
927 | struct s_lane *s= selfdestruct_first; | ||
928 | while( s != SELFDESTRUCT_END ) { | ||
929 | s->cancel_request= TRUE; | ||
930 | s= s->selfdestruct_next; | ||
931 | } | ||
932 | } | ||
933 | MUTEX_UNLOCK( &selfdestruct_cs ); | ||
934 | |||
935 | // When noticing their cancel, the lanes will remove themselves from | ||
936 | // the selfdestruct chain. | ||
937 | |||
938 | // TBD: Not sure if Windows (multi core) will require the timed approach, | ||
939 | // or single Yield. I don't have machine to test that (so leaving | ||
940 | // for timed approach). -- AKa 25-Oct-2008 | ||
941 | |||
942 | #ifdef PLATFORM_LINUX | ||
943 | // It seems enough for Linux to have a single yield here, which allows | ||
944 | // other threads (timer lane) to proceed. Without the yield, there is | ||
945 | // segfault. | ||
946 | // | ||
947 | YIELD(); | ||
948 | #else | ||
949 | // OS X 10.5 (Intel) needs more to avoid segfaults. | ||
950 | // | ||
951 | // "make test" is okay. 100's of "make require" are okay. | ||
952 | // | ||
953 | // Tested on MacBook Core Duo 2GHz and 10.5.5: | ||
954 | // -- AKa 25-Oct-2008 | ||
955 | // | ||
956 | #ifndef ATEXIT_WAIT_SECS | ||
957 | # define ATEXIT_WAIT_SECS (0.1) | ||
958 | #endif | ||
959 | { | ||
960 | double t_until= now_secs() + ATEXIT_WAIT_SECS; | ||
961 | |||
962 | while( selfdestruct_first != SELFDESTRUCT_END ) { | ||
963 | YIELD(); // give threads time to act on their cancel | ||
964 | |||
965 | if (now_secs() >= t_until) break; | ||
966 | } | ||
967 | } | ||
968 | #endif | ||
969 | |||
970 | //--- | ||
971 | // Kill the still free running threads | ||
972 | // | ||
973 | if ( selfdestruct_first != SELFDESTRUCT_END ) { | ||
974 | unsigned n=0; | ||
975 | MUTEX_LOCK( &selfdestruct_cs ); | ||
976 | { | ||
977 | struct s_lane *s= selfdestruct_first; | ||
978 | while( s != SELFDESTRUCT_END ) { | ||
979 | n++; | ||
980 | s= s->selfdestruct_next; | ||
981 | } | ||
982 | } | ||
983 | MUTEX_UNLOCK( &selfdestruct_cs ); | ||
984 | |||
985 | // Linux (at least 64-bit): CAUSES A SEGFAULT IF THIS BLOCK IS ENABLED | ||
986 | // and works without the block (so let's leave those lanes running) | ||
987 | // | ||
988 | #if 1 | ||
989 | // 2.0.2: at least timer lane is still here | ||
990 | // | ||
991 | //fprintf( stderr, "Left %d lane(s) with cancel request at process end.\n", n ); | ||
992 | #else | ||
993 | MUTEX_LOCK( &selfdestruct_cs ); | ||
994 | { | ||
995 | struct s_lane *s= selfdestruct_first; | ||
996 | while( s != SELFDESTRUCT_END ) { | ||
997 | struct s_lane *next_s= s->selfdestruct_next; | ||
998 | s->selfdestruct_next= NULL; // detach from selfdestruct chain | ||
999 | |||
1000 | THREAD_KILL( &s->thread ); | ||
1001 | s= next_s; | ||
1002 | n++; | ||
1003 | } | ||
1004 | selfdestruct_first= SELFDESTRUCT_END; | ||
1005 | } | ||
1006 | MUTEX_UNLOCK( &selfdestruct_cs ); | ||
1007 | |||
1008 | fprintf( stderr, "Killed %d lane(s) at process end.\n", n ); | ||
1009 | #endif | ||
1010 | } | ||
1011 | } | ||
1012 | |||
1013 | |||
1014 | // To allow free-running threads (longer lifespan than the handle's) | ||
1015 | // 'struct s_lane' are malloc/free'd and the handle only carries a pointer. | ||
1016 | // This is not deep userdata since the handle's not portable among lanes. | ||
1017 | // | ||
1018 | #define lua_toLane(L,i) (* ((struct s_lane**) lua_touserdata(L,i))) | ||
1019 | |||
1020 | |||
1021 | /* | ||
1022 | * Check if the thread in question ('L') has been signalled for cancel. | ||
1023 | * | ||
1024 | * Called by cancellation hooks and/or pending Linda operations (because then | ||
1025 | * the check won't affect performance). | ||
1026 | * | ||
1027 | * Returns TRUE if any locks are to be exited, and 'cancel_error()' called, | ||
1028 | * to make execution of the lane end. | ||
1029 | */ | ||
1030 | static bool_t cancel_test( lua_State *L ) { | ||
1031 | struct s_lane *s; | ||
1032 | |||
1033 | STACK_GROW(L,1); | ||
1034 | |||
1035 | STACK_CHECK(L) | ||
1036 | lua_pushlightuserdata( L, CANCEL_TEST_KEY ); | ||
1037 | lua_rawget( L, LUA_REGISTRYINDEX ); | ||
1038 | s= lua_touserdata( L, -1 ); // lightuserdata (true 's_lane' pointer) / nil | ||
1039 | lua_pop(L,1); | ||
1040 | STACK_END(L,0) | ||
1041 | |||
1042 | // 's' is NULL for the original main state (no-one can cancel that) | ||
1043 | // | ||
1044 | return s && s->cancel_request; | ||
1045 | } | ||
1046 | |||
1047 | static void cancel_error( lua_State *L ) { | ||
1048 | STACK_GROW(L,1); | ||
1049 | lua_pushlightuserdata( L, CANCEL_ERROR ); // special error value | ||
1050 | lua_error(L); // no return | ||
1051 | } | ||
1052 | |||
1053 | static void cancel_hook( lua_State *L, lua_Debug *ar ) { | ||
1054 | (void)ar; | ||
1055 | if (cancel_test(L)) cancel_error(L); | ||
1056 | } | ||
1057 | |||
1058 | |||
1059 | //--- | ||
1060 | // = _single( [cores_uint=1] ) | ||
1061 | // | ||
1062 | // Limits the process to use only 'cores' CPU cores. To be used for performance | ||
1063 | // testing on multicore devices. DEBUGGING ONLY! | ||
1064 | // | ||
1065 | LUAG_FUNC( _single ) { | ||
1066 | uint_t cores= luaG_optunsigned(L,1,1); | ||
1067 | |||
1068 | #ifdef PLATFORM_OSX | ||
1069 | #ifdef _UTILBINDTHREADTOCPU | ||
1070 | if (cores > 1) luaL_error( L, "Limiting to N>1 cores not possible." ); | ||
1071 | // requires 'chudInitialize()' | ||
1072 | utilBindThreadToCPU(0); // # of CPU to run on (we cannot limit to 2..N CPUs?) | ||
1073 | #else | ||
1074 | luaL_error( L, "Not available: compile with _UTILBINDTHREADTOCPU" ); | ||
1075 | #endif | ||
1076 | #else | ||
1077 | luaL_error( L, "not implemented!" ); | ||
1078 | #endif | ||
1079 | (void)cores; | ||
1080 | |||
1081 | return 0; | ||
1082 | } | ||
1083 | |||
1084 | |||
1085 | /* | ||
1086 | * str= lane_error( error_val|str ) | ||
1087 | * | ||
1088 | * Called if there's an error in some lane; add call stack to error message | ||
1089 | * just like 'lua.c' normally does. | ||
1090 | * | ||
1091 | * ".. will be called with the error message and its return value will be the | ||
1092 | * message returned on the stack by lua_pcall." | ||
1093 | * | ||
1094 | * Note: Rather than modifying the error message itself, it would be better | ||
1095 | * to provide the call stack (as string) completely separated. This would | ||
1096 | * work great with non-string error values as well (current system does not). | ||
1097 | * (This is NOT possible with the Lua 5.1 'lua_pcall()'; we could of course | ||
1098 | * implement a Lanes-specific 'pcall' of our own that does this). TBD!!! :) | ||
1099 | * --AKa 22-Jan-2009 | ||
1100 | */ | ||
1101 | #ifdef ERROR_FULL_STACK | ||
1102 | |||
1103 | static int lane_error( lua_State *L ) { | ||
1104 | lua_Debug ar; | ||
1105 | unsigned lev,n; | ||
1106 | |||
1107 | // [1]: error message (any type) | ||
1108 | |||
1109 | assert( lua_gettop(L)==1 ); | ||
1110 | |||
1111 | // Don't do stack survey for cancelled lanes. | ||
1112 | // | ||
1113 | #if 1 | ||
1114 | if (lua_touserdata(L,1) == CANCEL_ERROR) | ||
1115 | return 1; // just pass on | ||
1116 | #endif | ||
1117 | |||
1118 | // Place stack trace at 'registry[lane_error]' for the 'luc_pcall()' | ||
1119 | // caller to fetch. This bypasses the Lua 5.1 limitation of only one | ||
1120 | // return value from error handler to 'lua_pcall()' caller. | ||
1121 | |||
1122 | // It's adequate to push stack trace as a table. This gives the receiver | ||
1123 | // of the stack best means to format it to their liking. Also, it allows | ||
1124 | // us to add more stack info later, if needed. | ||
1125 | // | ||
1126 | // table of { "sourcefile.lua:<line>", ... } | ||
1127 | // | ||
1128 | STACK_GROW(L,3); | ||
1129 | lua_newtable(L); | ||
1130 | |||
1131 | // Best to start from level 1, but in some cases it might be a C function | ||
1132 | // and we don't get '.currentline' for that. It's okay - just keep level | ||
1133 | // and table index growing separate. --AKa 22-Jan-2009 | ||
1134 | // | ||
1135 | lev= 0; | ||
1136 | n=1; | ||
1137 | while( lua_getstack(L, ++lev, &ar ) ) { | ||
1138 | lua_getinfo(L, "Sl", &ar); | ||
1139 | if (ar.currentline > 0) { | ||
1140 | lua_pushinteger( L, n++ ); | ||
1141 | lua_pushfstring( L, "%s:%d", ar.short_src, ar.currentline ); | ||
1142 | lua_settable( L, -3 ); | ||
1143 | } | ||
1144 | } | ||
1145 | |||
1146 | lua_pushlightuserdata( L, STACK_TRACE_KEY ); | ||
1147 | lua_insert(L,-2); | ||
1148 | lua_settable( L, LUA_REGISTRYINDEX ); | ||
1149 | |||
1150 | assert( lua_gettop(L)== 1 ); | ||
1151 | |||
1152 | return 1; // the untouched error value | ||
1153 | } | ||
1154 | #endif | ||
1155 | |||
1156 | |||
1157 | //--- | ||
1158 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) | ||
1159 | static THREAD_RETURN_T __stdcall lane_main( void *vs ) | ||
1160 | #else | ||
1161 | static THREAD_RETURN_T lane_main( void *vs ) | ||
1162 | #endif | ||
1163 | { | ||
1164 | struct s_lane *s= (struct s_lane *)vs; | ||
1165 | int rc, rc2; | ||
1166 | lua_State *L= s->L; | ||
1167 | |||
1168 | s->status= RUNNING; // PENDING -> RUNNING | ||
1169 | |||
1170 | // Tie "set_finalizer()" to the state | ||
1171 | // | ||
1172 | lua_pushcfunction( L, LG_set_finalizer ); | ||
1173 | lua_setglobal( L, "set_finalizer" ); | ||
1174 | |||
1175 | #ifdef ERROR_FULL_STACK | ||
1176 | STACK_GROW( L, 1 ); | ||
1177 | lua_pushcfunction( L, lane_error ); | ||
1178 | lua_insert( L, 1 ); | ||
1179 | |||
1180 | // [1]: error handler | ||
1181 | // [2]: function to run | ||
1182 | // [3..top]: parameters | ||
1183 | // | ||
1184 | rc= lua_pcall( L, lua_gettop(L)-2, LUA_MULTRET, 1 /*error handler*/ ); | ||
1185 | // 0: no error | ||
1186 | // LUA_ERRRUN: a runtime error (error pushed on stack) | ||
1187 | // LUA_ERRMEM: memory allocation error | ||
1188 | // LUA_ERRERR: error while running the error handler (if any) | ||
1189 | |||
1190 | assert( rc!=LUA_ERRERR ); // since we've authored it | ||
1191 | |||
1192 | lua_remove(L,1); // remove error handler | ||
1193 | |||
1194 | // Lua 5.1 error handler is limited to one return value; taking stack trace | ||
1195 | // via registry | ||
1196 | // | ||
1197 | if (rc!=0) { | ||
1198 | STACK_GROW(L,1); | ||
1199 | lua_pushlightuserdata( L, STACK_TRACE_KEY ); | ||
1200 | lua_gettable(L, LUA_REGISTRYINDEX); | ||
1201 | |||
1202 | // For cancellation, a stack trace isn't placed | ||
1203 | // | ||
1204 | assert( lua_istable(L,2) || (lua_touserdata(L,1)==CANCEL_ERROR) ); | ||
1205 | |||
1206 | // Just leaving the stack trace table on the stack is enough to get | ||
1207 | // it through to the master. | ||
1208 | } | ||
1209 | |||
1210 | #else | ||
1211 | // This code does not use 'lane_error' | ||
1212 | // | ||
1213 | // [1]: function to run | ||
1214 | // [2..top]: parameters | ||
1215 | // | ||
1216 | rc= lua_pcall( L, lua_gettop(L)-1, LUA_MULTRET, 0 /*no error handler*/ ); | ||
1217 | // 0: no error | ||
1218 | // LUA_ERRRUN: a runtime error (error pushed on stack) | ||
1219 | // LUA_ERRMEM: memory allocation error | ||
1220 | #endif | ||
1221 | |||
1222 | //STACK_DUMP(L); | ||
1223 | // Call finalizers, if the script has set them up. | ||
1224 | // | ||
1225 | rc2= run_finalizers(L,rc); | ||
1226 | if (rc2!=0) { | ||
1227 | // Error within a finalizer! | ||
1228 | // | ||
1229 | // [-1]: error message | ||
1230 | |||
1231 | rc= rc2; // we're overruling the earlier script error or normal return | ||
1232 | |||
1233 | lua_insert( L,1 ); // make error message [1] | ||
1234 | lua_settop( L,1 ); // remove all rest | ||
1235 | |||
1236 | // Place an empty stack table just to keep the API simple (always when | ||
1237 | // there's an error, there's also stack table - though it may be empty). | ||
1238 | // | ||
1239 | lua_newtable(L); | ||
1240 | } | ||
1241 | |||
1242 | if (s->selfdestruct_next != NULL) { | ||
1243 | // We're a free-running thread and no-one's there to clean us up. | ||
1244 | // | ||
1245 | lua_close( s->L ); | ||
1246 | L= 0; | ||
1247 | |||
1248 | #if !( (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) ) | ||
1249 | SIGNAL_FREE( &s->done_signal_ ); | ||
1250 | MUTEX_FREE( &s->done_lock_ ); | ||
1251 | #endif | ||
1252 | selfdestruct_remove(s); // away from selfdestruct chain | ||
1253 | free(s); | ||
1254 | |||
1255 | } else { | ||
1256 | // leave results (1..top) or error message + stack trace (1..2) on the stack - master will copy them | ||
1257 | |||
1258 | enum e_status st= | ||
1259 | (rc==0) ? DONE | ||
1260 | : (lua_touserdata(L,1)==CANCEL_ERROR) ? CANCELLED | ||
1261 | : ERROR_ST; | ||
1262 | |||
1263 | // Posix no PTHREAD_TIMEDJOIN: | ||
1264 | // 'done_lock' protects the -> DONE|ERROR_ST|CANCELLED state change | ||
1265 | // | ||
1266 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) | ||
1267 | s->status= st; | ||
1268 | #else | ||
1269 | MUTEX_LOCK( &s->done_lock_ ); | ||
1270 | { | ||
1271 | s->status= st; | ||
1272 | SIGNAL_ONE( &s->done_signal_ ); // wake up master (while 's->done_lock' is on) | ||
1273 | } | ||
1274 | MUTEX_UNLOCK( &s->done_lock_ ); | ||
1275 | #endif | ||
1276 | } | ||
1277 | |||
1278 | return 0; // ignored | ||
1279 | } | ||
1280 | |||
1281 | |||
1282 | //--- | ||
1283 | // lane_ud= thread_new( function, [libs_str], | ||
1284 | // [cancelstep_uint=0], | ||
1285 | // [prio_int=0], | ||
1286 | // [globals_tbl], | ||
1287 | // [... args ...] ) | ||
1288 | // | ||
1289 | // Upvalues: metatable to use for 'lane_ud' | ||
1290 | // | ||
1291 | LUAG_FUNC( thread_new ) | ||
1292 | { | ||
1293 | lua_State *L2; | ||
1294 | struct s_lane *s; | ||
1295 | struct s_lane **ud; | ||
1296 | |||
1297 | const char *libs= lua_tostring( L, 2 ); | ||
1298 | uint_t cs= luaG_optunsigned( L, 3,0); | ||
1299 | int prio= luaL_optinteger( L, 4,0); | ||
1300 | uint_t glob= luaG_isany(L,5) ? 5:0; | ||
1301 | |||
1302 | #define FIXED_ARGS (5) | ||
1303 | uint_t args= lua_gettop(L) - FIXED_ARGS; | ||
1304 | |||
1305 | if (prio < THREAD_PRIO_MIN || prio > THREAD_PRIO_MAX) { | ||
1306 | luaL_error( L, "Priority out of range: %d..+%d (%d)", | ||
1307 | THREAD_PRIO_MIN, THREAD_PRIO_MAX, prio ); | ||
1308 | } | ||
1309 | |||
1310 | /* --- Create and prepare the sub state --- */ | ||
1311 | |||
1312 | L2 = luaL_newstate(); // uses standard 'realloc()'-based allocator, | ||
1313 | // sets the panic callback | ||
1314 | |||
1315 | if (!L2) luaL_error( L, "'luaL_newstate()' failed; out of memory" ); | ||
1316 | |||
1317 | STACK_GROW( L,2 ); | ||
1318 | |||
1319 | // Setting the globals table (needs to be done before loading stdlibs, | ||
1320 | // and the lane function) | ||
1321 | // | ||
1322 | if (glob!=0) { | ||
1323 | STACK_CHECK(L) | ||
1324 | if (!lua_istable(L,glob)) | ||
1325 | luaL_error( L, "Expected table, got %s", luaG_typename(L,glob) ); | ||
1326 | |||
1327 | lua_pushvalue( L, glob ); | ||
1328 | luaG_inter_move( L,L2, 1 ); // moves the table to L2 | ||
1329 | |||
1330 | // L2 [-1]: table of globals | ||
1331 | |||
1332 | // "You can change the global environment of a Lua thread using lua_replace" | ||
1333 | // (refman-5.0.pdf p. 30) | ||
1334 | // | ||
1335 | lua_replace( L2, LUA_GLOBALSINDEX ); | ||
1336 | STACK_END(L,0) | ||
1337 | } | ||
1338 | |||
1339 | // Selected libraries | ||
1340 | // | ||
1341 | if (libs) { | ||
1342 | const char *err= luaG_openlibs( L2, libs ); | ||
1343 | ASSERT_L( !err ); // bad libs should have been noticed by 'lanes.lua' | ||
1344 | |||
1345 | serialize_require( L2 ); | ||
1346 | } | ||
1347 | |||
1348 | // Lane main function | ||
1349 | // | ||
1350 | STACK_CHECK(L) | ||
1351 | lua_pushvalue( L, 1 ); | ||
1352 | luaG_inter_move( L,L2, 1 ); // L->L2 | ||
1353 | STACK_MID(L,0) | ||
1354 | |||
1355 | ASSERT_L( lua_gettop(L2) == 1 ); | ||
1356 | ASSERT_L( lua_isfunction(L2,1) ); | ||
1357 | |||
1358 | // revive arguments | ||
1359 | // | ||
1360 | if (args) luaG_inter_copy( L,L2, args ); // L->L2 | ||
1361 | STACK_MID(L,0) | ||
1362 | |||
1363 | ASSERT_L( (uint_t)lua_gettop(L2) == 1+args ); | ||
1364 | ASSERT_L( lua_isfunction(L2,1) ); | ||
1365 | |||
1366 | // 's' is allocated from heap, not Lua, since its life span may surpass | ||
1367 | // the handle's (if free running thread) | ||
1368 | // | ||
1369 | ud= lua_newuserdata( L, sizeof(struct s_lane*) ); | ||
1370 | ASSERT_L(ud); | ||
1371 | |||
1372 | s= *ud= malloc( sizeof(struct s_lane) ); | ||
1373 | ASSERT_L(s); | ||
1374 | |||
1375 | //memset( s, 0, sizeof(struct s_lane) ); | ||
1376 | s->L= L2; | ||
1377 | s->status= PENDING; | ||
1378 | s->cancel_request= FALSE; | ||
1379 | |||
1380 | #if !( (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) ) | ||
1381 | MUTEX_INIT( &s->done_lock_ ); | ||
1382 | SIGNAL_INIT( &s->done_signal_ ); | ||
1383 | #endif | ||
1384 | s->mstatus= NORMAL; | ||
1385 | s->selfdestruct_next= NULL; | ||
1386 | |||
1387 | // Set metatable for the userdata | ||
1388 | // | ||
1389 | lua_pushvalue( L, lua_upvalueindex(1) ); | ||
1390 | lua_setmetatable( L, -2 ); | ||
1391 | STACK_MID(L,1) | ||
1392 | |||
1393 | // Place 's' to registry, for 'cancel_test()' (even if 'cs'==0 we still | ||
1394 | // do cancel tests at pending send/receive). | ||
1395 | // | ||
1396 | lua_pushlightuserdata( L2, CANCEL_TEST_KEY ); | ||
1397 | lua_pushlightuserdata( L2, s ); | ||
1398 | lua_rawset( L2, LUA_REGISTRYINDEX ); | ||
1399 | |||
1400 | if (cs) { | ||
1401 | lua_sethook( L2, cancel_hook, LUA_MASKCOUNT, cs ); | ||
1402 | } | ||
1403 | |||
1404 | THREAD_CREATE( &s->thread, lane_main, s, prio ); | ||
1405 | STACK_END(L,1) | ||
1406 | |||
1407 | return 1; | ||
1408 | } | ||
1409 | |||
1410 | |||
1411 | //--- | ||
1412 | // = thread_gc( lane_ud ) | ||
1413 | // | ||
1414 | // Cleanup for a thread userdata. If the thread is still executing, leave it | ||
1415 | // alive as a free-running thread (will clean up itself). | ||
1416 | // | ||
1417 | // * Why NOT cancel/kill a loose thread: | ||
1418 | // | ||
1419 | // At least timer system uses a free-running thread, they should be handy | ||
1420 | // and the issue of cancelling/killing threads at gc is not very nice, either | ||
1421 | // (would easily cause waits at gc cycle, which we don't want). | ||
1422 | // | ||
1423 | // * Why YES kill a loose thread: | ||
1424 | // | ||
1425 | // Current way causes segfaults at program exit, if free-running threads are | ||
1426 | // in certain stages. Details are not clear, but this is the core reason. | ||
1427 | // If gc would kill threads then at process exit only one thread would remain. | ||
1428 | // | ||
1429 | // Todo: Maybe we should have a clear #define for selecting either behaviour. | ||
1430 | // | ||
1431 | LUAG_FUNC( thread_gc ) { | ||
1432 | struct s_lane *s= lua_toLane(L,1); | ||
1433 | |||
1434 | // We can read 's->status' without locks, but not wait for it | ||
1435 | // | ||
1436 | if (s->status < DONE) { | ||
1437 | // | ||
1438 | selfdestruct_add(s); | ||
1439 | assert( s->selfdestruct_next ); | ||
1440 | return 0; | ||
1441 | |||
1442 | } else if (s->mstatus==KILLED) { | ||
1443 | // Make sure a kill has proceeded, before cleaning up the data structure. | ||
1444 | // | ||
1445 | // If not doing 'THREAD_WAIT()' we should close the Lua state here | ||
1446 | // (can it be out of order, since we killed the lane abruptly?) | ||
1447 | // | ||
1448 | #if 0 | ||
1449 | lua_close( s->L ); | ||
1450 | #else | ||
1451 | fprintf( stderr, "** Joining with a killed thread (needs testing) **" ); | ||
1452 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) | ||
1453 | THREAD_WAIT( &s->thread, -1 ); | ||
1454 | #else | ||
1455 | THREAD_WAIT( &s->thread, &s->done_signal_, &s->done_lock_, &s->status, -1 ); | ||
1456 | #endif | ||
1457 | fprintf( stderr, "** Joined ok **" ); | ||
1458 | #endif | ||
1459 | } | ||
1460 | |||
1461 | // Clean up after a (finished) thread | ||
1462 | // | ||
1463 | #if (! ((defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN))) | ||
1464 | SIGNAL_FREE( &s->done_signal_ ); | ||
1465 | MUTEX_FREE( &s->done_lock_ ); | ||
1466 | free(s); | ||
1467 | #endif | ||
1468 | |||
1469 | return 0; | ||
1470 | } | ||
1471 | |||
1472 | |||
1473 | //--- | ||
1474 | // = thread_cancel( lane_ud [,timeout_secs=0.0] [,force_kill_bool=false] ) | ||
1475 | // | ||
1476 | // The originator thread asking us specifically to cancel the other thread. | ||
1477 | // | ||
1478 | // 'timeout': <0: wait forever, until the lane is finished | ||
1479 | // 0.0: just signal it to cancel, no time waited | ||
1480 | // >0: time to wait for the lane to detect cancellation | ||
1481 | // | ||
1482 | // 'force_kill': if true, and lane does not detect cancellation within timeout, | ||
1483 | // it is forcefully killed. Using this with 0.0 timeout means just kill | ||
1484 | // (unless the lane is already finished). | ||
1485 | // | ||
1486 | // Returns: true if the lane was already finished (DONE/ERROR_ST/CANCELLED) or if we | ||
1487 | // managed to cancel it. | ||
1488 | // false if the cancellation timed out, or a kill was needed. | ||
1489 | // | ||
1490 | LUAG_FUNC( thread_cancel ) | ||
1491 | { | ||
1492 | struct s_lane *s= lua_toLane(L,1); | ||
1493 | double secs= 0.0; | ||
1494 | uint_t force_i=2; | ||
1495 | bool_t force, done= TRUE; | ||
1496 | |||
1497 | if (lua_isnumber(L,2)) { | ||
1498 | secs= lua_tonumber(L,2); | ||
1499 | force_i++; | ||
1500 | } else if (lua_isnil(L,2)) | ||
1501 | force_i++; | ||
1502 | |||
1503 | force= lua_toboolean(L,force_i); // FALSE if nothing there | ||
1504 | |||
1505 | // We can read 's->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) | ||
1506 | // | ||
1507 | if (s->status < DONE) { | ||
1508 | s->cancel_request= TRUE; // it's now signalled to stop | ||
1509 | |||
1510 | done= thread_cancel( s, secs, force ); | ||
1511 | } | ||
1512 | |||
1513 | lua_pushboolean( L, done ); | ||
1514 | return 1; | ||
1515 | } | ||
1516 | |||
1517 | static bool_t thread_cancel( struct s_lane *s, double secs, bool_t force ) | ||
1518 | { | ||
1519 | bool_t done= | ||
1520 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) | ||
1521 | THREAD_WAIT( &s->thread, secs ); | ||
1522 | #else | ||
1523 | THREAD_WAIT( &s->thread, &s->done_signal_, &s->done_lock_, &s->status, secs ); | ||
1524 | #endif | ||
1525 | |||
1526 | if ((!done) && force) { | ||
1527 | // Killing is asynchronous; we _will_ wait for it to be done at | ||
1528 | // GC, to make sure the data structure can be released (alternative | ||
1529 | // would be use of "cancellation cleanup handlers" that at least | ||
1530 | // PThread seems to have). | ||
1531 | // | ||
1532 | THREAD_KILL( &s->thread ); | ||
1533 | s->mstatus= KILLED; // mark 'gc' to wait for it | ||
1534 | } | ||
1535 | return done; | ||
1536 | } | ||
1537 | |||
1538 | |||
1539 | //--- | ||
1540 | // str= thread_status( lane_ud ) | ||
1541 | // | ||
1542 | // Returns: "pending" not started yet | ||
1543 | // -> "running" started, doing its work.. | ||
1544 | // <-> "waiting" blocked in a receive() | ||
1545 | // -> "done" finished, results are there | ||
1546 | // / "error" finished at an error, error value is there | ||
1547 | // / "cancelled" execution cancelled by M (state gone) | ||
1548 | // | ||
1549 | LUAG_FUNC( thread_status ) | ||
1550 | { | ||
1551 | struct s_lane *s= lua_toLane(L,1); | ||
1552 | enum e_status st= s->status; // read just once (volatile) | ||
1553 | const char *str; | ||
1554 | |||
1555 | if (s->mstatus == KILLED) | ||
1556 | st= CANCELLED; | ||
1557 | |||
1558 | str= (st==PENDING) ? "pending" : | ||
1559 | (st==RUNNING) ? "running" : // like in 'co.status()' | ||
1560 | (st==WAITING) ? "waiting" : | ||
1561 | (st==DONE) ? "done" : | ||
1562 | (st==ERROR_ST) ? "error" : | ||
1563 | (st==CANCELLED) ? "cancelled" : NULL; | ||
1564 | ASSERT_L(str); | ||
1565 | |||
1566 | lua_pushstring( L, str ); | ||
1567 | return 1; | ||
1568 | } | ||
1569 | |||
1570 | |||
1571 | //--- | ||
1572 | // [...] | [nil, err_any, stack_tbl]= thread_join( lane_ud [, wait_secs=-1] ) | ||
1573 | // | ||
1574 | // timeout: returns nil | ||
1575 | // done: returns return values (0..N) | ||
1576 | // error: returns nil + error value + stack table | ||
1577 | // cancelled: returns nil | ||
1578 | // | ||
1579 | LUAG_FUNC( thread_join ) | ||
1580 | { | ||
1581 | struct s_lane *s= lua_toLane(L,1); | ||
1582 | double wait_secs= luaL_optnumber(L,2,-1.0); | ||
1583 | lua_State *L2= s->L; | ||
1584 | int ret; | ||
1585 | |||
1586 | bool_t done= | ||
1587 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) | ||
1588 | THREAD_WAIT( &s->thread, wait_secs ); | ||
1589 | #else | ||
1590 | THREAD_WAIT( &s->thread, &s->done_signal_, &s->done_lock_, &s->status, wait_secs ); | ||
1591 | #endif | ||
1592 | if (!done) | ||
1593 | return 0; // timeout: pushes none, leaves 'L2' alive | ||
1594 | |||
1595 | // Thread is DONE/ERROR_ST/CANCELLED; all ours now | ||
1596 | |||
1597 | STACK_GROW( L, 1 ); | ||
1598 | |||
1599 | switch( s->status ) { | ||
1600 | case DONE: { | ||
1601 | uint_t n= lua_gettop(L2); // whole L2 stack | ||
1602 | luaG_inter_move( L2,L, n ); | ||
1603 | ret= n; | ||
1604 | } break; | ||
1605 | |||
1606 | case ERROR_ST: | ||
1607 | lua_pushnil(L); | ||
1608 | luaG_inter_move( L2,L, 2 ); // error message at [-2], stack trace at [-1] | ||
1609 | ret= 3; | ||
1610 | break; | ||
1611 | |||
1612 | case CANCELLED: | ||
1613 | ret= 0; | ||
1614 | break; | ||
1615 | |||
1616 | default: | ||
1617 | fprintf( stderr, "Status: %d\n", s->status ); | ||
1618 | ASSERT_L( FALSE ); ret= 0; | ||
1619 | } | ||
1620 | lua_close(L2); | ||
1621 | |||
1622 | return ret; | ||
1623 | } | ||
1624 | |||
1625 | |||
1626 | /*---=== Timer support ===--- | ||
1627 | */ | ||
1628 | |||
1629 | /* | ||
1630 | * Push a timer gateway Linda object; only one deep userdata is | ||
1631 | * created for this, each lane will get its own proxy. | ||
1632 | * | ||
1633 | * Note: this needs to be done on the C side; Lua wouldn't be able | ||
1634 | * to even see, when we've been initialized for the very first | ||
1635 | * time (with us, they will be). | ||
1636 | */ | ||
1637 | static | ||
1638 | void push_timer_gateway( lua_State *L ) { | ||
1639 | |||
1640 | /* No need to lock; 'static' is just fine | ||
1641 | */ | ||
1642 | static DEEP_PRELUDE *p; // = NULL | ||
1643 | |||
1644 | STACK_CHECK(L) | ||
1645 | if (!p) { | ||
1646 | // Create the Linda (only on first time) | ||
1647 | // | ||
1648 | // proxy_ud= deep_userdata( idfunc ) | ||
1649 | // | ||
1650 | lua_pushcfunction( L, luaG_deep_userdata ); | ||
1651 | lua_pushcfunction( L, LG_linda_id ); | ||
1652 | lua_call( L, 1 /*args*/, 1 /*retvals*/ ); | ||
1653 | |||
1654 | ASSERT_L( lua_isuserdata(L,-1) ); | ||
1655 | |||
1656 | // Proxy userdata contents is only a 'DEEP_PRELUDE*' pointer | ||
1657 | // | ||
1658 | p= * (DEEP_PRELUDE**) lua_touserdata( L, -1 ); | ||
1659 | ASSERT_L(p && p->refcount==1 && p->deep); | ||
1660 | |||
1661 | // [-1]: proxy for accessing the Linda | ||
1662 | |||
1663 | } else { | ||
1664 | /* Push a proxy based on the deep userdata we stored. | ||
1665 | */ | ||
1666 | luaG_push_proxy( L, LG_linda_id, p ); | ||
1667 | } | ||
1668 | STACK_END(L,1) | ||
1669 | } | ||
1670 | |||
1671 | /* | ||
1672 | * secs= now_secs() | ||
1673 | * | ||
1674 | * Returns the current time, as seconds (millisecond resolution). | ||
1675 | */ | ||
1676 | LUAG_FUNC( now_secs ) | ||
1677 | { | ||
1678 | lua_pushnumber( L, now_secs() ); | ||
1679 | return 1; | ||
1680 | } | ||
1681 | |||
1682 | /* | ||
1683 | * wakeup_at_secs= wakeup_conv( date_tbl ) | ||
1684 | */ | ||
1685 | LUAG_FUNC( wakeup_conv ) | ||
1686 | { | ||
1687 | int year, month, day, hour, min, sec, isdst; | ||
1688 | struct tm tm= {0}; | ||
1689 | // | ||
1690 | // .year (four digits) | ||
1691 | // .month (1..12) | ||
1692 | // .day (1..31) | ||
1693 | // .hour (0..23) | ||
1694 | // .min (0..59) | ||
1695 | // .sec (0..61) | ||
1696 | // .yday (day of the year) | ||
1697 | // .isdst (daylight saving on/off) | ||
1698 | |||
1699 | STACK_CHECK(L) | ||
1700 | lua_getfield( L, 1, "year" ); year= lua_tointeger(L,-1); lua_pop(L,1); | ||
1701 | lua_getfield( L, 1, "month" ); month= lua_tointeger(L,-1); lua_pop(L,1); | ||
1702 | lua_getfield( L, 1, "day" ); day= lua_tointeger(L,-1); lua_pop(L,1); | ||
1703 | lua_getfield( L, 1, "hour" ); hour= lua_tointeger(L,-1); lua_pop(L,1); | ||
1704 | lua_getfield( L, 1, "min" ); min= lua_tointeger(L,-1); lua_pop(L,1); | ||
1705 | lua_getfield( L, 1, "sec" ); sec= lua_tointeger(L,-1); lua_pop(L,1); | ||
1706 | |||
1707 | // If Lua table has '.isdst' we trust that. If it does not, we'll let | ||
1708 | // 'mktime' decide on whether the time is within DST or not (value -1). | ||
1709 | // | ||
1710 | lua_getfield( L, 1, "isdst" ); | ||
1711 | isdst= lua_isboolean(L,-1) ? lua_toboolean(L,-1) : -1; | ||
1712 | lua_pop(L,1); | ||
1713 | STACK_END(L,0) | ||
1714 | |||
1715 | tm.tm_year= year-1900; | ||
1716 | tm.tm_mon= month-1; // 0..11 | ||
1717 | tm.tm_mday= day; // 1..31 | ||
1718 | tm.tm_hour= hour; // 0..23 | ||
1719 | tm.tm_min= min; // 0..59 | ||
1720 | tm.tm_sec= sec; // 0..60 | ||
1721 | tm.tm_isdst= isdst; // 0/1/negative | ||
1722 | |||
1723 | lua_pushnumber( L, (double) mktime( &tm ) ); // ms=0 | ||
1724 | return 1; | ||
1725 | } | ||
1726 | |||
1727 | |||
1728 | /*---=== Module linkage ===--- | ||
1729 | */ | ||
1730 | |||
1731 | #define REG_FUNC( name ) \ | ||
1732 | lua_pushcfunction( L, LG_##name ); \ | ||
1733 | lua_setglobal( L, #name ) | ||
1734 | |||
1735 | #define REG_FUNC2( name, val ) \ | ||
1736 | lua_pushcfunction( L, val ); \ | ||
1737 | lua_setglobal( L, #name ) | ||
1738 | |||
1739 | #define REG_STR2( name, val ) \ | ||
1740 | lua_pushstring( L, val ); \ | ||
1741 | lua_setglobal( L, #name ) | ||
1742 | |||
1743 | #define REG_INT2( name, val ) \ | ||
1744 | lua_pushinteger( L, val ); \ | ||
1745 | lua_setglobal( L, #name ) | ||
1746 | |||
1747 | |||
1748 | int | ||
1749 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) | ||
1750 | __declspec(dllexport) | ||
1751 | #endif | ||
1752 | luaopen_lanes( lua_State *L ) { | ||
1753 | const char *err; | ||
1754 | static volatile char been_here; // =0 | ||
1755 | |||
1756 | // One time initializations: | ||
1757 | // | ||
1758 | if (!been_here) { | ||
1759 | been_here= TRUE; | ||
1760 | |||
1761 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) | ||
1762 | now_secs(); // initialize 'now_secs()' internal offset | ||
1763 | #endif | ||
1764 | |||
1765 | #if (defined PLATFORM_OSX) && (defined _UTILBINDTHREADTOCPU) | ||
1766 | chudInitialize(); | ||
1767 | #endif | ||
1768 | |||
1769 | // Locks for 'tools.c' inc/dec counters | ||
1770 | // | ||
1771 | MUTEX_INIT( &deep_lock ); | ||
1772 | MUTEX_INIT( &mtid_lock ); | ||
1773 | |||
1774 | // Serialize calls to 'require' from now on, also in the primary state | ||
1775 | // | ||
1776 | MUTEX_RECURSIVE_INIT( &require_cs ); | ||
1777 | |||
1778 | serialize_require( L ); | ||
1779 | |||
1780 | // Selfdestruct chain handling | ||
1781 | // | ||
1782 | MUTEX_INIT( &selfdestruct_cs ); | ||
1783 | atexit( selfdestruct_atexit ); | ||
1784 | |||
1785 | //--- | ||
1786 | // Linux needs SCHED_RR to change thread priorities, and that is only | ||
1787 | // allowed for sudo'ers. SCHED_OTHER (default) has no priorities. | ||
1788 | // SCHED_OTHER threads are always lower priority than SCHED_RR. | ||
1789 | // | ||
1790 | // ^-- those apply to 2.6 kernel. IF **wishful thinking** these | ||
1791 | // constraints will change in the future, non-sudo priorities can | ||
1792 | // be enabled also for Linux. | ||
1793 | // | ||
1794 | #ifdef PLATFORM_LINUX | ||
1795 | sudo= geteuid()==0; // we are root? | ||
1796 | |||
1797 | // If lower priorities (-2..-1) are wanted, we need to lift the main | ||
1798 | // thread to SCHED_RR and 50 (medium) level. Otherwise, we're always below | ||
1799 | // the launched threads (even -2). | ||
1800 | // | ||
1801 | #ifdef LINUX_SCHED_RR | ||
1802 | if (sudo) { | ||
1803 | struct sched_param sp= {0}; sp.sched_priority= _PRIO_0; | ||
1804 | PT_CALL( pthread_setschedparam( pthread_self(), SCHED_RR, &sp) ); | ||
1805 | } | ||
1806 | #endif | ||
1807 | #endif | ||
1808 | err= init_keepers(); | ||
1809 | if (err) | ||
1810 | luaL_error( L, "Unable to initialize: %s", err ); | ||
1811 | } | ||
1812 | |||
1813 | // Linda identity function | ||
1814 | // | ||
1815 | REG_FUNC( linda_id ); | ||
1816 | |||
1817 | // metatable for threads | ||
1818 | // | ||
1819 | lua_newtable( L ); | ||
1820 | lua_pushcfunction( L, LG_thread_gc ); | ||
1821 | lua_setfield( L, -2, "__gc" ); | ||
1822 | |||
1823 | lua_pushcclosure( L, LG_thread_new, 1 ); // metatable as closure param | ||
1824 | lua_setglobal( L, "thread_new" ); | ||
1825 | |||
1826 | REG_FUNC( thread_status ); | ||
1827 | REG_FUNC( thread_join ); | ||
1828 | REG_FUNC( thread_cancel ); | ||
1829 | |||
1830 | REG_STR2( _version, VERSION ); | ||
1831 | REG_FUNC( _single ); | ||
1832 | |||
1833 | REG_FUNC2( _deep_userdata, luaG_deep_userdata ); | ||
1834 | |||
1835 | REG_FUNC( now_secs ); | ||
1836 | REG_FUNC( wakeup_conv ); | ||
1837 | |||
1838 | push_timer_gateway(L); | ||
1839 | lua_setglobal( L, "timer_gateway" ); | ||
1840 | |||
1841 | REG_INT2( max_prio, THREAD_PRIO_MAX ); | ||
1842 | |||
1843 | lua_pushlightuserdata( L, CANCEL_ERROR ); | ||
1844 | lua_setglobal( L, "cancel_error" ); | ||
1845 | |||
1846 | return 0; | ||
1847 | } | ||
1848 | |||
1849 | |||
diff --git a/src/lanes.lua b/src/lanes.lua new file mode 100644 index 0000000..c68506d --- /dev/null +++ b/src/lanes.lua | |||
@@ -0,0 +1,611 @@ | |||
1 | -- | ||
2 | -- LANES.LUA | ||
3 | -- | ||
4 | -- Multithreading and -core support for Lua | ||
5 | -- | ||
6 | -- Author: Asko Kauppi <akauppi@gmail.com> | ||
7 | -- | ||
8 | -- History: | ||
9 | -- Jun-08 AKa: major revise | ||
10 | -- 15-May-07 AKa: pthread_join():less version, some speedup & ability to | ||
11 | -- handle more threads (~ 8000-9000, up from ~ 5000) | ||
12 | -- 26-Feb-07 AKa: serialization working (C side) | ||
13 | -- 17-Sep-06 AKa: started the module (serialization) | ||
14 | -- | ||
15 | --[[ | ||
16 | =============================================================================== | ||
17 | |||
18 | Copyright (C) 2007-08 Asko Kauppi <akauppi@gmail.com> | ||
19 | |||
20 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
21 | of this software and associated documentation files (the "Software"), to deal | ||
22 | in the Software without restriction, including without limitation the rights | ||
23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
24 | copies of the Software, and to permit persons to whom the Software is | ||
25 | furnished to do so, subject to the following conditions: | ||
26 | |||
27 | The above copyright notice and this permission notice shall be included in | ||
28 | all copies or substantial portions of the Software. | ||
29 | |||
30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
36 | THE SOFTWARE. | ||
37 | |||
38 | =============================================================================== | ||
39 | ]]-- | ||
40 | |||
41 | module( "lanes", package.seeall ) | ||
42 | |||
43 | require "lua51-lanes" | ||
44 | assert( type(lanes)=="table" ) | ||
45 | |||
46 | local mm= lanes | ||
47 | |||
48 | local linda_id= assert( mm.linda_id ) | ||
49 | |||
50 | local thread_new= assert(mm.thread_new) | ||
51 | local thread_status= assert(mm.thread_status) | ||
52 | local thread_join= assert(mm.thread_join) | ||
53 | local thread_cancel= assert(mm.thread_cancel) | ||
54 | |||
55 | local _single= assert(mm._single) | ||
56 | local _version= assert(mm._version) | ||
57 | |||
58 | local _deep_userdata= assert(mm._deep_userdata) | ||
59 | |||
60 | local now_secs= assert( mm.now_secs ) | ||
61 | local wakeup_conv= assert( mm.wakeup_conv ) | ||
62 | local timer_gateway= assert( mm.timer_gateway ) | ||
63 | |||
64 | local max_prio= assert( mm.max_prio ) | ||
65 | |||
66 | -- This check is for sublanes requiring Lanes | ||
67 | -- | ||
68 | -- TBD: We could also have the C level expose 'string.gmatch' for us. But this is simpler. | ||
69 | -- | ||
70 | if not string then | ||
71 | error( "To use 'lanes', you will also need to have 'string' available.", 2 ) | ||
72 | end | ||
73 | |||
74 | -- | ||
75 | -- Cache globals for code that might run under sandboxing | ||
76 | -- | ||
77 | local assert= assert | ||
78 | local string_gmatch= assert( string.gmatch ) | ||
79 | local select= assert( select ) | ||
80 | local type= assert( type ) | ||
81 | local pairs= assert( pairs ) | ||
82 | local tostring= assert( tostring ) | ||
83 | local error= assert( error ) | ||
84 | local setmetatable= assert( setmetatable ) | ||
85 | local rawget= assert( rawget ) | ||
86 | |||
87 | ABOUT= | ||
88 | { | ||
89 | author= "Asko Kauppi <akauppi@gmail.com>", | ||
90 | description= "Running multiple Lua states in parallel", | ||
91 | license= "MIT/X11", | ||
92 | copyright= "Copyright (c) 2007-08, Asko Kauppi", | ||
93 | version= _version, | ||
94 | } | ||
95 | |||
96 | |||
97 | -- Making copies of necessary system libs will pass them on as upvalues; | ||
98 | -- only the first state doing "require 'lanes'" will need to have 'string' | ||
99 | -- and 'table' visible. | ||
100 | -- | ||
101 | local function WR(str) | ||
102 | io.stderr:write( str.."\n" ) | ||
103 | end | ||
104 | |||
105 | local function DUMP( tbl ) | ||
106 | if not tbl then return end | ||
107 | local str="" | ||
108 | for k,v in pairs(tbl) do | ||
109 | str= str..k.."="..tostring(v).."\n" | ||
110 | end | ||
111 | WR(str) | ||
112 | end | ||
113 | |||
114 | |||
115 | ---=== Laning ===--- | ||
116 | |||
117 | -- lane_h[1..n]: lane results, same as via 'lane_h:join()' | ||
118 | -- lane_h[0]: can be read to make sure a thread has finished (always gives 'true') | ||
119 | -- lane_h[-1]: error message, without propagating the error | ||
120 | -- | ||
121 | -- Reading a Lane result (or [0]) propagates a possible error in the lane | ||
122 | -- (and execution does not return). Cancelled lanes give 'nil' values. | ||
123 | -- | ||
124 | -- lane_h.state: "pending"/"running"/"waiting"/"done"/"error"/"cancelled" | ||
125 | -- | ||
126 | local lane_mt= { | ||
127 | __index= function( me, k ) | ||
128 | if type(k) == "number" then | ||
129 | -- 'me[0]=true' marks we've already taken in the results | ||
130 | -- | ||
131 | if not rawget( me, 0 ) then | ||
132 | -- Wait indefinately; either propagates an error or | ||
133 | -- returns the return values | ||
134 | -- | ||
135 | me[0]= true -- marker, even on errors | ||
136 | |||
137 | local t= { thread_join(me._ud) } -- wait indefinate | ||
138 | -- | ||
139 | -- { ... } "done": regular return, 0..N results | ||
140 | -- { } "cancelled" | ||
141 | -- { nil, err_str, stack_tbl } "error" | ||
142 | |||
143 | local st= thread_status(me._ud) | ||
144 | if st=="done" then | ||
145 | -- Use 'pairs' and not 'ipairs' so that nil holes in | ||
146 | -- the returned values are tolerated. | ||
147 | -- | ||
148 | for i,v in pairs(t) do | ||
149 | me[i]= v | ||
150 | end | ||
151 | elseif st=="error" then | ||
152 | assert( t[1]==nil and t[2] and type(t[3])=="table" ) | ||
153 | me[-1]= t[2] | ||
154 | -- me[-2] could carry the stack table, but even | ||
155 | -- me[-1] is rather unnecessary (and undocumented); | ||
156 | -- use ':join()' instead. --AKa 22-Jan-2009 | ||
157 | elseif st=="cancelled" then | ||
158 | -- do nothing | ||
159 | else | ||
160 | error( "Unexpected status: "..st ) | ||
161 | end | ||
162 | end | ||
163 | |||
164 | -- Check errors even if we'd first peeked them via [-1] | ||
165 | -- and then came for the actual results. | ||
166 | -- | ||
167 | local err= rawget(me, -1) | ||
168 | if err~=nil and k~=-1 then | ||
169 | -- Note: Lua 5.1 interpreter is not prepared to show | ||
170 | -- non-string errors, so we use 'tostring()' here | ||
171 | -- to get meaningful output. --AKa 22-Jan-2009 | ||
172 | -- | ||
173 | -- Also, the stack dump we get is no good; it only | ||
174 | -- lists our internal Lanes functions. There seems | ||
175 | -- to be no way to switch it off, though. | ||
176 | |||
177 | -- Level 3 should show the line where 'h[x]' was read | ||
178 | -- but this only seems to work for string messages | ||
179 | -- (Lua 5.1.4). No idea, why. --AKa 22-Jan-2009 | ||
180 | -- | ||
181 | error( tostring(err), 3 ) -- level 3 should show the line where 'h[x]' was read | ||
182 | end | ||
183 | return rawget( me, k ) | ||
184 | -- | ||
185 | elseif k=="status" then -- me.status | ||
186 | return thread_status(me._ud) | ||
187 | -- | ||
188 | else | ||
189 | error( "Unknown key: "..k ) | ||
190 | end | ||
191 | end | ||
192 | } | ||
193 | |||
194 | ----- | ||
195 | -- h= lanes.gen( [libs_str|opt_tbl [, ...],] lane_func ) ( [...] ) | ||
196 | -- | ||
197 | -- 'libs': nil: no libraries available (default) | ||
198 | -- "": only base library ('assert', 'print', 'unpack' etc.) | ||
199 | -- "math,os": math + os + base libraries (named ones + base) | ||
200 | -- "*": all standard libraries available | ||
201 | -- | ||
202 | -- 'opt': .priority: int (-2..+2) smaller is lower priority (0 = default) | ||
203 | -- | ||
204 | -- .cancelstep: bool | uint | ||
205 | -- false: cancellation check only at pending Linda operations | ||
206 | -- (send/receive) so no runtime performance penalty (default) | ||
207 | -- true: adequate cancellation check (same as 100) | ||
208 | -- >0: cancellation check every x Lua lines (small number= faster | ||
209 | -- reaction but more performance overhead) | ||
210 | -- | ||
211 | -- .globals: table of globals to set for a new thread (passed by value) | ||
212 | -- | ||
213 | -- ... (more options may be introduced later) ... | ||
214 | -- | ||
215 | -- Calling with a function parameter ('lane_func') ends the string/table | ||
216 | -- modifiers, and prepares a lane generator. One can either finish here, | ||
217 | -- and call the generator later (maybe multiple times, with different parameters) | ||
218 | -- or add on actual thread arguments to also ignite the thread on the same call. | ||
219 | -- | ||
220 | local lane_proxy | ||
221 | |||
222 | local valid_libs= { | ||
223 | ["package"]= true, | ||
224 | ["table"]= true, | ||
225 | ["io"]= true, | ||
226 | ["os"]= true, | ||
227 | ["string"]= true, | ||
228 | ["math"]= true, | ||
229 | ["debug"]= true, | ||
230 | -- | ||
231 | ["base"]= true, | ||
232 | ["coroutine"]= true, | ||
233 | ["*"]= true | ||
234 | } | ||
235 | |||
236 | function gen( ... ) | ||
237 | local opt= {} | ||
238 | local libs= nil | ||
239 | local lev= 2 -- level for errors | ||
240 | |||
241 | local n= select('#',...) | ||
242 | |||
243 | if n==0 then | ||
244 | error( "No parameters!" ) | ||
245 | end | ||
246 | |||
247 | for i=1,n-1 do | ||
248 | local v= select(i,...) | ||
249 | if type(v)=="string" then | ||
250 | libs= libs and libs..","..v or v | ||
251 | elseif type(v)=="table" then | ||
252 | for k,vv in pairs(v) do | ||
253 | opt[k]= vv | ||
254 | end | ||
255 | elseif v==nil then | ||
256 | -- skip | ||
257 | else | ||
258 | error( "Bad parameter: "..tostring(v) ) | ||
259 | end | ||
260 | end | ||
261 | |||
262 | local func= select(n,...) | ||
263 | if type(func)~="function" then | ||
264 | error( "Last parameter not function: "..tostring(func) ) | ||
265 | end | ||
266 | |||
267 | -- Check 'libs' already here, so the error goes in the right place | ||
268 | -- (otherwise will be noticed only once the generator is called) | ||
269 | -- | ||
270 | if libs then | ||
271 | for s in string_gmatch(libs, "[%a*]+") do | ||
272 | if not valid_libs[s] then | ||
273 | error( "Bad library name: "..s ) | ||
274 | end | ||
275 | end | ||
276 | end | ||
277 | |||
278 | local prio, cs, g_tbl | ||
279 | |||
280 | for k,v in pairs(opt) do | ||
281 | if k=="priority" then prio= v | ||
282 | elseif k=="cancelstep" then cs= (v==true) and 100 or | ||
283 | (v==false) and 0 or | ||
284 | type(v)=="number" and v or | ||
285 | error( "Bad cancelstep: "..tostring(v), lev ) | ||
286 | elseif k=="globals" then g_tbl= v | ||
287 | --.. | ||
288 | elseif k==1 then error( "unkeyed option: ".. tostring(v), lev ) | ||
289 | else error( "Bad option: ".. tostring(k), lev ) | ||
290 | end | ||
291 | end | ||
292 | |||
293 | -- Lane generator | ||
294 | -- | ||
295 | return function(...) | ||
296 | return lane_proxy( thread_new( func, libs, cs, prio, g_tbl, | ||
297 | ... ) ) -- args | ||
298 | end | ||
299 | end | ||
300 | |||
301 | lane_proxy= function( ud ) | ||
302 | local proxy= { | ||
303 | _ud= ud, | ||
304 | |||
305 | -- void= me:cancel() | ||
306 | -- | ||
307 | cancel= function(me) thread_cancel(me._ud) end, | ||
308 | |||
309 | -- [...] | [nil,err,stack_tbl]= me:join( [wait_secs=-1] ) | ||
310 | -- | ||
311 | join= function( me, wait ) | ||
312 | return thread_join( me._ud, wait ) | ||
313 | end, | ||
314 | } | ||
315 | assert( proxy._ud ) | ||
316 | setmetatable( proxy, lane_mt ) | ||
317 | |||
318 | return proxy | ||
319 | end | ||
320 | |||
321 | |||
322 | ---=== Lindas ===--- | ||
323 | |||
324 | -- We let the C code attach methods to userdata directly | ||
325 | |||
326 | ----- | ||
327 | -- linda_ud= lanes.linda() | ||
328 | -- | ||
329 | function linda() | ||
330 | local proxy= _deep_userdata( linda_id ) | ||
331 | assert( (type(proxy) == "userdata") and getmetatable(proxy) ) | ||
332 | return proxy | ||
333 | end | ||
334 | |||
335 | |||
336 | ---=== Timers ===--- | ||
337 | |||
338 | -- | ||
339 | -- On first 'require "lanes"', a timer lane is spawned that will maintain | ||
340 | -- timer tables and sleep in between the timer events. All interaction with | ||
341 | -- the timer lane happens via a 'timer_gateway' Linda, which is common to | ||
342 | -- all that 'require "lanes"'. | ||
343 | -- | ||
344 | -- Linda protocol to timer lane: | ||
345 | -- | ||
346 | -- TGW_KEY: linda_h, key, [wakeup_at_secs], [repeat_secs] | ||
347 | -- | ||
348 | local TGW_KEY= "(timer control)" -- the key does not matter, a 'weird' key may help debugging | ||
349 | local first_time_key= "first time" | ||
350 | |||
351 | local first_time= timer_gateway:get(first_time_key) == nil | ||
352 | timer_gateway:set(first_time_key,true) | ||
353 | |||
354 | -- | ||
355 | -- Timer lane; initialize only on the first 'require "lanes"' instance (which naturally | ||
356 | -- has 'table' always declared) | ||
357 | -- | ||
358 | if first_time then | ||
359 | local table_remove= assert( table.remove ) | ||
360 | local table_insert= assert( table.insert ) | ||
361 | |||
362 | -- | ||
363 | -- { [deep_linda_lightuserdata]= { [deep_linda_lightuserdata]=linda_h, | ||
364 | -- [key]= { wakeup_secs [,period_secs] } [, ...] }, | ||
365 | -- } | ||
366 | -- | ||
367 | -- Collection of all running timers, indexed with linda's & key. | ||
368 | -- | ||
369 | -- Note that we need to use the deep lightuserdata identifiers, instead | ||
370 | -- of 'linda_h' themselves as table indices. Otherwise, we'd get multiple | ||
371 | -- entries for the same timer. | ||
372 | -- | ||
373 | -- The 'hidden' reference to Linda proxy is used in 'check_timers()' but | ||
374 | -- also important to keep the Linda alive, even if all outside world threw | ||
375 | -- away pointers to it (which would ruin uniqueness of the deep pointer). | ||
376 | -- Now we're safe. | ||
377 | -- | ||
378 | local collection= {} | ||
379 | |||
380 | -- | ||
381 | -- set_timer( linda_h, key [,wakeup_at_secs [,period_secs]] ) | ||
382 | -- | ||
383 | local function set_timer( linda, key, wakeup_at, period ) | ||
384 | |||
385 | assert( wakeup_at==nil or wakeup_at>0.0 ) | ||
386 | assert( period==nil or period>0.0 ) | ||
387 | |||
388 | local linda_deep= linda:deep() | ||
389 | assert( linda_deep ) | ||
390 | |||
391 | -- Find or make a lookup for this timer | ||
392 | -- | ||
393 | local t1= collection[linda_deep] | ||
394 | if not t1 then | ||
395 | t1= { [linda_deep]= linda } -- proxy to use the Linda | ||
396 | collection[linda_deep]= t1 | ||
397 | end | ||
398 | |||
399 | if wakeup_at==nil then | ||
400 | -- Clear the timer | ||
401 | -- | ||
402 | t1[key]= nil | ||
403 | |||
404 | -- Remove empty tables from collection; speeds timer checks and | ||
405 | -- lets our 'safety reference' proxy be gc:ed as well. | ||
406 | -- | ||
407 | local empty= true | ||
408 | for k,_ in pairs(t1) do | ||
409 | if k~= linda_deep then | ||
410 | empty= false; break | ||
411 | end | ||
412 | end | ||
413 | if empty then | ||
414 | collection[linda_deep]= nil | ||
415 | end | ||
416 | |||
417 | -- Note: any unread timer value is left at 'linda[key]' intensionally; | ||
418 | -- clearing a timer just stops it. | ||
419 | else | ||
420 | -- New timer or changing the timings | ||
421 | -- | ||
422 | local t2= t1[key] | ||
423 | if not t2 then | ||
424 | t2= {}; t1[key]= t2 | ||
425 | end | ||
426 | |||
427 | t2[1]= wakeup_at | ||
428 | t2[2]= period -- can be 'nil' | ||
429 | end | ||
430 | end | ||
431 | |||
432 | ----- | ||
433 | -- [next_wakeup_at]= check_timers() | ||
434 | -- | ||
435 | -- Check timers, and wake up the ones expired (if any) | ||
436 | -- | ||
437 | -- Returns the closest upcoming (remaining) wakeup time (or 'nil' if none). | ||
438 | -- | ||
439 | local function check_timers() | ||
440 | |||
441 | local now= now_secs() | ||
442 | local next_wakeup | ||
443 | |||
444 | for linda_deep,t1 in pairs(collection) do | ||
445 | for key,t2 in pairs(t1) do | ||
446 | -- | ||
447 | if key==linda_deep then | ||
448 | -- no 'continue' in Lua :/ | ||
449 | else | ||
450 | -- 't2': { wakeup_at_secs [,period_secs] } | ||
451 | -- | ||
452 | local wakeup_at= t2[1] | ||
453 | local period= t2[2] -- may be 'nil' | ||
454 | |||
455 | if wakeup_at <= now then | ||
456 | local linda= t1[linda_deep] | ||
457 | assert(linda) | ||
458 | |||
459 | linda:set( key, now ) | ||
460 | |||
461 | -- 'pairs()' allows the values to be modified (and even | ||
462 | -- removed) as far as keys are not touched | ||
463 | |||
464 | if not period then | ||
465 | -- one-time timer; gone | ||
466 | -- | ||
467 | t1[key]= nil | ||
468 | wakeup_at= nil -- no 'continue' in Lua :/ | ||
469 | else | ||
470 | -- repeating timer; find next wakeup (may jump multiple repeats) | ||
471 | -- | ||
472 | repeat | ||
473 | wakeup_at= wakeup_at+period | ||
474 | until wakeup_at > now | ||
475 | |||
476 | t2[1]= wakeup_at | ||
477 | end | ||
478 | end | ||
479 | |||
480 | if wakeup_at and ((not next_wakeup) or (wakeup_at < next_wakeup)) then | ||
481 | next_wakeup= wakeup_at | ||
482 | end | ||
483 | end | ||
484 | end -- t2 loop | ||
485 | end -- t1 loop | ||
486 | |||
487 | return next_wakeup -- may be 'nil' | ||
488 | end | ||
489 | |||
490 | ----- | ||
491 | -- Snore loop (run as a lane on the background) | ||
492 | -- | ||
493 | -- High priority, to get trustworthy timings. | ||
494 | -- | ||
495 | -- We let the timer lane be a "free running" thread; no handle to it | ||
496 | -- remains. | ||
497 | -- | ||
498 | gen( "io", { priority=max_prio }, function() | ||
499 | |||
500 | while true do | ||
501 | local next_wakeup= check_timers() | ||
502 | |||
503 | -- Sleep until next timer to wake up, or a set/clear command | ||
504 | -- | ||
505 | local secs= next_wakeup and (next_wakeup - now_secs()) or nil | ||
506 | local linda= timer_gateway:receive( secs, TGW_KEY ) | ||
507 | |||
508 | if linda then | ||
509 | local key= timer_gateway:receive( 0.0, TGW_KEY ) | ||
510 | local wakeup_at= timer_gateway:receive( 0.0, TGW_KEY ) | ||
511 | local period= timer_gateway:receive( 0.0, TGW_KEY ) | ||
512 | assert( key and wakeup_at and period ) | ||
513 | |||
514 | set_timer( linda, key, wakeup_at, period>0 and period or nil ) | ||
515 | end | ||
516 | end | ||
517 | end )() | ||
518 | end | ||
519 | |||
520 | ----- | ||
521 | -- = timer( linda_h, key_val, date_tbl|first_secs [,period_secs] ) | ||
522 | -- | ||
523 | function timer( linda, key, a, period ) | ||
524 | |||
525 | if a==0.0 then | ||
526 | -- Caller expects to get current time stamp in Linda, on return | ||
527 | -- (like the timer had expired instantly); it would be good to set this | ||
528 | -- as late as possible (to give most current time) but also we want it | ||
529 | -- to precede any possible timers that might start striking. | ||
530 | -- | ||
531 | linda:set( key, now_secs() ) | ||
532 | |||
533 | if not period or period==0.0 then | ||
534 | timer_gateway:send( TGW_KEY, linda, key, nil, nil ) -- clear the timer | ||
535 | return -- nothing more to do | ||
536 | end | ||
537 | a= period | ||
538 | end | ||
539 | |||
540 | local wakeup_at= type(a)=="table" and wakeup_conv(a) -- given point of time | ||
541 | or now_secs()+a | ||
542 | -- queue to timer | ||
543 | -- | ||
544 | timer_gateway:send( TGW_KEY, linda, key, wakeup_at, period ) | ||
545 | end | ||
546 | |||
547 | |||
548 | ---=== Lock & atomic generators ===--- | ||
549 | |||
550 | -- These functions are just surface sugar, but make solutions easier to read. | ||
551 | -- Not many applications should even need explicit locks or atomic counters. | ||
552 | |||
553 | -- | ||
554 | -- lock_f= lanes.genlock( linda_h, key [,N_uint=1] ) | ||
555 | -- | ||
556 | -- = lock_f( +M ) -- acquire M | ||
557 | -- ...locked... | ||
558 | -- = lock_f( -M ) -- release M | ||
559 | -- | ||
560 | -- Returns an access function that allows 'N' simultaneous entries between | ||
561 | -- acquire (+M) and release (-M). For binary locks, use M==1. | ||
562 | -- | ||
563 | function genlock( linda, key, N ) | ||
564 | linda:limit(key,N) | ||
565 | linda:set(key,nil) -- clears existing data | ||
566 | |||
567 | -- | ||
568 | -- [true [, ...]= trues(uint) | ||
569 | -- | ||
570 | local function trues(n) | ||
571 | if n>0 then return true,trues(n-1) end | ||
572 | end | ||
573 | |||
574 | return | ||
575 | function(M) | ||
576 | if M>0 then | ||
577 | -- 'nil' timeout allows 'key' to be numeric | ||
578 | linda:send( nil, key, trues(M) ) -- suspends until been able to push them | ||
579 | else | ||
580 | for i=1,-M do | ||
581 | linda:receive( key ) | ||
582 | end | ||
583 | end | ||
584 | end | ||
585 | end | ||
586 | |||
587 | |||
588 | -- | ||
589 | -- atomic_f= lanes.genatomic( linda_h, key [,initial_num=0.0] ) | ||
590 | -- | ||
591 | -- int= atomic_f( [diff_num=1.0] ) | ||
592 | -- | ||
593 | -- Returns an access function that allows atomic increment/decrement of the | ||
594 | -- number in 'key'. | ||
595 | -- | ||
596 | function genatomic( linda, key, initial_val ) | ||
597 | linda:limit(key,2) -- value [,true] | ||
598 | linda:set(key,initial_val or 0.0) -- clears existing data (also queue) | ||
599 | |||
600 | return | ||
601 | function(diff) | ||
602 | -- 'nil' allows 'key' to be numeric | ||
603 | linda:send( nil, key, true ) -- suspends until our 'true' is in | ||
604 | local val= linda:get(key) + (diff or 1.0) | ||
605 | linda:set( key, val ) -- releases the lock, by emptying queue | ||
606 | return val | ||
607 | end | ||
608 | end | ||
609 | |||
610 | |||
611 | --the end | ||
diff --git a/src/threading.c b/src/threading.c new file mode 100644 index 0000000..68d1e41 --- /dev/null +++ b/src/threading.c | |||
@@ -0,0 +1,721 @@ | |||
1 | /* | ||
2 | * THREADING.C Copyright (c) 2007-08, Asko Kauppi | ||
3 | * | ||
4 | * Lua Lanes OS threading specific code. | ||
5 | * | ||
6 | * References: | ||
7 | * <http://www.cse.wustl.edu/~schmidt/win32-cv-1.html> | ||
8 | */ | ||
9 | |||
10 | /* | ||
11 | =============================================================================== | ||
12 | |||
13 | Copyright (C) 2007-08 Asko Kauppi <akauppi@gmail.com> | ||
14 | |||
15 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
16 | of this software and associated documentation files (the "Software"), to deal | ||
17 | in the Software without restriction, including without limitation the rights | ||
18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
19 | copies of the Software, and to permit persons to whom the Software is | ||
20 | furnished to do so, subject to the following conditions: | ||
21 | |||
22 | The above copyright notice and this permission notice shall be included in | ||
23 | all copies or substantial portions of the Software. | ||
24 | |||
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
31 | THE SOFTWARE. | ||
32 | |||
33 | =============================================================================== | ||
34 | */ | ||
35 | #include <stdio.h> | ||
36 | #include <stdlib.h> | ||
37 | #include <assert.h> | ||
38 | #include <errno.h> | ||
39 | #include <math.h> | ||
40 | |||
41 | #include "threading.h" | ||
42 | #include "lua.h" | ||
43 | |||
44 | #if !((defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC)) | ||
45 | # include <sys/time.h> | ||
46 | #endif | ||
47 | |||
48 | |||
49 | #if defined(PLATFORM_LINUX) || defined(PLATFORM_CYGWIN) | ||
50 | # include <sys/types.h> | ||
51 | # include <unistd.h> | ||
52 | #endif | ||
53 | |||
54 | /* Linux needs to check, whether it's been run as root | ||
55 | */ | ||
56 | #ifdef PLATFORM_LINUX | ||
57 | volatile bool_t sudo; | ||
58 | #endif | ||
59 | |||
60 | #ifdef _MSC_VER | ||
61 | // ".. selected for automatic inline expansion" (/O2 option) | ||
62 | # pragma warning( disable : 4711 ) | ||
63 | // ".. type cast from function pointer ... to data pointer" | ||
64 | # pragma warning( disable : 4054 ) | ||
65 | #endif | ||
66 | |||
67 | //#define THREAD_CREATE_RETRIES_MAX 20 | ||
68 | // loops (maybe retry forever?) | ||
69 | |||
70 | /* | ||
71 | * FAIL is for unexpected API return values - essentially programming | ||
72 | * error in _this_ code. | ||
73 | */ | ||
74 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) | ||
75 | static void FAIL( const char *funcname, int rc ) { | ||
76 | fprintf( stderr, "%s() failed! (%d)\n", funcname, rc ); | ||
77 | abort(); | ||
78 | } | ||
79 | #endif | ||
80 | |||
81 | |||
82 | /* | ||
83 | * Returns millisecond timing (in seconds) for the current time. | ||
84 | * | ||
85 | * Note: This function should be called once in single-threaded mode in Win32, | ||
86 | * to get it initialized. | ||
87 | */ | ||
88 | time_d now_secs(void) { | ||
89 | |||
90 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) | ||
91 | /* | ||
92 | * Windows FILETIME values are "100-nanosecond intervals since | ||
93 | * January 1, 1601 (UTC)" (MSDN). Well, we'd want Unix Epoch as | ||
94 | * the offset and it seems, so would they: | ||
95 | * | ||
96 | * <http://msdn.microsoft.com/en-us/library/ms724928(VS.85).aspx> | ||
97 | */ | ||
98 | SYSTEMTIME st; | ||
99 | FILETIME ft; | ||
100 | ULARGE_INTEGER uli; | ||
101 | static ULARGE_INTEGER uli_epoch; // Jan 1st 1970 0:0:0 | ||
102 | |||
103 | if (uli_epoch.HighPart==0) { | ||
104 | st.wYear= 1970; | ||
105 | st.wMonth= 1; // Jan | ||
106 | st.wDay= 1; | ||
107 | st.wHour= st.wMinute= st.wSecond= st.wMilliseconds= 0; | ||
108 | |||
109 | if (!SystemTimeToFileTime( &st, &ft )) | ||
110 | FAIL( "SystemTimeToFileTime", GetLastError() ); | ||
111 | |||
112 | uli_epoch.LowPart= ft.dwLowDateTime; | ||
113 | uli_epoch.HighPart= ft.dwHighDateTime; | ||
114 | } | ||
115 | |||
116 | GetSystemTime( &st ); // current system date/time in UTC | ||
117 | if (!SystemTimeToFileTime( &st, &ft )) | ||
118 | FAIL( "SystemTimeToFileTime", GetLastError() ); | ||
119 | |||
120 | uli.LowPart= ft.dwLowDateTime; | ||
121 | uli.HighPart= ft.dwHighDateTime; | ||
122 | |||
123 | /* 'double' has less accuracy than 64-bit int, but if it were to degrade, | ||
124 | * it would do so gracefully. In practise, the integer accuracy is not | ||
125 | * of the 100ns class but just 1ms (Windows XP). | ||
126 | */ | ||
127 | # if 1 | ||
128 | // >= 2.0.3 code | ||
129 | return (double) ((uli.QuadPart - uli_epoch.QuadPart)/10000) / 1000.0; | ||
130 | # elif 0 | ||
131 | // fix from Kriss Daniels, see: | ||
132 | // <http://luaforge.net/forum/forum.php?thread_id=22704&forum_id=1781> | ||
133 | // | ||
134 | // "seem to be getting negative numbers from the old version, probably number | ||
135 | // conversion clipping, this fixes it and maintains ms resolution" | ||
136 | // | ||
137 | // This was a bad fix, and caused timer test 5 sec timers to disappear. | ||
138 | // --AKa 25-Jan-2009 | ||
139 | // | ||
140 | return ((double)((signed)((uli.QuadPart/10000) - (uli_epoch.QuadPart/10000)))) / 1000.0; | ||
141 | # else | ||
142 | // <= 2.0.2 code | ||
143 | return (double)(uli.QuadPart - uli_epoch.QuadPart) / 10000000.0; | ||
144 | # endif | ||
145 | #else | ||
146 | struct timeval tv; | ||
147 | // { | ||
148 | // time_t tv_sec; /* seconds since Jan. 1, 1970 */ | ||
149 | // suseconds_t tv_usec; /* and microseconds */ | ||
150 | // }; | ||
151 | |||
152 | int rc= gettimeofday( &tv, NULL /*time zone not used any more (in Linux)*/ ); | ||
153 | assert( rc==0 ); | ||
154 | |||
155 | return ((double)tv.tv_sec) + ((tv.tv_usec)/1000) / 1000.0; | ||
156 | #endif | ||
157 | } | ||
158 | |||
159 | |||
160 | /* | ||
161 | */ | ||
162 | time_d SIGNAL_TIMEOUT_PREPARE( double secs ) { | ||
163 | if (secs<=0.0) return secs; | ||
164 | else return now_secs() + secs; | ||
165 | } | ||
166 | |||
167 | |||
168 | #if !((defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC)) | ||
169 | /* | ||
170 | * Prepare 'abs_secs' kind of timeout to 'timespec' format | ||
171 | */ | ||
172 | static void prepare_timeout( struct timespec *ts, time_d abs_secs ) { | ||
173 | assert(ts); | ||
174 | assert( abs_secs >= 0.0 ); | ||
175 | |||
176 | if (abs_secs==0.0) | ||
177 | abs_secs= now_secs(); | ||
178 | |||
179 | ts->tv_sec= floor( abs_secs ); | ||
180 | ts->tv_nsec= ((long)((abs_secs - ts->tv_sec) * 1000.0 +0.5)) * 1000000UL; // 1ms = 1000000ns | ||
181 | } | ||
182 | #endif | ||
183 | |||
184 | |||
185 | /*---=== Threading ===---*/ | ||
186 | |||
187 | //--- | ||
188 | // It may be meaningful to explicitly limit the new threads' C stack size. | ||
189 | // We should know how much Lua needs in the C stack, all Lua side allocations | ||
190 | // are done in heap so they don't count. | ||
191 | // | ||
192 | // Consequence of _not_ limiting the stack is running out of virtual memory | ||
193 | // with 1000-5000 threads on 32-bit systems. | ||
194 | // | ||
195 | // Note: using external C modules may be affected by the stack size check. | ||
196 | // if having problems, set back to '0' (default stack size of the system). | ||
197 | // | ||
198 | // Win32: 64K (?) | ||
199 | // Win64: xxx | ||
200 | // | ||
201 | // Linux x86: 2MB Ubuntu 7.04 via 'pthread_getstacksize()' | ||
202 | // Linux x64: xxx | ||
203 | // Linux ARM: xxx | ||
204 | // | ||
205 | // OS X 10.4.9: 512K <http://developer.apple.com/qa/qa2005/qa1419.html> | ||
206 | // valid values N * 4KB | ||
207 | // | ||
208 | #ifndef _THREAD_STACK_SIZE | ||
209 | # if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PLATFORM_CYGWIN) | ||
210 | # define _THREAD_STACK_SIZE 0 | ||
211 | // Win32: does it work with less? | ||
212 | # elif (defined PLATFORM_OSX) | ||
213 | # define _THREAD_STACK_SIZE (524288/2) // 262144 | ||
214 | // OS X: "make test" works on 65536 and even below | ||
215 | // "make perftest" works on >= 4*65536 == 262144 (not 3*65536) | ||
216 | # elif (defined PLATFORM_LINUX) && (defined __i386) | ||
217 | # define _THREAD_STACK_SIZE (2097152/16) // 131072 | ||
218 | // Linux x86 (Ubuntu 7.04): "make perftest" works on /16 (not on /32) | ||
219 | # elif (defined PLATFORM_BSD) && (defined __i386) | ||
220 | # define _THREAD_STACK_SIZE (1048576/8) // 131072 | ||
221 | // FreeBSD 6.2 SMP i386: ("gmake perftest" works on /8 (not on /16) | ||
222 | # endif | ||
223 | #endif | ||
224 | |||
225 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) | ||
226 | // | ||
227 | void MUTEX_INIT( MUTEX_T *ref ) { | ||
228 | *ref= CreateMutex( NULL /*security attr*/, FALSE /*not locked*/, NULL ); | ||
229 | if (!ref) FAIL( "CreateMutex", GetLastError() ); | ||
230 | } | ||
231 | void MUTEX_FREE( MUTEX_T *ref ) { | ||
232 | if (!CloseHandle(*ref)) FAIL( "CloseHandle (mutex)", GetLastError() ); | ||
233 | *ref= NULL; | ||
234 | } | ||
235 | void MUTEX_LOCK( MUTEX_T *ref ) { | ||
236 | DWORD rc= WaitForSingleObject(*ref,INFINITE); | ||
237 | if (rc!=0) FAIL( "WaitForSingleObject", rc==WAIT_FAILED ? GetLastError() : rc ); | ||
238 | } | ||
239 | void MUTEX_UNLOCK( MUTEX_T *ref ) { | ||
240 | if (!ReleaseMutex(*ref)) | ||
241 | FAIL( "ReleaseMutex", GetLastError() ); | ||
242 | } | ||
243 | /* MSDN: "If you would like to use the CRT in ThreadProc, use the | ||
244 | _beginthreadex function instead (of CreateThread)." | ||
245 | MSDN: "you can create at most 2028 threads" | ||
246 | */ | ||
247 | void | ||
248 | THREAD_CREATE( THREAD_T *ref, | ||
249 | THREAD_RETURN_T (__stdcall *func)( void * ), | ||
250 | // Note: Visual C++ requires '__stdcall' where it is | ||
251 | void *data, int prio /* -3..+3 */ ) { | ||
252 | |||
253 | HANDLE h= (HANDLE)_beginthreadex( NULL, // security | ||
254 | _THREAD_STACK_SIZE, | ||
255 | func, | ||
256 | data, | ||
257 | 0, // flags (0/CREATE_SUSPENDED) | ||
258 | NULL // thread id (not used) | ||
259 | ); | ||
260 | |||
261 | if (h == INVALID_HANDLE_VALUE) FAIL( "CreateThread", GetLastError() ); | ||
262 | |||
263 | if (prio!= 0) { | ||
264 | int win_prio= (prio == +3) ? THREAD_PRIORITY_TIME_CRITICAL : | ||
265 | (prio == +2) ? THREAD_PRIORITY_HIGHEST : | ||
266 | (prio == +1) ? THREAD_PRIORITY_ABOVE_NORMAL : | ||
267 | (prio == -1) ? THREAD_PRIORITY_BELOW_NORMAL : | ||
268 | (prio == -2) ? THREAD_PRIORITY_LOWEST : | ||
269 | THREAD_PRIORITY_IDLE; // -3 | ||
270 | |||
271 | if (!SetThreadPriority( h, win_prio )) | ||
272 | FAIL( "SetThreadPriority", GetLastError() ); | ||
273 | } | ||
274 | *ref= h; | ||
275 | } | ||
276 | // | ||
277 | bool_t THREAD_WAIT( THREAD_T *ref, double secs ) { | ||
278 | long ms= (long)((secs*1000.0)+0.5); | ||
279 | |||
280 | DWORD rc= WaitForSingleObject( *ref, ms<0 ? INFINITE:ms /*timeout*/ ); | ||
281 | // | ||
282 | // (WAIT_ABANDONED) | ||
283 | // WAIT_OBJECT_0 success (0) | ||
284 | // WAIT_TIMEOUT | ||
285 | // WAIT_FAILED more info via GetLastError() | ||
286 | |||
287 | if (rc == WAIT_TIMEOUT) return FALSE; | ||
288 | if (rc != 0) FAIL( "WaitForSingleObject", rc ); | ||
289 | *ref= NULL; // thread no longer usable | ||
290 | return TRUE; | ||
291 | } | ||
292 | // | ||
293 | void THREAD_KILL( THREAD_T *ref ) { | ||
294 | if (!TerminateThread( *ref, 0 )) FAIL("TerminateThread", GetLastError()); | ||
295 | *ref= NULL; | ||
296 | } | ||
297 | // | ||
298 | void SIGNAL_INIT( SIGNAL_T *ref ) { | ||
299 | // 'manual reset' event type selected, to be able to wake up all the | ||
300 | // waiting threads. | ||
301 | // | ||
302 | HANDLE h= CreateEvent( NULL, // security attributes | ||
303 | TRUE, // TRUE: manual event | ||
304 | FALSE, // Initial state | ||
305 | NULL ); // name | ||
306 | |||
307 | if (h == NULL) FAIL( "CreateEvent", GetLastError() ); | ||
308 | *ref= h; | ||
309 | } | ||
310 | void SIGNAL_FREE( SIGNAL_T *ref ) { | ||
311 | if (!CloseHandle(*ref)) FAIL( "CloseHandle (event)", GetLastError() ); | ||
312 | *ref= NULL; | ||
313 | } | ||
314 | // | ||
315 | bool_t SIGNAL_WAIT( SIGNAL_T *ref, MUTEX_T *mu_ref, time_d abs_secs ) { | ||
316 | DWORD rc; | ||
317 | long ms; | ||
318 | |||
319 | if (abs_secs<0.0) | ||
320 | ms= INFINITE; | ||
321 | else if (abs_secs==0.0) | ||
322 | ms= 0; | ||
323 | else { | ||
324 | ms= (long) ((abs_secs - now_secs())*1000.0 + 0.5); | ||
325 | |||
326 | // If the time already passed, still try once (ms==0). A short timeout | ||
327 | // may have turned negative or 0 because of the two time samples done. | ||
328 | // | ||
329 | if (ms<0) ms= 0; | ||
330 | } | ||
331 | |||
332 | // Unlock and start a wait, atomically (like condition variables do) | ||
333 | // | ||
334 | rc= SignalObjectAndWait( *mu_ref, // "object to signal" (unlock) | ||
335 | *ref, // "object to wait on" | ||
336 | ms, | ||
337 | FALSE ); // not alertable | ||
338 | |||
339 | // All waiting locks are woken here; each competes for the lock in turn. | ||
340 | // | ||
341 | // Note: We must get the lock even if we've timed out; it makes upper | ||
342 | // level code equivalent to how PThread does it. | ||
343 | // | ||
344 | MUTEX_LOCK(mu_ref); | ||
345 | |||
346 | if (rc==WAIT_TIMEOUT) return FALSE; | ||
347 | if (rc!=0) FAIL( "SignalObjectAndWait", rc ); | ||
348 | return TRUE; | ||
349 | } | ||
350 | void SIGNAL_ALL( SIGNAL_T *ref ) { | ||
351 | /* | ||
352 | * MSDN tries to scare that 'PulseEvent' is bad, unreliable and should not be | ||
353 | * used. Use condition variables instead (wow, they have that!?!); which will | ||
354 | * ONLY WORK on Vista and 2008 Server, it seems... so MS, isn't it. | ||
355 | * | ||
356 | * I refuse to believe that; using 'PulseEvent' is probably just as good as | ||
357 | * using Windows (XP) in the first place. Just don't use APC's (asynchronous | ||
358 | * process calls) in your C side coding. | ||
359 | */ | ||
360 | // PulseEvent on manual event: | ||
361 | // | ||
362 | // Release ALL threads waiting for it (and go instantly back to unsignalled | ||
363 | // status = future threads to start a wait will wait) | ||
364 | // | ||
365 | if (!PulseEvent( *ref )) | ||
366 | FAIL( "PulseEvent", GetLastError() ); | ||
367 | } | ||
368 | #else | ||
369 | // PThread (Linux, OS X, ...) | ||
370 | // | ||
371 | // On OS X, user processes seem to be able to change priorities. | ||
372 | // On Linux, SCHED_RR and su privileges are required.. !-( | ||
373 | // | ||
374 | #include <errno.h> | ||
375 | #include <sys/time.h> | ||
376 | // | ||
377 | static void _PT_FAIL( int rc, const char *name, const char *file, uint_t line ) { | ||
378 | const char *why= (rc==EINVAL) ? "EINVAL" : | ||
379 | (rc==EBUSY) ? "EBUSY" : | ||
380 | (rc==EPERM) ? "EPERM" : | ||
381 | (rc==ENOMEM) ? "ENOMEM" : | ||
382 | (rc==ESRCH) ? "ESRCH" : | ||
383 | //... | ||
384 | ""; | ||
385 | fprintf( stderr, "%s %d: %s failed, %d %s\n", file, line, name, rc, why ); | ||
386 | abort(); | ||
387 | } | ||
388 | #define PT_CALL( call ) { int rc= call; if (rc!=0) _PT_FAIL( rc, #call, __FILE__, __LINE__ ); } | ||
389 | // | ||
390 | void SIGNAL_INIT( SIGNAL_T *ref ) { | ||
391 | PT_CALL( pthread_cond_init(ref,NULL /*attr*/) ); | ||
392 | } | ||
393 | void SIGNAL_FREE( SIGNAL_T *ref ) { | ||
394 | PT_CALL( pthread_cond_destroy(ref) ); | ||
395 | } | ||
396 | // | ||
397 | /* | ||
398 | * Timeout is given as absolute since we may have fake wakeups during | ||
399 | * a timed out sleep. A Linda with some other key read, or just because | ||
400 | * PThread cond vars can wake up unwantedly. | ||
401 | */ | ||
402 | bool_t SIGNAL_WAIT( SIGNAL_T *ref, pthread_mutex_t *mu, time_d abs_secs ) { | ||
403 | if (abs_secs<0.0) { | ||
404 | PT_CALL( pthread_cond_wait( ref, mu ) ); // infinite | ||
405 | } else { | ||
406 | int rc; | ||
407 | struct timespec ts; | ||
408 | |||
409 | assert( abs_secs != 0.0 ); | ||
410 | prepare_timeout( &ts, abs_secs ); | ||
411 | |||
412 | rc= pthread_cond_timedwait( ref, mu, &ts ); | ||
413 | |||
414 | if (rc==ETIMEDOUT) return FALSE; | ||
415 | if (rc) { _PT_FAIL( rc, "pthread_cond_timedwait()", __FILE__, __LINE__ ); } | ||
416 | } | ||
417 | return TRUE; | ||
418 | } | ||
419 | // | ||
420 | void SIGNAL_ONE( SIGNAL_T *ref ) { | ||
421 | PT_CALL( pthread_cond_signal(ref) ); // wake up ONE (or no) waiting thread | ||
422 | } | ||
423 | // | ||
424 | void SIGNAL_ALL( SIGNAL_T *ref ) { | ||
425 | PT_CALL( pthread_cond_broadcast(ref) ); // wake up ALL waiting threads | ||
426 | } | ||
427 | // | ||
428 | void THREAD_CREATE( THREAD_T* ref, | ||
429 | THREAD_RETURN_T (*func)( void * ), | ||
430 | void *data, int prio /* -2..+2 */ ) { | ||
431 | pthread_attr_t _a; | ||
432 | pthread_attr_t *a= &_a; | ||
433 | struct sched_param sp; | ||
434 | |||
435 | PT_CALL( pthread_attr_init(a) ); | ||
436 | |||
437 | #ifndef PTHREAD_TIMEDJOIN | ||
438 | // We create a NON-JOINABLE thread. This is mainly due to the lack of | ||
439 | // 'pthread_timedjoin()', but does offer other benefits (s.a. earlier | ||
440 | // freeing of the thread's resources). | ||
441 | // | ||
442 | PT_CALL( pthread_attr_setdetachstate(a,PTHREAD_CREATE_DETACHED) ); | ||
443 | #endif | ||
444 | |||
445 | // Use this to find a system's default stack size (DEBUG) | ||
446 | #if 0 | ||
447 | { size_t n; pthread_attr_getstacksize( a, &n ); | ||
448 | fprintf( stderr, "Getstack: %u\n", (unsigned int)n ); } | ||
449 | // 524288 on OS X | ||
450 | // 2097152 on Linux x86 (Ubuntu 7.04) | ||
451 | // 1048576 on FreeBSD 6.2 SMP i386 | ||
452 | #endif | ||
453 | |||
454 | #if (defined _THREAD_STACK_SIZE) && (_THREAD_STACK_SIZE > 0) | ||
455 | PT_CALL( pthread_attr_setstacksize( a, _THREAD_STACK_SIZE ) ); | ||
456 | #endif | ||
457 | |||
458 | bool_t normal= | ||
459 | #if defined(PLATFORM_LINUX) && defined(LINUX_SCHED_RR) | ||
460 | !sudo; // with sudo, even normal thread must use SCHED_RR | ||
461 | #else | ||
462 | prio == 0; // create a default thread if | ||
463 | #endif | ||
464 | if (!normal) { | ||
465 | // NB: PThreads priority handling is about as twisty as one can get it | ||
466 | // (and then some). DON*T TRUST ANYTHING YOU READ ON THE NET!!! | ||
467 | |||
468 | // "The specified scheduling parameters are only used if the scheduling | ||
469 | // parameter inheritance attribute is PTHREAD_EXPLICIT_SCHED." | ||
470 | // | ||
471 | PT_CALL( pthread_attr_setinheritsched( a, PTHREAD_EXPLICIT_SCHED ) ); | ||
472 | |||
473 | //--- | ||
474 | // "Select the scheduling policy for the thread: one of SCHED_OTHER | ||
475 | // (regular, non-real-time scheduling), SCHED_RR (real-time, | ||
476 | // round-robin) or SCHED_FIFO (real-time, first-in first-out)." | ||
477 | // | ||
478 | // "Using the RR policy ensures that all threads having the same | ||
479 | // priority level will be scheduled equally, regardless of their activity." | ||
480 | // | ||
481 | // "For SCHED_FIFO and SCHED_RR, the only required member of the | ||
482 | // sched_param structure is the priority sched_priority. For SCHED_OTHER, | ||
483 | // the affected scheduling parameters are implementation-defined." | ||
484 | // | ||
485 | // "The priority of a thread is specified as a delta which is added to | ||
486 | // the priority of the process." | ||
487 | // | ||
488 | // ".. priority is an integer value, in the range from 1 to 127. | ||
489 | // 1 is the least-favored priority, 127 is the most-favored." | ||
490 | // | ||
491 | // "Priority level 0 cannot be used: it is reserved for the system." | ||
492 | // | ||
493 | // "When you use specify a priority of -99 in a call to | ||
494 | // pthread_setschedparam(), the priority of the target thread is | ||
495 | // lowered to the lowest possible value." | ||
496 | // | ||
497 | // ... | ||
498 | |||
499 | // ** CONCLUSION ** | ||
500 | // | ||
501 | // PThread priorities are _hugely_ system specific, and we need at | ||
502 | // least OS specific settings. Hopefully, Linuxes and OS X versions | ||
503 | // are uniform enough, among each other... | ||
504 | // | ||
505 | #ifdef PLATFORM_OSX | ||
506 | // AK 10-Apr-07 (OS X PowerPC 10.4.9): | ||
507 | // | ||
508 | // With SCHED_RR, 26 seems to be the "normal" priority, where setting | ||
509 | // it does not seem to affect the order of threads processed. | ||
510 | // | ||
511 | // With SCHED_OTHER, the range 25..32 is normal (maybe the same 26, | ||
512 | // but the difference is not so clear with OTHER). | ||
513 | // | ||
514 | // 'sched_get_priority_min()' and '..max()' give 15, 47 as the | ||
515 | // priority limits. This could imply, user mode applications won't | ||
516 | // be able to use values outside of that range. | ||
517 | // | ||
518 | #define _PRIO_MODE SCHED_OTHER | ||
519 | |||
520 | // OS X 10.4.9 (PowerPC) gives ENOTSUP for process scope | ||
521 | //#define _PRIO_SCOPE PTHREAD_SCOPE_PROCESS | ||
522 | |||
523 | #define _PRIO_HI 32 // seems to work (_carefully_ picked!) | ||
524 | #define _PRIO_0 26 // detected | ||
525 | #define _PRIO_LO 1 // seems to work (tested) | ||
526 | |||
527 | #elif defined(PLATFORM_LINUX) | ||
528 | // (based on Ubuntu Linux 2.6.15 kernel) | ||
529 | // | ||
530 | // SCHED_OTHER is the default policy, but does not allow for priorities. | ||
531 | // SCHED_RR allows priorities, all of which (1..99) are higher than | ||
532 | // a thread with SCHED_OTHER policy. | ||
533 | // | ||
534 | // <http://kerneltrap.org/node/6080> | ||
535 | // <http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library> | ||
536 | // <http://www.net.in.tum.de/~gregor/docs/pthread-scheduling.html> | ||
537 | // | ||
538 | // Manuals suggest checking #ifdef _POSIX_THREAD_PRIORITY_SCHEDULING, | ||
539 | // but even Ubuntu does not seem to define it. | ||
540 | // | ||
541 | #define _PRIO_MODE SCHED_RR | ||
542 | |||
543 | // NTLP 2.5: only system scope allowed (being the basic reason why | ||
544 | // root privileges are required..) | ||
545 | //#define _PRIO_SCOPE PTHREAD_SCOPE_PROCESS | ||
546 | |||
547 | #define _PRIO_HI 99 | ||
548 | #define _PRIO_0 50 | ||
549 | #define _PRIO_LO 1 | ||
550 | |||
551 | #elif defined(PLATFORM_BSD) | ||
552 | // | ||
553 | // <http://www.net.in.tum.de/~gregor/docs/pthread-scheduling.html> | ||
554 | // | ||
555 | // "When control over the thread scheduling is desired, then FreeBSD | ||
556 | // with the libpthread implementation is by far the best choice .." | ||
557 | // | ||
558 | #define _PRIO_MODE SCHED_OTHER | ||
559 | #define _PRIO_SCOPE PTHREAD_SCOPE_PROCESS | ||
560 | #define _PRIO_HI 31 | ||
561 | #define _PRIO_0 15 | ||
562 | #define _PRIO_LO 1 | ||
563 | |||
564 | #elif defined(PLATFORM_CYGWIN) | ||
565 | // | ||
566 | // TBD: Find right values for Cygwin | ||
567 | // | ||
568 | #else | ||
569 | #error "Unknown OS: not implemented!" | ||
570 | #endif | ||
571 | |||
572 | #ifdef _PRIO_SCOPE | ||
573 | PT_CALL( pthread_attr_setscope( a, _PRIO_SCOPE ) ); | ||
574 | #endif | ||
575 | PT_CALL( pthread_attr_setschedpolicy( a, _PRIO_MODE ) ); | ||
576 | |||
577 | #define _PRIO_AN (_PRIO_0 + ((_PRIO_HI-_PRIO_0)/2) ) | ||
578 | #define _PRIO_BN (_PRIO_LO + ((_PRIO_0-_PRIO_LO)/2) ) | ||
579 | |||
580 | sp.sched_priority= | ||
581 | (prio == +2) ? _PRIO_HI : | ||
582 | (prio == +1) ? _PRIO_AN : | ||
583 | #if defined(PLATFORM_LINUX) && defined(LINUX_SCHED_RR) | ||
584 | (prio == 0) ? _PRIO_0 : | ||
585 | #endif | ||
586 | (prio == -1) ? _PRIO_BN : _PRIO_LO; | ||
587 | |||
588 | PT_CALL( pthread_attr_setschedparam( a, &sp ) ); | ||
589 | } | ||
590 | |||
591 | //--- | ||
592 | // Seems on OS X, _POSIX_THREAD_THREADS_MAX is some kind of system | ||
593 | // thread limit (not userland thread). Actual limit for us is way higher. | ||
594 | // PTHREAD_THREADS_MAX is not defined (even though man page refers to it!) | ||
595 | // | ||
596 | # ifndef THREAD_CREATE_RETRIES_MAX | ||
597 | // Don't bother with retries; a failure is a failure | ||
598 | // | ||
599 | { | ||
600 | int rc= pthread_create( ref, a, func, data ); | ||
601 | if (rc) _PT_FAIL( rc, "pthread_create()", __FILE__, __LINE__-1 ); | ||
602 | } | ||
603 | # else | ||
604 | # error "This code deprecated" | ||
605 | /* | ||
606 | // Wait slightly if thread creation has exchausted the system | ||
607 | // | ||
608 | { uint_t retries; | ||
609 | for( retries=0; retries<THREAD_CREATE_RETRIES_MAX; retries++ ) { | ||
610 | |||
611 | int rc= pthread_create( ref, a, func, data ); | ||
612 | // | ||
613 | // OS X / Linux: | ||
614 | // EAGAIN: ".. lacked the necessary resources to create | ||
615 | // another thread, or the system-imposed limit on the | ||
616 | // total number of threads in a process | ||
617 | // [PTHREAD_THREADS_MAX] would be exceeded." | ||
618 | // EINVAL: attr is invalid | ||
619 | // Linux: | ||
620 | // EPERM: no rights for given parameters or scheduling (no sudo) | ||
621 | // ENOMEM: (known to fail with this code, too - not listed in man) | ||
622 | |||
623 | if (rc==0) break; // ok! | ||
624 | |||
625 | // In practise, exhaustion seems to be coming from memory, not a | ||
626 | // maximum number of threads. Keep tuning... ;) | ||
627 | // | ||
628 | if (rc==EAGAIN) { | ||
629 | //fprintf( stderr, "Looping (retries=%d) ", retries ); // DEBUG | ||
630 | |||
631 | // Try again, later. | ||
632 | |||
633 | Yield(); | ||
634 | } else { | ||
635 | _PT_FAIL( rc, "pthread_create()", __FILE__, __LINE__ ); | ||
636 | } | ||
637 | } | ||
638 | } | ||
639 | */ | ||
640 | # endif | ||
641 | |||
642 | if (a) { | ||
643 | PT_CALL( pthread_attr_destroy(a) ); | ||
644 | } | ||
645 | } | ||
646 | // | ||
647 | /* | ||
648 | * Wait for a thread to finish. | ||
649 | * | ||
650 | * 'mu_ref' is a lock we should use for the waiting; initially unlocked. | ||
651 | * Same lock as passed to THREAD_EXIT. | ||
652 | * | ||
653 | * Returns TRUE for succesful wait, FALSE for timed out | ||
654 | */ | ||
655 | #ifdef PTHREAD_TIMEDJOIN | ||
656 | bool_t THREAD_WAIT( THREAD_T *ref, double secs ) | ||
657 | #else | ||
658 | bool_t THREAD_WAIT( THREAD_T *ref, SIGNAL_T *signal_ref, MUTEX_T *mu_ref, volatile enum e_status *st_ref, double secs ) | ||
659 | #endif | ||
660 | { | ||
661 | struct timespec ts_store; | ||
662 | const struct timespec *timeout= NULL; | ||
663 | bool_t done; | ||
664 | |||
665 | // Do timeout counting before the locks | ||
666 | // | ||
667 | #ifdef PTHREAD_TIMEDJOIN | ||
668 | if (secs>=0.0) { | ||
669 | #else | ||
670 | if (secs>0.0) { | ||
671 | #endif | ||
672 | prepare_timeout( &ts_store, now_secs()+secs ); | ||
673 | timeout= &ts_store; | ||
674 | } | ||
675 | |||
676 | #ifdef PTHREAD_TIMEDJOIN | ||
677 | /* Thread is joinable | ||
678 | */ | ||
679 | if (!timeout) { | ||
680 | PT_CALL( pthread_join( *ref, NULL /*ignore exit value*/ )); | ||
681 | done= TRUE; | ||
682 | } else { | ||
683 | int rc= PTHREAD_TIMEDJOIN( *ref, NULL, timeout ); | ||
684 | if ((rc!=0) && (rc!=ETIMEDOUT)) { | ||
685 | _PT_FAIL( rc, "PTHREAD_TIMEDJOIN", __FILE__, __LINE__-2 ); | ||
686 | } | ||
687 | done= rc==0; | ||
688 | } | ||
689 | #else | ||
690 | /* Since we've set the thread up as PTHREAD_CREATE_DETACHED, we cannot | ||
691 | * join with it. Use the cond.var. | ||
692 | */ | ||
693 | MUTEX_LOCK( mu_ref ); | ||
694 | |||
695 | // 'secs'==0.0 does not need to wait, just take the current status | ||
696 | // within the 'mu_ref' locks | ||
697 | // | ||
698 | if (secs != 0.0) { | ||
699 | while( *st_ref < DONE ) { | ||
700 | if (!timeout) { | ||
701 | PT_CALL( pthread_cond_wait( signal_ref, mu_ref )); | ||
702 | } else { | ||
703 | int rc= pthread_cond_timedwait( signal_ref, mu_ref, timeout ); | ||
704 | if (rc==ETIMEDOUT) break; | ||
705 | if (rc!=0) _PT_FAIL( rc, "pthread_cond_timedwait", __FILE__, __LINE__-2 ); | ||
706 | } | ||
707 | } | ||
708 | } | ||
709 | done= *st_ref >= DONE; // DONE|ERROR_ST|CANCELLED | ||
710 | |||
711 | MUTEX_UNLOCK( mu_ref ); | ||
712 | #endif | ||
713 | return done; | ||
714 | } | ||
715 | // | ||
716 | void THREAD_KILL( THREAD_T *ref ) { | ||
717 | pthread_cancel( *ref ); | ||
718 | } | ||
719 | #endif | ||
720 | |||
721 | static const lua_Alloc alloc_f= 0; | ||
diff --git a/src/threading.h b/src/threading.h new file mode 100644 index 0000000..4a83229 --- /dev/null +++ b/src/threading.h | |||
@@ -0,0 +1,196 @@ | |||
1 | /* | ||
2 | * THREADING.H | ||
3 | */ | ||
4 | #ifndef THREADING_H | ||
5 | #define THREADING_H | ||
6 | |||
7 | /* Platform detection | ||
8 | */ | ||
9 | #ifdef _WIN32_WCE | ||
10 | #define PLATFORM_POCKETPC | ||
11 | #elif (defined _WIN32) | ||
12 | #define PLATFORM_WIN32 | ||
13 | #elif (defined __linux__) | ||
14 | #define PLATFORM_LINUX | ||
15 | #elif (defined __APPLE__) && (defined __MACH__) | ||
16 | #define PLATFORM_OSX | ||
17 | #elif (defined __NetBSD__) || (defined __FreeBSD__) || (defined BSD) | ||
18 | #define PLATFORM_BSD | ||
19 | #elif (defined __QNX__) | ||
20 | #define PLATFORM_QNX | ||
21 | #elif (defined __CYGWIN__) | ||
22 | #define PLATFORM_CYGWIN | ||
23 | #else | ||
24 | #error "Unknown platform!" | ||
25 | #endif | ||
26 | |||
27 | typedef int bool_t; | ||
28 | #ifndef FALSE | ||
29 | # define FALSE 0 | ||
30 | # define TRUE 1 | ||
31 | #endif | ||
32 | |||
33 | typedef unsigned int uint_t; | ||
34 | |||
35 | #if defined(PLATFORM_WIN32) && defined(__GNUC__) | ||
36 | /* MinGW with MSVCR80.DLL */ | ||
37 | /* Do this BEFORE including time.h so that it is declaring _mktime32() | ||
38 | * as it would have declared mktime(). | ||
39 | */ | ||
40 | # define mktime _mktime32 | ||
41 | #endif | ||
42 | #include <time.h> | ||
43 | |||
44 | /* Note: ERROR is a defined entity on Win32 | ||
45 | */ | ||
46 | enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED }; | ||
47 | |||
48 | |||
49 | /*---=== Locks & Signals ===--- | ||
50 | */ | ||
51 | |||
52 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) | ||
53 | #define WIN32_LEAN_AND_MEAN | ||
54 | // 'SignalObjectAndWait' needs this (targets Windows 2000 and above) | ||
55 | #define _WIN32_WINNT 0x0400 | ||
56 | #include <windows.h> | ||
57 | #include <process.h> | ||
58 | |||
59 | // MSDN: http://msdn2.microsoft.com/en-us/library/ms684254.aspx | ||
60 | // | ||
61 | // CRITICAL_SECTION can be used for simple code protection. Mutexes are | ||
62 | // needed for use with the SIGNAL system. | ||
63 | // | ||
64 | #define MUTEX_T HANDLE | ||
65 | void MUTEX_INIT( MUTEX_T *ref ); | ||
66 | #define MUTEX_RECURSIVE_INIT(ref) MUTEX_INIT(ref) /* always recursive in Win32 */ | ||
67 | void MUTEX_FREE( MUTEX_T *ref ); | ||
68 | void MUTEX_LOCK( MUTEX_T *ref ); | ||
69 | void MUTEX_UNLOCK( MUTEX_T *ref ); | ||
70 | |||
71 | typedef unsigned THREAD_RETURN_T; | ||
72 | |||
73 | #define SIGNAL_T HANDLE | ||
74 | |||
75 | #define YIELD() Sleep(0) | ||
76 | #else | ||
77 | // PThread (Linux, OS X, ...) | ||
78 | // | ||
79 | #include <pthread.h> | ||
80 | |||
81 | #ifdef PLATFORM_LINUX | ||
82 | # define _MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP | ||
83 | #else | ||
84 | /* OS X, ... */ | ||
85 | # define _MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE | ||
86 | #endif | ||
87 | |||
88 | #define MUTEX_T pthread_mutex_t | ||
89 | #define MUTEX_INIT(ref) pthread_mutex_init(ref,NULL) | ||
90 | #define MUTEX_RECURSIVE_INIT(ref) \ | ||
91 | { pthread_mutexattr_t a; pthread_mutexattr_init( &a ); \ | ||
92 | pthread_mutexattr_settype( &a, _MUTEX_RECURSIVE ); \ | ||
93 | pthread_mutex_init(ref,&a); pthread_mutexattr_destroy( &a ); \ | ||
94 | } | ||
95 | #define MUTEX_FREE(ref) pthread_mutex_destroy(ref) | ||
96 | #define MUTEX_LOCK(ref) pthread_mutex_lock(ref) | ||
97 | #define MUTEX_UNLOCK(ref) pthread_mutex_unlock(ref) | ||
98 | |||
99 | typedef void * THREAD_RETURN_T; | ||
100 | |||
101 | typedef pthread_cond_t SIGNAL_T; | ||
102 | |||
103 | void SIGNAL_ONE( SIGNAL_T *ref ); | ||
104 | |||
105 | // Yield is non-portable: | ||
106 | // | ||
107 | // OS X 10.4.8/9 has pthread_yield_np() | ||
108 | // Linux 2.4 has pthread_yield() if _GNU_SOURCE is #defined | ||
109 | // FreeBSD 6.2 has pthread_yield() | ||
110 | // ... | ||
111 | // | ||
112 | #ifdef PLATFORM_OSX | ||
113 | #define YIELD() pthread_yield_np() | ||
114 | #else | ||
115 | #define YIELD() pthread_yield() | ||
116 | #endif | ||
117 | #endif | ||
118 | |||
119 | void SIGNAL_INIT( SIGNAL_T *ref ); | ||
120 | void SIGNAL_FREE( SIGNAL_T *ref ); | ||
121 | void SIGNAL_ALL( SIGNAL_T *ref ); | ||
122 | |||
123 | /* | ||
124 | * 'time_d': <0.0 for no timeout | ||
125 | * 0.0 for instant check | ||
126 | * >0.0 absolute timeout in secs + ms | ||
127 | */ | ||
128 | typedef double time_d; | ||
129 | time_d now_secs(void); | ||
130 | |||
131 | time_d SIGNAL_TIMEOUT_PREPARE( double rel_secs ); | ||
132 | |||
133 | bool_t SIGNAL_WAIT( SIGNAL_T *ref, MUTEX_T *mu, time_d timeout ); | ||
134 | |||
135 | |||
136 | /*---=== Threading ===--- | ||
137 | */ | ||
138 | |||
139 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) | ||
140 | |||
141 | typedef HANDLE THREAD_T; | ||
142 | // | ||
143 | void THREAD_CREATE( THREAD_T *ref, | ||
144 | THREAD_RETURN_T (__stdcall *func)( void * ), | ||
145 | void *data, int prio /* -3..+3 */ ); | ||
146 | |||
147 | # define THREAD_PRIO_MIN (-3) | ||
148 | # define THREAD_PRIO_MAX (+3) | ||
149 | |||
150 | #else | ||
151 | /* Platforms that have a timed 'pthread_join()' can get away with a simpler | ||
152 | * implementation. Others will use a condition variable. | ||
153 | */ | ||
154 | # ifdef USE_PTHREAD_TIMEDJOIN | ||
155 | # ifdef PLATFORM_OSX | ||
156 | # error "No 'pthread_timedjoin()' on this system" | ||
157 | # else | ||
158 | /* Linux, ... */ | ||
159 | # define PTHREAD_TIMEDJOIN pthread_timedjoin_np | ||
160 | # endif | ||
161 | # endif | ||
162 | |||
163 | typedef pthread_t THREAD_T; | ||
164 | |||
165 | void THREAD_CREATE( THREAD_T *ref, | ||
166 | THREAD_RETURN_T (*func)( void * ), | ||
167 | void *data, int prio /* -2..+2 */ ); | ||
168 | |||
169 | # if defined(PLATFORM_LINUX) | ||
170 | volatile bool_t sudo; | ||
171 | # ifdef LINUX_SCHED_RR | ||
172 | # define THREAD_PRIO_MIN (sudo ? -2 : 0) | ||
173 | # else | ||
174 | # define THREAD_PRIO_MIN (0) | ||
175 | # endif | ||
176 | # define THREAD_PRIO_MAX (sudo ? +2 : 0) | ||
177 | # else | ||
178 | # define THREAD_PRIO_MIN (-2) | ||
179 | # define THREAD_PRIO_MAX (+2) | ||
180 | # endif | ||
181 | #endif | ||
182 | |||
183 | /* | ||
184 | * Win32 and PTHREAD_TIMEDJOIN allow waiting for a thread with a timeout. | ||
185 | * Posix without PTHREAD_TIMEDJOIN needs to use a condition variable approach. | ||
186 | */ | ||
187 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) | ||
188 | bool_t THREAD_WAIT( THREAD_T *ref, double secs ); | ||
189 | #else | ||
190 | bool_t THREAD_WAIT( THREAD_T *ref, SIGNAL_T *signal_ref, MUTEX_T *mu_ref, volatile enum e_status *st_ref, double secs ); | ||
191 | #endif | ||
192 | |||
193 | void THREAD_KILL( THREAD_T *ref ); | ||
194 | |||
195 | #endif | ||
196 | // THREADING_H | ||
diff --git a/src/tools.c b/src/tools.c new file mode 100644 index 0000000..a2ec517 --- /dev/null +++ b/src/tools.c | |||
@@ -0,0 +1,1198 @@ | |||
1 | /* | ||
2 | * TOOLS.C Copyright (c) 2002-08, Asko Kauppi | ||
3 | * | ||
4 | * Lua tools to support Lanes. | ||
5 | */ | ||
6 | |||
7 | /* | ||
8 | =============================================================================== | ||
9 | |||
10 | Copyright (C) 2002-08 Asko Kauppi <akauppi@gmail.com> | ||
11 | |||
12 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
13 | of this software and associated documentation files (the "Software"), to deal | ||
14 | in the Software without restriction, including without limitation the rights | ||
15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
16 | copies of the Software, and to permit persons to whom the Software is | ||
17 | furnished to do so, subject to the following conditions: | ||
18 | |||
19 | The above copyright notice and this permission notice shall be included in | ||
20 | all copies or substantial portions of the Software. | ||
21 | |||
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
28 | THE SOFTWARE. | ||
29 | |||
30 | =============================================================================== | ||
31 | */ | ||
32 | |||
33 | #include "tools.h" | ||
34 | |||
35 | #include "lualib.h" | ||
36 | #include "lauxlib.h" | ||
37 | |||
38 | #include <stdio.h> | ||
39 | #include <string.h> | ||
40 | #include <ctype.h> | ||
41 | #include <stdlib.h> | ||
42 | |||
43 | static volatile lua_CFunction hijacked_tostring; // = NULL | ||
44 | |||
45 | MUTEX_T deep_lock; | ||
46 | MUTEX_T mtid_lock; | ||
47 | |||
48 | /*---=== luaG_dump ===---*/ | ||
49 | |||
50 | void luaG_dump( lua_State* L ) { | ||
51 | |||
52 | int top= lua_gettop(L); | ||
53 | int i; | ||
54 | |||
55 | fprintf( stderr, "\n\tDEBUG STACK:\n" ); | ||
56 | |||
57 | if (top==0) | ||
58 | fprintf( stderr, "\t(none)\n" ); | ||
59 | |||
60 | for( i=1; i<=top; i++ ) { | ||
61 | int type= lua_type( L, i ); | ||
62 | |||
63 | fprintf( stderr, "\t[%d]= (%s) ", i, lua_typename(L,type) ); | ||
64 | |||
65 | // Print item contents here... | ||
66 | // | ||
67 | // Note: this requires 'tostring()' to be defined. If it is NOT, | ||
68 | // enable it for more debugging. | ||
69 | // | ||
70 | STACK_CHECK(L) | ||
71 | STACK_GROW( L, 2 ) | ||
72 | |||
73 | lua_getglobal( L, "tostring" ); | ||
74 | // | ||
75 | // [-1]: tostring function, or nil | ||
76 | |||
77 | if (!lua_isfunction(L,-1)) { | ||
78 | fprintf( stderr, "('tostring' not available)" ); | ||
79 | } else { | ||
80 | lua_pushvalue( L, i ); | ||
81 | lua_call( L, 1 /*args*/, 1 /*retvals*/ ); | ||
82 | |||
83 | // Don't trust the string contents | ||
84 | // | ||
85 | fprintf( stderr, "%s", lua_tostring(L,-1) ); | ||
86 | } | ||
87 | lua_pop(L,1); | ||
88 | STACK_END(L,0) | ||
89 | fprintf( stderr, "\n" ); | ||
90 | } | ||
91 | fprintf( stderr, "\n" ); | ||
92 | } | ||
93 | |||
94 | |||
95 | /*---=== luaG_openlibs ===---*/ | ||
96 | |||
97 | static const luaL_Reg libs[] = { | ||
98 | { LUA_LOADLIBNAME, luaopen_package }, | ||
99 | { LUA_TABLIBNAME, luaopen_table }, | ||
100 | { LUA_IOLIBNAME, luaopen_io }, | ||
101 | { LUA_OSLIBNAME, luaopen_os }, | ||
102 | { LUA_STRLIBNAME, luaopen_string }, | ||
103 | { LUA_MATHLIBNAME, luaopen_math }, | ||
104 | { LUA_DBLIBNAME, luaopen_debug }, | ||
105 | // | ||
106 | { "base", NULL }, // ignore "base" (already acquired it) | ||
107 | { "coroutine", NULL }, // part of Lua 5.1 base package | ||
108 | { NULL, NULL } | ||
109 | }; | ||
110 | |||
111 | static bool_t openlib( lua_State *L, const char *name, size_t len ) { | ||
112 | |||
113 | unsigned i; | ||
114 | bool_t all= strncmp( name, "*", len ) == 0; | ||
115 | |||
116 | for( i=0; libs[i].name; i++ ) { | ||
117 | if (all || (strncmp(name, libs[i].name, len) ==0)) { | ||
118 | if (libs[i].func) { | ||
119 | STACK_GROW(L,2); | ||
120 | lua_pushcfunction( L, libs[i].func ); | ||
121 | lua_pushstring( L, libs[i].name ); | ||
122 | lua_call( L, 1, 0 ); | ||
123 | } | ||
124 | if (!all) return TRUE; | ||
125 | } | ||
126 | } | ||
127 | return all; | ||
128 | } | ||
129 | |||
130 | /* | ||
131 | * Like 'luaL_openlibs()' but allows the set of libraries be selected | ||
132 | * | ||
133 | * NULL no libraries, not even base | ||
134 | * "" base library only | ||
135 | * "io,string" named libraries | ||
136 | * "*" all libraries | ||
137 | * | ||
138 | * Base ("unpack", "print" etc.) is always added, unless 'libs' is NULL. | ||
139 | * | ||
140 | * Returns NULL for ok, position of error within 'libs' on failure. | ||
141 | */ | ||
142 | #define is_name_char(c) (isalpha(c) || (c)=='*') | ||
143 | |||
144 | const char *luaG_openlibs( lua_State *L, const char *libs ) { | ||
145 | const char *p; | ||
146 | unsigned len; | ||
147 | |||
148 | if (!libs) return NULL; // no libs, not even 'base' | ||
149 | |||
150 | // 'lua.c' stops GC during initialization so perhaps its a good idea. :) | ||
151 | // | ||
152 | lua_gc(L, LUA_GCSTOP, 0); | ||
153 | |||
154 | // Anything causes 'base' to be taken in | ||
155 | // | ||
156 | STACK_GROW(L,2); | ||
157 | lua_pushcfunction( L, luaopen_base ); | ||
158 | lua_pushliteral( L, "" ); | ||
159 | lua_call( L, 1, 0 ); | ||
160 | |||
161 | for( p= libs; *p; p+=len ) { | ||
162 | len=0; | ||
163 | while (*p && !is_name_char(*p)) p++; // bypass delimiters | ||
164 | while (is_name_char(p[len])) len++; // bypass name | ||
165 | if (len && (!openlib( L, p, len ))) | ||
166 | break; | ||
167 | } | ||
168 | lua_gc(L, LUA_GCRESTART, 0); | ||
169 | |||
170 | return *p ? p : NULL; | ||
171 | } | ||
172 | |||
173 | |||
174 | |||
175 | /*---=== Deep userdata ===---*/ | ||
176 | |||
177 | /* The deep portion must be allocated separately of any Lua state's; it's | ||
178 | * lifespan may be longer than that of the creating state. | ||
179 | */ | ||
180 | #define DEEP_MALLOC malloc | ||
181 | #define DEEP_FREE free | ||
182 | |||
183 | /* | ||
184 | * 'registry[REGKEY]' is a two-way lookup table for 'idfunc's and those type's | ||
185 | * metatables: | ||
186 | * | ||
187 | * metatable -> idfunc | ||
188 | * idfunc -> metatable | ||
189 | */ | ||
190 | #define DEEP_LOOKUP_KEY ((void*)set_deep_lookup) | ||
191 | // any unique light userdata | ||
192 | |||
193 | static void push_registry_subtable( lua_State *L, void *token ); | ||
194 | |||
195 | /* | ||
196 | * Sets up [-1]<->[-2] two-way lookups, and ensures the lookup table exists. | ||
197 | * Pops the both values off the stack. | ||
198 | */ | ||
199 | void set_deep_lookup( lua_State *L ) { | ||
200 | |||
201 | STACK_GROW(L,3); | ||
202 | |||
203 | STACK_CHECK(L) | ||
204 | #if 1 | ||
205 | push_registry_subtable( L, DEEP_LOOKUP_KEY ); | ||
206 | #else | ||
207 | /* ..to be removed.. */ | ||
208 | lua_pushlightuserdata( L, DEEP_LOOKUP_KEY ); | ||
209 | lua_rawget( L, LUA_REGISTRYINDEX ); | ||
210 | |||
211 | if (lua_isnil(L,-1)) { | ||
212 | // First time here; let's make the lookup | ||
213 | // | ||
214 | lua_pop(L,1); | ||
215 | |||
216 | lua_newtable(L); | ||
217 | lua_pushlightuserdata( L, DEEP_LOOKUP_KEY ); | ||
218 | lua_pushvalue(L,-2); | ||
219 | // | ||
220 | // [-3]: {}Â (2nd ref) | ||
221 | // [-2]: DEEP_LOOKUP_KEY | ||
222 | // [-1]: {} | ||
223 | |||
224 | lua_rawset( L, LUA_REGISTRYINDEX ); | ||
225 | // | ||
226 | // [-1]: lookup table (empty) | ||
227 | } | ||
228 | #endif | ||
229 | STACK_MID(L,1) | ||
230 | |||
231 | lua_insert(L,-3); | ||
232 | |||
233 | // [-3]: lookup table | ||
234 | // [-2]: A | ||
235 | // [-1]: B | ||
236 | |||
237 | lua_pushvalue( L,-1 ); // B | ||
238 | lua_pushvalue( L,-3 ); // A | ||
239 | lua_rawset( L, -5 ); // B->A | ||
240 | lua_rawset( L, -3 ); // A->B | ||
241 | lua_pop( L,1 ); | ||
242 | |||
243 | STACK_END(L,-2) | ||
244 | } | ||
245 | |||
246 | /* | ||
247 | * Pops the key (metatable or idfunc) off the stack, and replaces with the | ||
248 | * deep lookup value (idfunc/metatable/nil). | ||
249 | */ | ||
250 | void get_deep_lookup( lua_State *L ) { | ||
251 | |||
252 | STACK_GROW(L,1); | ||
253 | |||
254 | STACK_CHECK(L) | ||
255 | lua_pushlightuserdata( L, DEEP_LOOKUP_KEY ); | ||
256 | lua_rawget( L, LUA_REGISTRYINDEX ); | ||
257 | |||
258 | if (!lua_isnil(L,-1)) { | ||
259 | // [-2]: key (metatable or idfunc) | ||
260 | // [-1]: lookup table | ||
261 | |||
262 | lua_insert( L, -2 ); | ||
263 | lua_rawget( L, -2 ); | ||
264 | |||
265 | // [-2]: lookup table | ||
266 | // [-1]: value (metatable / idfunc / nil) | ||
267 | } | ||
268 | lua_remove(L,-2); | ||
269 | // remove lookup, or unused key | ||
270 | STACK_END(L,0) | ||
271 | } | ||
272 | |||
273 | /* | ||
274 | * Return the registered ID function for 'index' (deep userdata proxy), | ||
275 | * or NULL if 'index' is not a deep userdata proxy. | ||
276 | */ | ||
277 | static | ||
278 | lua_CFunction get_idfunc( lua_State *L, int index ) { | ||
279 | lua_CFunction ret; | ||
280 | |||
281 | index= STACK_ABS(L,index); | ||
282 | |||
283 | STACK_GROW(L,1); | ||
284 | |||
285 | STACK_CHECK(L) | ||
286 | if (!lua_getmetatable( L, index )) | ||
287 | return NULL; // no metatable | ||
288 | |||
289 | // [-1]: metatable of [index] | ||
290 | |||
291 | get_deep_lookup(L); | ||
292 | // | ||
293 | // [-1]: idfunc/nil | ||
294 | |||
295 | ret= lua_tocfunction(L,-1); | ||
296 | lua_pop(L,1); | ||
297 | STACK_END(L,0) | ||
298 | return ret; | ||
299 | } | ||
300 | |||
301 | |||
302 | /* | ||
303 | * void= mt.__gc( proxy_ud ) | ||
304 | * | ||
305 | * End of life for a proxy object; reduce the deep reference count and clean | ||
306 | * it up if reaches 0. | ||
307 | */ | ||
308 | static | ||
309 | int deep_userdata_gc( lua_State *L ) { | ||
310 | DEEP_PRELUDE **proxy= (DEEP_PRELUDE**)lua_touserdata( L, 1 ); | ||
311 | DEEP_PRELUDE *p= *proxy; | ||
312 | int v; | ||
313 | |||
314 | *proxy= 0; // make sure we don't use it any more | ||
315 | |||
316 | MUTEX_LOCK( &deep_lock ); | ||
317 | v= --(p->refcount); | ||
318 | MUTEX_UNLOCK( &deep_lock ); | ||
319 | |||
320 | if (v==0) { | ||
321 | int pushed; | ||
322 | |||
323 | // Call 'idfunc( "delete", deep_ptr )' to make deep cleanup | ||
324 | // | ||
325 | lua_CFunction idfunc= get_idfunc(L,1); | ||
326 | ASSERT_L(idfunc); | ||
327 | |||
328 | lua_settop(L,0); // clean stack so we can call 'idfunc' directly | ||
329 | |||
330 | // void= idfunc( "delete", lightuserdata ) | ||
331 | // | ||
332 | lua_pushliteral( L, "delete" ); | ||
333 | lua_pushlightuserdata( L, p->deep ); | ||
334 | pushed= idfunc(L); | ||
335 | |||
336 | if (pushed) | ||
337 | luaL_error( L, "Bad idfunc on \"delete\": returned something" ); | ||
338 | |||
339 | DEEP_FREE( (void*)p ); | ||
340 | } | ||
341 | return 0; | ||
342 | } | ||
343 | |||
344 | |||
345 | /* | ||
346 | * Push a proxy userdata on the stack. | ||
347 | * | ||
348 | * Initializes necessary structures if it's the first time 'idfunc' is being | ||
349 | * used in this Lua state (metatable, registring it). Otherwise, increments the | ||
350 | * reference count. | ||
351 | */ | ||
352 | void luaG_push_proxy( lua_State *L, lua_CFunction idfunc, DEEP_PRELUDE *prelude ) { | ||
353 | DEEP_PRELUDE **proxy; | ||
354 | |||
355 | MUTEX_LOCK( &deep_lock ); | ||
356 | ++(prelude->refcount); // one more proxy pointing to this deep data | ||
357 | MUTEX_UNLOCK( &deep_lock ); | ||
358 | |||
359 | STACK_GROW(L,4); | ||
360 | |||
361 | STACK_CHECK(L) | ||
362 | |||
363 | proxy= lua_newuserdata( L, sizeof( DEEP_PRELUDE* ) ); | ||
364 | ASSERT_L(proxy); | ||
365 | *proxy= prelude; | ||
366 | |||
367 | // Get/create metatable for 'idfunc' (in this state) | ||
368 | // | ||
369 | lua_pushcfunction( L, idfunc ); // key | ||
370 | get_deep_lookup(L); | ||
371 | // | ||
372 | // [-2]: proxy | ||
373 | // [-1]: metatable / nil | ||
374 | |||
375 | if (lua_isnil(L,-1)) { | ||
376 | // No metatable yet; make one and register it | ||
377 | // | ||
378 | lua_pop(L,1); | ||
379 | |||
380 | // tbl= idfunc( "metatable" ) | ||
381 | // | ||
382 | lua_pushcfunction( L, idfunc ); | ||
383 | lua_pushliteral( L, "metatable" ); | ||
384 | lua_call( L, 1 /*args*/, 1 /*results*/ ); | ||
385 | // | ||
386 | // [-2]: proxy | ||
387 | // [-1]: metatable (returned by 'idfunc') | ||
388 | |||
389 | if (!lua_istable(L,-1)) | ||
390 | luaL_error( L, "Bad idfunc on \"metatable\": did not return one" ); | ||
391 | |||
392 | // Add '__gc' method | ||
393 | // | ||
394 | lua_pushcfunction( L, deep_userdata_gc ); | ||
395 | lua_setfield( L, -2, "__gc" ); | ||
396 | |||
397 | // Memorize for later rounds | ||
398 | // | ||
399 | lua_pushvalue( L,-1 ); | ||
400 | lua_pushcfunction( L, idfunc ); | ||
401 | // | ||
402 | // [-4]: proxy | ||
403 | // [-3]: metatable (2nd ref) | ||
404 | // [-2]: metatable | ||
405 | // [-1]: idfunc | ||
406 | |||
407 | set_deep_lookup(L); | ||
408 | } | ||
409 | STACK_MID(L,2) | ||
410 | ASSERT_L( lua_isuserdata(L,-2) ); | ||
411 | ASSERT_L( lua_istable(L,-1) ); | ||
412 | |||
413 | // [-2]: proxy userdata | ||
414 | // [-1]: metatable to use | ||
415 | |||
416 | lua_setmetatable( L, -2 ); | ||
417 | |||
418 | STACK_END(L,1) | ||
419 | // [-1]: proxy userdata | ||
420 | } | ||
421 | |||
422 | |||
423 | /* | ||
424 | * Create a deep userdata | ||
425 | * | ||
426 | * proxy_ud= deep_userdata( idfunc [, ...] ) | ||
427 | * | ||
428 | * Creates a deep userdata entry of the type defined by 'idfunc'. | ||
429 | * Other parameters are passed on to the 'idfunc' "new" invocation. | ||
430 | * | ||
431 | * 'idfunc' must fulfill the following features: | ||
432 | * | ||
433 | * lightuserdata= idfunc( "new" [, ...] ) -- creates a new deep data instance | ||
434 | * void= idfunc( "delete", lightuserdata ) -- releases a deep data instance | ||
435 | * tbl= idfunc( "metatable" ) -- gives metatable for userdata proxies | ||
436 | * | ||
437 | * Reference counting and true userdata proxying are taken care of for the | ||
438 | * actual data type. | ||
439 | * | ||
440 | * Types using the deep userdata system (and only those!) can be passed between | ||
441 | * separate Lua states via 'luaG_inter_move()'. | ||
442 | * | ||
443 | * Returns: 'proxy' userdata for accessing the deep data via 'luaG_todeep()' | ||
444 | */ | ||
445 | int luaG_deep_userdata( lua_State *L ) { | ||
446 | lua_CFunction idfunc= lua_tocfunction( L,1 ); | ||
447 | int pushed; | ||
448 | |||
449 | DEEP_PRELUDE *prelude= DEEP_MALLOC( sizeof(DEEP_PRELUDE) ); | ||
450 | ASSERT_L(prelude); | ||
451 | |||
452 | prelude->refcount= 0; // 'luaG_push_proxy' will lift it to 1 | ||
453 | |||
454 | STACK_GROW(L,1); | ||
455 | STACK_CHECK(L) | ||
456 | |||
457 | // Replace 'idfunc' with "new" in the stack (keep possible other params) | ||
458 | // | ||
459 | lua_remove(L,1); | ||
460 | lua_pushliteral( L, "new" ); | ||
461 | lua_insert(L,1); | ||
462 | |||
463 | // lightuserdata= idfunc( "new" [, ...] ) | ||
464 | // | ||
465 | pushed= idfunc(L); | ||
466 | |||
467 | if ((pushed!=1) || lua_type(L,-1) != LUA_TLIGHTUSERDATA) | ||
468 | luaL_error( L, "Bad idfunc on \"new\": did not return light userdata" ); | ||
469 | |||
470 | prelude->deep= lua_touserdata(L,-1); | ||
471 | ASSERT_L(prelude->deep); | ||
472 | |||
473 | lua_pop(L,1); // pop deep data | ||
474 | |||
475 | luaG_push_proxy( L, idfunc, prelude ); | ||
476 | // | ||
477 | // [-1]: proxy userdata | ||
478 | |||
479 | STACK_END(L,1) | ||
480 | return 1; | ||
481 | } | ||
482 | |||
483 | |||
484 | /* | ||
485 | * Access deep userdata through a proxy. | ||
486 | * | ||
487 | * Reference count is not changed, and access to the deep userdata is not | ||
488 | * serialized. It is the module's responsibility to prevent conflicting usage. | ||
489 | */ | ||
490 | void *luaG_todeep( lua_State *L, lua_CFunction idfunc, int index ) { | ||
491 | DEEP_PRELUDE **proxy; | ||
492 | |||
493 | STACK_CHECK(L) | ||
494 | if (get_idfunc(L,index) != idfunc) | ||
495 | return NULL; // no metatable, or wrong kind | ||
496 | |||
497 | proxy= (DEEP_PRELUDE**)lua_touserdata( L, index ); | ||
498 | STACK_END(L,0) | ||
499 | |||
500 | return (*proxy)->deep; | ||
501 | } | ||
502 | |||
503 | |||
504 | /* | ||
505 | * Copy deep userdata between two separate Lua states. | ||
506 | * | ||
507 | * Returns: | ||
508 | * the id function of the copied value, or NULL for non-deep userdata | ||
509 | * (not copied) | ||
510 | */ | ||
511 | static | ||
512 | lua_CFunction luaG_copydeep( lua_State *L, lua_State *L2, int index ) { | ||
513 | DEEP_PRELUDE **proxy; | ||
514 | DEEP_PRELUDE *p; | ||
515 | |||
516 | lua_CFunction idfunc; | ||
517 | |||
518 | idfunc= get_idfunc( L, index ); | ||
519 | if (!idfunc) return NULL; // not a deep userdata | ||
520 | |||
521 | // Increment reference count | ||
522 | // | ||
523 | proxy= (DEEP_PRELUDE**)lua_touserdata( L, index ); | ||
524 | p= *proxy; | ||
525 | |||
526 | luaG_push_proxy( L2, idfunc, p ); | ||
527 | // | ||
528 | // L2 [-1]: proxy userdata | ||
529 | |||
530 | return idfunc; | ||
531 | } | ||
532 | |||
533 | |||
534 | |||
535 | /*---=== Inter-state copying ===---*/ | ||
536 | |||
537 | /*-- Metatable copying --*/ | ||
538 | |||
539 | /* | ||
540 | * 'reg[ REG_MT_KNOWN ]'= { | ||
541 | * [Â table ]= id_uint, | ||
542 | * ... | ||
543 | * [ id_uint ]= table, | ||
544 | * ... | ||
545 | * } | ||
546 | */ | ||
547 | |||
548 | /* | ||
549 | * Push a registry subtable (keyed by unique 'token') onto the stack. | ||
550 | * If the subtable does not exist, it is created and chained. | ||
551 | */ | ||
552 | static | ||
553 | void push_registry_subtable( lua_State *L, void *token ) { | ||
554 | |||
555 | STACK_GROW(L,3); | ||
556 | |||
557 | STACK_CHECK(L) | ||
558 | |||
559 | lua_pushlightuserdata( L, token ); | ||
560 | lua_rawget( L, LUA_REGISTRYINDEX ); | ||
561 | // | ||
562 | // [-1]: nil/subtable | ||
563 | |||
564 | if (lua_isnil(L,-1)) { | ||
565 | lua_pop(L,1); | ||
566 | lua_newtable(L); // value | ||
567 | lua_pushlightuserdata( L, token ); // key | ||
568 | lua_pushvalue(L,-2); | ||
569 | // | ||
570 | // [-3]: value (2nd ref) | ||
571 | // [-2]: key | ||
572 | // [-1]: value | ||
573 | |||
574 | lua_rawset( L, LUA_REGISTRYINDEX ); | ||
575 | } | ||
576 | STACK_END(L,1) | ||
577 | |||
578 | ASSERT_L( lua_istable(L,-1) ); | ||
579 | } | ||
580 | |||
581 | #define REG_MTID ( (void*) get_mt_id ) | ||
582 | |||
583 | /* | ||
584 | * Get a unique ID for metatable at [i]. | ||
585 | */ | ||
586 | static | ||
587 | uint_t get_mt_id( lua_State *L, int i ) { | ||
588 | static uint_t last_id= 0; | ||
589 | uint_t id; | ||
590 | |||
591 | i= STACK_ABS(L,i); | ||
592 | |||
593 | STACK_GROW(L,3); | ||
594 | |||
595 | STACK_CHECK(L) | ||
596 | push_registry_subtable( L, REG_MTID ); | ||
597 | lua_pushvalue(L, i); | ||
598 | lua_rawget( L, -2 ); | ||
599 | // | ||
600 | // [-2]: reg[REG_MTID] | ||
601 | // [-1]: nil/uint | ||
602 | |||
603 | id= lua_tointeger(L,-1); // 0 for nil | ||
604 | lua_pop(L,1); | ||
605 | STACK_MID(L,1) | ||
606 | |||
607 | if (id==0) { | ||
608 | MUTEX_LOCK( &mtid_lock ); | ||
609 | id= ++last_id; | ||
610 | MUTEX_UNLOCK( &mtid_lock ); | ||
611 | |||
612 | /* Create two-way references: id_uint <-> table | ||
613 | */ | ||
614 | lua_pushvalue(L,i); | ||
615 | lua_pushinteger(L,id); | ||
616 | lua_rawset( L, -3 ); | ||
617 | |||
618 | lua_pushinteger(L,id); | ||
619 | lua_pushvalue(L,i); | ||
620 | lua_rawset( L, -3 ); | ||
621 | } | ||
622 | lua_pop(L,1); // remove 'reg[REG_MTID]' reference | ||
623 | |||
624 | STACK_END(L,0) | ||
625 | |||
626 | return id; | ||
627 | } | ||
628 | |||
629 | |||
630 | static int buf_writer( lua_State *L, const void* b, size_t n, void* B ) { | ||
631 | (void)L; | ||
632 | luaL_addlstring((luaL_Buffer*) B, (const char *)b, n); | ||
633 | return 0; | ||
634 | } | ||
635 | |||
636 | |||
637 | /* | ||
638 | * Check if we've already copied the same table from 'L', and | ||
639 | * reuse the old copy. This allows table upvalues shared by multiple | ||
640 | * local functions to point to the same table, also in the target. | ||
641 | * | ||
642 | * Always pushes a table to 'L2'. | ||
643 | * | ||
644 | * Returns TRUE if the table was cached (no need to fill it!); FALSE if | ||
645 | * it's a virgin. | ||
646 | */ | ||
647 | static | ||
648 | bool_t push_cached_table( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i ) { | ||
649 | bool_t ret; | ||
650 | |||
651 | ASSERT_L( hijacked_tostring ); | ||
652 | ASSERT_L( L2_cache_i != 0 ); | ||
653 | |||
654 | STACK_GROW(L,2); | ||
655 | STACK_GROW(L2,3); | ||
656 | |||
657 | // Create an identity string for table at [i]; it should stay unique at | ||
658 | // least during copying of the data (then we can clear the caches). | ||
659 | // | ||
660 | STACK_CHECK(L) | ||
661 | lua_pushcfunction( L, hijacked_tostring ); | ||
662 | lua_pushvalue( L, i ); | ||
663 | lua_call( L, 1 /*args*/, 1 /*retvals*/ ); | ||
664 | // | ||
665 | // [-1]: "table: 0x...." | ||
666 | |||
667 | STACK_END(L,1) | ||
668 | ASSERT_L( lua_type(L,-1) == LUA_TSTRING ); | ||
669 | |||
670 | // L2_cache[id_str]= [{...}] | ||
671 | // | ||
672 | STACK_CHECK(L2) | ||
673 | |||
674 | // We don't need to use the from state ('L') in ID since the life span | ||
675 | // is only for the duration of a copy (both states are locked). | ||
676 | // | ||
677 | lua_pushstring( L2, lua_tostring(L,-1) ); | ||
678 | lua_pop(L,1); // remove the 'tostring(tbl)' value (in L!) | ||
679 | |||
680 | //fprintf( stderr, "<< ID: %s >>\n", lua_tostring(L2,-1) ); | ||
681 | |||
682 | lua_pushvalue( L2, -1 ); | ||
683 | lua_rawget( L2, L2_cache_i ); | ||
684 | // | ||
685 | // [-2]: identity string ("table: 0x...") | ||
686 | // [-1]: table|nil | ||
687 | |||
688 | if (lua_isnil(L2,-1)) { | ||
689 | lua_pop(L2,1); | ||
690 | lua_newtable(L2); | ||
691 | lua_pushvalue(L2,-1); | ||
692 | lua_insert(L2,-3); | ||
693 | // | ||
694 | // [-3]: new table (2nd ref) | ||
695 | // [-2]: identity string | ||
696 | // [-1]: new table | ||
697 | |||
698 | lua_rawset(L2, L2_cache_i); | ||
699 | // | ||
700 | // [-1]: new table (tied to 'L2_cache' table') | ||
701 | |||
702 | ret= FALSE; // brand new | ||
703 | |||
704 | } else { | ||
705 | lua_remove(L2,-2); | ||
706 | ret= TRUE; // from cache | ||
707 | } | ||
708 | STACK_END(L2,1) | ||
709 | // | ||
710 | // L2 [-1]: table to use as destination | ||
711 | |||
712 | ASSERT_L( lua_istable(L2,-1) ); | ||
713 | return ret; | ||
714 | } | ||
715 | |||
716 | |||
717 | /* | ||
718 | * Check if we've already copied the same function from 'L', and reuse the old | ||
719 | * copy. | ||
720 | * | ||
721 | * Always pushes a function to 'L2'. | ||
722 | */ | ||
723 | static void inter_copy_func( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i ); | ||
724 | |||
725 | static | ||
726 | void push_cached_func( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i ) { | ||
727 | // TBD: Merge this and same code for tables | ||
728 | |||
729 | ASSERT_L( hijacked_tostring ); | ||
730 | ASSERT_L( L2_cache_i != 0 ); | ||
731 | |||
732 | STACK_GROW(L,2); | ||
733 | STACK_GROW(L2,3); | ||
734 | |||
735 | STACK_CHECK(L) | ||
736 | lua_pushcfunction( L, hijacked_tostring ); | ||
737 | lua_pushvalue( L, i ); | ||
738 | lua_call( L, 1 /*args*/, 1 /*retvals*/ ); | ||
739 | // | ||
740 | // [-1]: "function: 0x...." | ||
741 | |||
742 | STACK_END(L,1) | ||
743 | ASSERT_L( lua_type(L,-1) == LUA_TSTRING ); | ||
744 | |||
745 | // L2_cache[id_str]= function | ||
746 | // | ||
747 | STACK_CHECK(L2) | ||
748 | |||
749 | // We don't need to use the from state ('L') in ID since the life span | ||
750 | // is only for the duration of a copy (both states are locked). | ||
751 | // | ||
752 | lua_pushstring( L2, lua_tostring(L,-1) ); | ||
753 | lua_pop(L,1); // remove the 'tostring(tbl)' value (in L!) | ||
754 | |||
755 | //fprintf( stderr, "<< ID: %s >>\n", lua_tostring(L2,-1) ); | ||
756 | |||
757 | lua_pushvalue( L2, -1 ); | ||
758 | lua_rawget( L2, L2_cache_i ); | ||
759 | // | ||
760 | // [-2]: identity string ("function: 0x...") | ||
761 | // [-1]: function|nil|true (true means: we're working on it; recursive) | ||
762 | |||
763 | if (lua_isnil(L2,-1)) { | ||
764 | lua_pop(L2,1); | ||
765 | |||
766 | // Set to 'true' for the duration of creation; need to find self-references | ||
767 | // via upvalues | ||
768 | // | ||
769 | lua_pushboolean(L2,TRUE); | ||
770 | lua_setfield( L2, L2_cache_i, lua_tostring(L2,-2) ); | ||
771 | |||
772 | inter_copy_func( L2, L2_cache_i, L, i ); // pushes a copy of the func | ||
773 | |||
774 | lua_pushvalue(L2,-1); | ||
775 | lua_insert(L2,-3); | ||
776 | // | ||
777 | // [-3]: function (2nd ref) | ||
778 | // [-2]: identity string | ||
779 | // [-1]: function | ||
780 | |||
781 | lua_rawset(L2,L2_cache_i); | ||
782 | // | ||
783 | // [-1]: function (tied to 'L2_cache' table') | ||
784 | |||
785 | } else if (lua_isboolean(L2,-1)) { | ||
786 | // Loop in preparing upvalues; either direct or via a table | ||
787 | // | ||
788 | // Note: This excludes the case where a function directly addresses | ||
789 | // itself as an upvalue (recursive lane creation). | ||
790 | // | ||
791 | luaL_error( L, "Recursive use of upvalues; cannot copy the function" ); | ||
792 | |||
793 | } else { | ||
794 | lua_remove(L2,-2); | ||
795 | } | ||
796 | STACK_END(L2,1) | ||
797 | // | ||
798 | // L2 [-1]: function | ||
799 | |||
800 | ASSERT_L( lua_isfunction(L2,-1) ); | ||
801 | } | ||
802 | |||
803 | |||
804 | /* | ||
805 | * Copy a function over, which has not been found in the cache. | ||
806 | */ | ||
807 | enum e_vt { | ||
808 | VT_NORMAL, VT_KEY, VT_METATABLE | ||
809 | }; | ||
810 | static bool_t inter_copy_one_( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i, enum e_vt value_type ); | ||
811 | |||
812 | static void inter_copy_func( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i ) { | ||
813 | |||
814 | lua_CFunction cfunc= lua_tocfunction( L,i ); | ||
815 | unsigned n; | ||
816 | |||
817 | ASSERT_L( L2_cache_i != 0 ); | ||
818 | |||
819 | STACK_GROW(L,2); | ||
820 | |||
821 | STACK_CHECK(L) | ||
822 | if (!cfunc) { // Lua function | ||
823 | luaL_Buffer b; | ||
824 | const char *s; | ||
825 | size_t sz; | ||
826 | int tmp; | ||
827 | const char *name= NULL; | ||
828 | |||
829 | #if 0 | ||
830 | // "To get information about a function you push it onto the | ||
831 | // stack and start the what string with the character '>'." | ||
832 | // | ||
833 | { lua_Debug ar; | ||
834 | lua_pushvalue( L, i ); | ||
835 | lua_getinfo(L, ">n", &ar); // fills 'name' and 'namewhat', pops function | ||
836 | name= ar.namewhat; | ||
837 | |||
838 | fprintf( stderr, "NAME: %s\n", name ); // just gives NULL | ||
839 | } | ||
840 | #endif | ||
841 | // 'lua_dump()' needs the function at top of stack | ||
842 | // | ||
843 | if (i!=-1) lua_pushvalue( L, i ); | ||
844 | |||
845 | luaL_buffinit(L,&b); | ||
846 | tmp= lua_dump(L, buf_writer, &b); | ||
847 | ASSERT_L(tmp==0); | ||
848 | // | ||
849 | // "value returned is the error code returned by the last call | ||
850 | // to the writer" (and we only return 0) | ||
851 | |||
852 | luaL_pushresult(&b); // pushes dumped string on 'L' | ||
853 | s= lua_tolstring(L,-1,&sz); | ||
854 | ASSERT_L( s && sz ); | ||
855 | |||
856 | if (i!=-1) lua_remove( L, -2 ); | ||
857 | |||
858 | // Note: Line numbers seem to be taken precisely from the | ||
859 | // original function. 'name' is not used since the chunk | ||
860 | // is precompiled (it seems...). | ||
861 | // | ||
862 | // TBD: Can we get the function's original name through, as well? | ||
863 | // | ||
864 | if (luaL_loadbuffer(L2, s, sz, name) != 0) { | ||
865 | // chunk is precompiled so only LUA_ERRMEM can happen | ||
866 | // "Otherwise, it pushes an error message" | ||
867 | // | ||
868 | STACK_GROW( L,1 ); | ||
869 | luaL_error( L, "%s", lua_tostring(L2,-1) ); | ||
870 | } | ||
871 | lua_pop(L,1); // remove the dumped string | ||
872 | STACK_MID(L,0) | ||
873 | } | ||
874 | |||
875 | /* push over any upvalues; references to this function will come from | ||
876 | * cache so we don't end up in eternal loop. | ||
877 | */ | ||
878 | for( n=0; lua_getupvalue( L, i, 1+n ) != NULL; n++ ) { | ||
879 | if ((!cfunc) && lua_equal(L,i,-1)) { | ||
880 | /* Lua closure that has a (recursive) upvalue to itself | ||
881 | */ | ||
882 | lua_pushvalue( L2, -((int)n)-1 ); | ||
883 | } else { | ||
884 | if (!inter_copy_one_( L2, L2_cache_i, L, lua_gettop(L), VT_NORMAL )) | ||
885 | luaL_error( L, "Cannot copy upvalue type '%s'", luaG_typename(L,-1) ); | ||
886 | } | ||
887 | lua_pop(L,1); | ||
888 | } | ||
889 | // L2: function + 'n' upvalues (>=0) | ||
890 | |||
891 | STACK_MID(L,0) | ||
892 | |||
893 | if (cfunc) { | ||
894 | lua_pushcclosure( L2, cfunc, n ); // eats up upvalues | ||
895 | } else { | ||
896 | // Set upvalues (originally set to 'nil' by 'lua_load') | ||
897 | // | ||
898 | int func_index= lua_gettop(L2)-n; | ||
899 | |||
900 | for( ; n>0; n-- ) { | ||
901 | const char *rc= lua_setupvalue( L2, func_index, n ); | ||
902 | // | ||
903 | // "assigns the value at the top of the stack to the upvalue and returns its name. | ||
904 | // It also pops the value from the stack." | ||
905 | |||
906 | ASSERT_L(rc); // not having enough slots? | ||
907 | } | ||
908 | } | ||
909 | STACK_END(L,0) | ||
910 | } | ||
911 | |||
912 | |||
913 | /* | ||
914 | * Copies a value from 'L' state (at index 'i') to 'L2' state. Does not remove | ||
915 | * the original value. | ||
916 | * | ||
917 | * NOTE: Both the states must be solely in the current OS thread's posession. | ||
918 | * | ||
919 | * 'i' is an absolute index (no -1, ...) | ||
920 | * | ||
921 | * Returns TRUE if value was pushed, FALSE if its type is non-supported. | ||
922 | */ | ||
923 | static bool_t inter_copy_one_( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i, enum e_vt vt ) | ||
924 | { | ||
925 | bool_t ret= TRUE; | ||
926 | |||
927 | STACK_GROW( L2, 1 ); | ||
928 | |||
929 | STACK_CHECK(L2) | ||
930 | |||
931 | switch ( lua_type(L,i) ) { | ||
932 | /* Basic types allowed both as values, and as table keys */ | ||
933 | |||
934 | case LUA_TBOOLEAN: | ||
935 | lua_pushboolean( L2, lua_toboolean(L, i) ); | ||
936 | break; | ||
937 | |||
938 | case LUA_TNUMBER: | ||
939 | /* LNUM patch support (keeping integer accuracy) */ | ||
940 | #ifdef LUA_LNUM | ||
941 | if (lua_isinteger(L,i)) { | ||
942 | lua_pushinteger( L2, lua_tointeger(L, i) ); | ||
943 | break; | ||
944 | } | ||
945 | #endif | ||
946 | lua_pushnumber( L2, lua_tonumber(L, i) ); | ||
947 | break; | ||
948 | |||
949 | case LUA_TSTRING: { | ||
950 | size_t len; const char *s = lua_tolstring( L, i, &len ); | ||
951 | lua_pushlstring( L2, s, len ); | ||
952 | } break; | ||
953 | |||
954 | case LUA_TLIGHTUSERDATA: | ||
955 | lua_pushlightuserdata( L2, lua_touserdata(L, i) ); | ||
956 | break; | ||
957 | |||
958 | /* The following types are not allowed as table keys */ | ||
959 | |||
960 | case LUA_TUSERDATA: if (vt==VT_KEY) { ret=FALSE; break; } | ||
961 | /* Allow only deep userdata entities to be copied across | ||
962 | */ | ||
963 | if (!luaG_copydeep( L, L2, i )) { | ||
964 | // Cannot copy it full; copy as light userdata | ||
965 | // | ||
966 | lua_pushlightuserdata( L2, lua_touserdata(L, i) ); | ||
967 | } break; | ||
968 | |||
969 | case LUA_TNIL: if (vt==VT_KEY) { ret=FALSE; break; } | ||
970 | lua_pushnil(L2); | ||
971 | break; | ||
972 | |||
973 | case LUA_TFUNCTION: if (vt==VT_KEY) { ret=FALSE; break; } { | ||
974 | /* | ||
975 | * Passing C functions is risky; if they refer to LUA_ENVIRONINDEX | ||
976 | * and/or LUA_REGISTRYINDEX they might work unintended (not work) | ||
977 | * at the target. | ||
978 | * | ||
979 | * On the other hand, NOT copying them causes many self tests not | ||
980 | * to work (timer, hangtest, ...) | ||
981 | * | ||
982 | * The trouble is, we cannot KNOW if the function at hand is safe | ||
983 | * or not. We cannot study it's behaviour. We could trust the user, | ||
984 | * but they might not even know they're sending lua_CFunction over | ||
985 | * (as upvalues etc.). | ||
986 | */ | ||
987 | #if 0 | ||
988 | if (lua_iscfunction(L,i)) | ||
989 | luaL_error( L, "Copying lua_CFunction between Lua states is risky, and currently disabled." ); | ||
990 | #endif | ||
991 | STACK_CHECK(L2) | ||
992 | push_cached_func( L2, L2_cache_i, L, i ); | ||
993 | ASSERT_L( lua_isfunction(L2,-1) ); | ||
994 | STACK_END(L2,1) | ||
995 | } break; | ||
996 | |||
997 | case LUA_TTABLE: if (vt==VT_KEY) { ret=FALSE; break; } { | ||
998 | |||
999 | STACK_CHECK(L) | ||
1000 | STACK_CHECK(L2) | ||
1001 | |||
1002 | /* Check if we've already copied the same table from 'L' (during this transmission), and | ||
1003 | * reuse the old copy. This allows table upvalues shared by multiple | ||
1004 | * local functions to point to the same table, also in the target. | ||
1005 | * Also, this takes care of cyclic tables and multiple references | ||
1006 | * to the same subtable. | ||
1007 | * | ||
1008 | * Note: Even metatables need to go through this test; to detect | ||
1009 | * loops s.a. those in required module tables (getmetatable(lanes).lanes == lanes) | ||
1010 | */ | ||
1011 | if (push_cached_table( L2, L2_cache_i, L, i )) { | ||
1012 | ASSERT_L( lua_istable(L2, -1) ); // from cache | ||
1013 | break; | ||
1014 | } | ||
1015 | ASSERT_L( lua_istable(L2,-1) ); | ||
1016 | |||
1017 | STACK_GROW( L, 2 ); | ||
1018 | STACK_GROW( L2, 2 ); | ||
1019 | |||
1020 | lua_pushnil(L); // start iteration | ||
1021 | while( lua_next( L, i ) ) { | ||
1022 | uint_t val_i= lua_gettop(L); | ||
1023 | uint_t key_i= val_i-1; | ||
1024 | |||
1025 | /* Only basic key types are copied over; others ignored | ||
1026 | */ | ||
1027 | if (inter_copy_one_( L2, 0 /*key*/, L, key_i, VT_KEY )) { | ||
1028 | /* | ||
1029 | * Contents of metatables are copied with cache checking; | ||
1030 | * important to detect loops. | ||
1031 | */ | ||
1032 | if (inter_copy_one_( L2, L2_cache_i, L, val_i, VT_NORMAL )) { | ||
1033 | ASSERT_L( lua_istable(L2,-3) ); | ||
1034 | lua_rawset( L2, -3 ); // add to table (pops key & val) | ||
1035 | } else { | ||
1036 | luaL_error( L, "Unable to copy over type '%s' (in %s)", | ||
1037 | luaG_typename(L,val_i), | ||
1038 | vt==VT_NORMAL ? "table":"metatable" ); | ||
1039 | } | ||
1040 | } | ||
1041 | lua_pop( L, 1 ); // pop value (next round) | ||
1042 | } | ||
1043 | STACK_MID(L,0) | ||
1044 | STACK_MID(L2,1) | ||
1045 | |||
1046 | /* Metatables are expected to be immutable, and copied only once. | ||
1047 | */ | ||
1048 | if (lua_getmetatable( L, i )) { | ||
1049 | // | ||
1050 | // L [-1]: metatable | ||
1051 | |||
1052 | uint_t mt_id= get_mt_id( L, -1 ); // Unique id for the metatable | ||
1053 | |||
1054 | STACK_GROW(L2,4); | ||
1055 | |||
1056 | push_registry_subtable( L2, REG_MTID ); | ||
1057 | STACK_MID(L2,2); | ||
1058 | lua_pushinteger( L2, mt_id ); | ||
1059 | lua_rawget( L2, -2 ); | ||
1060 | // | ||
1061 | // L2 ([-3]: copied table) | ||
1062 | // [-2]: reg[REG_MTID] | ||
1063 | // [-1]: nil/metatable pre-known in L2 | ||
1064 | |||
1065 | STACK_MID(L2,3); | ||
1066 | |||
1067 | if (lua_isnil(L2,-1)) { /* L2 did not know the metatable */ | ||
1068 | lua_pop(L2,1); | ||
1069 | STACK_MID(L2,2); | ||
1070 | ASSERT_L( lua_istable(L,-1) ); | ||
1071 | if (inter_copy_one_( L2, L2_cache_i /*for function cacheing*/, L, lua_gettop(L) /*[-1]*/, VT_METATABLE )) { | ||
1072 | // | ||
1073 | // L2 ([-3]: copied table) | ||
1074 | // [-2]: reg[REG_MTID] | ||
1075 | // [-1]: metatable (copied from L) | ||
1076 | |||
1077 | STACK_MID(L2,3); | ||
1078 | // mt_id -> metatable | ||
1079 | // | ||
1080 | lua_pushinteger(L2,mt_id); | ||
1081 | lua_pushvalue(L2,-2); | ||
1082 | lua_rawset(L2,-4); | ||
1083 | |||
1084 | // metatable -> mt_id | ||
1085 | // | ||
1086 | lua_pushvalue(L2,-1); | ||
1087 | lua_pushinteger(L2,mt_id); | ||
1088 | lua_rawset(L2,-4); | ||
1089 | |||
1090 | STACK_MID(L2,3); | ||
1091 | } else { | ||
1092 | luaL_error( L, "Error copying a metatable" ); | ||
1093 | } | ||
1094 | STACK_MID(L2,3); | ||
1095 | } | ||
1096 | // L2 ([-3]: copied table) | ||
1097 | // [-2]: reg[REG_MTID] | ||
1098 | // [-1]: metatable (pre-known or copied from L) | ||
1099 | |||
1100 | lua_remove(L2,-2); // take away 'reg[REG_MTID]' | ||
1101 | // | ||
1102 | // L2: ([-2]: copied table) | ||
1103 | // [-1]: metatable for that table | ||
1104 | |||
1105 | lua_setmetatable( L2, -2 ); | ||
1106 | |||
1107 | // L2: [-1]: copied table (with metatable set if source had it) | ||
1108 | |||
1109 | lua_pop(L,1); // remove source metatable (L, not L2!) | ||
1110 | } | ||
1111 | STACK_END(L2,1) | ||
1112 | STACK_END(L,0) | ||
1113 | } break; | ||
1114 | |||
1115 | /* The following types cannot be copied */ | ||
1116 | |||
1117 | case LUA_TTHREAD: | ||
1118 | ret=FALSE; break; | ||
1119 | } | ||
1120 | |||
1121 | STACK_END(L2, ret? 1:0) | ||
1122 | |||
1123 | return ret; | ||
1124 | } | ||
1125 | |||
1126 | |||
1127 | /* | ||
1128 | * Akin to 'lua_xmove' but copies values between _any_ Lua states. | ||
1129 | * | ||
1130 | * NOTE: Both the states must be solely in the current OS thread's posession. | ||
1131 | * | ||
1132 | * Note: Parameters are in this order ('L' = from first) to be same as 'lua_xmove'. | ||
1133 | */ | ||
1134 | void luaG_inter_copy( lua_State* L, lua_State *L2, uint_t n ) | ||
1135 | { | ||
1136 | uint_t top_L= lua_gettop(L); | ||
1137 | uint_t top_L2= lua_gettop(L2); | ||
1138 | uint_t i; | ||
1139 | |||
1140 | /* steal Lua library's 'luaB_tostring()' from the first call. Other calls | ||
1141 | * don't have to have access to it. | ||
1142 | * | ||
1143 | * Note: multiple threads won't come here at once; this function will | ||
1144 | * be called before there can be multiple threads (no locking needed). | ||
1145 | */ | ||
1146 | if (!hijacked_tostring) { | ||
1147 | STACK_GROW( L,1 ); | ||
1148 | |||
1149 | STACK_CHECK(L) | ||
1150 | lua_getglobal( L, "tostring" ); | ||
1151 | // | ||
1152 | // [-1]: function|nil | ||
1153 | |||
1154 | hijacked_tostring= lua_tocfunction( L, -1 ); | ||
1155 | lua_pop(L,1); | ||
1156 | STACK_END(L,0) | ||
1157 | |||
1158 | if (!hijacked_tostring) { | ||
1159 | luaL_error( L, "Need to see 'tostring()' once" ); | ||
1160 | } | ||
1161 | } | ||
1162 | |||
1163 | if (n > top_L) | ||
1164 | luaL_error( L, "Not enough values: %d < %d", top_L, n ); | ||
1165 | |||
1166 | STACK_GROW( L2, n+1 ); | ||
1167 | |||
1168 | /* | ||
1169 | * Make a cache table for the duration of this copy. Collects tables and | ||
1170 | * function entries, avoiding the same entries to be passed on as multiple | ||
1171 | * copies. ESSENTIAL i.e. for handling upvalue tables in the right manner! | ||
1172 | */ | ||
1173 | lua_newtable(L2); | ||
1174 | |||
1175 | for (i=top_L-n+1; i <= top_L; i++) { | ||
1176 | if (!inter_copy_one_( L2, top_L2+1, L, i, VT_NORMAL )) { | ||
1177 | |||
1178 | luaL_error( L, "Cannot copy type: %s", luaG_typename(L,i) ); | ||
1179 | } | ||
1180 | } | ||
1181 | |||
1182 | /* | ||
1183 | * Remove the cache table. Persistant caching would cause i.e. multiple | ||
1184 | * messages passed in the same table to use the same table also in receiving | ||
1185 | * end. | ||
1186 | */ | ||
1187 | lua_remove( L2, top_L2+1 ); | ||
1188 | |||
1189 | ASSERT_L( (uint_t)lua_gettop(L) == top_L ); | ||
1190 | ASSERT_L( (uint_t)lua_gettop(L2) == top_L2+n ); | ||
1191 | } | ||
1192 | |||
1193 | |||
1194 | void luaG_inter_move( lua_State* L, lua_State *L2, uint_t n ) | ||
1195 | { | ||
1196 | luaG_inter_copy( L, L2, n ); | ||
1197 | lua_pop( L,(int)n ); | ||
1198 | } | ||
diff --git a/src/tools.h b/src/tools.h new file mode 100644 index 0000000..d155c65 --- /dev/null +++ b/src/tools.h | |||
@@ -0,0 +1,72 @@ | |||
1 | /* | ||
2 | * TOOLS.H | ||
3 | */ | ||
4 | #ifndef TOOLS_H | ||
5 | #define TOOLS_H | ||
6 | |||
7 | #include "lua.h" | ||
8 | #include "threading.h" | ||
9 | // MUTEX_T | ||
10 | |||
11 | #include <assert.h> | ||
12 | |||
13 | // Note: The < -10000 test is to leave registry/global/upvalue indices untouched | ||
14 | // | ||
15 | #define /*int*/ STACK_ABS(L,n) \ | ||
16 | ( ((n) >= 0 || (n) <= -10000) ? (n) : lua_gettop(L) +(n) +1 ) | ||
17 | |||
18 | #ifdef NDEBUG | ||
19 | #define _ASSERT_L(lua,c) /*nothing*/ | ||
20 | #define STACK_CHECK(L) /*nothing*/ | ||
21 | #define STACK_MID(L,c) /*nothing*/ | ||
22 | #define STACK_END(L,c) /*nothing*/ | ||
23 | #define STACK_DUMP(L) /*nothing*/ | ||
24 | #define DEBUG() /*nothing*/ | ||
25 | #else | ||
26 | #define _ASSERT_L(lua,c) { if (!(c)) luaL_error( lua, "ASSERT failed: %s:%d '%s'", __FILE__, __LINE__, #c ); } | ||
27 | // | ||
28 | #define STACK_CHECK(L) { int _oldtop_##L = lua_gettop(L); | ||
29 | #define STACK_MID(L,change) { int a= lua_gettop(L)-_oldtop_##L; int b= (change); \ | ||
30 | if (a != b) luaL_error( L, "STACK ASSERT failed (%d not %d): %s:%d", a, b, __FILE__, __LINE__ ); } | ||
31 | #define STACK_END(L,change) STACK_MID(L,change) } | ||
32 | |||
33 | #define STACK_DUMP(L) luaG_dump(L); | ||
34 | #define DEBUG() fprintf( stderr, "<<%s %d>>\n", __FILE__, __LINE__ ); | ||
35 | #endif | ||
36 | #define ASSERT_L(c) _ASSERT_L(L,c) | ||
37 | |||
38 | #define STACK_GROW(L,n) { if (!lua_checkstack(L,n)) luaL_error( L, "Cannot grow stack!" ); } | ||
39 | |||
40 | #define LUAG_FUNC( func_name ) static int LG_##func_name( lua_State *L ) | ||
41 | |||
42 | #define luaG_optunsigned(L,i,d) ((uint_t) luaL_optinteger(L,i,d)) | ||
43 | #define luaG_tounsigned(L,i) ((uint_t) lua_tointeger(L,i)) | ||
44 | |||
45 | #define luaG_isany(L,i) (!lua_isnil(L,i)) | ||
46 | |||
47 | #define luaG_typename( L, index ) lua_typename( L, lua_type(L,index) ) | ||
48 | |||
49 | void luaG_dump( lua_State* L ); | ||
50 | |||
51 | const char *luaG_openlibs( lua_State *L, const char *libs ); | ||
52 | |||
53 | int luaG_deep_userdata( lua_State *L ); | ||
54 | void *luaG_todeep( lua_State *L, lua_CFunction idfunc, int index ); | ||
55 | |||
56 | typedef struct { | ||
57 | volatile int refcount; | ||
58 | void *deep; | ||
59 | } DEEP_PRELUDE; | ||
60 | |||
61 | void luaG_push_proxy( lua_State *L, lua_CFunction idfunc, DEEP_PRELUDE *deep_userdata ); | ||
62 | |||
63 | void luaG_inter_copy( lua_State *L, lua_State *L2, uint_t n ); | ||
64 | void luaG_inter_move( lua_State *L, lua_State *L2, uint_t n ); | ||
65 | |||
66 | // Lock for reference counter inc/dec locks (to be initialized by outside code) | ||
67 | // | ||
68 | extern MUTEX_T deep_lock; | ||
69 | extern MUTEX_T mtid_lock; | ||
70 | |||
71 | #endif | ||
72 | // TOOLS_H | ||
diff --git a/tests/argtable.lua b/tests/argtable.lua new file mode 100644 index 0000000..5ed5d4e --- /dev/null +++ b/tests/argtable.lua | |||
@@ -0,0 +1,38 @@ | |||
1 | -- | ||
2 | -- ARGTABLE.LUA Copyright (c) 2007, Asko Kauppi <akauppi@gmail.com> | ||
3 | -- | ||
4 | -- Command line parameter parsing | ||
5 | -- | ||
6 | -- NOTE: Wouldn't hurt having such a service built-in to Lua...? :P | ||
7 | -- | ||
8 | |||
9 | local m= {} | ||
10 | |||
11 | -- tbl= argtable(...) | ||
12 | -- | ||
13 | -- Returns a table with 1..N indices being 'value' parameters, and any | ||
14 | -- "-flag[=xxx]" or "--flag[=xxx]" parameters set to { flag=xxx/true }. | ||
15 | -- | ||
16 | -- In other words, makes handling command line parameters simple. :) | ||
17 | -- | ||
18 | -- 15 --> {Â 15Â } | ||
19 | -- -20 --> { -20 } | ||
20 | -- -a --> { ['a']=true } | ||
21 | -- --some=15 --> { ['some']=15 } | ||
22 | -- --more=big --> { ['more']='big' } | ||
23 | -- | ||
24 | function m.argtable(...) | ||
25 | local ret= {} | ||
26 | for i=1,select('#',...) do | ||
27 | local v= select(i,...) | ||
28 | local flag,val= string.match( v, "^%-+([^=]+)%=?(.*)" ) | ||
29 | if flag and not tonumber(v) then | ||
30 | ret[flag]= (val=="") and true or tonumber(val) or val | ||
31 | else | ||
32 | table.insert( ret, v ) -- 1..N | ||
33 | end | ||
34 | end | ||
35 | return ret | ||
36 | end | ||
37 | |||
38 | return m | ||
diff --git a/tests/assert.lua b/tests/assert.lua new file mode 100644 index 0000000..85febfb --- /dev/null +++ b/tests/assert.lua | |||
@@ -0,0 +1,318 @@ | |||
1 | -- | ||
2 | -- ASSERT.LUA Copyright (c) 2006-07, <akauppi@gmail.com> | ||
3 | -- | ||
4 | -- Converting the Lua 'assert' function into a namespace table (without | ||
5 | -- breaking compatibility with the basic 'assert()' calling). | ||
6 | -- | ||
7 | -- This module allows shorthand use s.a. 'assert.table()' for asserting | ||
8 | -- variable types, and is also being used by Lua-super constraints system | ||
9 | -- for testing function parameter & return types. | ||
10 | -- | ||
11 | -- All in all, a worthy module and could be part of Lua future versions. | ||
12 | -- | ||
13 | -- Note: the 'assert' table is available for your own assertions, too. Just add | ||
14 | -- more functions s.a. 'assert.myobj()' to check for custom invariants. | ||
15 | -- They will then be available for the constraints check, too. | ||
16 | -- | ||
17 | -- Author: <akauppi@gmail.com> | ||
18 | -- | ||
19 | --[[ | ||
20 | /****************************************************************************** | ||
21 | * Lua 5.1.1 support and extension functions (assert.lua) | ||
22 | * | ||
23 | * Copyright (C) 2006-07, Asko Kauppi. | ||
24 | * | ||
25 | * NOTE: This license concerns only the particular source file; not necessarily | ||
26 | * the project with which it has been delivered (the project may have a more | ||
27 | * restrictive license, s.a. [L]GPL). | ||
28 | * | ||
29 | * Permission is hereby granted, free of charge, to any person obtaining | ||
30 | * a copy of this software and associated documentation files (the | ||
31 | * "Software"), to deal in the Software without restriction, including | ||
32 | * without limitation the rights to use, copy, modify, merge, publish, | ||
33 | * distribute, sublicense, and/or sell copies of the Software, and to | ||
34 | * permit persons to whom the Software is furnished to do so, subject to | ||
35 | * the following conditions: | ||
36 | * | ||
37 | * The above copyright notice and this permission notice shall be | ||
38 | * included in all copies or substantial portions of the Software. | ||
39 | * | ||
40 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
41 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
42 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
43 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
44 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
45 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
46 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
47 | ******************************************************************************/ | ||
48 | ]]-- | ||
49 | |||
50 | local m= { _info= { MODULE= "Assert.* functions for constraints, and unit testing", | ||
51 | AUTHOR= "akauppi@gmail.com", | ||
52 | VERSION= 20070603, -- last change (yyyymmdd) | ||
53 | LICENSE= "MIT/X11" } } | ||
54 | |||
55 | -- Global changes: | ||
56 | -- 'assert' redefined, in a backwards compatible way | ||
57 | -- | ||
58 | -- Module functions: | ||
59 | -- none | ||
60 | |||
61 | assert( type(assert) == "function" ) -- system assert function | ||
62 | |||
63 | ----- | ||
64 | -- Integer range: INT_MIN..INT_MAX | ||
65 | -- | ||
66 | local function try_maxint( n ) | ||
67 | return (n > n-1) and n-1 -- false when outside the integer range | ||
68 | end | ||
69 | |||
70 | local INT_MAX= | ||
71 | try_maxint( 2^64 ) or | ||
72 | try_maxint( 2^32 ) or | ||
73 | try_maxint( 2^24 ) or -- float (24-bit mantissa) | ||
74 | assert( false ) | ||
75 | |||
76 | local INT_MIN= -(INT_MAX+1) | ||
77 | |||
78 | |||
79 | ---=== assert.*() ===--- | ||
80 | |||
81 | local at_msg= "type assertion error" -- TBD: better messages, about that exact situation | ||
82 | local av_msg= "value assertion error" | ||
83 | |||
84 | -- void= _assert( val [, msg_str [, lev_uint]]Â ) | ||
85 | -- | ||
86 | local function _assert( cond, msg, lev ) | ||
87 | -- original 'assert' provides no level override, so we use 'error'. | ||
88 | -- | ||
89 | if not cond then | ||
90 | error( msg or "assertion failed!", (lev or 1)+1 ) | ||
91 | end | ||
92 | end | ||
93 | |||
94 | -- Note: following code uses the _new_ 'assert()' by purpose, since it provides | ||
95 | -- a level override (original doesn't) | ||
96 | -- | ||
97 | local function assert_v( v0 ) | ||
98 | return function(v,msg) | ||
99 | _assert( v == v0, msg or av_msg, 2 ) | ||
100 | return v | ||
101 | end | ||
102 | end | ||
103 | local function assert_t( str ) | ||
104 | return function(v,msg) | ||
105 | _assert( type(v) == str, msg or at_msg, 2 ) | ||
106 | return v | ||
107 | end | ||
108 | end | ||
109 | local function assert_t2( str ) | ||
110 | return function(v,subtype,msg) | ||
111 | local t,st= type(v) | ||
112 | _assert( t==str and ((not subtype) or (st==subtype)), | ||
113 | msg or at_msg, 2 ) | ||
114 | return v | ||
115 | end | ||
116 | end | ||
117 | |||
118 | assert= | ||
119 | { | ||
120 | __call= function(_,v,msg) -- plain 'assert()' (compatibility) | ||
121 | if v then return v end | ||
122 | _assert( v, msg, 2 ) | ||
123 | end, | ||
124 | |||
125 | -- Hopefully, Lua will allow use of 'nil', 'function' and other reserved words as table | ||
126 | -- shortcuts in the future (5.1.1 does not). | ||
127 | -- | ||
128 | ["nil"]= assert_v( nil ), | ||
129 | boolean= assert_t "boolean", | ||
130 | table= assert_t2 "table", | ||
131 | ["function"]= assert_t "function", | ||
132 | userdata= assert_t2 "userdata", | ||
133 | |||
134 | string= function( v, msg ) | ||
135 | local s= tostring(v) | ||
136 | _assert( s, msg or at_msg, 2 ) | ||
137 | return v | ||
138 | end, | ||
139 | |||
140 | char= function( v, msg ) | ||
141 | -- 'char' is _not_ doing int->string conversion | ||
142 | _assert( type(v)=="string" and v:len()==1, msg or at_msg, 2 ) | ||
143 | return v | ||
144 | end, | ||
145 | |||
146 | number= function( v, msg ) | ||
147 | _assert( tonumber(v), msg or at_msg, 2 ) | ||
148 | return v | ||
149 | end, | ||
150 | |||
151 | int= function( v, msg ) | ||
152 | local n= tonumber(v) | ||
153 | _assert( n and (n >= INT_MIN) and (n <= INT_MAX) and math.floor(n) == n, | ||
154 | msg or at_msg, 2 ) | ||
155 | return v | ||
156 | end, | ||
157 | |||
158 | uint= function( v, msg ) | ||
159 | local n= tonumber(v) | ||
160 | -- unsigned integer upper range is the same as integers' (there's no | ||
161 | -- real unsigned within the Lua) | ||
162 | _assert( n and (n >= 0) and (n <= INT_MAX) and math.floor(n) == n, | ||
163 | msg or at_msg, 2 ) | ||
164 | return v | ||
165 | end, | ||
166 | |||
167 | ['true']= assert_v( true ), | ||
168 | ['false']= assert_v( false ), | ||
169 | |||
170 | string_or_table= function( v, msg ) | ||
171 | assert( tostring(v) or type(v)=="table", msg or at_msg, 2 ) | ||
172 | return v | ||
173 | end, | ||
174 | |||
175 | number_or_string= function( v, msg ) | ||
176 | assert( tonumber(v) or type(v)=="table", msg or at_msg, 2 ) | ||
177 | return v | ||
178 | end, | ||
179 | |||
180 | any= function( v, msg ) | ||
181 | assert( v ~= nil, msg or av_msg, 2 ) | ||
182 | return v | ||
183 | end, | ||
184 | |||
185 | -- Range assertion, with extra parameters per instance | ||
186 | -- | ||
187 | -- Note: values may be of _any_ type that can do >=, <= comparisons. | ||
188 | -- | ||
189 | range= function( lo, hi ) | ||
190 | _assert( lo and hi and lo <= hi, "Bad limits", 2 ) | ||
191 | -- make sure the limits make sense (just once) | ||
192 | |||
193 | return function(v,msg,lev) | ||
194 | if ( (lo and v<lo) or (hi and v>hi) ) then | ||
195 | msg= msg or "not in range: ("..(lo or "")..","..(hi or "")..")" | ||
196 | _assert( false, msg, 2 ) | ||
197 | end | ||
198 | return v | ||
199 | end | ||
200 | end, | ||
201 | |||
202 | -- Table contents assertion | ||
203 | -- - no unknown (non-numeric) keys are allowed | ||
204 | -- - numeric keys are ignored | ||
205 | -- | ||
206 | -- Constraints patch should point to this, when using the ":{ ... }" constraint. | ||
207 | -- | ||
208 | ["{}"]= function( tbl ) | ||
209 | |||
210 | -- check all keys in 't' (including numeric, if any) that they do exist, | ||
211 | -- and carry the right type | ||
212 | -- | ||
213 | local function subf1(v,t,msg,lev) | ||
214 | _assert(lev) | ||
215 | for k,f in pairs(t) do | ||
216 | -- 'f' is an assert function, or subtable | ||
217 | local ft= type(f) | ||
218 | if ft=="function" then | ||
219 | f( v[k], msg, lev+1 ) | ||
220 | elseif ft=="table" then | ||
221 | _assert( type(v[k])=="table", msg or "no subtable "..tostring(k), lev+1 ) | ||
222 | subf1( v[k], f, msg, lev+1 ) | ||
223 | else | ||
224 | error( "Bad constraints table for '"..tostring(k).."'! (not a function)", lev+1 ) | ||
225 | end | ||
226 | end | ||
227 | end | ||
228 | |||
229 | -- check there are no other (non-numeric) keys in 'v' | ||
230 | local function subf2(v,t,msg,lev) | ||
231 | _assert(lev) | ||
232 | for k,vv in pairs(v) do | ||
233 | if type(k)=="number" then | ||
234 | -- skip them | ||
235 | elseif not t[k] then | ||
236 | _assert( false, msg or "extra field: '"..tostring(k).."'", lev+1 ) | ||
237 | elseif type(vv)=="table" then | ||
238 | subf2( vv, t[k], msg, lev+1 ) | ||
239 | end | ||
240 | end | ||
241 | end | ||
242 | |||
243 | _assert( type(tbl)=="table", "Wrong parameter to assert['{}']" ) | ||
244 | |||
245 | return function( v, msg, lev ) | ||
246 | lev= (lev or 1)+1 | ||
247 | _assert( type(v)=="table" ,msg, lev ) | ||
248 | subf1( v, tbl, msg, lev ) | ||
249 | subf2( v, tbl, msg, lev ) | ||
250 | return v | ||
251 | end | ||
252 | end, | ||
253 | |||
254 | -- ... | ||
255 | } | ||
256 | setmetatable( assert, assert ) | ||
257 | |||
258 | assert.void= assert["nil"] | ||
259 | |||
260 | |||
261 | ----- | ||
262 | -- void= assert.fails( function [,err_msg_str] ) | ||
263 | -- | ||
264 | -- Special assert function, to make sure the call within it fails, and gives a | ||
265 | -- specific error message (to be used in unit testing). | ||
266 | -- | ||
267 | function assert.fails( func_block, err_msg ) | ||
268 | -- | ||
269 | local st,err= pcall( func_block ) | ||
270 | if st then | ||
271 | _assert( false, "Block expected to fail, but didn't.", 2 ) | ||
272 | elseif err_msg and err ~= err_msg then | ||
273 | _assert( false, "Failed with wrong error message: \n".. | ||
274 | "'"..err.."'\nexpected: '"..err_msg.."'", 2 ) | ||
275 | end | ||
276 | end | ||
277 | |||
278 | |||
279 | ----- | ||
280 | -- void= assert.failsnot( function [,err_msg_str] ) | ||
281 | -- | ||
282 | -- Similar to 'assert.fails' but expects the code to survive. | ||
283 | -- | ||
284 | function assert.failsnot( func_block, err_msg ) | ||
285 | -- | ||
286 | local st,err= pcall( func_block ) | ||
287 | if not st then | ||
288 | _assert( false, "Block expected NOT to fail, but did.".. | ||
289 | (err and "\n\tError: '"..err.."'" or ""), 2 ) | ||
290 | end | ||
291 | end | ||
292 | |||
293 | |||
294 | ----- | ||
295 | -- void= assert.nilerr( function [,err_msg_str] ) | ||
296 | -- | ||
297 | -- Expects the function to return with 'nil,err' failure code, with | ||
298 | -- optionally error string matching. Similar to --> 'assert.fails()' | ||
299 | -- | ||
300 | function assert.nilerr( func_block, err_msg ) | ||
301 | -- | ||
302 | local v,err= func_block() | ||
303 | _assert( v==nil, "Expected to return nil, but didn't: "..tostring(v), 2 ) | ||
304 | if err_msg and err ~= err_msg then | ||
305 | _assert( false, "Failed with wrong error message: \n".. | ||
306 | "'"..err.."'\nexpected: '"..err_msg.."'", 2 ) | ||
307 | end | ||
308 | end | ||
309 | |||
310 | |||
311 | -- Sanity check | ||
312 | -- | ||
313 | assert( true ) | ||
314 | assert.fails( function() assert( false ) end ) | ||
315 | assert.fails( function() assert( nil ) end ) | ||
316 | |||
317 | |||
318 | return m -- just info | ||
diff --git a/tests/atomic.lua b/tests/atomic.lua new file mode 100644 index 0000000..a027453 --- /dev/null +++ b/tests/atomic.lua | |||
@@ -0,0 +1,18 @@ | |||
1 | -- | ||
2 | -- ATOMIC.LUA | ||
3 | -- | ||
4 | -- Test program for Lua Lanes | ||
5 | -- | ||
6 | |||
7 | require "lanes" | ||
8 | |||
9 | local linda= lanes.linda() | ||
10 | local key= "$" | ||
11 | |||
12 | local f= lanes.genatomic( linda, key, 5 ) | ||
13 | |||
14 | local v | ||
15 | v= f(); print(v); assert(v==6) | ||
16 | v= f(-0.5); print(v); assert(v==5.5) | ||
17 | |||
18 | v= f(-10); print(v); assert(v==-4.5) | ||
diff --git a/tests/basic.lua b/tests/basic.lua new file mode 100644 index 0000000..ee31ed1 --- /dev/null +++ b/tests/basic.lua | |||
@@ -0,0 +1,331 @@ | |||
1 | -- | ||
2 | -- BASIC.LUA Copyright (c) 2007-08, Asko Kauppi <akauppi@gmail.com> | ||
3 | -- | ||
4 | -- Selftests for Lua Lanes | ||
5 | -- | ||
6 | -- To do: | ||
7 | -- - ... | ||
8 | -- | ||
9 | |||
10 | require "lanes" | ||
11 | require "assert" -- assert.fails() | ||
12 | |||
13 | local lanes_gen= assert( lanes.gen ) | ||
14 | local lanes_linda= assert( lanes.linda ) | ||
15 | |||
16 | local tostring= assert( tostring ) | ||
17 | |||
18 | local function PRINT(...) | ||
19 | local str="" | ||
20 | for i=1,select('#',...) do | ||
21 | str= str..tostring(select(i,...)).."\t" | ||
22 | end | ||
23 | if io then | ||
24 | io.stderr:write(str.."\n") | ||
25 | end | ||
26 | end | ||
27 | |||
28 | |||
29 | ---=== Local helpers ===--- | ||
30 | |||
31 | local tables_match | ||
32 | |||
33 | -- true if 'a' is a subtable of 'b' | ||
34 | -- | ||
35 | local function subtable( a, b ) | ||
36 | -- | ||
37 | assert( type(a)=="table" and type(b)=="table" ) | ||
38 | |||
39 | for k,v in pairs(b) do | ||
40 | if type(v)~=type(a[k]) then | ||
41 | return false -- not subtable (different types, or missing key) | ||
42 | elseif type(v)=="table" then | ||
43 | if not tables_match(v,a[k]) then return false end | ||
44 | else | ||
45 | if a[k] ~= v then return false end | ||
46 | end | ||
47 | end | ||
48 | return true -- is a subtable | ||
49 | end | ||
50 | |||
51 | -- true when contents of 'a' and 'b' are identific | ||
52 | -- | ||
53 | tables_match= function( a, b ) | ||
54 | return subtable( a, b ) and subtable( b, a ) | ||
55 | end | ||
56 | |||
57 | |||
58 | ---=== Tasking (basic) ===--- | ||
59 | |||
60 | local function task( a, b, c ) | ||
61 | --error "111" -- testing error messages | ||
62 | assert(hey) | ||
63 | local v=0 | ||
64 | for i=a,b,c do | ||
65 | v= v+i | ||
66 | end | ||
67 | return v, hey | ||
68 | end | ||
69 | |||
70 | local task_launch= lanes_gen( "", { globals={hey=true} }, task ) | ||
71 | -- base stdlibs, normal priority | ||
72 | |||
73 | -- 'task_launch' is a factory of multithreaded tasks, we can launch several: | ||
74 | |||
75 | local lane1= task_launch( 100,200,3 ) | ||
76 | local lane2= task_launch( 200,300,4 ) | ||
77 | |||
78 | -- At this stage, states may be "pending", "running" or "done" | ||
79 | |||
80 | local st1,st2= lane1.status, lane2.status | ||
81 | PRINT(st1,st2) | ||
82 | assert( st1=="pending" or st1=="running" or st1=="done" ) | ||
83 | assert( st2=="pending" or st2=="running" or st2=="done" ) | ||
84 | |||
85 | -- Accessing results ([1..N]) pends until they are available | ||
86 | -- | ||
87 | PRINT("waiting...") | ||
88 | local v1, v1_hey= lane1[1], lane1[2] | ||
89 | local v2, v2_hey= lane2[1], lane2[2] | ||
90 | |||
91 | PRINT( v1, v1_hey ) | ||
92 | assert( v1_hey == true ) | ||
93 | |||
94 | PRINT( v2, v2_hey ) | ||
95 | assert( v2_hey == true ) | ||
96 | |||
97 | assert( lane1.status == "done" ) | ||
98 | assert( lane1.status == "done" ) | ||
99 | |||
100 | |||
101 | ---=== Tasking (cancelling) ===--- | ||
102 | |||
103 | local task_launch2= lanes_gen( "", { cancelstep=100, globals={hey=true} }, task ) | ||
104 | |||
105 | local N=999999999 | ||
106 | local lane9= task_launch2(1,N,1) -- huuuuuuge... | ||
107 | |||
108 | -- Wait until state changes "pending"->"running" | ||
109 | -- | ||
110 | local st | ||
111 | local t0= os.time() | ||
112 | while os.time()-t0 < 5 do | ||
113 | st= lane9.status | ||
114 | io.stderr:write( (i==1) and st.." " or '.' ) | ||
115 | if st~="pending" then break end | ||
116 | end | ||
117 | PRINT(" "..st) | ||
118 | |||
119 | if st=="error" then | ||
120 | local _= lane9[0] -- propagate the error here | ||
121 | end | ||
122 | if st=="done" then | ||
123 | error( "Looping to "..N.." was not long enough (cannot test cancellation)" ) | ||
124 | end | ||
125 | assert( st=="running" ) | ||
126 | |||
127 | lane9:cancel() | ||
128 | |||
129 | local t0= os.time() | ||
130 | while os.time()-t0 < 5 do | ||
131 | st= lane9.status | ||
132 | io.stderr:write( (i==1) and st.." " or '.' ) | ||
133 | if st~="running" then break end | ||
134 | end | ||
135 | PRINT(" "..st) | ||
136 | assert( st == "cancelled" ) | ||
137 | |||
138 | |||
139 | ---=== Communications ===--- | ||
140 | |||
141 | local function WR(...) io.stderr:write(...) end | ||
142 | |||
143 | local chunk= function( linda ) | ||
144 | |||
145 | local function receive() return linda:receive( "->" ) end | ||
146 | local function send(...) linda:send( "<-", ... ) end | ||
147 | |||
148 | WR( "Lane starts!\n" ) | ||
149 | |||
150 | local v | ||
151 | v=receive(); WR( v.." received\n" ); assert( v==1 ) | ||
152 | v=receive(); WR( v.." received\n" ); assert( v==2 ) | ||
153 | v=receive(); WR( v.." received\n" ); assert( v==3 ) | ||
154 | |||
155 | send( 1,2,3 ); WR( "1,2,3 sent\n" ) | ||
156 | send 'a'; WR( "'a' sent\n" ) | ||
157 | send { 'a', 'b', 'c', d=10 }; WR( "{'a','b','c',d=10} sent\n" ) | ||
158 | |||
159 | v=receive(); WR( v.." received\n" ); assert( v==4 ) | ||
160 | |||
161 | WR( "Lane ends!\n" ) | ||
162 | end | ||
163 | |||
164 | local linda= lanes_linda() | ||
165 | assert( type(linda) == "userdata" ) | ||
166 | -- | ||
167 | -- ["->"] master -> slave | ||
168 | -- ["<-"] slave <- master | ||
169 | |||
170 | local function PEEK() return linda:get("<-") end | ||
171 | local function SEND(...) linda:send( "->", ... ) end | ||
172 | local function RECEIVE() return linda:receive( "<-" ) end | ||
173 | |||
174 | local t= lanes_gen("io",chunk)(linda) -- prepare & launch | ||
175 | |||
176 | SEND(1); WR( "1 sent\n" ) | ||
177 | SEND(2); WR( "2 sent\n" ) | ||
178 | for i=1,100 do | ||
179 | WR "." | ||
180 | assert( PEEK() == nil ) -- nothing coming in, yet | ||
181 | end | ||
182 | SEND(3); WR( "3 sent\n" ) | ||
183 | |||
184 | local a,b,c= RECEIVE(), RECEIVE(), RECEIVE() | ||
185 | WR( a..", "..b..", "..c.." received\n" ) | ||
186 | assert( a==1 and b==2 and c==3 ) | ||
187 | |||
188 | local a= RECEIVE(); WR( a.." received\n" ) | ||
189 | assert( a=='a' ) | ||
190 | |||
191 | local a= RECEIVE(); WR( type(a).." received\n" ) | ||
192 | assert( tables_match( a, {'a','b','c',d=10} ) ) | ||
193 | |||
194 | assert( PEEK() == nil ) | ||
195 | SEND(4) | ||
196 | |||
197 | |||
198 | ---=== Stdlib naming ===--- | ||
199 | |||
200 | local function io_os_f() | ||
201 | assert(io) | ||
202 | assert(os) | ||
203 | assert(print) | ||
204 | return true | ||
205 | end | ||
206 | |||
207 | local f1= lanes_gen( "io,os", io_os_f ) -- any delimiter will do | ||
208 | local f2= lanes_gen( "io+os", io_os_f ) | ||
209 | local f3= lanes_gen( "io,os,base", io_os_f ) | ||
210 | |||
211 | assert.fails( function() lanes_gen( "xxx", io_os_f ) end ) | ||
212 | |||
213 | assert( f1()[1] ) | ||
214 | assert( f2()[1] ) | ||
215 | assert( f3()[1] ) | ||
216 | |||
217 | |||
218 | ---=== Comms criss cross ===--- | ||
219 | |||
220 | -- We make two identical lanes, which are using the same Linda channel. | ||
221 | -- | ||
222 | local tc= lanes_gen( "io", | ||
223 | function( linda, ch_in, ch_out ) | ||
224 | |||
225 | local function STAGE(str) | ||
226 | io.stderr:write( ch_in..": "..str.."\n" ) | ||
227 | linda:send( nil, ch_out, str ) | ||
228 | local v= linda:receive( nil, ch_in ) | ||
229 | assert(v==str) | ||
230 | end | ||
231 | STAGE("Hello") | ||
232 | STAGE("I was here first!") | ||
233 | STAGE("So waht?") | ||
234 | end | ||
235 | ) | ||
236 | |||
237 | local linda= lanes_linda() | ||
238 | |||
239 | local a,b= tc(linda, "A","B"), tc(linda, "B","A") -- launching two lanes, twisted comms | ||
240 | |||
241 | local _= a[1],b[1] -- waits until they are both ready | ||
242 | |||
243 | |||
244 | ---=== Receive & send of code ===--- | ||
245 | |||
246 | local upvalue="123" | ||
247 | |||
248 | local function chunk2( linda ) | ||
249 | assert( upvalue=="123" ) -- even when running as separate thread | ||
250 | |||
251 | -- function name & line number should be there even as separate thread | ||
252 | -- | ||
253 | local info= debug.getinfo(1) -- 1 = us | ||
254 | -- | ||
255 | for k,v in pairs(info) do PRINT(k,v) end | ||
256 | |||
257 | assert( info.nups == 2 ) -- one upvalue + PRINT | ||
258 | assert( info.what == "Lua" ) | ||
259 | |||
260 | --assert( info.name == "chunk2" ) -- name does not seem to come through | ||
261 | assert( string.match( info.source, "^@tests[/\\]basic.lua$" ) ) | ||
262 | assert( string.match( info.short_src, "^tests[/\\]basic.lua$" ) ) | ||
263 | |||
264 | -- These vary so let's not be picky (they're there..) | ||
265 | -- | ||
266 | assert( info.linedefined > 200 ) -- start of 'chunk2' | ||
267 | assert( info.currentline > info.linedefined ) -- line of 'debug.getinfo' | ||
268 | assert( info.lastlinedefined > info.currentline ) -- end of 'chunk2' | ||
269 | |||
270 | local func,k= linda:receive( "down" ) | ||
271 | assert( type(func)=="function" ) | ||
272 | assert( k=="down" ) | ||
273 | |||
274 | func(linda) | ||
275 | |||
276 | local str= linda:receive( "down" ) | ||
277 | assert( str=="ok" ) | ||
278 | |||
279 | linda:send( "up", function() return ":)" end, "ok2" ) | ||
280 | end | ||
281 | |||
282 | local linda= lanes.linda() | ||
283 | |||
284 | local t2= lanes_gen( "debug,string", chunk2 )(linda) -- prepare & launch | ||
285 | |||
286 | linda:send( "down", function(linda) linda:send( "up", "ready!" ) end, | ||
287 | "ok" ) | ||
288 | |||
289 | -- wait to see if the tiny function gets executed | ||
290 | -- | ||
291 | local s= linda:receive( "up" ) | ||
292 | PRINT(s) | ||
293 | assert( s=="ready!" ) | ||
294 | |||
295 | -- returns of the 'chunk2' itself | ||
296 | -- | ||
297 | local f= linda:receive( "up" ) | ||
298 | assert( type(f)=="function" ) | ||
299 | |||
300 | local s2= f() | ||
301 | assert( s2==":)" ) | ||
302 | |||
303 | local ok2= linda:receive( "up" ) | ||
304 | assert( ok2 == "ok2" ) | ||
305 | |||
306 | |||
307 | ---=== :join test ===--- | ||
308 | |||
309 | -- NOTE: 'unpack()' cannot be used on the lane handle; it will always return nil | ||
310 | -- (unless [1..n] has been read earlier, in which case it would seemingly | ||
311 | -- work). | ||
312 | |||
313 | local S= lanes_gen( "table", | ||
314 | function(arg) | ||
315 | aux= {} | ||
316 | for i, v in ipairs(arg) do | ||
317 | table.insert (aux, 1, v) | ||
318 | end | ||
319 | return unpack(aux) | ||
320 | end ) | ||
321 | |||
322 | h= S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values | ||
323 | |||
324 | local a,b,c,d= h:join() | ||
325 | assert(a==14) | ||
326 | assert(b==13) | ||
327 | assert(c==12) | ||
328 | assert(d==nil) | ||
329 | |||
330 | -- | ||
331 | io.stderr:write "Done! :)\n" | ||
diff --git a/tests/cyclic.lua b/tests/cyclic.lua new file mode 100644 index 0000000..06452bd --- /dev/null +++ b/tests/cyclic.lua | |||
@@ -0,0 +1,64 @@ | |||
1 | -- | ||
2 | -- CYCLIC.LUA | ||
3 | -- | ||
4 | -- Test program for Lua Lanes | ||
5 | -- | ||
6 | |||
7 | require "lanes" | ||
8 | |||
9 | local table_concat= assert(table.concat) | ||
10 | |||
11 | local function WR(str,...) | ||
12 | for i=1,select('#',...) do | ||
13 | str= str.."\t"..tostring( select(i,...) ) | ||
14 | end | ||
15 | io.stderr:write( str..'\n' ) | ||
16 | end | ||
17 | |||
18 | local function same(k,l) | ||
19 | return k==l and "same" or ("NOT SAME: "..k.." "..l) | ||
20 | end | ||
21 | |||
22 | local a= {} | ||
23 | local b= {a} | ||
24 | a[1]= b | ||
25 | |||
26 | -- Getting the tables as upvalues should still have the <-> linkage | ||
27 | -- | ||
28 | local function lane1() | ||
29 | WR( "Via upvalue: ", same(a,b[1]), same(a[1],b) ) | ||
30 | assert( a[1]==b ) | ||
31 | assert( b[1]==a ) | ||
32 | end | ||
33 | local L1= lanes.gen( "io", lane1 )() | ||
34 | -- ...running | ||
35 | |||
36 | -- Getting the tables as parameters should also keep the linkage | ||
37 | -- | ||
38 | local function lane2( aa, bb ) | ||
39 | WR( "Via parameters:", same(aa,bb[1]), same(aa[1],bb) ) | ||
40 | assert( aa[1]==bb ) | ||
41 | assert( bb[1]==aa ) | ||
42 | end | ||
43 | local L2= lanes.gen( "io", lane2 )( a, b ) | ||
44 | -- ...running | ||
45 | |||
46 | -- Really unnecessary, but let's try a directly recursive table | ||
47 | -- | ||
48 | c= {} | ||
49 | c.a= c | ||
50 | |||
51 | local function lane3( cc ) | ||
52 | WR( "Directly recursive: ", same(cc, cc.a) ) | ||
53 | assert( cc and cc.a==cc ) | ||
54 | end | ||
55 | local L3= lanes.gen("io", lane3)(c) | ||
56 | |||
57 | -- Without a wait, exit from the main lane will close the process | ||
58 | -- | ||
59 | -- Waiting for multiple lanes at once could be done using a Linda | ||
60 | -- (but we're okay waiting them in order) | ||
61 | -- | ||
62 | L1:join() | ||
63 | L2:join() | ||
64 | L3:join() | ||
diff --git a/tests/ehynes.lua b/tests/ehynes.lua new file mode 100644 index 0000000..4cc370e --- /dev/null +++ b/tests/ehynes.lua | |||
@@ -0,0 +1,52 @@ | |||
1 | -- | ||
2 | -- Test from <ehynes at dharmagaia.com> | ||
3 | -- | ||
4 | require 'lanes' | ||
5 | |||
6 | local function PRINT_FMT( fmt, ... ) | ||
7 | io.stderr:write( string.format(fmt,...).."\n" ) | ||
8 | end | ||
9 | |||
10 | -- a linda for sending messages | ||
11 | local linda = lanes.linda() | ||
12 | |||
13 | -- a linda message receiver | ||
14 | local receiver_gen = lanes.gen( 'base', 'os', 'string', 'io', | ||
15 | function (message_name) | ||
16 | PRINT_FMT( 'receiver for message %s entered', message_name ) | ||
17 | local n = 1 | ||
18 | while linda:receive(message_name) do | ||
19 | PRINT_FMT( '%s %d receieved', message_name, n ) | ||
20 | n = n + 1 | ||
21 | end | ||
22 | end | ||
23 | ) | ||
24 | |||
25 | -- create a receiver | ||
26 | local receiver1 = receiver_gen('message') | ||
27 | |||
28 | -- create a second receiver (a second receiver in the same linda | ||
29 | -- appears to be needed to trigger the delays) | ||
30 | -- | ||
31 | -- AKa 4-Aug-2008: No, with svn version it isn't. But it causes the 2nd | ||
32 | -- message to be hanging... | ||
33 | -- | ||
34 | local receiver2 = receiver_gen('another message') | ||
35 | |||
36 | -- a function to pause and log the execution for debugging | ||
37 | local function logf(s, f, ...) | ||
38 | os.execute('sleep 1') | ||
39 | PRINT_FMT( "*** %s", s ) | ||
40 | f(...) | ||
41 | end | ||
42 | |||
43 | -- first message sent is received right away | ||
44 | logf('first message sent', linda.send, linda, 'message', true) | ||
45 | |||
46 | -- second message sent is not received immediatly | ||
47 | logf('second message sent', linda.send, linda, 'message', true) | ||
48 | |||
49 | -- third message sent triggers receipt of both second and third messages | ||
50 | logf('third message sent', linda.send, linda, 'message', true) | ||
51 | |||
52 | logf('all done', function() end) | ||
diff --git a/tests/error.lua b/tests/error.lua new file mode 100644 index 0000000..673bcb5 --- /dev/null +++ b/tests/error.lua | |||
@@ -0,0 +1,47 @@ | |||
1 | -- | ||
2 | -- Error reporting | ||
3 | -- | ||
4 | -- Note: this code is supposed to end in errors; not included in 'make test' | ||
5 | -- | ||
6 | |||
7 | require "lanes" | ||
8 | |||
9 | local function lane() | ||
10 | |||
11 | local subf= function() -- this so that we can see the call stack | ||
12 | --error "aa" | ||
13 | error({}) | ||
14 | error(error) | ||
15 | end | ||
16 | local subf2= function() | ||
17 | subf() | ||
18 | end | ||
19 | subf2() | ||
20 | end | ||
21 | |||
22 | local function cleanup(err) | ||
23 | end | ||
24 | |||
25 | local lgen = lanes.gen("*", { --[[finalizer=cleanup]] }, lane) | ||
26 | |||
27 | --- | ||
28 | io.stderr:write( "\n** Error catching **\n" ) | ||
29 | -- | ||
30 | local h= lgen() | ||
31 | local _,err,stack= h:join() -- wait for the lane (no automatic error propagation) | ||
32 | |||
33 | if err then | ||
34 | assert( type(stack)=="table" ) | ||
35 | io.stderr:write( "Lane error: "..tostring(err).."\n" ) | ||
36 | |||
37 | io.stderr:write( "\t", table.concat(stack,"\n\t"), "\n" ); | ||
38 | end | ||
39 | |||
40 | --- | ||
41 | io.stderr:write( "\n** Error propagation **\n" ) | ||
42 | -- | ||
43 | local h2= lgen() | ||
44 | local _= h2[0] | ||
45 | assert(false) -- does NOT get here | ||
46 | |||
47 | --never ends | ||
diff --git a/tests/fibonacci.lua b/tests/fibonacci.lua new file mode 100644 index 0000000..8867e14 --- /dev/null +++ b/tests/fibonacci.lua | |||
@@ -0,0 +1,75 @@ | |||
1 | -- | ||
2 | -- FIBONACCI.LUA Copyright (c) 2007-08, Asko Kauppi <akauppi@gmail.com> | ||
3 | -- | ||
4 | -- Parallel calculation of Fibonacci numbers | ||
5 | -- | ||
6 | -- A sample of task splitting like Intel TBB library does. | ||
7 | -- | ||
8 | -- References: | ||
9 | -- Intel Threading Building Blocks, 'test all' | ||
10 | -- <http://shareit.intel.com/WikiHome/Articles/111111316> | ||
11 | -- | ||
12 | |||
13 | -- Need to say it's 'local' so it can be an upvalue | ||
14 | -- | ||
15 | local lanes= require "lanes" | ||
16 | |||
17 | local function WR(str) | ||
18 | io.stderr:write( str.."\n" ) | ||
19 | end | ||
20 | |||
21 | -- Threshold for serial calculation (optimal depends on multithreading fixed | ||
22 | -- cost and is system specific) | ||
23 | -- | ||
24 | local KNOWN= { [0]=0, 1,1,2,3,5,8,13,21,34,55,89,144 } | ||
25 | |||
26 | -- | ||
27 | -- uint= fib( n_uint ) | ||
28 | -- | ||
29 | local function fib( n ) | ||
30 | -- | ||
31 | local sum | ||
32 | local floor= assert(math.floor) | ||
33 | |||
34 | WR( "fib("..n..")" ) | ||
35 | |||
36 | if n <= #KNOWN then | ||
37 | sum= KNOWN[n] | ||
38 | else | ||
39 | -- Splits into two; this task remains waiting for the results | ||
40 | -- | ||
41 | local gen_f= lanes.gen( "io,math,debug", fib ) | ||
42 | |||
43 | local n1=floor(n/2) +1 | ||
44 | local n2=floor(n/2) -1 + n%2 | ||
45 | |||
46 | WR( "splitting "..n.." -> "..n1.." "..n2 ) | ||
47 | |||
48 | local a= gen_f( n1 ) | ||
49 | local b= gen_f( n2 ) | ||
50 | |||
51 | -- children running... | ||
52 | |||
53 | local a2= a[1]^2 | ||
54 | local b2= b[1]^2 | ||
55 | |||
56 | sum = (n%2==1) and a2+b2 or a2-b2 | ||
57 | end | ||
58 | |||
59 | io.stderr:write( "fib("..n..") = "..sum.."\n" ) | ||
60 | |||
61 | return sum | ||
62 | end | ||
63 | |||
64 | -- | ||
65 | -- Right answers from: <http://sonic.net/~douglasi/fibo.htm> | ||
66 | -- | ||
67 | local right= { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, 1836311903, 2971215073, 4807526976, 7778742049, 12586269025, 20365011074, 32951280099, 53316291173, 86267571272, 139583862445, 225851433717, 365435296162, 591286729879, 956722026041, 1548008755920, 2504730781961, 4052739537881, 6557470319842, 10610209857723, 17167680177565, 27777890035288, 44945570212853, 72723460248141, 117669030460994, 190392490709135, 308061521170129, 498454011879264, 806515533049393, 1304969544928657, 2111485077978050, 3416454622906707, 5527939700884757, 8944394323791464, 14472334024676220, 23416728348467684, 37889062373143900, 61305790721611580, 99194853094755490, 160500643816367070, 259695496911122560, 420196140727489660, 679891637638612200, 1100087778366101900, 1779979416004714000, 2880067194370816000, 4660046610375530000, 7540113804746346000, 12200160415121877000, 19740274219868226000, 31940434634990105000, 51680708854858334000, 83621143489848440000, 135301852344706780000, 218922995834555200000 | ||
68 | } | ||
69 | assert( #right==99 ) | ||
70 | |||
71 | local N= 80 | ||
72 | local res= fib(N) | ||
73 | print( right[N] ) | ||
74 | assert( res==right[N] ) | ||
75 | |||
diff --git a/tests/fifo.lua b/tests/fifo.lua new file mode 100644 index 0000000..898b04d --- /dev/null +++ b/tests/fifo.lua | |||
@@ -0,0 +1,43 @@ | |||
1 | -- | ||
2 | -- FIFO.LUA | ||
3 | -- | ||
4 | -- Sample program for Lua Lanes | ||
5 | -- | ||
6 | |||
7 | require "lanes" | ||
8 | |||
9 | local linda= lanes.linda() | ||
10 | local atomic_inc= lanes.genatomic( linda, "FIFO_n" ) | ||
11 | |||
12 | assert( atomic_inc()==1 ) | ||
13 | assert( atomic_inc()==2 ) | ||
14 | |||
15 | local function FIFO() | ||
16 | local my_channel= "FIFO"..atomic_inc() | ||
17 | |||
18 | return { | ||
19 | -- Giving explicit 'nil' timeout allows numbers to be used as 'my_channel' | ||
20 | -- | ||
21 | send= function(...) linda:send( nil, my_channel, ... ) end, | ||
22 | receive= function(timeout) linda:receive( timeout, my_channel ) end | ||
23 | } | ||
24 | end | ||
25 | |||
26 | local A= FIFO() | ||
27 | local B= FIFO() | ||
28 | |||
29 | print "Sending to A.." | ||
30 | A:send( 1,2,3,4,5 ) | ||
31 | |||
32 | print "Sending to B.." | ||
33 | B:send( 'a','b','c' ) | ||
34 | |||
35 | print "Reading A.." | ||
36 | print( A:receive( 1.0 ) ) | ||
37 | |||
38 | print "Reading B.." | ||
39 | print( B:receive( 2.0 ) ) | ||
40 | |||
41 | -- Note: A and B can be passed between threads, or used as upvalues | ||
42 | -- by multiple threads (other parts will be copied but the 'linda' | ||
43 | -- handle is shared userdata and will thus point to the single place) | ||
diff --git a/tests/finalizer.lua b/tests/finalizer.lua new file mode 100644 index 0000000..c94b36d --- /dev/null +++ b/tests/finalizer.lua | |||
@@ -0,0 +1,81 @@ | |||
1 | -- | ||
2 | -- Test resource cleanup | ||
3 | -- | ||
4 | -- This feature was ... by discussion on the Lua list about exceptions. | ||
5 | -- The idea is to always run a certain block at exit, whether due to success | ||
6 | -- or error. Normally, 'pcall' would be used for such but as Lua already | ||
7 | -- does that, simply giving a 'cleanup=function' parameter is a logical | ||
8 | -- thing to do. -- AKa 22-Jan-2009 | ||
9 | -- | ||
10 | |||
11 | require "lanes" | ||
12 | |||
13 | local FN= "finalizer-test.tmp" | ||
14 | |||
15 | local cleanup | ||
16 | |||
17 | local which= os.time() % 2 -- 0/1 | ||
18 | |||
19 | local function lane() | ||
20 | |||
21 | set_finalizer(cleanup) | ||
22 | |||
23 | local f,err= io.open(FN,"w") | ||
24 | if not f then | ||
25 | error( "Could not create "..FN..": "..err ) | ||
26 | end | ||
27 | |||
28 | f:write( "Test file that should get removed." ) | ||
29 | |||
30 | io.stderr:write( "File "..FN.." created\n" ) | ||
31 | |||
32 | if which==0 then | ||
33 | error("aa") -- exception here; the value needs NOT be a string | ||
34 | end | ||
35 | |||
36 | -- no exception | ||
37 | end | ||
38 | |||
39 | -- | ||
40 | -- This is called at the end of the lane; whether succesful or not. | ||
41 | -- Gets the 'error()' parameter as parameter ('nil' if normal return). | ||
42 | -- | ||
43 | cleanup= function(err) | ||
44 | |||
45 | -- An error in finalizer will override an error (or success) in the main | ||
46 | -- chunk. | ||
47 | -- | ||
48 | --error( "This is important!" ) | ||
49 | |||
50 | if err then | ||
51 | io.stderr:write( "Cleanup after error: "..tostring(err).."\n" ) | ||
52 | else | ||
53 | io.stderr:write( "Cleanup after normal return\n" ) | ||
54 | end | ||
55 | |||
56 | local _,err2= os.remove(FN) | ||
57 | assert(not err2) -- if this fails, it will be shown in the calling script | ||
58 | -- as an error from the lane itself | ||
59 | |||
60 | io.stderr:write( "Removed file "..FN.."\n" ) | ||
61 | end | ||
62 | |||
63 | local lgen = lanes.gen("*", lane) | ||
64 | |||
65 | io.stderr:write "Launching the lane!\n" | ||
66 | |||
67 | local h= lgen() | ||
68 | |||
69 | local _,err,stack= h:join() -- wait for the lane (no automatic error propagation) | ||
70 | if err then | ||
71 | assert(stack) | ||
72 | io.stderr:write( "Lane error: "..tostring(err).."\n" ) | ||
73 | io.stderr:write( "\t", table.concat(stack,"\t\n"), "\n" ) | ||
74 | end | ||
75 | |||
76 | local f= io.open(FN,"r") | ||
77 | if f then | ||
78 | error( "CLEANUP DID NOT WORK: "..FN.." still exists!" ) | ||
79 | end | ||
80 | |||
81 | io.stderr:write "Finished!\n" | ||
diff --git a/tests/hangtest.lua b/tests/hangtest.lua new file mode 100644 index 0000000..d0bbea4 --- /dev/null +++ b/tests/hangtest.lua | |||
@@ -0,0 +1,26 @@ | |||
1 | -- | ||
2 | -- Test case for hang on [1]s and :join()s. | ||
3 | -- | ||
4 | |||
5 | require "lanes" | ||
6 | |||
7 | local function ret(b) | ||
8 | return b | ||
9 | end | ||
10 | local lgen = lanes.gen("*", {}, ret) | ||
11 | |||
12 | for i=1,10000 do | ||
13 | local ln = lgen(i) | ||
14 | |||
15 | print( "getting result for "..i ) | ||
16 | |||
17 | -- Hangs here forever every few hundred runs or so, | ||
18 | -- can be illustrated by putting another print() statement | ||
19 | -- after, which will never be called. | ||
20 | -- | ||
21 | local result = ln[1]; | ||
22 | |||
23 | assert (result == i); | ||
24 | end | ||
25 | |||
26 | print "Finished!" | ||
diff --git a/tests/irayo_closure.lua b/tests/irayo_closure.lua new file mode 100644 index 0000000..faf08fd --- /dev/null +++ b/tests/irayo_closure.lua | |||
@@ -0,0 +1,35 @@ | |||
1 | -- | ||
2 | -- Bugs filed by irayo Jul-2008 | ||
3 | -- | ||
4 | --[[ | ||
5 | "Another issue I've noticed is trying to pass a table with a function | ||
6 | that uses closures in it as a global variable into a new lane. This | ||
7 | causes a segmentation fault and it appears to be related to the | ||
8 | luaG_inter_move function near line 835-836 or so in lanes.c, but I | ||
9 | haven't investigated further. | ||
10 | e.g. { globals = { data = 1, func = function() useclosurehere() end } }" | ||
11 | ]] | ||
12 | |||
13 | require "lanes" | ||
14 | |||
15 | local function testrun() | ||
16 | assert( print ) | ||
17 | assert( data==1 ) | ||
18 | assert( type(func)=="function" ) | ||
19 | func() -- "using the closure" | ||
20 | return true | ||
21 | end | ||
22 | |||
23 | -- Should also work without these lines, but currently doesn't (bug in Lanes, | ||
24 | -- a function thrown over isn't connected to receiving lane's globals) | ||
25 | -- | ||
26 | --local print=print | ||
27 | --local assert=assert | ||
28 | |||
29 | local function useclosurehere() | ||
30 | assert( print ) | ||
31 | print "using the closure" | ||
32 | end | ||
33 | |||
34 | local lane= lanes.gen( "", { globals = { data=1, func=useclosurehere } }, testrun )() | ||
35 | print(lane[1]) | ||
diff --git a/tests/irayo_recursive.lua b/tests/irayo_recursive.lua new file mode 100644 index 0000000..82e5a54 --- /dev/null +++ b/tests/irayo_recursive.lua | |||
@@ -0,0 +1,18 @@ | |||
1 | -- | ||
2 | -- Bugs filed by irayo Jul-2008 | ||
3 | -- | ||
4 | --[[ | ||
5 | This code showed lack of caching 'select', 'type' etc. in 'src/lanes.lua'. | ||
6 | ]] | ||
7 | local function recurse() | ||
8 | print("level "..i); | ||
9 | if i > 10 then return "finished" end | ||
10 | |||
11 | require "lanes" | ||
12 | |||
13 | local lane = lanes.gen( "*", { globals = { ["i"]= i + 1 } }, recurse ) () | ||
14 | return lane[1] | ||
15 | end | ||
16 | |||
17 | i = 0; | ||
18 | recurse() | ||
diff --git a/tests/keeper.lua b/tests/keeper.lua new file mode 100644 index 0000000..5c8c23a --- /dev/null +++ b/tests/keeper.lua | |||
@@ -0,0 +1,47 @@ | |||
1 | -- | ||
2 | -- KEEPER.LUA | ||
3 | -- | ||
4 | -- Test program for Lua Lanes | ||
5 | -- | ||
6 | |||
7 | require "lanes" | ||
8 | |||
9 | local function keeper(linda) | ||
10 | local mt= { | ||
11 | __index= function( _, key ) | ||
12 | return linda:get( key ) | ||
13 | end, | ||
14 | __newindex= function( _, key, val ) | ||
15 | linda:set( key, val ) | ||
16 | end | ||
17 | } | ||
18 | return setmetatable( {}, mt ) | ||
19 | end | ||
20 | |||
21 | -- | ||
22 | local lindaA= lanes.linda() | ||
23 | local A= keeper( lindaA ) | ||
24 | |||
25 | local lindaB= lanes.linda() | ||
26 | local B= keeper( lindaB ) | ||
27 | |||
28 | A.some= 1 | ||
29 | print( A.some ) | ||
30 | assert( A.some==1 ) | ||
31 | |||
32 | B.some= "hoo" | ||
33 | assert( B.some=="hoo" ) | ||
34 | assert( A.some==1 ) | ||
35 | |||
36 | function lane() | ||
37 | local a= keeper(lindaA) | ||
38 | print( a.some ) | ||
39 | assert( a.some==1 ) | ||
40 | a.some= 2 | ||
41 | end | ||
42 | |||
43 | local h= lanes.gen( "io", lane )() | ||
44 | h:join() | ||
45 | |||
46 | print( A.some ) -- 2 | ||
47 | assert( A.some==2 ) | ||
diff --git a/tests/launchtest.lua b/tests/launchtest.lua new file mode 100644 index 0000000..5e3037f --- /dev/null +++ b/tests/launchtest.lua | |||
@@ -0,0 +1,78 @@ | |||
1 | -- | ||
2 | -- LAUNCHTEST.LUA Copyright (c) 2007-08, Asko Kauppi <akauppi@gmail.com> | ||
3 | -- | ||
4 | -- Tests launching speed of N threads | ||
5 | -- | ||
6 | -- Usage: | ||
7 | -- [time]Â lua -lstrict launchtest.lua [threads] [-libs[=io,os,math,...]] | ||
8 | -- | ||
9 | -- threads: number of threads to launch (like: 2000) :) | ||
10 | -- libs: combination of "os","io","math","package", ... | ||
11 | -- just "-libs" for all libraries | ||
12 | -- | ||
13 | -- Note: | ||
14 | -- One _can_ reach the system threading level, ie. doing 10000 on | ||
15 | -- PowerBook G4: | ||
16 | -- << | ||
17 | -- pthread_create( ref, &a, lane_main, data ) failed @ line 316: 35 | ||
18 | -- Command terminated abnormally. | ||
19 | -- << | ||
20 | -- | ||
21 | -- (Lua Lanes _can_ be made tolerable to such congestion cases. Just | ||
22 | -- currently, it is not. btw, 5000 seems to run okay - system limit | ||
23 | -- being 2040 simultaneous threads) | ||
24 | -- | ||
25 | -- To do: | ||
26 | -- - ... | ||
27 | -- | ||
28 | |||
29 | local N= 1000 -- threads/loops to use | ||
30 | local M= 1000 -- sieves from 1..M | ||
31 | local LIBS= nil -- default: load no libraries | ||
32 | |||
33 | local function HELP() | ||
34 | io.stderr:write( "Usage: lua launchtest.lua [threads] [-libs[=io,os,math,...]]\n" ) | ||
35 | exit(1) | ||
36 | end | ||
37 | |||
38 | local m= require "argtable" | ||
39 | local argtable= assert(m.argtable) | ||
40 | |||
41 | for k,v in pairs( argtable(...) ) do | ||
42 | if k==1 then N= tonumber(v) or HELP() | ||
43 | elseif k=="libs" then LIBS= (v==true) and "*" or v | ||
44 | else HELP() | ||
45 | end | ||
46 | end | ||
47 | |||
48 | require "lanes" | ||
49 | |||
50 | local g= lanes.gen( LIBS, function(i) | ||
51 | --io.stderr:write( i.."\t" ) | ||
52 | return i | ||
53 | end ) | ||
54 | |||
55 | local t= {} | ||
56 | |||
57 | for i=1,N do | ||
58 | t[i]= g(i) | ||
59 | end | ||
60 | |||
61 | if false then | ||
62 | -- just finish here, without waiting for threads to finish | ||
63 | -- | ||
64 | local st= t[N].status | ||
65 | print(st) -- if that is "done", they flew already! :) | ||
66 | else | ||
67 | -- mark that all have been launched, now wait for them to return | ||
68 | -- | ||
69 | io.stderr:write( N.." lanes launched.\n" ) | ||
70 | |||
71 | for i=1,N do | ||
72 | local rc= t[i]:join() | ||
73 | assert( rc==i ) | ||
74 | end | ||
75 | |||
76 | io.stderr:write( N.." lanes finished.\n" ) | ||
77 | end | ||
78 | |||
diff --git a/tests/objects.lua b/tests/objects.lua new file mode 100644 index 0000000..8f56a5f --- /dev/null +++ b/tests/objects.lua | |||
@@ -0,0 +1,76 @@ | |||
1 | -- | ||
2 | -- OBJECTS.LUA | ||
3 | -- | ||
4 | -- Tests that objects (metatables) can be passed between lanes. | ||
5 | -- | ||
6 | |||
7 | require "lanes" | ||
8 | |||
9 | local linda= lanes.linda() | ||
10 | |||
11 | local start_lane= lanes.gen( "io", | ||
12 | function( obj1 ) | ||
13 | |||
14 | assert( obj1.v ) | ||
15 | assert( obj1.print ) | ||
16 | |||
17 | assert( obj1 ) | ||
18 | local mt1= getmetatable(obj1) | ||
19 | assert(mt1) | ||
20 | |||
21 | local obj2= linda:receive("") | ||
22 | assert( obj2 ) | ||
23 | local mt2= getmetatable(obj2) | ||
24 | assert(mt2) | ||
25 | assert( mt1==mt2 ) | ||
26 | |||
27 | local v= obj1:print() | ||
28 | assert( v=="aaa" ) | ||
29 | |||
30 | v= obj2:print() | ||
31 | assert( v=="bbb" ) | ||
32 | |||
33 | return true | ||
34 | end | ||
35 | ) | ||
36 | |||
37 | |||
38 | local WR= function(str) | ||
39 | io.stderr:write( tostring(str).."\n") | ||
40 | end | ||
41 | |||
42 | |||
43 | -- Lanes identifies metatables and copies them only once per each lane. | ||
44 | -- | ||
45 | -- Having methods in the metatable will make passing objects lighter than | ||
46 | -- having the methods 'fixed' in the object tables themselves. | ||
47 | -- | ||
48 | local o_mt= { | ||
49 | __index= function( me, key ) | ||
50 | if key=="print" then | ||
51 | return function() WR(me.v); return me.v end | ||
52 | end | ||
53 | end | ||
54 | } | ||
55 | |||
56 | local function obj_gen(v) | ||
57 | local o= { v=v } | ||
58 | setmetatable( o, o_mt ) | ||
59 | return o | ||
60 | end | ||
61 | |||
62 | local a= obj_gen("aaa") | ||
63 | local b= obj_gen("bbb") | ||
64 | |||
65 | assert( a and b ) | ||
66 | |||
67 | local mt_a= getmetatable(a) | ||
68 | local mt_b= getmetatable(b) | ||
69 | assert( mt_a and mt_a==mt_b ) | ||
70 | |||
71 | local h= start_lane(a) -- 1st object as parameter | ||
72 | |||
73 | linda:send( "", b ) -- 2nd object via Linda | ||
74 | |||
75 | assert( h[1]==true ) -- wait for return | ||
76 | |||
diff --git a/tests/perftest.lua b/tests/perftest.lua new file mode 100644 index 0000000..8ce1b3c --- /dev/null +++ b/tests/perftest.lua | |||
@@ -0,0 +1,184 @@ | |||
1 | -- | ||
2 | -- PERFTEST.LUA Copyright (c) 2007-08, Asko Kauppi <akauppi@gmail.com> | ||
3 | -- | ||
4 | -- Performance comparison of multithreaded Lua (= how much ballast does using | ||
5 | -- Lua Lanes introduce) | ||
6 | -- | ||
7 | -- Usage: | ||
8 | -- [time] lua -lstrict perftest.lua [threads] [-plain|-single[=2..n]] [-time] [-prio[=-2..+2[,-2..+2]]] | ||
9 | -- | ||
10 | -- threads: number of threads to launch (loops in 'plain' mode) | ||
11 | -- -plain: runs in nonthreaded mode, to get a comparison baseline | ||
12 | -- -single: runs using just a single CPU core (or 'n' cores if given) | ||
13 | -- -prio: sets odd numbered threads to higher/lower priority | ||
14 | -- | ||
15 | -- History: | ||
16 | -- AKa 20-Jul-08: updated to Lanes 2008 | ||
17 | -- AK 14-Apr-07: works on Win32 | ||
18 | -- | ||
19 | -- To do: | ||
20 | -- (none?) | ||
21 | -- | ||
22 | |||
23 | -- On MSYS, stderr is buffered. In this test it matters. | ||
24 | -- Seems, even with this MSYS wants to buffer linewise, needing '\n' | ||
25 | -- before actual output. | ||
26 | -- | ||
27 | local MSYS= os.getenv("OSTYPE")=="msys" | ||
28 | |||
29 | |||
30 | require "lanes" | ||
31 | |||
32 | local m= require "argtable" | ||
33 | local argtable= assert( m.argtable ) | ||
34 | |||
35 | local N= 1000 -- threads/loops to use | ||
36 | local M= 1000 -- sieves from 1..M | ||
37 | local PLAIN= false -- single threaded (true) or using Lanes (false) | ||
38 | local SINGLE= false -- cores to use (false / 1..n) | ||
39 | local TIME= false -- use Lua for the timing | ||
40 | local PRIO_ODD, PRIO_EVEN -- -3..+3 | ||
41 | |||
42 | local function HELP() | ||
43 | io.stderr:write( "Usage: lua perftest.lua [threads]\n" ) | ||
44 | end | ||
45 | |||
46 | -- nil -> +2 | ||
47 | -- odd_prio[,even_prio] | ||
48 | -- | ||
49 | local function prio_param(v) | ||
50 | if v==true then return 2,-2 end | ||
51 | |||
52 | local a,b= string.match( v, "^([%+%-]?%d+)%,([%+%-]?%d+)$" ) | ||
53 | if a then | ||
54 | return tonumber(a), tonumber(b) | ||
55 | elseif tonumber(v) then | ||
56 | return tonumber(v) | ||
57 | else | ||
58 | error( "Bad priority: "..v ) | ||
59 | end | ||
60 | end | ||
61 | |||
62 | for k,v in pairs( argtable(...) ) do | ||
63 | if k==1 then N= tonumber(v) or HELP() | ||
64 | elseif k=="plain" then PLAIN= true | ||
65 | elseif k=="single" then SINGLE= v -- true/number | ||
66 | elseif k=="time" then TIME= true | ||
67 | elseif k=="prio" then PRIO_ODD, PRIO_EVEN= prio_param(v) | ||
68 | else HELP() | ||
69 | end | ||
70 | end | ||
71 | |||
72 | PRIO_ODD= PRIO_ODD or 0 | ||
73 | PRIO_EVEN= PRIO_EVEN or 0 | ||
74 | |||
75 | |||
76 | -- SAMPLE ADOPTED FROM Lua 5.1.1 test/sieve.lua -- | ||
77 | |||
78 | -- the sieve of of Eratosthenes programmed with coroutines | ||
79 | -- typical usage: lua -e N=1000 sieve.lua | column | ||
80 | |||
81 | -- AK: Wrapped within a surrounding function, so we can pass it to Lanes | ||
82 | -- Note that coroutines can perfectly fine be used within each Lane. :) | ||
83 | -- | ||
84 | -- AKa 20-Jul-2008: Now the wrapping to one function is no longer needed; | ||
85 | -- Lanes 2008 can take the used functions as upvalues. | ||
86 | -- | ||
87 | local function sieve_lane(N,id) | ||
88 | |||
89 | if MSYS then | ||
90 | io.stderr:setvbuf "no" | ||
91 | end | ||
92 | |||
93 | -- generate all the numbers from 2 to n | ||
94 | local function gen (n) | ||
95 | return coroutine.wrap(function () | ||
96 | for i=2,n do coroutine.yield(i) end | ||
97 | end) | ||
98 | end | ||
99 | |||
100 | -- filter the numbers generated by `g', removing multiples of `p' | ||
101 | local function filter (p, g) | ||
102 | return coroutine.wrap(function () | ||
103 | while 1 do | ||
104 | local n = g() | ||
105 | if n == nil then return end | ||
106 | if math.mod(n, p) ~= 0 then coroutine.yield(n) end | ||
107 | end | ||
108 | end) | ||
109 | end | ||
110 | |||
111 | local ret= {} -- returned values: { 2, 3, 5, 7, 11, ... } | ||
112 | N=N or 1000 -- from caller | ||
113 | local x = gen(N) -- generate primes up to N | ||
114 | while 1 do | ||
115 | local n = x() -- pick a number until done | ||
116 | if n == nil then break end | ||
117 | --print(n) -- must be a prime number | ||
118 | table.insert( ret, n ) | ||
119 | |||
120 | x = filter(n, x) -- now remove its multiples | ||
121 | end | ||
122 | |||
123 | io.stderr:write(id..(MSYS and "\n" or "\t")) -- mark we're ready | ||
124 | |||
125 | return ret | ||
126 | end | ||
127 | -- ** END OF LANE ** -- | ||
128 | |||
129 | |||
130 | -- Keep preparation code outside of the performance test | ||
131 | -- | ||
132 | local f_even= lanes.gen( "base,coroutine,math,table,io", -- "*" = all | ||
133 | { priority= PRIO_EVEN }, sieve_lane ) | ||
134 | |||
135 | local f_odd= lanes.gen( "base,coroutine,math,table,io", -- "*" = all | ||
136 | { priority= PRIO_ODD }, sieve_lane ) | ||
137 | |||
138 | io.stderr:write( "*** Counting primes 1.."..M.." "..N.." times ***\n\n" ) | ||
139 | |||
140 | local t0= TIME and os.time() | ||
141 | |||
142 | if PLAIN then | ||
143 | io.stderr:write( "Plain (no multithreading):\n" ) | ||
144 | |||
145 | for i=1,N do | ||
146 | local tmp= sieve_lane(M,i) | ||
147 | assert( type(tmp)=="table" and tmp[1]==2 and tmp[168]==997 ) | ||
148 | end | ||
149 | else | ||
150 | if SINGLE then | ||
151 | io.stderr:write( (tonumber(SINGLE) and SINGLE or 1) .. " core(s):\n" ) | ||
152 | lanes.single(SINGLE) -- limit to N cores (just OS X) | ||
153 | else | ||
154 | io.stderr:write( "Multi core:\n" ) | ||
155 | end | ||
156 | |||
157 | if PRIO_ODD ~= PRIO_EVEN then | ||
158 | io.stderr:write( ( PRIO_ODD > PRIO_EVEN and "ODD" or "EVEN" ).. | ||
159 | " LANES should come first (odd:"..PRIO_ODD..", even:"..PRIO_EVEN..")\n\n" ) | ||
160 | else | ||
161 | io.stderr:write( "EVEN AND ODD lanes should be mingled (both: "..PRIO_ODD..")\n\n" ) | ||
162 | end | ||
163 | local t= {} | ||
164 | for i=1,N do | ||
165 | t[i]= ((i%2==0) and f_even or f_odd) (M,i) | ||
166 | end | ||
167 | |||
168 | -- Make sure all lanes finished | ||
169 | -- | ||
170 | for i=1,N do | ||
171 | local tmp= t[i]:join() | ||
172 | assert( type(tmp)=="table" and tmp[1]==2 and tmp[168]==997 ) | ||
173 | end | ||
174 | end | ||
175 | |||
176 | io.stderr:write "\n" | ||
177 | |||
178 | if TIME then | ||
179 | local t= os.time() - t0 | ||
180 | io.stderr:write( "*** TIMING: "..t.." seconds ***\n" ) | ||
181 | end | ||
182 | |||
183 | -- | ||
184 | -- end | ||
diff --git a/tests/recursive.lua b/tests/recursive.lua new file mode 100644 index 0000000..49c03d3 --- /dev/null +++ b/tests/recursive.lua | |||
@@ -0,0 +1,21 @@ | |||
1 | -- | ||
2 | -- RECURSIVE.LUA | ||
3 | -- | ||
4 | -- Test program for Lua Lanes | ||
5 | -- | ||
6 | |||
7 | io.stderr:write( "depth:" ) | ||
8 | local function func( depth ) | ||
9 | io.stderr:write(" " .. depth) | ||
10 | if depth > 10 then | ||
11 | return "done!" | ||
12 | end | ||
13 | |||
14 | require "lanes" | ||
15 | local lane= lanes.gen("*", func)( depth+1 ) | ||
16 | return lane[1] | ||
17 | end | ||
18 | |||
19 | local v= func(0) | ||
20 | assert(v=="done!") | ||
21 | io.stderr:write("\n") | ||
diff --git a/tests/require.lua b/tests/require.lua new file mode 100644 index 0000000..2cfe780 --- /dev/null +++ b/tests/require.lua | |||
@@ -0,0 +1,30 @@ | |||
1 | -- | ||
2 | -- REQUIRE.LUA | ||
3 | -- | ||
4 | -- Test that 'require' works from sublanes | ||
5 | -- | ||
6 | require 'lanes' | ||
7 | |||
8 | local function a_lane() | ||
9 | -- To require 'math' we still actually need to have it initialized for | ||
10 | -- the lane. | ||
11 | -- | ||
12 | require "math" | ||
13 | assert( math and math.sqrt ) | ||
14 | assert( math.sqrt(4)==2 ) | ||
15 | |||
16 | assert( lanes==nil ) | ||
17 | require "lanes" | ||
18 | assert( lanes and lanes.gen ) | ||
19 | |||
20 | local h= lanes.gen( function() return 42 end ) () | ||
21 | local v= h[1] | ||
22 | |||
23 | return v==42 | ||
24 | end | ||
25 | |||
26 | local gen= lanes.gen( "math,package,string,table", a_lane ) | ||
27 | |||
28 | local h= gen() | ||
29 | local ret= h[1] | ||
30 | assert( ret==true ) | ||
diff --git a/tests/timer.lua b/tests/timer.lua new file mode 100644 index 0000000..e95f326 --- /dev/null +++ b/tests/timer.lua | |||
@@ -0,0 +1,93 @@ | |||
1 | -- | ||
2 | -- TIMER.LUA | ||
3 | -- | ||
4 | -- Sample program for Lua Lanes | ||
5 | -- | ||
6 | |||
7 | -- On MSYS, stderr is buffered. In this test it matters. | ||
8 | io.stderr:setvbuf "no" | ||
9 | |||
10 | |||
11 | require "lanes" | ||
12 | |||
13 | local linda= lanes.linda() | ||
14 | |||
15 | local function PRINT(str) | ||
16 | io.stderr:write(str.."\n") | ||
17 | end | ||
18 | |||
19 | local T1= "1s" -- these keys can be anything... | ||
20 | local T2= "5s" | ||
21 | |||
22 | local step= {} | ||
23 | |||
24 | lanes.timer( linda, T1, 1.0, 1.0 ) | ||
25 | step[T1]= 1.0 | ||
26 | |||
27 | PRINT( "\n*** Timers every second (not synced to wall clock) ***\n" ) | ||
28 | |||
29 | local v_first | ||
30 | local v_last= {} -- { [channel]= num } | ||
31 | local T2_first_round= true | ||
32 | |||
33 | local caught= {} -- { [T1]= bool, [T2]= bool } | ||
34 | |||
35 | while true do | ||
36 | io.stderr:write("waiting...\t") | ||
37 | local v,channel= linda:receive( 6.0, T1,T2 ) | ||
38 | assert( channel==T1 or channel==T2 ) | ||
39 | caught[channel]= true | ||
40 | |||
41 | io.stderr:write( ((channel==T1) and "" or "\t\t").. string.format("%.3f",v),"\n" ) | ||
42 | assert( type(v)=="number" ) | ||
43 | |||
44 | if v_last[channel] then | ||
45 | if channel==T2 and T2_first_round then | ||
46 | -- do not make measurements, first round is not 5secs due to wall clock adjustment | ||
47 | T2_first_round= false | ||
48 | else | ||
49 | assert( math.abs(v-v_last[channel]- step[channel]) < 0.02 ) | ||
50 | end | ||
51 | end | ||
52 | |||
53 | if not v_first then | ||
54 | v_first= v | ||
55 | elseif v-v_first > 3.0 and (not step[T2]) then | ||
56 | PRINT( "\n*** Adding timers every 5 second (synced to wall clock) ***\n" ) | ||
57 | |||
58 | -- The first event can be in the past (just cut seconds down to 5s) | ||
59 | -- | ||
60 | local date= os.date("*t") | ||
61 | date.sec = date.sec - date.sec%5 | ||
62 | |||
63 | lanes.timer( linda, T2, date, 5.0 ) | ||
64 | step[T2]= 5.0 | ||
65 | |||
66 | elseif v-v_first > 10 then -- exit condition | ||
67 | break | ||
68 | end | ||
69 | v_last[channel]= v | ||
70 | end | ||
71 | |||
72 | -- Windows version had a bug where T2 timers were not coming through, at all. | ||
73 | -- AKa 24-Jan-2009 | ||
74 | -- | ||
75 | assert( caught[T1] ) | ||
76 | assert( caught[T2] ) | ||
77 | |||
78 | PRINT( "\n*** Clearing timers ***\n" ) | ||
79 | |||
80 | lanes.timer( linda, T1, 0 ) -- reset; no reoccuring ticks | ||
81 | lanes.timer( linda, T2, 0 ) | ||
82 | |||
83 | linda:receive( 0, T1 ) -- clear out; there could be one tick left | ||
84 | linda:receive( 0, T2 ) | ||
85 | |||
86 | assert( linda:get(T1) == nil ) | ||
87 | assert( linda:get(T2) == nil ) | ||
88 | |||
89 | PRINT "...making sure no ticks are coming..." | ||
90 | |||
91 | local v= linda:receive( 1.5, T1,T2 ) -- should not get any | ||
92 | assert(v==nil) | ||
93 | |||
diff --git a/tools/bin2c.lua b/tools/bin2c.lua new file mode 100644 index 0000000..352d18e --- /dev/null +++ b/tools/bin2c.lua | |||
@@ -0,0 +1,131 @@ | |||
1 | -- | ||
2 | -- BIN2C.LUA [filename] [-o output.lch] | ||
3 | -- | ||
4 | -- Convert files to byte arrays for automatic loading with 'lua_dobuffer'. | ||
5 | -- | ||
6 | -- Based on 'etc/bin2c.c' of Lua 5.0.1 sources by: | ||
7 | -- Luiz Henrique de Figueiredo (lhf@tecgraf.puc-rio.br) | ||
8 | -- | ||
9 | -- Changes: | ||
10 | -- | ||
11 | -- 12-Dec-07/AKa: changed the output to have just the "{ ... }" part; others | ||
12 | -- (variable name) can be explicitly given before the '#include' | ||
13 | -- 16-Nov-07/AKa: taken into luaSub | ||
14 | -- 16-Mar-04/AKa: added 'glua_wrap()' support | ||
15 | -- xx-Jan-04/AKa: subdirectory names are not included in debug info | ||
16 | -- | ||
17 | |||
18 | local function USAGE() | ||
19 | io.stderr:write "lua bin2c.lua [filename] [-o output.lch]" | ||
20 | os.exit(-1) | ||
21 | end | ||
22 | |||
23 | local out_f -- file to output to (stdout if nil) | ||
24 | |||
25 | local function OUT( ... ) | ||
26 | (out_f or io.stdout): write( ... ); -- ; actually needed by Lua | ||
27 | (out_f or io.stdout): write "\n" | ||
28 | end | ||
29 | |||
30 | local HEAD= "{ " | ||
31 | local START= ' ' | ||
32 | local FILL= '%3d,' | ||
33 | local STOP= "" | ||
34 | local TAIL= "};\n" | ||
35 | |||
36 | -- | ||
37 | local function dump( f ) | ||
38 | -- | ||
39 | OUT [[ | ||
40 | /* bin2c.lua generated code -- DO NOT EDIT | ||
41 | * | ||
42 | * To use from C source: | ||
43 | * char my_chunk[]= | ||
44 | * #include "my.lch" | ||
45 | */]] | ||
46 | |||
47 | local str= HEAD..'\n'..START | ||
48 | local len= 0 | ||
49 | |||
50 | while true do | ||
51 | for n=1,20 do | ||
52 | local c= f:read(1) | ||
53 | if c then | ||
54 | str= str..string.format( FILL, string.byte(c) ) | ||
55 | len= len+1 | ||
56 | else | ||
57 | OUT( str..STOP.. string.format( TAIL, len ) ) | ||
58 | return -- the end | ||
59 | end | ||
60 | end | ||
61 | OUT(str..STOP) | ||
62 | str= START | ||
63 | end | ||
64 | end | ||
65 | |||
66 | -- | ||
67 | local function fdump( fn ) | ||
68 | -- | ||
69 | local f= io.open( fn, "rb" ) -- must open as binary | ||
70 | |||
71 | if not f then | ||
72 | error( "bin2c: cannot open "..fn ) | ||
73 | else | ||
74 | dump( f ) | ||
75 | f:close() | ||
76 | end | ||
77 | end | ||
78 | |||
79 | -- | ||
80 | local function main( argv ) | ||
81 | -- | ||
82 | local fn= argv.o | ||
83 | if fn then | ||
84 | local f,err= io.open( fn, "w" ) | ||
85 | assert( f, "Unable to write '"..fn.."': "..(err or "") ) | ||
86 | |||
87 | out_f= f | ||
88 | end | ||
89 | |||
90 | if argv[2] then | ||
91 | USAGE() | ||
92 | elseif argv[1] then | ||
93 | fdump( argv[1] ) | ||
94 | else -- use stdin (no params) | ||
95 | if os.getenv("WINDIR") then | ||
96 | error "using stdin not allowed on Win32!" -- it wouldn't be binary | ||
97 | end | ||
98 | dump(io.stdin) | ||
99 | end | ||
100 | |||
101 | if out_f then | ||
102 | out_f:close() | ||
103 | end | ||
104 | end | ||
105 | |||
106 | -- | ||
107 | local argv= {} | ||
108 | local valid_flags= { o=1 } -- lookup: 0=no value, 1=value | ||
109 | |||
110 | -- Need to use while since '-o' advances 'i' by 2 | ||
111 | -- | ||
112 | local args= select('#',...) | ||
113 | local i=1 | ||
114 | |||
115 | while i<=args do | ||
116 | local v= select(i,...) | ||
117 | local flag= string.match( v, "^%-(.+)" ) | ||
118 | |||
119 | if flag then | ||
120 | if not valid_flags[flag] then | ||
121 | error( "Unknown flag: -"..flag ) | ||
122 | end | ||
123 | argv[flag]= (i+1<=args) and select(i+1,...) or true | ||
124 | i= i+1 | ||
125 | else | ||
126 | table.insert( argv, v ) -- [1..N] | ||
127 | end | ||
128 | i= i+1 | ||
129 | end | ||
130 | |||
131 | return main(argv) | ||