├── .gitignore
├── .runsettings
├── .vscode
└── launch.json
├── ABOUT
├── BUGS
├── CHANGES
├── CMakeLists.txt
├── COPYRIGHT
├── Lanes.args.json
├── Lanes.sln
├── Lanes.vcxproj
├── Lanes.vcxproj.filters
├── Makefile
├── README
├── Shared.makefile
├── TODO
├── deep_userdata_example
├── DUE.makefile
├── deep_userdata_example.args.json
├── deep_userdata_example.cpp
├── deep_userdata_example.vcxproj
├── deep_userdata_example.vcxproj.filters
└── deeptest.lua
├── dist.info
├── docs
├── Lua multithreading choices.graffle
├── Lua multithreading choices.svg
├── comparison.html
├── index.html
├── multi.png
└── performance.ods
├── lanes-4.0.0-0.rockspec
├── make-vc.cmd
├── setup-vc.cmd
├── src
├── Lanes.makefile
├── _pch.cpp
├── _pch.hpp
├── allocator.cpp
├── allocator.hpp
├── cancel.cpp
├── cancel.hpp
├── compat.cpp
├── compat.hpp
├── debug.hpp
├── debugspew.hpp
├── deep.cpp
├── deep.hpp
├── intercopycontext.cpp
├── intercopycontext.hpp
├── keeper.cpp
├── keeper.hpp
├── lane.cpp
├── lane.hpp
├── lanes.cpp
├── lanes.hpp
├── lanes.lua
├── lanesconf.h
├── linda.cpp
├── linda.hpp
├── lindafactory.cpp
├── lindafactory.hpp
├── luaerrors.hpp
├── macros_and_utils.hpp
├── nameof.cpp
├── nameof.hpp
├── platform.h
├── stackindex.hpp
├── state.cpp
├── state.hpp
├── threading.cpp
├── threading.hpp
├── threading_osx.h
├── tools.cpp
├── tools.hpp
├── tracker.cpp
├── tracker.hpp
├── unique.hpp
├── uniquekey.hpp
├── universe.cpp
└── universe.hpp
├── tests
├── appendud.lua
├── argtable.lua
├── assert.lua
├── atexit.lua
├── atomic.lua
├── basic.lua
├── cancel.lua
├── cyclic.lua
├── deadlock.lua
├── ehynes.lua
├── errhangtest.lua
├── error.lua
├── fibonacci.lua
├── fifo.lua
├── finalizer.lua
├── func_is_string.lua
├── hangtest.lua
├── irayo_closure.lua
├── irayo_recursive.lua
├── keeper.lua
├── lanes_as_upvalue.lua
├── launchtest.lua
├── linda_perf.lua
├── manual_register.lua
├── nameof.lua
├── objects.lua
├── package.lua
├── parallel_os_calls.lua
├── perftest.lua
├── pingpong.lua
├── protect_allocator.lua
├── protectproxy.lua
├── recursive.lua
├── require.lua
├── rupval.lua
├── timer.lua
├── tobeclosed.lua
└── track_lanes.lua
└── unit_tests
├── .gitignore
├── UnitTests.makefile
├── UnitTests.vcxproj
├── UnitTests.vcxproj.filters
├── _pch.cpp
├── _pch.hpp
├── catch_amalgamated.cpp
├── catch_amalgamated.hpp
├── deep_tests.cpp
├── embedded_tests.cpp
├── init_and_shutdown.cpp
├── lane_tests.cpp
├── legacy_tests.cpp
├── linda_tests.cpp
├── scripts
├── _assert.lua
├── _utils.lua
├── coro
│ ├── basics.lua
│ └── error_handling.lua
├── lane
│ ├── cooperative_shutdown.lua
│ ├── stdlib_naming.lua
│ ├── tasking_basic.lua
│ ├── tasking_cancelling.lua
│ ├── tasking_cancelling_with_hook.lua
│ ├── tasking_comms_criss_cross.lua
│ ├── tasking_communications.lua
│ ├── tasking_error.lua
│ ├── tasking_join_test.lua
│ ├── tasking_send_receive_code.lua
│ └── uncooperative_shutdown.lua
└── linda
│ ├── multiple_keepers.lua
│ ├── send_receive.lua
│ ├── send_registered_userdata.lua
│ └── wake_period.lua
├── shared.cpp
└── shared.h
/.gitignore:
--------------------------------------------------------------------------------
1 | _Output
2 | _Tmp
3 | _LuaVersions
4 | .vs
5 | .vscode
6 | *.dll
7 | *.exe
8 | *.gch
9 | *.map
10 | *.o
11 | *.so
12 | *.user
--------------------------------------------------------------------------------
/.runsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | --list-tests --verbosity high
6 | 300000
7 |
8 |
11 |
15 |
16 |
25 | UnitTests
26 |
27 |
28 | on
29 |
30 |
31 | Combine
32 |
33 |
34 | Solution
35 | .
36 |
37 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 |
5 | {
6 | "name": "Debug MinGW-w64",
7 | "type": "cppdbg",
8 | "request": "launch",
9 | "program": "${workspaceFolder}/unit_tests/UnitTests.exe",
10 | "args": [
11 | //"--list-tests",
12 | "--rng-seed 0",
13 | "-s scripted_tests.lane.tasking_cancelling"
14 | ],
15 | "stopAtEntry": true,
16 | "cwd": "${workspaceFolder}",
17 | "environment": [
18 | {
19 | "name" : "LUA_CPATH",
20 | "value" : "./src/?.dll;./deep_userdata_example/?.dll"
21 | },
22 | {
23 | "name" : "LUA_PATH",
24 | "value" : "./src/?.lua;./tests/?.lua"
25 | }
26 | ],
27 | "externalConsole": false, // or true, depending on your preference
28 | "MIMode": "gdb",
29 | "miDebuggerPath": "C:/msys64/ucrt64/bin/gdb.exe", // Replace with your GDB path
30 | "setupCommands": [
31 | {
32 | "description": "Enable pretty-printing for gdb",
33 | "text": "-enable-pretty-printing",
34 | "ignoreFailures": true
35 | },
36 | {
37 | "description": "Show GDB commands",
38 | "text": "-interpreter-exec console \"monitor set debug 1\"",
39 | "ignoreFailures": true
40 | }
41 | ]
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/ABOUT:
--------------------------------------------------------------------------------
1 |
2 | Lua Lanes
3 | ---------
4 |
5 | Lanes is a lightweight, native, lazy evaluating multithreading library for
6 | Lua 5.1 to Lua 5.4. 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).
18 |
19 | Starting with version 3.0, Lanes is compatible with LuaJIT 2.
20 |
--------------------------------------------------------------------------------
/BUGS:
--------------------------------------------------------------------------------
1 | bugs are to be reported on github.
2 |
--------------------------------------------------------------------------------
/CHANGES:
--------------------------------------------------------------------------------
1 | CHANGES:
2 |
3 | CHANGE 2: BGe 05-Jun-25
4 | * Internal changes
5 | - Lanes is implemented in C++20: thread, condition_variable, mutex, string_view, variant, lambdas, templates, and more!
6 | - Almost all platform-specific code is gone (only a small bit for thread priority and affinity remains).
7 | - Decoda support inactive by default.
8 | - Deep userdata interface fully revamped to C++20 too.
9 | * Lanes API changes
10 | - Version is now 4.0.0
11 | - Lanes module:
12 | - shared library is now lanes_core.[so|dll] instead of lanes/core.[so|dll]
13 | - lanes.register() is also available as lanes_register() in the exported C API.
14 | - lanes.sleep() accepts a new argument "indefinitely" to block forever (until hard cancellation is received).
15 | - function set_debug_threadname() available inside a Lane is renamed lane_threadname(); can now both read and write the name.
16 | - new function lanes.finally(). Installs a function that gets called at Lanes shutdown after attempting to terminate all lanes.
17 | If some lanes still run after the finalizer, Universe::__gc with raise an error or freeze, depending on its return value.
18 | - new function lanes.collectgarbage(), to force a full GC cycle in the keeper states.
19 | - new function lanes.thread_priority_range(), to query the valid range of priorities.
20 | - Configuration settings:
21 | - Boolean parameters only accept boolean values.
22 | - allocator provider function is called with a string hint to distinguish internal allocations, lane and keeper states.
23 | - demote_full_userdata removed. Use __lanesconvert instead (see below).
24 | - keepers_gc_threshold added. Controls when GC runs inside keepers.
25 | - nb_keepers changed to nb_user_keepers. limited to 100 keepers on top of the internal keeper used by the timer Linda.
26 | - strip_functions added. Only useful for Lua 5.3+.
27 | - verbose_errors removed. Use lane error_trace_level instead.
28 | - with_timers is false by default.
29 | - Non-deep full userdata are processed during module registration just like ordinary module C functions, making them valid transferable (up)values (for example: io.stdin).
30 | - thread API errors cause a Lua error instead of aborting the program.
31 | - thread priorities can now be set using the native range of values, if desired.
32 | - Lanes:
33 | - Can no longer be "killed" by hard-stopping their thread without any resource cleanup (see lane:cancel()).
34 | - lanes.gen() settings:
35 | - stricter check of base libraries (can raise an error if it doesn't exist in the Lua flavor it's built against).
36 | - error_trace_level added. Replaces the global verbose_errors setting.
37 | - name added. Can be used to set the name early (before the lane body calls lane_threadname()).
38 | - New generator lanes.coro() to start a lane as a coroutine.
39 | - New __close metamethod that calls join().
40 | - lane:join() returns nil, error in case of problem, else returns true followed by the lane body return values.
41 | - lane:get_debug_threadname() renamed get_threadname().
42 | - cancel_test() returns "soft"/"hard" instead of true when a cancellation request is active.
43 | - Lindas:
44 | - lanes.linda()
45 | - Arguments can be provided in any order.
46 | - Accepts a callback to be invoked by __close (see below).
47 | - Providing "auto" as name when constructing a Linda cause Lanes to provide a name built from the source location of the construction.
48 | - Specifying a group to lanes.linda() is mandatory when Lanes is configured with user Keepers.
49 | - linda:deep() result no longer contains the raw C pointer of the Linda object.
50 | - new function linda:receive_batched() to replace linda:receive(linda.batched). linda.batched special value is removed.
51 | - linda :receive(), :send(), :get(), :set(), :limit() return nil, error in case of problem. Returned values in case of success change too.
52 | - linda:limit() can be used to read the value if no new limit is provided.
53 | - linda:restrict() can restrain the use of send/receive or set/get on any key.
54 | - New __close metamethod that calls any suitable handler that was provided at Linda creation.
55 | - linda:dump() outputs .limit as 'unlimited' instead of -1 for unlimited keys.
56 | - linda:wake() can wake up threads waiting for a Linda without doing any I/O on it.
57 | - linda.status reads the cancel status of the Linda.
58 | - new function linda:collectgarbage() to force collection of all stale data in the associated keeper.
59 | - Deep userdata are an acceptable key to send data into (for example, another linda).
60 | - Full userdata conversion:
61 | - __lanesconvert added.
62 | - __lanesignore removed. Use __lanesconvert instead.
63 |
64 | CHANGE 1: BGe 9-Apr-24
65 | * reset changelog, next entry will list API changes since last C-implementation.
66 |
67 | (end)
68 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
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.6)
9 |
10 |
11 | FIND_PACKAGE(Lua51 REQUIRED)
12 | INCLUDE_DIRECTORIES(${LUA_INCLUDE_DIR})
13 |
14 | if(USE_PTHREAD)
15 | ADD_DEFINITIONS(-DHAVE_WIN32_PTHREAD)
16 | endif(USE_PTHREAD)
17 | #2DO - patch threading.c to suppot cygwin.
18 | # The following values are just a guess.
19 | # WARNING: test segfault under Cygwin
20 | IF(CYGWIN)
21 | ADD_DEFINITIONS(-D_PRIO_MODE=SCHED_FIFO)
22 | ADD_DEFINITIONS(-D_PRIO_HI=15) # maximum that doesn't crash
23 | ADD_DEFINITIONS(-D_PRIO_0=0)
24 | ADD_DEFINITIONS(-D_PRIO_LO=-15) # ???
25 | ADD_DEFINITIONS(-Dpthread_yield=sched_yield)
26 | ENDIF(CYGWIN)
27 |
28 |
29 | # Build
30 | INCLUDE_DIRECTORIES(src)
31 | aux_source_directory(./src LANES_SRC)
32 | ADD_LIBRARY(lanes_core MODULE ${LANES_SRC})
33 |
34 | IF(UNIX AND NOT CYGWIN)
35 | SET(LIBS pthread)
36 | ENDIF(UNIX AND NOT CYGWIN)
37 |
38 |
39 | if(WIN32)
40 | TARGET_LINK_LIBRARIES(lanes_core ${LUA_LIBRARY} ${LIBS})
41 | else(WIN32)
42 | TARGET_LINK_LIBRARIES(lanes_core ${LIBS})
43 | endif(WIN32)
44 |
45 | SET_TARGET_PROPERTIES(lanes_core PROPERTIES PREFIX "")
46 |
47 | # Install all files and documentation
48 | set(INSTALL_LMOD share/lua/lmod CACHE PATH "Directory to install Lua modules.")
49 | set(INSTALL_CMOD share/lua/cmod CACHE PATH "Directory to install Lua binary modules.")
50 | set(INSTALL_DATA share/${PROJECT_NAME} CACHE PATH "Directory the package can store documentation, tests or other data in.")
51 | set(INSTALL_DOC ${INSTALL_DATA}/doc CACHE PATH "Recommended directory to install documentation into.")
52 | set(INSTALL_TEST ${INSTALL_DATA}/test CACHE PATH "Recommended directory to install tests into.")
53 |
54 | INSTALL (TARGETS lanes_core DESTINATION ${INSTALL_CMOD})
55 | INSTALL (FILES src/lanes.lua DESTINATION ${INSTALL_LMOD})
56 |
57 | INSTALL (FILES ABOUT BUGS COPYRIGHT CHANGES README TODO DESTINATION ${INSTALL_DATA})
58 | INSTALL (DIRECTORY docs/ DESTINATION ${INSTALL_DOC})
59 | INSTALL (DIRECTORY tests/ DESTINATION ${INSTALL_TEST})
60 |
--------------------------------------------------------------------------------
/COPYRIGHT:
--------------------------------------------------------------------------------
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-11 Asko Kauppi,
10 | Copyright (C) 2010-12 Benoit Germain,
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 | (end of COPYRIGHT)
33 |
--------------------------------------------------------------------------------
/Lanes.args.json:
--------------------------------------------------------------------------------
1 | {
2 | "FileVersion": 2,
3 | "Id": "1db7d861-eefd-49dc-a8e2-3fc2bd6ad49d",
4 | "Items": [
5 | {
6 | "Id": "6871d067-5644-4f35-8d5d-b1be97ff58c1",
7 | "Command": "ad hoc",
8 | "Items": [
9 | {
10 | "Id": "8a72a101-aa21-40b1-9a98-f76d95b46ae5",
11 | "Command": "cancel.lua hook"
12 | },
13 | {
14 | "Id": "408af77c-b41d-486d-91fb-4e690ee27124",
15 | "Command": "-e \"require'lanes'.configure{on_state_create=print}\""
16 | }
17 | ]
18 | },
19 | {
20 | "Id": "7edf5d9e-4a19-4aed-86dd-db3879040658",
21 | "Command": "UnitTests",
22 | "Items": [
23 | {
24 | "Id": "c13625a5-0ba9-421b-bebc-96fa8f031717",
25 | "Command": "Linda",
26 | "Items": [
27 | {
28 | "Id": "a1766559-b2f5-4775-a2f7-d5fb4a91c79f",
29 | "Command": "send_receive.lua"
30 | }
31 | ]
32 | }
33 | ]
34 | },
35 | {
36 | "Id": "9ed7e352-5e90-43a9-84a2-9bff816fb4c8",
37 | "Command": "Legacy",
38 | "Items": [
39 | {
40 | "Id": "574ebff8-6b20-46cd-a7a2-86b2e97c9492",
41 | "Command": "atexit.lua"
42 | },
43 | {
44 | "Id": "24d2ddcb-be70-4263-b79c-8986031034ce",
45 | "Command": "atomic.lua"
46 | },
47 | {
48 | "Id": "bc664cf1-e733-4771-ade9-55b67cf26aa0",
49 | "Command": "basic.lua"
50 | },
51 | {
52 | "Id": "4805db8d-9126-4ca5-8533-2ac689dd622c",
53 | "Command": "cancel.lua"
54 | },
55 | {
56 | "Id": "10cb77d0-5b13-4244-961b-45b2c03cb30d",
57 | "Command": "coverage.lua"
58 | },
59 | {
60 | "Id": "11fabdb2-5527-456b-95e4-dde03926dd16",
61 | "Command": "deadlock.lua"
62 | },
63 | {
64 | "Id": "b420633f-dcf7-4ba1-aed6-e00cddfa9f6a",
65 | "Command": "errhangtest.lua"
66 | },
67 | {
68 | "Id": "9b8c4c9a-829d-47cb-9dbc-05f9a0cbaf20",
69 | "Command": "error.lua"
70 | },
71 | {
72 | "Id": "8986c442-6eab-49cc-94e6-94b5e1e2f630",
73 | "Command": "fifo.lua"
74 | },
75 | {
76 | "Id": "1fd4849c-8f0b-45cb-b1e0-08a5565aa8a3",
77 | "Command": "finalizer.lua"
78 | },
79 | {
80 | "Id": "c98c2169-34cd-45a1-9373-4df8e418d937",
81 | "Command": "func_is_string.lua"
82 | },
83 | {
84 | "Id": "b4cbd3c4-109c-4106-9bab-fb9ff44ea93d",
85 | "Command": "ignore.lua"
86 | },
87 | {
88 | "Id": "de4ab896-373a-44d9-af01-c3e22a205197",
89 | "Command": "irayo_recursive.lua"
90 | },
91 | {
92 | "Id": "83feda37-b85c-489d-94e8-2d9f87134c7f",
93 | "Command": "keeper.lua"
94 | },
95 | {
96 | "Id": "cb8833d2-cd8c-4dd4-9808-4b887ccedf45",
97 | "Command": "lanes_as_upvalue.lua"
98 | },
99 | {
100 | "Id": "13551306-493f-45e7-aacc-fbf134ca2441",
101 | "Command": "linda_perf.lua"
102 | },
103 | {
104 | "Id": "b54bec40-7f76-422a-a486-e6c232fd2229",
105 | "Command": "-e \"TEST1=50 PREFILL1=1000 FILL1=100000\" linda_perf.lua"
106 | },
107 | {
108 | "Id": "62468c84-6050-4de2-864a-bb1d5ce81fa9",
109 | "Command": "launchtest.lua 1"
110 | },
111 | {
112 | "Id": "30a80bc0-51b1-46ed-b326-e53c3d3c55f2",
113 | "Command": "manual_register.lua"
114 | },
115 | {
116 | "Id": "e756bd9e-512f-40ac-b990-2f3040384419",
117 | "Command": "nameof.lua"
118 | },
119 | {
120 | "Id": "d3e42f6c-3976-407e-a3f8-04f07eae0fff",
121 | "Command": "objects.lua"
122 | },
123 | {
124 | "Id": "00f8f64c-bfa2-483d-a0dd-230940cc1860",
125 | "Command": "package.lua"
126 | },
127 | {
128 | "Id": "d842776e-dee0-4808-a051-6b6ce588e601",
129 | "Command": "perftest.lua"
130 | },
131 | {
132 | "Id": "b9d7165b-c8b6-4559-9fcc-7905841815d9",
133 | "Command": "protect_allocator.lua"
134 | },
135 | {
136 | "Id": "5f2d8aa4-1416-4c43-ba11-0d6c2b2b3b2f",
137 | "Command": "require.lua"
138 | },
139 | {
140 | "Id": "0d8a5929-cbee-4367-b13c-1a5e547eb29d",
141 | "Command": "rupval.lua"
142 | },
143 | {
144 | "Id": "f7a50188-7ac9-46ca-959e-226e5bce69f6",
145 | "Command": "timer.lua"
146 | },
147 | {
148 | "Id": "f14c84e5-7fc1-4706-ade7-614c23c4ea7d",
149 | "Command": "tobeclosed.lua"
150 | },
151 | {
152 | "Id": "f4538cac-85e5-4a28-aa65-6f10879f649a",
153 | "Command": "track_lanes.lua"
154 | }
155 | ]
156 | }
157 | ]
158 | }
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Documentation is available online at http://lualanes.github.io/lanes/
2 | (should be the same as what is in the docs folder of the distribution).
3 |
4 | =====================
5 | Usage on Windows:
6 | =====================
7 |
8 | For once, Win32 thread prioritazion scheme is actually a good one, and
9 | it works. :) Windows users, feel yourself as VIP citizens!!
10 |
11 | -------------------
12 | Windows / MSYS:
13 | -------------------
14 |
15 | On MSYS, 'stderr' output seems to be buffered. You might want to make
16 | it auto-flush, to help you track & debug your scripts. Like this:
17 |
18 | io.stderr:setvbuf "no"
19 |
20 | Even after this, MSYS insists on linewise buffering; it will flush at
21 | each newline only.
22 |
23 |
24 | ===================
25 | Usage on Linux:
26 | ===================
27 |
28 | Linux NTPL 2.5 (Ubuntu 7.04) was used in the testing of Lua Lanes.
29 |
30 | This document (http://www.icir.org/gregor/tools/pthread-scheduling.html)
31 | describes fairly well, what (all) is wrong with Linux threading, even today.
32 |
33 | For other worthy links:
34 | http://kerneltrap.org/node/6080
35 | http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library
36 |
37 | In short, you cannot use thread prioritation in Linux. Unless you run as
38 | root, and I _truly_ would not recommend that. Lobby for yet-another thread
39 | implementation for Linux, and mail -> akauppi@gmail.com about it. :)
40 |
41 | CAVEAT: Anyone sufficiently knowledgeable with pthread scheduling is free to
42 | contact me bnt DOT germain AT gmail DOT com) with a suitable edit
43 | if this no longer applies on more recent kernels.
44 |
45 |
46 | ======================
47 | Usage on Mac OS X:
48 | ======================
49 |
50 | No real problems in OS X, _once_ everything is set up right...
51 |
52 | In short, have your Lua core compiled with LUA_USE_DLOPEN and LUA_USE_POSIX
53 | instead of the (default as of 5.1) LUA_DL_DYLD and LUA_USE_MACOSX. This is
54 | crucial to have each module loaded only once (even if initialized separately
55 | for each thread) and for the static & global variables within the modules to
56 | actually be process-wide. Lua Lanes cannot live without...
57 | Lua 5.2 is fine in that regard (check luaconf.h to be safe).
58 |
59 | Another issue is making sure you only have _one_ Lua core. Your 'lua' binary
60 | must link dynamically to a .dylib, it must _not_ carry a personal copy of Lua
61 | core with itself. If it does, you will gain many mysterious malloc errors
62 | when entering multithreading realm.
63 |
64 | <<
65 | 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
66 | <<
67 |
68 | rm lua.o luac.o
69 | gcc -dynamiclib -install_name /usr/local/lib/liblua.5.1.dylib \
70 | -compatibility_version 5.1 -current_version 5.1.2 \
71 | -o liblua.5.1.2.dylib *.o
72 |
73 | gcc -fno-common -DLUA_USE_POSIX -DLUA_USE_DLOPEN -DLUA_USE_READLINE -lreadline -L. -llua.5.1.2 lua.c -o lua
74 |
75 | That should be it. :)
76 |
77 | Fink 'lua51' packaging has the necessary changes since 5.1.2-3.
78 |
79 |
80 | =====================
81 | Usage on FreeBSD:
82 | =====================
83 |
84 | Unlike in Linux, also the Lua engine used with Lanes needs to be compiled with
85 | '-lpthread'. Otherwise, the following malloc errors are received:
86 |
87 | <<
88 | lua in free(): warning: recursive call
89 | PANIC: unprotected error in call to Lua API (not enough memory)
90 | <<
91 |
92 | Here are the Lua compilation steps that proved to work (FreeBSD 6.2 i386):
93 |
94 | gmake freebsd
95 | rm lua.o luac.o liblua.a
96 | gcc -shared -lm -Wl,-E -o liblua.5.1.2.so *.o
97 | gcc -O2 -Wall -DLUA_USE_LINUX -lm -Wl,-E -lpthread -lreadline -L. -llua.5.1.2 lua.c -o lua
98 |
99 | To compile Lanes, use 'gmake' or simply:
100 |
101 | cc -O2 -Wall -llua.5.1.2 -lpthread -shared -o out/bsd-x86/lua51-lanes.so \
102 | -DGLUA_LUA51 gluax.c lanes.c
103 |
104 | To place Lua into ~/local, set the following environment variables (this is
105 | just a reminder for myself...):
106 |
107 | export CPATH=.../local/include
108 | export LIBRARY_PATH=.../local/lib
109 | export LD_LIBRARY_PATH=.../local/lib
110 |
111 | =======================
112 | Manual installation
113 | =======================
114 |
115 | After running GNU make, in directory `src` you will find files
116 | `lanes.lua` and `lanes/core.so`. The first goes into a directory on
117 | your LUA_PATH, and the `lanes` directory goes into a directory on your
118 | LUA_CPATH. If you are not sure how this works, try creating
119 |
120 | /usr/local/share/lua/5.1/lanes.lua
121 | /usr/local/lib/lua/5.1/lanes/core.so
122 |
123 | =======================
124 | Note about LuaJIT
125 | =======================
126 | It looks like LuaJIT makes some assumptions about the usage of its allocator.
127 | Namely, when a Lua state closes, memory allocated from its alloc function might be freed, even if said memory
128 | isn't actually owned by the state (for example if the allocator was used explicitly after retrieving if with lua_getallocf)
129 | Therefore it seems to be a bad idea, when creating a new lua_State, to propagate the allocator
130 | from another state, as closing the first state would invalidate all the memory used by the second one...
131 | The best is therefore to leave the 'allocator' configuration option unset when running LuaJIT.
132 |
133 | (end)
134 |
--------------------------------------------------------------------------------
/Shared.makefile:
--------------------------------------------------------------------------------
1 | CC := g++ -std=c++20
2 |
3 | # LuaRocks gives 'LIBFLAG' from the outside
4 | #
5 | LIBFLAG := -shared
6 |
7 | OPT_FLAGS := -O2
8 | #OPT_FLAGS := -O0 -g3
9 |
10 | ifeq "$(findstring MINGW,$(shell uname -s))" "MINGW"
11 | # MinGW MSYS on Windows
12 | #
13 | _SO := dll
14 | _LUAEXT := .exe
15 | TIME := timeit.exe
16 | else
17 | _SO := so
18 | _LUAEXT :=
19 | endif
20 |
21 | ifeq "$(LUAROCKS)" ""
22 | ifeq "$(findstring MINGW,$(shell uname -s))" "MINGW"
23 | # MinGW MSYS on Windows
24 | #
25 | # - 'lua' and 'luac' expected to be on the path
26 | # - %LUA_DEV% must lead to include files and libraries (Lua for Windows >= 5.1.3.14)
27 | # - %MSCVR80% must be the full pathname of 'msvcr80.dll'
28 | #
29 | ifeq "$(LUA_DEV)" ""
30 | $(warning LUA_DEV not defined - try i.e. 'make LUA_DEV=/c/Program\ Files/Lua/5.1')
31 | # this assumes Lua was built and installed from source and everything is located in default folders (/usr/local/include and /usr/local/bin)
32 | LUA_FLAGS := -I "/usr/local/include"
33 | LUA_LIBS := $(word 1,$(shell which lua54.$(_SO) 2>/dev/null) $(shell which lua53.$(_SO) 2>/dev/null) $(shell which lua52.$(_SO) 2>/dev/null) $(shell which lua51$(_SO) 2>/dev/null))
34 | $(info detected LUA_LIBS as $(LUA_LIBS))
35 | else
36 | LUA_FLAGS := -I "$(LUA_DEV)/include"
37 | LUA_LIBS := "$(LUA_DEV)/lua5.1.dll" -lgcc
38 | endif
39 | LIBFLAG := -shared -Wl,-Map,lanes.map
40 | else
41 | # Autodetect LUA_FLAGS and/or LUA_LIBS
42 | #
43 | ifneq "$(shell which pkg-config)" ""
44 | ifeq "$(shell pkg-config --exists luajit && echo 1)" "1"
45 | LUA_FLAGS := $(shell pkg-config --cflags luajit)
46 | LUA_LIBS := $(shell pkg-config --libs luajit)
47 | #
48 | # Debian: -I/usr/include/luajit-2.0
49 | # -lluajit-5.1
50 | else
51 | ifeq "$(shell pkg-config --exists lua5.1 && echo 1)" "1"
52 | LUA_FLAGS := $(shell pkg-config --cflags lua5.1)
53 | LUA_LIBS := $(shell pkg-config --libs lua5.1)
54 | #
55 | # Ubuntu: -I/usr/include/lua5.1
56 | # -llua5.1
57 | else
58 | ifeq "$(shell pkg-config --exists lua && echo 1)" "1"
59 | LUA_FLAGS := $(shell pkg-config --cflags lua)
60 | LUA_LIBS := $(shell pkg-config --libs lua)
61 | #
62 | # OS X fink with pkg-config:
63 | # -I/sw/include
64 | # -L/sw/lib -llua -lm
65 | else
66 | $(warning *** 'pkg-config' existed but did not know of 'lua[5.1]' - Good luck!)
67 | LUA_FLAGS :=
68 | LUA_LIBS := -llua
69 | endif
70 | endif
71 | endif
72 | else
73 | # No 'pkg-config'; try defaults
74 | #
75 | ifeq "$(shell uname -s)" "Darwin"
76 | $(warning *** Assuming 'fink' at default path)
77 | LUA_FLAGS := -I/sw/include
78 | LUA_LIBS := -L/sw/lib -llua
79 | else
80 | $(warning *** Assuming an arbitrary Lua installation; try installing 'pkg-config')
81 | LUA_FLAGS :=
82 | LUA_LIBS := -llua
83 | endif
84 | endif
85 | endif
86 |
87 | ifeq "$(shell uname -s)" "Darwin"
88 | # Some machines need 'MACOSX_DEPLOYMENT_TARGET=10.3' for using '-undefined dynamic_lookup'
89 | # (at least PowerPC running 10.4.11); does not harm the others
90 | #
91 | CC := MACOSX_DEPLOYMENT_TARGET=10.3 gcc
92 | LIBFLAG := -bundle -undefined dynamic_lookup
93 | endif
94 |
95 | CFLAGS := -Wall -Werror $(OPT_FLAGS) $(LUA_FLAGS)
96 | LIBS :=
97 | endif
98 |
99 | #---
100 | # PThread platform specifics
101 | #
102 | ifeq "$(shell uname -s)" "Linux"
103 | # -D_GNU_SOURCE needed for 'pthread_mutexattr_settype'
104 | CFLAGS += -D_GNU_SOURCE -fPIC
105 |
106 | # Use of -DUSE_PTHREAD_TIMEDJOIN is possible, but not recommended (slower & keeps threads
107 | # unreleased somewhat longer)
108 | #CFLAGS += -DUSE_PTHREAD_TIMEDJOIN
109 |
110 | LIBS += -lpthread
111 | endif
112 |
113 | ifeq "$(shell uname -s)" "BSD"
114 | LIBS += -lpthread
115 | endif
116 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | TODO:
2 |
3 | - Testing Lane killing (not cancellation, but actual killing)
4 |
5 | - Like luaproc: Lanes to have M:N relationship to kernel threads
6 | (= give a maximum number of kernel threads to run, then juggle those to run a Lane,
7 | until the lane suspends, blocks, or exits)
8 | (default could be twice the kernel threads of the CPU count, or something.)
9 |
10 | - Like luaproc:
11 | "only the basic standard
12 | library and our own library are automatically loaded into each new Lua process. The re-
13 | maining standard libraries (io, os, table, string, math, and debug) are pre-registered and
14 | can be loaded with a standard call to Lua’s require function. "
15 |
16 | - Lanes so/dll to have a second interface; C code sending data to a linda of given void*
17 |
--------------------------------------------------------------------------------
/deep_userdata_example/DUE.makefile:
--------------------------------------------------------------------------------
1 | #
2 | # Lanes/deep_userdata_example/DUE.makefile
3 | #
4 |
5 | include ../Shared.makefile
6 |
7 | _TARGET := deep_userdata_example.$(_SO)
8 |
9 | _SRC := $(wildcard *.cpp) ../src/compat.cpp ../src/deep.cpp
10 |
11 | _OBJ := $(_SRC:.cpp=.o)
12 |
13 | #---
14 | all: $(_TARGET)
15 | $(info CC: $(CC))
16 | $(info _TARGET: $(_TARGET))
17 | $(info _SRC: $(_SRC))
18 |
19 | _pch.hpp.gch: ../src/_pch.hpp
20 | $(CC) -I "../.." $(CFLAGS) -x c++-header $< -o _pch.hpp.gch
21 |
22 | %.o: %.cpp _pch.hpp.gch DUE.makefile
23 | $(CC) -I "../.." $(CFLAGS) -c $< -o $@
24 |
25 | # Note: Don't put $(LUA_LIBS) ahead of $^; MSYS will not like that (I think)
26 | #
27 | $(_TARGET): $(_OBJ)
28 | $(CC) $(LIBFLAG) $^ $(LIBS) $(LUA_LIBS) -o $@
29 |
30 | install:
31 | install -m 644 $(_TARGET) $(LUA_LIBDIR)/
32 |
33 | clean:
34 | -rm -rf $(_TARGET) *.o *.map *.gch
35 |
36 | .PHONY: all clean
37 |
--------------------------------------------------------------------------------
/deep_userdata_example/deep_userdata_example.args.json:
--------------------------------------------------------------------------------
1 | {
2 | "FileVersion": 2,
3 | "Id": "4c40bd18-3bab-46d7-8f14-602a6fbe5910",
4 | "Items": [
5 | {
6 | "Id": "d9d87866-8c63-44a8-b88a-1d42097985d4",
7 | "Command": "DeepTest",
8 | "Items": [
9 | {
10 | "Id": "d762af99-3873-4084-a9a1-ae42f57802a0",
11 | "Command": "deeptest.lua"
12 | },
13 | {
14 | "Id": "a8d142a9-be5f-459d-8b80-61c7f3a263a1",
15 | "Command": "-e \"REPEAT=1000, SIZE=1000\" -i deeptest.lua"
16 | },
17 | {
18 | "Id": "10e30bb2-dc23-4882-b918-b5939c14e588",
19 | "Command": "-e \"REPEAT=1000, SIZE=1000 DEEP='stack_abuser'\" -i deeptest.lua"
20 | }
21 | ]
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/deep_userdata_example/deep_userdata_example.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {e3eeb49e-1b60-4885-b634-80e39d11acc7}
14 |
15 |
16 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
17 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
18 |
19 |
20 |
21 |
22 | Lanes
23 |
24 |
25 | Lanes
26 |
27 |
28 | Source Files
29 |
30 |
31 |
32 |
33 | Lanes
34 |
35 |
36 | Lanes
37 |
38 |
39 | Lanes
40 |
41 |
42 | Lanes
43 |
44 |
45 | Lanes
46 |
47 |
48 | Lanes
49 |
50 |
51 |
52 |
53 | test
54 |
55 |
56 | Make
57 |
58 |
59 |
--------------------------------------------------------------------------------
/dist.info:
--------------------------------------------------------------------------------
1 | --- This file is part of LuaDist project
2 |
3 | name = "lanes"
4 | version = "2.0.11"
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 | }
15 |
--------------------------------------------------------------------------------
/docs/Lua multithreading choices.graffle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuaLanes/lanes/bfdc7a92c4e3e99522abb6d90ef2cbb021f36fc8/docs/Lua multithreading choices.graffle
--------------------------------------------------------------------------------
/docs/multi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuaLanes/lanes/bfdc7a92c4e3e99522abb6d90ef2cbb021f36fc8/docs/multi.png
--------------------------------------------------------------------------------
/docs/performance.ods:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuaLanes/lanes/bfdc7a92c4e3e99522abb6d90ef2cbb021f36fc8/docs/performance.ods
--------------------------------------------------------------------------------
/lanes-4.0.0-0.rockspec:
--------------------------------------------------------------------------------
1 | --
2 | -- Lanes rockspec
3 | --
4 | -- Ref:
5 | --
6 | --
7 |
8 | package = "Lanes"
9 |
10 | version = "4.0.0-0"
11 |
12 | source= {
13 | url= "git+https://github.com/LuaLanes/lanes.git",
14 | branch= "v4.0.0"
15 | }
16 |
17 | description = {
18 | summary= "Multithreading support for Lua",
19 | detailed= [[
20 | Lua Lanes is a portable, message passing multithreading library
21 | providing the possibility to run multiple Lua states in parallel.
22 | ]],
23 | license= "MIT/X11",
24 | homepage="https://github.com/LuaLanes/lanes",
25 | maintainer="Benoit Germain "
26 | }
27 |
28 | -- Q: What is the difference of "windows" and "win32"? Seems there is none;
29 | -- so should we list either one or both?
30 | --
31 | supported_platforms= { "win32",
32 | "macosx",
33 | "linux",
34 | "freebsd", -- TBD: not tested
35 | "msys", -- TBD: not supported by LuaRocks 1.0 (or is it?)
36 | }
37 |
38 | dependencies= {
39 | "lua >= 5.1", -- builds with either 5.1/LuaJIT, 5.2, 5.3 and 5.4
40 | }
41 |
42 | build = {
43 | type = "builtin",
44 | platforms =
45 | {
46 | linux =
47 | {
48 | modules =
49 | {
50 | ["lanes_core"] =
51 | {
52 | libraries = "pthread"
53 | },
54 | }
55 | }
56 | },
57 | modules =
58 | {
59 | ["lanes_core"] =
60 | {
61 | sources =
62 | {
63 | "src/_pch.cpp",
64 | "src/allocator.cpp",
65 | "src/cancel.cpp",
66 | "src/compat.cpp",
67 | "src/deep.cpp",
68 | "src/intercopycontext.cpp",
69 | "src/keeper.cpp",
70 | "src/lane.cpp",
71 | "src/lanes.cpp",
72 | "src/linda.cpp",
73 | "src/lindafactory.cpp",
74 | "src/nameof.cpp",
75 | "src/state.cpp",
76 | "src/threading.cpp",
77 | "src/tools.cpp",
78 | "src/tracker.cpp",
79 | "src/universe.cpp"
80 | },
81 | incdirs = { "src"},
82 | },
83 | lanes = "src/lanes.lua"
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/setup-vc.cmd:
--------------------------------------------------------------------------------
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=%ProgramFiles%\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=%ProgramFiles%\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=%ProgramFiles%\Microsoft Visual Studio 9.0
33 | if exist "%VSINSTALLDIR%\VC\vcvarsall.bat" goto WARN_VC
34 | set VSINSTALLDIR=%ProgramFiles(x86)%\Microsoft Visual Studio 9.0
35 | if exist "%VSINSTALLDIR%\VC\vcvarsall.bat" goto WARN_VC
36 |
37 | :TRY_VC10
38 | set VSINSTALLDIR=%ProgramFiles%\Microsoft Visual Studio 10.0
39 | if exist "%VSINSTALLDIR%\VC\vcvarsall.bat" goto WARN_VC
40 | set VSINSTALLDIR=%ProgramFiles(x86)%\Microsoft Visual Studio 10.0
41 | if exist "%VSINSTALLDIR%\VC\vcvarsall.bat" goto WARN_VC
42 |
43 | :WARN_VC
44 | echo.
45 | echo *** Warning: Visual C++ 2008/2010 in use ***
46 | echo.
47 | echo Using VC++2005 is recommended for runtime compatibility issues
48 | echo (LuaBinaries and LfW use it; if you compile everything from
49 | echo scratch, ignore this message)
50 | echo.
51 |
52 | :FOUND_VC
53 | set VCINSTALLDIR=%VSINSTALLDIR%\vc
54 |
55 | REM vcvars.bat sets the following values right:
56 | REM
57 | REM PATH=...
58 | REM INCLUDE=%VCINSTALLDIR%\ATLMFC\INCLUDE;%VCINSTALLDIR%\INCLUDE;%VCINSTALLDIR%\PlatformSDK\include;%FrameworkSDKDir%\include;%INCLUDE%
59 | REM LIB=%VCINSTALLDIR%\ATLMFC\LIB;%VCINSTALLDIR%\LIB;%VCINSTALLDIR%\PlatformSDK\lib;%FrameworkSDKDir%\lib;%LIB%
60 | REM LIBPATH=%FrameworkDir%\%FrameworkVersion%;%VCINSTALLDIR%\ATLMFC\LIB
61 | REM
62 | call "%VSINSTALLDIR%\VC\vcvarsall.bat"
63 |
64 | REM 'timeit.exe' is part of the MS Server Res Kit Tools (needed for "make perftest")
65 | REM
66 | set _RESKIT=%ProgramFiles%\Windows Resource Kits\Tools\
67 | if not exist "%_RESKIT%\timeit.exe" goto WARN_NOTIMEIT
68 | PATH=%PATH%;%_RESKIT%
69 | goto EXIT
70 |
71 | :WARN_NOTIMEIT
72 | echo.
73 | echo ** WARNING: Windows Server 2003 Resource Kit Tools - not detected
74 | echo You will need the 'timeit' utility to run 'make perftest'
75 | echo http://www.microsoft.com/downloads/details.aspx?familyid=9D467A69-57FF-4AE7-96EE-B18C4790CFFD
76 | echo.
77 | goto EXIT
78 |
79 | REM ---
80 | :ERR_NOVC
81 | echo.
82 | echo ** ERROR: Visual C++ 2005/08/10 Express - not detected
83 | echo You can set the environment variables separately, and run 'make-vc.cmd'
84 | echo or download the compiler from:
85 | echo http://msdn.microsoft.com/vstudio/express/downloads/
86 | echo.
87 | goto EXIT
88 |
89 | :ERR_NOSDK
90 | echo.
91 | echo ** ERROR: Windows Server 2003 Platform SDK - not detected
92 | echo You will need the core API's of it to compile Win32 applications.
93 | echo http://www.microsoft.com/downloads/details.aspx?familyid=0BAF2B35-C656-4969-ACE8-E4C0C0716ADB
94 | echo.
95 | goto EXIT
96 |
97 | :EXIT
98 | set _SDK=
99 | set _RESKIT=
100 |
--------------------------------------------------------------------------------
/src/Lanes.makefile:
--------------------------------------------------------------------------------
1 | #
2 | # Lanes/src/Lanes.makefile
3 | #
4 | # make Manual build
5 | # make LUAROCKS=1 CFLAGS=... LIBFLAG=... LuaRocks automated build
6 | #
7 |
8 | include ../Shared.makefile
9 |
10 | _TARGET := lanes_core.$(_SO)
11 |
12 | _SRC := $(wildcard *.cpp)
13 |
14 | _OBJ := $(_SRC:.cpp=.o)
15 |
16 | #---
17 | all: info $(_TARGET)
18 |
19 | info:
20 | $(info CC: $(CC))
21 | $(info _SRC: $(_SRC))
22 |
23 | _pch.hpp.gch: _pch.hpp
24 | $(CC) $(CFLAGS) -x c++-header _pch.hpp -o _pch.hpp.gch
25 |
26 | %.o: %.cpp _pch.hpp.gch *.h *.hpp Lanes.makefile
27 | $(CC) $(CFLAGS) -c $< -o $@
28 |
29 | # Note: Don't put $(LUA_LIBS) ahead of $^; MSYS will not like that (I think)
30 | #
31 | $(_TARGET): $(_OBJ)
32 | $(CC) $(LIBFLAG) $^ $(LIBS) $(LUA_LIBS) -o $@
33 |
34 | clean:
35 | -rm -rf $(_TARGET) *.o *.map *.gch
36 |
37 | #---
38 | # NSLU2 "slug" Linux ARM
39 | #
40 | nslu2:
41 | $(MAKE) all CFLAGS="$(CFLAGS) -I/opt/include -L/opt/lib -D_GNU_SOURCE -lpthread"
42 |
43 | #---
44 | # Cross compiling to Win32 (MinGW on OS X Intel)
45 | #
46 | # Point WIN32_LUA51 to an extraction of LuaBinaries dll8 and dev packages.
47 | #
48 | # Note: Only works on platforms with same endianess (i.e. not from PowerPC OS X,
49 | # since 'luac' uses the host endianess)
50 | #
51 | # EXPERIMENTAL; NOT TESTED OF LATE.
52 | #
53 | MINGW_GCC = mingw32-gcc
54 | # i686-pc-mingw32-gcc
55 |
56 | win32: $(WIN32_LUA51)/include/lua.h
57 | $(MAKE) build CC=$(MINGW_GCC) \
58 | LUA_FLAGS=-I$(WIN32_LUA51)/include \
59 | LUA_LIBS="-L$(WIN32_LUA51) -llua51" \
60 | _SO=dll \
61 | SO_FLAGS=-shared
62 |
63 | $(WIN32_LUA51)/include/lua.h:
64 | @echo "Usage: make win32 WIN32_LUA51="
65 | @echo " [MINGW_GCC=...mingw32-gcc]"
66 | @false
67 |
68 | .PHONY: all info clean nslu2 win32
69 |
--------------------------------------------------------------------------------
/src/_pch.cpp:
--------------------------------------------------------------------------------
1 | #include "_pch.hpp"
--------------------------------------------------------------------------------
/src/_pch.hpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #ifndef __PROSPERO__
14 | #include
15 | #endif // __PROSPERO__
16 | #include
17 | #include
18 | #include
19 | #include
20 | //#include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | #ifdef _MSC_VER
30 |
31 | // warning level /Wall triggers a bunch of warnings in Lua headers. we can't do anything about that, so suppress them
32 | #pragma warning(push)
33 | #pragma warning(disable : 4820) // 'n' bytes padding added after data member 'x'
34 |
35 | #endif // _MSC_VER
36 |
37 | #ifdef __cplusplus
38 | extern "C"
39 | {
40 | #endif // __cplusplus
41 | #include "lua.h"
42 | #include "lualib.h"
43 | #include "lauxlib.h"
44 | #ifdef __cplusplus
45 | }
46 | #endif // __cplusplus
47 |
48 | #ifdef _MSC_VER
49 |
50 | #pragma warning(pop)
51 |
52 | #pragma warning(disable : 4061) // enumerator 'x' in switch of 'y' is not explicitly handled by a case label
53 | #pragma warning(disable : 4514) // 'x': unreferenced inline function has been removed
54 | #pragma warning(disable : 4623) // 'x': default constructor was implicitly defined as deleted
55 | #pragma warning(disable : 4623) // 'x': default constructor was implicitly defined as deleted
56 | #pragma warning(disable : 4625) // 'x': copy constructor was implicitly defined as deleted
57 | #pragma warning(disable : 4626) // 'x': assignment operator was implicitly defined as deleted
58 | #pragma warning(disable : 4820) // 'n' bytes padding added after data member 'x'
59 | #pragma warning(disable : 5026) // 'x': move constructor was implicitly defined as deleted
60 | #pragma warning(disable : 5027) // 'x': move assignment operator was implicitly defined as deleted
61 | #pragma warning(disable : 5039) // 'x': pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception.
62 | #pragma warning(disable : 5045) // Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified
63 | #pragma warning(disable : 5246) // 'x': the initialization of a subobject should be wrapped in braces
64 |
65 | #endif // _MSC_VER
66 |
--------------------------------------------------------------------------------
/src/allocator.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * ALLOCATOR.CPP Copyright (c) 2017-2024, Benoit Germain
3 | */
4 |
5 | /*
6 | ===============================================================================
7 |
8 | Copyright (C) 2017-2024 Benoit Germain
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in
18 | all copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 | THE SOFTWARE.
27 |
28 | ===============================================================================
29 | */
30 |
31 | #include "_pch.hpp"
32 | #include "allocator.hpp"
33 |
34 | namespace lanes
35 | {
36 | AllocatorDefinition& AllocatorDefinition::Validated(lua_State* const L_, StackIndex const idx_)
37 | {
38 | lanes::AllocatorDefinition* const _def{ luaG_tofulluserdata(L_, idx_) };
39 | // raise an error and don't return if the full userdata at the specified index is not a valid AllocatorDefinition
40 | if (!_def) {
41 | raise_luaL_error(L_, "Bad config.allocator function, provided value is not a userdata");
42 | }
43 | if (lua_rawlen(L_, idx_) < sizeof(lanes::AllocatorDefinition)) {
44 | raise_luaL_error(L_, "Bad config.allocator function, provided value is too small to contain a valid AllocatorDefinition");
45 | }
46 | if (_def->version != kAllocatorVersion) {
47 | raise_luaL_error(L_, "Bad config.allocator function, AllocatorDefinition version mismatch");
48 | }
49 | return *_def;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/allocator.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "compat.hpp"
4 |
5 | // #################################################################################################
6 |
7 | namespace lanes {
8 |
9 | // everything we need to provide to lua_newstate()
10 | class AllocatorDefinition
11 | {
12 | private:
13 | // xxh64 of string "kAllocatorVersion_1" generated at https://www.pelock.com/products/hash-calculator
14 | static constexpr auto kAllocatorVersion{ static_cast(0xCF9D321B0DFB5715ull) };
15 |
16 | public:
17 | using version_t = std::remove_const_t;
18 |
19 | private:
20 | // can't make these members const because we need to be able to copy an AllocatorDefinition into another
21 | // so the members are not const, but they are private to avoid accidents.
22 | version_t version{ kAllocatorVersion };
23 | lua_Alloc allocF{ nullptr };
24 | void* allocUD{ nullptr };
25 |
26 | public:
27 |
28 | [[nodiscard]]
29 | static void* operator new(size_t const size_) noexcept = delete; // can't create one outside of a Lua state
30 | [[nodiscard]]
31 | static void* operator new(size_t const size_, lua_State* const L_) noexcept { return lua_newuserdatauv(L_, size_, UserValueCount{ 0 }); }
32 | // always embedded somewhere else or "in-place constructed" as a full userdata
33 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception
34 | static void operator delete([[maybe_unused]] void* const p_, [[maybe_unused]] lua_State* const L_) {}
35 |
36 | ~AllocatorDefinition() = default;
37 |
38 | AllocatorDefinition(lua_Alloc const allocF_, void* const allocUD_) noexcept
39 | : allocF{ allocF_ }
40 | , allocUD{ allocUD_ }
41 | {
42 | }
43 |
44 | // rule of 5
45 | AllocatorDefinition() = default;
46 | AllocatorDefinition(AllocatorDefinition const& rhs_) = default;
47 | AllocatorDefinition(AllocatorDefinition&& rhs_) = default;
48 | AllocatorDefinition& operator=(AllocatorDefinition const& rhs_) = default;
49 | AllocatorDefinition& operator=(AllocatorDefinition&& rhs_) = default;
50 |
51 | [[nodiscard]]
52 | static AllocatorDefinition& Validated(lua_State* L_, StackIndex idx_);
53 |
54 | void initFrom(lua_State* const L_)
55 | {
56 | allocF = lua_getallocf(L_, &allocUD);
57 | }
58 |
59 | void installIn(lua_State* const L_) const
60 | {
61 | if (allocF) {
62 | lua_setallocf(L_, allocF, allocUD);
63 | }
64 | }
65 |
66 | [[nodiscard]]
67 | lua_State* newState() const
68 | {
69 | return lua_newstate(allocF, allocUD);
70 | }
71 |
72 | [[nodiscard]]
73 | void* alloc(size_t const nsize_) const
74 | {
75 | return allocF(allocUD, nullptr, 0, nsize_);
76 | }
77 |
78 | [[nodiscard]]
79 | void* alloc(void* const ptr_, size_t const osize_, size_t const nsize_) const
80 | {
81 | return allocF(allocUD, ptr_, osize_, nsize_);
82 | }
83 |
84 | void free(void* const ptr_, size_t const osize_) const
85 | {
86 | std::ignore = allocF(allocUD, ptr_, osize_, 0);
87 | }
88 | };
89 |
90 | } // namespace lanes
91 |
--------------------------------------------------------------------------------
/src/cancel.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "macros_and_utils.hpp"
4 | #include "uniquekey.hpp"
5 |
6 | // #################################################################################################
7 |
8 | // Lane cancellation request modes
9 | enum class [[nodiscard]] CancelRequest : uint8_t
10 | {
11 | None, // no pending cancel request
12 | Soft, // user wants the lane to cancel itself manually on cancel_test()
13 | Hard // user wants the lane to be interrupted (meaning code won't return from those functions) from inside linda:send/receive calls
14 | };
15 |
16 | struct [[nodiscard]] CancelOp
17 | {
18 | CancelRequest mode;
19 | LuaHookMask hookMask;
20 | };
21 |
22 | enum class [[nodiscard]] CancelResult : uint8_t
23 | {
24 | Timeout,
25 | Cancelled
26 | };
27 |
28 | // xxh64 of string "kCancelError" generated at https://www.pelock.com/products/hash-calculator
29 | static constexpr UniqueKey kCancelError{ 0x0630345FEF912746ull, "lanes.cancel_error" }; // 'raise_cancel_error' sentinel
30 |
31 | // #################################################################################################
32 |
33 | [[nodiscard]]
34 | CancelRequest CheckCancelRequest(lua_State* L_);
35 |
36 | // #################################################################################################
37 |
38 | [[noreturn]]
39 | static inline void raise_cancel_error(lua_State* const L_)
40 | {
41 | STACK_GROW(L_, 1);
42 | kCancelError.pushKey(L_); // special error value
43 | raise_lua_error(L_);
44 | }
45 |
46 | // #################################################################################################
47 |
48 | LUAG_FUNC(cancel_test);
49 | LUAG_FUNC(lane_cancel);
50 |
--------------------------------------------------------------------------------
/src/debug.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "lanesconf.h"
4 | #include "luaerrors.hpp"
5 | #include "unique.hpp"
6 |
7 | // #################################################################################################
8 |
9 | #if HAVE_LUA_ASSERT()
10 |
11 | // file name, line number, function name
12 | using SourceLocation = std::tuple;
13 | inline SourceLocation Where(std::source_location const& where_ = std::source_location::current())
14 | {
15 | std::string_view const _path{ where_.file_name() };
16 | // drop the directory structure
17 | std::string_view const _fileName{ _path.substr(_path.find_last_of("\\/")+1) };
18 | std::string_view const _func{ where_.function_name() };
19 | return std::make_tuple(_fileName, where_.line(), _func);
20 | }
21 |
22 | inline void LUA_ASSERT_IMPL(lua_State* const L_, bool const cond_, std::string_view const& txt_, SourceLocation const& where_ = Where())
23 | {
24 | if (!cond_) {
25 | auto const& [_file, _line, _func] = where_;
26 | raise_luaL_error(L_, "%s:%d: LUA_ASSERT '%s' IN %s", _file.data(), _line, txt_.data(), _func.data());
27 | }
28 | }
29 |
30 | #define LUA_ASSERT(L_, cond_) LUA_ASSERT_IMPL(L_, (cond_) ? true : false, #cond_)
31 | #define LUA_ASSERT_CODE(code_) code_
32 |
33 | class StackChecker final
34 | {
35 | private:
36 | lua_State* const L;
37 | int oldtop;
38 |
39 | public:
40 | DECLARE_UNIQUE_TYPE(Relative, int);
41 | DECLARE_UNIQUE_TYPE(Absolute, int);
42 |
43 | // offer a way to bypass C assert during unit testing
44 | static inline bool CallsCassert{ true };
45 |
46 | StackChecker(lua_State* const L_, Relative const offset_, SourceLocation const& where_ = Where())
47 | : L{ L_ }
48 | , oldtop{ lua_gettop(L_) - offset_ }
49 | {
50 | if ((offset_ < 0) || (oldtop < 0)) {
51 | assert(!CallsCassert);
52 | auto const& [_file, _line, _func] = where_;
53 | raise_luaL_error(L, "%s:%d: STACK INIT ASSERT (%d not %d) IN %s", _file.data(), _line, lua_gettop(L), offset_, _func.data());
54 | }
55 | }
56 |
57 | StackChecker(lua_State* const L_, Absolute const pos_, SourceLocation const& where_ = Where())
58 | : L{ L_ }
59 | , oldtop{ 0 }
60 | {
61 | if (lua_gettop(L) != pos_) {
62 | assert(!CallsCassert);
63 | auto const& [_file, _line, _func] = where_;
64 | raise_luaL_error(L, "%s:%d: STACK INIT ASSERT (%d not %d) IN %s", _file.data(), _line, lua_gettop(L), pos_, _func.data());
65 | }
66 | }
67 |
68 | StackChecker& operator=(StackChecker const& rhs_)
69 | {
70 | assert(L == rhs_.L);
71 | oldtop = rhs_.oldtop;
72 | return *this;
73 | }
74 |
75 | // verify if the distance between the current top and the initial one is what we expect
76 | void check(int const expected_, SourceLocation const& where_ = Where())
77 | {
78 | if (expected_ != LUA_MULTRET) {
79 | int const _actual{ lua_gettop(L) - oldtop };
80 | if (_actual != expected_) {
81 | assert(!CallsCassert);
82 | auto const& [_file, _line, _func] = where_;
83 | raise_luaL_error(L, "%s:%d: STACK CHECK ASSERT (%d not %d) IN %s", _file.data(), _line, _actual, expected_, _func.data());
84 | }
85 | }
86 | }
87 | };
88 |
89 | #define STACK_CHECK_START_REL(L, offset_) \
90 | StackChecker _stackChecker_##L \
91 | { \
92 | L, StackChecker::Relative{ offset_ }, \
93 | }
94 | #define STACK_CHECK_START_ABS(L, offset_) \
95 | StackChecker _stackChecker_##L \
96 | { \
97 | L, StackChecker::Absolute{ offset_ }, \
98 | }
99 | #define STACK_CHECK_RESET_REL(L, offset_) \
100 | _stackChecker_##L = StackChecker \
101 | { \
102 | L, StackChecker::Relative{ offset_ }, \
103 | }
104 | #define STACK_CHECK_RESET_ABS(L, offset_) \
105 | _stackChecker_##L = StackChecker \
106 | { \
107 | L, StackChecker::Absolute{ offset_ }, \
108 | }
109 | #define STACK_CHECK(L, offset_) _stackChecker_##L.check(offset_)
110 |
111 | #else // HAVE_LUA_ASSERT()
112 |
113 | #define LUA_ASSERT(L_, c) ((void) 0) // nothing
114 | #define LUA_ASSERT_CODE(code_) ((void) 0)
115 |
116 | #define STACK_CHECK_START_REL(L_, offset_)
117 | #define STACK_CHECK_START_ABS(L_, offset_)
118 | #define STACK_CHECK_RESET_REL(L_, offset_)
119 | #define STACK_CHECK_RESET_ABS(L_, offset_)
120 | #define STACK_CHECK(L_, offset_)
121 |
122 | #endif // HAVE_LUA_ASSERT()
123 |
--------------------------------------------------------------------------------
/src/debugspew.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "lanesconf.h"
4 | #include "universe.hpp"
5 |
6 | // #################################################################################################
7 |
8 | #if USE_DEBUG_SPEW()
9 |
10 | class DebugSpewIndentScope final
11 | {
12 | private:
13 | Universe* const U{};
14 |
15 | public:
16 | static std::string_view const debugspew_indent;
17 |
18 | DebugSpewIndentScope(Universe* U_)
19 | : U{ U_ }
20 | {
21 | if (U) {
22 | U->debugspewIndentDepth.fetch_add(1, std::memory_order_relaxed);
23 | }
24 | }
25 |
26 | ~DebugSpewIndentScope()
27 | {
28 | if (U) {
29 | U->debugspewIndentDepth.fetch_sub(1, std::memory_order_relaxed);
30 | }
31 | }
32 | };
33 |
34 | // #################################################################################################
35 |
36 | inline std::string_view DebugSpewIndent(Universe const* const U_)
37 | {
38 | return DebugSpewIndentScope::debugspew_indent.substr(0, static_cast(U_->debugspewIndentDepth.load(std::memory_order_relaxed)));
39 | }
40 |
41 | inline auto& DebugSpew(Universe const* const U_)
42 | {
43 | if (!U_) {
44 | return std::cerr;
45 | }
46 | return std::cerr << DebugSpewIndent(U_) << " ";
47 | }
48 | #define DEBUGSPEW_CODE(_code) _code
49 | #define DEBUGSPEW_OR_NOT(a_, b_) a_
50 | #define DEBUGSPEW_PARAM_COMMA(param_) param_,
51 | #define DEBUGSPEW_COMMA_PARAM(param_) , param_
52 |
53 | #else // USE_DEBUG_SPEW()
54 |
55 | #define DEBUGSPEW_CODE(_code)
56 | #define DEBUGSPEW_OR_NOT(a_, b_) b_
57 | #define DEBUGSPEW_PARAM_COMMA(param_)
58 | #define DEBUGSPEW_COMMA_PARAM(param_)
59 |
60 | #endif // USE_DEBUG_SPEW()
61 |
--------------------------------------------------------------------------------
/src/deep.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | /*
4 | * public 'deep' API to be used by external modules if they want to implement Lanes-aware userdata
5 | * said modules can either link against lanes, or embed compat.cpp/h deep.cpp/h tools.cpp/h universe.cpp/h
6 | */
7 |
8 | #include "lanesconf.h"
9 | #include "uniquekey.hpp"
10 |
11 | // forwards
12 | enum class LookupMode;
13 | class DeepFactory;
14 | class Universe;
15 |
16 | // #################################################################################################
17 |
18 | // xxh64 of string "kDeepVersion_1" generated at https://www.pelock.com/products/hash-calculator
19 | static constexpr UniqueKey kDeepVersion{ 0x91171AEC6641E9DBull, "kDeepVersion" };
20 |
21 | // should be used as header for deep userdata
22 | // a deep userdata is a full userdata that stores a single pointer to the actual DeepPrelude-derived object
23 | class DeepPrelude
24 | {
25 | friend class DeepFactory;
26 |
27 | private:
28 | UniqueKey const magic{ kDeepVersion };
29 | // when stored in a keeper state, the full userdata doesn't have a metatable, so we need direct access to the factory
30 | DeepFactory& factory;
31 |
32 | protected:
33 | // data is destroyed when refcount is 0
34 | std::atomic refcount{ 0 };
35 |
36 | protected:
37 | DeepPrelude(DeepFactory& factory_)
38 | : factory{ factory_ }
39 | {
40 | }
41 |
42 | public:
43 | void push(lua_State* L_) const;
44 | [[nodiscard]]
45 | int getRefcount() const { return refcount.load(std::memory_order_relaxed); }
46 | };
47 |
48 | // #################################################################################################
49 |
50 | // external C modules should create a single object implementing that interface for each Deep userdata class they want to expose
51 | class DeepFactory
52 | {
53 | private:
54 | // the current count of deep object instances
55 | mutable std::atomic deepObjectCount{};
56 |
57 | protected:
58 | // protected non-virtual destructor: Lanes won't manage the Factory's lifetime
59 | DeepFactory() = default;
60 | virtual ~DeepFactory() = default;
61 |
62 | public:
63 | // non-copyable, non-movable
64 | DeepFactory(DeepFactory const&) = delete;
65 | DeepFactory(DeepFactory const&&) = delete;
66 | DeepFactory& operator=(DeepFactory const&) = delete;
67 | DeepFactory& operator=(DeepFactory const&&) = delete;
68 |
69 | private:
70 | // NVI: private overrides
71 | virtual void createMetatable(lua_State* L_) const = 0;
72 | virtual void deleteDeepObjectInternal(lua_State* L_, DeepPrelude* o_) const = 0;
73 | [[nodiscard]]
74 | virtual DeepPrelude* newDeepObjectInternal(lua_State* L_) const = 0;
75 | [[nodiscard]]
76 | virtual std::string_view moduleName() const = 0;
77 |
78 | private:
79 | [[nodiscard]]
80 | static int DeepGC(lua_State* L_);
81 | void storeDeepLookup(lua_State* L_) const;
82 |
83 | public:
84 | // NVI: public interface
85 | static void DeleteDeepObject(lua_State* L_, DeepPrelude* o_);
86 | [[nodiscard]]
87 | int getObjectCount() const { return deepObjectCount.load(std::memory_order_relaxed); }
88 | [[nodiscard]]
89 | static bool IsDeepUserdata(lua_State* L_, StackIndex idx_);
90 | [[nodiscard]]
91 | static DeepFactory* LookupFactory(lua_State* L_, StackIndex index_, LookupMode mode_);
92 | static void PushDeepProxy(DestState L_, DeepPrelude* o_, UserValueCount nuv_, LookupMode mode_, lua_State* errL_);
93 | void pushDeepUserdata(DestState L_, UserValueCount nuv_) const;
94 | [[nodiscard]]
95 | DeepPrelude* toDeep(lua_State* L_, StackIndex index_) const;
96 | };
97 |
98 | // #################################################################################################
99 |
--------------------------------------------------------------------------------
/src/intercopycontext.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "tools.hpp"
4 |
5 | // forwards
6 | class Universe;
7 |
8 | // #################################################################################################
9 |
10 | enum class [[nodiscard]] VT
11 | {
12 | NORMAL, // keep this one first so that it's the value we get when we default-construct
13 | KEY,
14 | METATABLE
15 | };
16 |
17 | enum class [[nodiscard]] InterCopyResult
18 | {
19 | Success,
20 | NotEnoughValues,
21 | Error
22 | };
23 |
24 | // #################################################################################################
25 |
26 | DECLARE_UNIQUE_TYPE(CacheIndex, StackIndex);
27 | DECLARE_UNIQUE_TYPE(SourceIndex, StackIndex);
28 | class InterCopyContext final
29 | {
30 | public:
31 | Universe* const U;
32 | DestState const L2;
33 | SourceState const L1;
34 | CacheIndex const L2_cache_i;
35 | SourceIndex L1_i; // that one can change when we reuse the context
36 | VT vt; // that one can change when we reuse the context
37 | LookupMode const mode;
38 | std::string_view name; // that one can change when we reuse the context
39 |
40 | private:
41 | [[nodiscard]]
42 | std::string_view findLookupName() const;
43 | // when mode == LookupMode::FromKeeper, L1 is a keeper state and L2 is not, therefore L2 is the state where we want to raise the error
44 | // whon mode != LookupMode::FromKeeper, L1 is not a keeper state, therefore L1 is the state where we want to raise the error
45 | lua_State* getErrL() const { return (mode == LookupMode::FromKeeper) ? L2.value() : L1.value(); }
46 | [[nodiscard]]
47 | LuaType processConversion() const;
48 |
49 | // for use in copyCachedFunction
50 | void copyFunction() const;
51 | void lookupNativeFunction() const;
52 |
53 | // for use in inter_copy_function
54 | void copyCachedFunction() const;
55 | [[nodiscard]]
56 | bool lookupTable() const;
57 |
58 | // for use in inter_copy_table
59 | void interCopyKeyValuePair() const;
60 | [[nodiscard]]
61 | bool pushCachedMetatable() const;
62 | [[nodiscard]]
63 | bool pushCachedTable() const;
64 |
65 | // for use in inter_copy_userdata
66 | [[nodiscard]]
67 | bool lookupUserdata() const;
68 | [[nodiscard]]
69 | bool tryCopyClonable() const;
70 | [[nodiscard]]
71 | bool tryCopyDeep() const;
72 |
73 | // copying a single Lua stack item
74 | [[nodiscard]]
75 | bool interCopyBoolean() const;
76 | [[nodiscard]]
77 | bool interCopyFunction() const;
78 | [[nodiscard]]
79 | bool interCopyLightuserdata() const;
80 | [[nodiscard]]
81 | bool interCopyNil() const;
82 | [[nodiscard]]
83 | bool interCopyNumber() const;
84 | [[nodiscard]]
85 | bool interCopyString() const;
86 | [[nodiscard]]
87 | bool interCopyTable() const;
88 | [[nodiscard]]
89 | bool interCopyUserdata() const;
90 |
91 | public:
92 | [[nodiscard]]
93 | InterCopyResult interCopy(int n_) const;
94 | [[nodiscard]]
95 | InterCopyResult interCopyOne() const;
96 | [[nodiscard]]
97 | InterCopyResult interCopyPackage() const;
98 | [[nodiscard]]
99 | InterCopyResult interMove(int n_) const;
100 | };
101 |
--------------------------------------------------------------------------------
/src/keeper.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "uniquekey.hpp"
4 |
5 | // forwards
6 | class Linda;
7 | class Universe;
8 |
9 | DECLARE_UNIQUE_TYPE(KeeperState,lua_State*);
10 | DECLARE_UNIQUE_TYPE(LindaLimit, int);
11 | DECLARE_UNIQUE_TYPE(KeeperIndex, int);
12 |
13 | // #################################################################################################
14 |
15 | enum class [[nodiscard]] LindaRestrict
16 | {
17 | None,
18 | SetGet,
19 | SendReceive
20 | };
21 |
22 | // #################################################################################################
23 |
24 | struct Keeper
25 | {
26 | std::mutex mutex;
27 | KeeperState K{ static_cast(nullptr) };
28 |
29 | ~Keeper() = default;
30 | Keeper() = default;
31 | // non-copyable, non-movable
32 | Keeper(Keeper const&) = delete;
33 | Keeper(Keeper const&&) = delete;
34 | Keeper& operator=(Keeper const&) = delete;
35 | Keeper& operator=(Keeper const&&) = delete;
36 |
37 | [[nodiscard]]
38 | static int PushLindaStorage(Linda& linda_, DestState L_);
39 | };
40 |
41 | // #################################################################################################
42 |
43 | struct Keepers
44 | {
45 | private:
46 | struct DeleteKV
47 | {
48 | Universe& U;
49 | size_t count{};
50 | void operator()(Keeper* k_) const;
51 | };
52 | // can't use std::unique_ptr because of interactions with placement new and custom deleters
53 | // and I'm not using std::vector because I don't have an allocator to plug on the Universe (yet)
54 | struct KV
55 | {
56 | std::unique_ptr keepers;
57 | size_t nbKeepers{};
58 | };
59 | std::variant keeper_array;
60 | std::atomic_flag isClosing;
61 |
62 | public:
63 | int gc_threshold{ 0 };
64 |
65 | public:
66 | // can only be instanced as a data member
67 | static void* operator new(size_t const size_) = delete;
68 |
69 | Keepers() = default;
70 | void collectGarbage();
71 | [[nodiscard]]
72 | bool close();
73 | [[nodiscard]]
74 | Keeper* getKeeper(KeeperIndex idx_);
75 | [[nodiscard]]
76 | int getNbKeepers() const;
77 | void initialize(Universe& U_, lua_State* L_, size_t nbKeepers_, int gc_threshold_);
78 | };
79 |
80 | // #################################################################################################
81 |
82 | DECLARE_UNIQUE_TYPE(KeeperCallResult, std::optional);
83 |
84 | // xxh64 of string "kNilSentinel" generated at https://www.pelock.com/products/hash-calculator
85 | static constexpr UniqueKey kNilSentinel{ 0xC457D4EDDB05B5E4ull, "lanes.null" };
86 |
87 | // xxh64 of string "kRestrictedChannel" generated at https://www.pelock.com/products/hash-calculator
88 | static constexpr UniqueKey kRestrictedChannel{ 0x4C8B879ECDE110F7ull };
89 |
90 | using keeper_api_t = lua_CFunction;
91 | #define KEEPER_API(_op) keepercall_##_op
92 |
93 | // lua_Cfunctions to run inside a keeper state
94 | [[nodiscard]]
95 | int keepercall_collectgarbage(lua_State* L_);
96 | [[nodiscard]]
97 | int keepercall_count(lua_State* L_);
98 | [[nodiscard]]
99 | int keepercall_destruct(lua_State* L_);
100 | [[nodiscard]]
101 | int keepercall_get(lua_State* L_);
102 | [[nodiscard]]
103 | int keepercall_limit(lua_State* L_);
104 | [[nodiscard]]
105 | int keepercall_receive(lua_State* L_);
106 | [[nodiscard]]
107 | int keepercall_receive_batched(lua_State* L_);
108 | [[nodiscard]]
109 | int keepercall_restrict(lua_State* L_);
110 | [[nodiscard]]
111 | int keepercall_send(lua_State* L_);
112 | [[nodiscard]]
113 | int keepercall_set(lua_State* L_);
114 |
115 | [[nodiscard]]
116 | KeeperCallResult keeper_call(KeeperState K_, keeper_api_t func_, lua_State* L_, Linda* linda_, StackIndex starting_index_);
117 |
118 | LUAG_FUNC(collectgarbage);
119 |
--------------------------------------------------------------------------------
/src/lanes.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "lanesconf.h"
4 |
5 | #define LANES_VERSION_MAJOR 4
6 | #define LANES_VERSION_MINOR 0
7 | #define LANES_VERSION_PATCH 0
8 |
9 | #define LANES_MIN_VERSION_REQUIRED(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR > MAJOR) || (LANES_VERSION_MAJOR == MAJOR && (LANES_VERSION_MINOR > MINOR || (LANES_VERSION_MINOR == MINOR && LANES_VERSION_PATCH >= PATCH))))
10 | #define LANES_VERSION_LESS_THAN(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR < MAJOR) || (LANES_VERSION_MAJOR == MAJOR && (LANES_VERSION_MINOR < MINOR || (LANES_VERSION_MINOR == MINOR && LANES_VERSION_PATCH < PATCH))))
11 | #define LANES_VERSION_LESS_OR_EQUAL(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR < MAJOR) || (LANES_VERSION_MAJOR == MAJOR && (LANES_VERSION_MINOR < MINOR || (LANES_VERSION_MINOR == MINOR && LANES_VERSION_PATCH <= PATCH))))
12 | #define LANES_VERSION_GREATER_THAN(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR > MAJOR) || (LANES_VERSION_MAJOR == MAJOR && (LANES_VERSION_MINOR > MINOR || (LANES_VERSION_MINOR == MINOR && LANES_VERSION_PATCH > PATCH))))
13 | #define LANES_VERSION_GREATER_OR_EQUAL(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR > MAJOR) || (LANES_VERSION_MAJOR == MAJOR && (LANES_VERSION_MINOR > MINOR || (LANES_VERSION_MINOR == MINOR && LANES_VERSION_PATCH >= PATCH))))
14 |
15 | LANES_API int luaopen_lanes_core(lua_State* L_);
16 |
17 | // Call this to work with embedded Lanes instead of calling luaopen_lanes_core()
18 | LANES_API void luaopen_lanes_embedded(lua_State* L_, lua_CFunction luaopen_lanes_);
19 | using luaopen_lanes_embedded_t = void (*)(lua_State* L_, lua_CFunction luaopen_lanes_);
20 | static_assert(std::is_same_v, "signature changed: check all uses of luaopen_lanes_embedded_t");
21 |
22 | LANES_API int lanes_register(lua_State* L_);
23 |
--------------------------------------------------------------------------------
/src/lanesconf.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "platform.h"
4 |
5 | // here is a place that's as good as any other to list a few coding conventions that I will endeavour to follow:
6 | //
7 | // indentation:
8 | // ------------
9 | // spaces everywhere
10 | // case indented inside switch braces
11 | // accessibility keywords indented inside class braces
12 | // matching braces have the same indentation
13 | //
14 | // identifiers:
15 | // ------------
16 | // style is camel case. scope of variable is optionally specified with a single lowercase letter.
17 | // constants: prefix k, followed by an uppercase letter
18 | // program-level global variable: in 'global' namespace, prefix g, followed by an uppercase letter
19 | // file-level types: in anonymous namespace
20 | // file-level static variable: in anonymous::'local' namespace, prefix s, followed by an uppercase letter
21 | // file-level function (static or not): no prefix, start with an uppercase letter
22 | // class/struct/enum type: no prefix, start with an uppercase letter
23 | // static class member/method: no prefix, start with an uppercase letter
24 | // regular class member/method: no prefix, start with a lowercase letter
25 | // function argument: suffix _
26 | // static function variable: prefix s, followed by an uppercase letter
27 | // function local variable: prefix _, followed by an uppercase letter
28 | // named lambda capture: no prefix, start with a lowercase letter
29 | // stuff for external consumption in a 'lanes' namespace
30 |
31 | #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC)
32 | #ifdef __cplusplus
33 | #define LANES_API extern "C" __declspec(dllexport)
34 | #else
35 | #define LANES_API extern __declspec(dllexport)
36 | #endif // __cplusplus
37 | #else
38 | #ifdef __cplusplus
39 | #define LANES_API extern "C"
40 | #else
41 | #define LANES_API extern
42 | #endif // __cplusplus
43 | #endif // (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC)
44 |
45 | // kind of MSVC-specific
46 | #ifdef _DEBUG
47 | #define HAVE_LUA_ASSERT() 1
48 | #else // NDEBUG
49 | #define HAVE_LUA_ASSERT() 0
50 | #endif // NDEBUG
51 |
52 | #define USE_DEBUG_SPEW() 0
53 | #define HAVE_DECODA_SUPPORT() 0
54 |
--------------------------------------------------------------------------------
/src/linda.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "cancel.hpp"
4 | #include "deep.hpp"
5 | #include "universe.hpp"
6 |
7 | struct Keeper;
8 |
9 | // #################################################################################################
10 |
11 | DECLARE_UNIQUE_TYPE(LindaGroup, int);
12 |
13 | class Linda final
14 | : public DeepPrelude // Deep userdata MUST start with this header
15 | {
16 | public:
17 | class [[nodiscard]] KeeperOperationInProgress final
18 | {
19 | private:
20 | Linda& linda;
21 | lua_State* const L{}; // just here for inspection while debugging
22 |
23 | public:
24 | KeeperOperationInProgress(Linda& linda_, lua_State* const L_)
25 | : linda{ linda_ }
26 | , L{ L_ }
27 | {
28 | [[maybe_unused]] UnusedInt const _prev{ linda.keeperOperationCount.fetch_add(1, std::memory_order_seq_cst) };
29 | }
30 |
31 | public:
32 | ~KeeperOperationInProgress()
33 | {
34 | [[maybe_unused]] UnusedInt const _prev{ linda.keeperOperationCount.fetch_sub(1, std::memory_order_seq_cst) };
35 | }
36 | };
37 |
38 | enum class [[nodiscard]] Status
39 | {
40 | Active,
41 | Cancelled
42 | };
43 | using enum Status;
44 |
45 | public:
46 | Universe* const U{ nullptr }; // the universe this linda belongs to
47 |
48 | private:
49 | static constexpr size_t kEmbeddedNameLength = 24;
50 | using EmbeddedName = std::array;
51 | // depending on the name length, it is either embedded inside the Linda, or allocated separately
52 | std::variant nameVariant{};
53 | // counts the keeper operations in progress
54 | std::atomic keeperOperationCount{};
55 | lua_Duration wakePeriod{};
56 |
57 | public:
58 | std::condition_variable readHappened{};
59 | std::condition_variable writeHappened{};
60 | KeeperIndex const keeperIndex{ -1 }; // the keeper associated to this linda
61 | Status cancelStatus{ Status::Active };
62 |
63 | public:
64 | [[nodiscard]]
65 | static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internalAllocator.alloc(size_); }
66 | // always embedded somewhere else or "in-place constructed" as a full userdata
67 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception
68 | static void operator delete(void* p_, Universe* U_) { U_->internalAllocator.free(p_, sizeof(Linda)); }
69 | // this one is for us, to make sure memory is freed by the correct allocator
70 | static void operator delete(void* p_) { static_cast(p_)->U->internalAllocator.free(p_, sizeof(Linda)); }
71 |
72 | ~Linda();
73 | Linda(Universe* U_, std::string_view const& name_, lua_Duration wake_period_, LindaGroup group_);
74 | Linda() = delete;
75 | // non-copyable, non-movable
76 | Linda(Linda const&) = delete;
77 | Linda(Linda const&&) = delete;
78 | Linda& operator=(Linda const&) = delete;
79 | Linda& operator=(Linda const&&) = delete;
80 |
81 | private:
82 | [[nodiscard]]
83 | static Linda* CreateTimerLinda(lua_State* L_);
84 | static void DeleteTimerLinda(lua_State* L_, Linda* linda_);
85 | void freeAllocatedName();
86 | void setName(std::string_view const& name_);
87 |
88 | public:
89 | [[nodiscard]]
90 | Keeper* acquireKeeper() const;
91 | [[nodiscard]]
92 | static Linda* CreateTimerLinda(lua_State* const L_, Passkey const) { return CreateTimerLinda(L_); }
93 | static void DeleteTimerLinda(lua_State* const L_, Linda* const linda_, Passkey const) { DeleteTimerLinda(L_, linda_); }
94 | [[nodiscard]]
95 | std::string_view getName() const;
96 | [[nodiscard]]
97 | auto getWakePeriod() const { return wakePeriod; }
98 | [[nodiscard]]
99 | bool inKeeperOperation() const { return keeperOperationCount.load(std::memory_order_seq_cst) != 0; }
100 | template
101 | [[nodiscard]]
102 | T obfuscated() const
103 | {
104 | // xxh64 of string "kObfuscator" generated at https://www.pelock.com/products/hash-calculator
105 | static constexpr UniqueKey kObfuscator{ 0x7B8AA1F99A3BD782ull };
106 | return std::bit_cast(std::bit_cast(this) ^ kObfuscator.storage);
107 | };
108 | void releaseKeeper(Keeper* keeper_) const;
109 | [[nodiscard]]
110 | static int ProtectedCall(lua_State* L_, lua_CFunction f_);
111 | void pushCancelString(lua_State* L_) const;
112 | [[nodiscard]]
113 | KeeperOperationInProgress startKeeperOperation(lua_State* const L_) { return KeeperOperationInProgress{ *this, L_ }; };
114 | [[nodiscard]]
115 | Keeper* whichKeeper() const { return U->keepers.getKeeper(keeperIndex); }
116 | };
117 |
--------------------------------------------------------------------------------
/src/lindafactory.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "deep.hpp"
4 |
5 | // #################################################################################################
6 |
7 | class LindaFactory final
8 | : public DeepFactory
9 | {
10 | public:
11 | // TODO: I'm not totally happy with having a 'global' variable. Maybe it should be dynamically created and stored somewhere in the universe?
12 | static LindaFactory Instance;
13 |
14 | ~LindaFactory() override = default;
15 | LindaFactory(luaL_Reg const lindaMT_[])
16 | : mLindaMT{ lindaMT_ }
17 | {
18 | }
19 |
20 | private:
21 | luaL_Reg const* const mLindaMT{ nullptr };
22 |
23 | void createMetatable(lua_State* L_) const override;
24 | void deleteDeepObjectInternal(lua_State* L_, DeepPrelude* o_) const override;
25 | [[nodiscard]]
26 | std::string_view moduleName() const override;
27 | [[nodiscard]]
28 | DeepPrelude* newDeepObjectInternal(lua_State* L_) const override;
29 | };
30 |
--------------------------------------------------------------------------------
/src/luaerrors.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "stackindex.hpp"
4 |
5 | // #################################################################################################
6 |
7 | // use this instead of Lua's lua_error
8 | [[noreturn]]
9 | static inline void raise_lua_error(lua_State* const L_)
10 | {
11 | std::ignore = lua_error(L_); // doesn't return
12 | assert(false); // we should never get here, but i'm paranoid
13 | }
14 |
15 | // #################################################################################################
16 |
17 | // use this instead of Lua's luaL_error
18 | template
19 | [[noreturn]]
20 | static inline void raise_luaL_error(lua_State* const L_, std::string_view const& fmt_, ARGS... args_)
21 | {
22 | std::ignore = luaL_error(L_, fmt_.data(), std::forward(args_)...); // doesn't return
23 | assert(false); // we should never get here, but i'm paranoid
24 | }
25 |
26 | // #################################################################################################
27 |
28 | // use this instead of Lua's luaL_argerror
29 | template
30 | [[noreturn]]
31 | static inline void raise_luaL_argerror(lua_State* const L_, StackIndex const arg_, std::string_view const& extramsg_)
32 | {
33 | std::ignore = luaL_argerror(L_, arg_, extramsg_.data()); // doesn't return
34 | assert(false); // we should never get here, but i'm paranoid
35 | }
36 |
37 | // #################################################################################################
38 |
39 | #if LUA_VERSION_NUM >= 504
40 | // use this instead of Lua's luaL_typeerror
41 | template
42 | [[noreturn]]
43 | static inline void raise_luaL_typeerror(lua_State* const L_, StackIndex const arg_, std::string_view const& tname_)
44 | {
45 | std::ignore = luaL_typeerror(L_, arg_, tname_.data()); // doesn't return
46 | assert(false); // we should never get here, but i'm paranoid
47 | }
48 | #endif // LUA_VERSION_NUM
49 |
--------------------------------------------------------------------------------
/src/macros_and_utils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "debug.hpp"
4 | #include "luaerrors.hpp"
5 | #include "unique.hpp"
6 |
7 | using namespace std::chrono_literals;
8 |
9 | // #################################################################################################
10 |
11 | inline void STACK_GROW(lua_State* const L_, int const n_)
12 | {
13 | if (!lua_checkstack(L_, n_)) {
14 | raise_luaL_error(L_, "Cannot grow stack!");
15 | }
16 | }
17 |
18 | // #################################################################################################
19 |
20 | #define LUAG_FUNC(func_name) int LG_##func_name(lua_State* const L_)
21 |
22 | // #################################################################################################
23 |
24 | // 1 unit of lua_Duration lasts 1 second (using default period of std::ratio<1>)
25 | using lua_Duration = std::chrono::template duration;
26 |
27 | // #################################################################################################
28 |
29 | DECLARE_UNIQUE_TYPE(SourceState, lua_State*);
30 | DECLARE_UNIQUE_TYPE(DestState, lua_State*);
31 |
32 | // #################################################################################################
33 |
34 | // A helper to issue an error if the provided optional doesn't contain a value
35 | // we can't use std::optional::value_or(luaL_error(...)), because the 'or' value is always evaluated
36 | template
37 | concept IsOptional = requires(T x)
38 | {
39 | x.value_or(T{});
40 | };
41 |
42 | template
43 | requires IsOptional
44 | typename T::value_type const& OptionalValue(T const& x_, Ts... args_)
45 | {
46 | if (!x_.has_value()) {
47 | raise_luaL_error(std::forward(args_)...);
48 | }
49 | return x_.value();
50 | }
51 |
52 | // #################################################################################################
53 |
54 | struct PasskeyToken {};
55 | constexpr PasskeyToken PK{};
56 | template
57 | class Passkey final
58 | {
59 | private:
60 | friend T;
61 | Passkey(PasskeyToken) {}
62 | // rule of 5 ignored out of laziness here
63 | };
64 |
--------------------------------------------------------------------------------
/src/nameof.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "macros_and_utils.hpp"
4 |
5 | LUAG_FUNC(nameof);
6 |
--------------------------------------------------------------------------------
/src/platform.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #if (defined __MINGW32__) || (defined __MINGW64__) // detect mingw before windows, because mingw defines _WIN32
4 | #define PLATFORM_MINGW
5 | //#pragma message("PLATFORM_MINGW")
6 | #elif (defined _WIN32_WCE)
7 | #define PLATFORM_POCKETPC
8 | //#pragma message("PLATFORM_POCKETPC")
9 | #elif defined(_XBOX)
10 | #define PLATFORM_XBOX
11 | //#pragma message("PLATFORM_XBOX")
12 | #elif (defined _WIN32)
13 | #define PLATFORM_WIN32
14 | //#pragma message("PLATFORM_WIN32")
15 | #if !defined(NOMINMAX)
16 | #define NOMINMAX
17 | #endif // NOMINMAX
18 | #elif (defined __linux__)
19 | #define PLATFORM_LINUX
20 | //#pragma message("PLATFORM_LINUX")
21 | #elif (defined __APPLE__) && (defined __MACH__)
22 | #define PLATFORM_OSX
23 | //#pragma message("PLATFORM_OSX")
24 | #elif (defined __NetBSD__) || (defined __FreeBSD__) || (defined BSD)
25 | #define PLATFORM_BSD
26 | #elif (defined __QNX__)
27 | #define PLATFORM_QNX
28 | #elif (defined __CYGWIN__)
29 | #define PLATFORM_CYGWIN
30 | #else
31 | #error "Unknown platform!"
32 | #endif
33 |
--------------------------------------------------------------------------------
/src/stackindex.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "unique.hpp"
4 |
5 | DECLARE_UNIQUE_TYPE(StackIndex, int);
6 | static_assert(std::is_trivial_v);
7 |
8 | DECLARE_UNIQUE_TYPE(TableIndex, int);
9 | static_assert(std::is_trivial_v);
10 |
11 | DECLARE_UNIQUE_TYPE(UserValueIndex, int);
12 | static_assert(std::is_trivial_v);
13 |
14 | DECLARE_UNIQUE_TYPE(UserValueCount, int);
15 | static_assert(std::is_trivial_v);
16 |
17 | DECLARE_UNIQUE_TYPE(UnusedInt, int);
18 | static_assert(std::is_trivial_v);
19 |
20 | // #################################################################################################
21 |
22 | static constexpr StackIndex kIdxRegistry{ LUA_REGISTRYINDEX };
23 | static constexpr StackIndex kIdxNone{ 0 };
24 | static constexpr StackIndex kIdxTop{ -1 };
25 |
--------------------------------------------------------------------------------
/src/state.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "debugspew.hpp"
4 | #include "macros_and_utils.hpp"
5 |
6 | // forwards
7 | class Universe;
8 |
9 | namespace state {
10 | [[nodiscard]]
11 | lua_State* CreateState(Universe* U_, lua_State* from_, std::string_view const& hint_);
12 | [[nodiscard]]
13 | lua_State* NewLaneState(Universe* U_, SourceState from_, std::optional const& libs_);
14 | LUAG_FUNC(supported_libs);
15 | } // namespace state
16 |
--------------------------------------------------------------------------------
/src/threading.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "platform.h"
4 | #include "unique.hpp"
5 |
6 | #define THREADAPI_WINDOWS 1
7 | #define THREADAPI_PTHREAD 2
8 |
9 | #if __has_include()
10 | #include
11 | #define HAVE_PTHREAD 1
12 | //#pragma message("HAVE_PTHREAD")
13 | #else
14 | #define HAVE_PTHREAD 0
15 | #endif //
16 |
17 | #if __has_include()
18 | #define WIN32_LEAN_AND_MEAN
19 | #include
20 | #define HAVE_WIN32 1
21 | //#pragma message("HAVE_WIN32")
22 | #elif __has_include()
23 | #include
24 | #define HAVE_WIN32 1
25 | //#pragma message("HAVE_WIN32")
26 | #else // no nor
27 | #define HAVE_WIN32 0
28 | #endif //
29 |
30 | #if HAVE_PTHREAD
31 | // unless proven otherwise, if pthread is available, let's assume that's what std::thread is using
32 | #define THREADAPI THREADAPI_PTHREAD
33 | //#pragma message ( "THREADAPI_PTHREAD" )
34 | #elif HAVE_WIN32
35 | //#pragma message ( "THREADAPI_WINDOWS" )
36 | #define THREADAPI THREADAPI_WINDOWS
37 | #include
38 | #else // unknown
39 | #error "unsupported threading API"
40 | #endif // unknown
41 |
42 | static constexpr int kThreadPrioDefault{ -999 };
43 |
44 | // #################################################################################################
45 | // #################################################################################################
46 | #if THREADAPI == THREADAPI_WINDOWS
47 |
48 |
49 | /*
50 | #define XSTR(x) STR(x)
51 | #define STR(x) #x
52 | #pragma message( "The value of _WIN32_WINNT: " XSTR(_WIN32_WINNT))
53 | */
54 |
55 | static constexpr int kThreadPrioMin{ -3 };
56 | static constexpr int kThreadPrioMax{ +3 };
57 |
58 | // #################################################################################################
59 | // #################################################################################################
60 | #else // THREADAPI == THREADAPI_PTHREAD
61 | // #################################################################################################
62 | // #################################################################################################
63 |
64 | // PThread (Linux, OS X, ...)
65 |
66 | #if defined(PLATFORM_LINUX) && !defined(LINUX_SCHED_RR)
67 | static constexpr int kThreadPrioMin{ 0 };
68 | #else
69 | static constexpr int kThreadPrioMin{ -3 };
70 | #endif
71 | static constexpr int kThreadPrioMax{ +3 };
72 |
73 | #endif // THREADAPI == THREADAPI_PTHREAD
74 | // #################################################################################################
75 | // #################################################################################################
76 |
77 | DECLARE_UNIQUE_TYPE(SudoFlag, bool);
78 | DECLARE_UNIQUE_TYPE(NativePrioFlag, bool);
79 |
80 | std::pair THREAD_NATIVE_PRIOS();
81 |
82 | void THREAD_SETNAME(std::string_view const& name_);
83 |
84 | void THREAD_SET_PRIORITY(lua_State* L_, int prio_, NativePrioFlag native_, SudoFlag sudo_);
85 |
86 | void THREAD_SET_AFFINITY(lua_State* L_, unsigned int aff_);
87 |
88 | void THREAD_SET_PRIORITY(lua_State* L_, std::thread& thread_, int prio_, NativePrioFlag native_, SudoFlag sudo_);
89 |
--------------------------------------------------------------------------------
/src/threading_osx.h:
--------------------------------------------------------------------------------
1 | /*
2 | * THREADING_OSX.H
3 | * http://yyshen.github.io/2015/01/18/binding_threads_to_cores_osx.html
4 | */
5 | #pragma once
6 |
7 | #include
8 | #include
9 | #include
10 |
11 | #define SYSCTL_CORE_COUNT "machdep.cpu.core_count"
12 |
13 | struct cpu_set_t
14 | {
15 | uint32_t count;
16 | } ;
17 |
18 | static inline void CPU_ZERO(cpu_set_t *cs) { cs->count = 0; }
19 | static inline void CPU_SET(int num, cpu_set_t *cs) { cs->count |= (1 << num); }
20 | [[nodiscard]]
21 | static inline int CPU_ISSET(int num, cpu_set_t *cs) { return (cs->count & (1 << num)); }
22 |
23 | [[nodiscard]]
24 | int sched_getaffinity(pid_t pid, size_t cpu_size, cpu_set_t *cpu_set)
25 | {
26 | int32_t core_count = 0;
27 | size_t len = sizeof(core_count);
28 | int ret = sysctlbyname(SYSCTL_CORE_COUNT, &core_count, &len, 0, 0);
29 | if (ret)
30 | {
31 | // printf("error while get core count %d\n", ret);
32 | return -1;
33 | }
34 | cpu_set->count = 0;
35 | for (int i = 0; i < core_count; i++)
36 | {
37 | cpu_set->count |= (1 << i);
38 | }
39 |
40 | return 0;
41 | }
42 |
43 | [[nodiscard]]
44 | int pthread_setaffinity_np(pthread_t thread, size_t cpu_size, cpu_set_t *cpu_set)
45 | {
46 | thread_port_t mach_thread;
47 | int core = 0;
48 |
49 | for (core = 0; core < 8 * cpu_size; core++)
50 | {
51 | if (CPU_ISSET(core, cpu_set))
52 | break;
53 | }
54 | // printf("binding to core %d\n", core);
55 | thread_affinity_policy_data_t policy = { core };
56 | mach_thread = pthread_mach_thread_np(thread);
57 | thread_policy_set(mach_thread, THREAD_AFFINITY_POLICY, (thread_policy_t)&policy, 1);
58 |
59 | return 0;
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/src/tools.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "uniquekey.hpp"
4 |
5 | class Universe;
6 |
7 | enum class LookupMode
8 | {
9 | LaneBody, // send the lane body directly from the source to the destination lane. keep this one first so that it's the value we get when we default-construct
10 | ToKeeper, // send a function from a lane to a keeper state
11 | FromKeeper // send a function from a keeper state to a lane
12 | };
13 |
14 | // #################################################################################################
15 |
16 | enum class [[nodiscard]] FuncSubType
17 | {
18 | Bytecode,
19 | Native,
20 | FastJIT
21 | };
22 |
23 | [[nodiscard]]
24 | FuncSubType luaG_getfuncsubtype(lua_State* L_, StackIndex i_);
25 |
26 | // #################################################################################################
27 |
28 | // xxh64 of string "kConfigRegKey" generated at https://www.pelock.com/products/hash-calculator
29 | static constexpr RegistryUniqueKey kConfigRegKey{ 0x608379D20A398046ull }; // registry key to access the configuration
30 |
31 | // xxh64 of string "kLookupRegKey" generated at https://www.pelock.com/products/hash-calculator
32 | static constexpr RegistryUniqueKey kLookupRegKey{ 0xBF1FC5CF3C6DD47Bull }; // registry key to access the lookup database
33 |
34 | // #################################################################################################
35 |
36 | namespace tools {
37 | void PopulateFuncLookupTable(lua_State* L_, StackIndex i_, std::string_view const& name_);
38 | [[nodiscard]]
39 | std::string_view PushFQN(lua_State* L_, StackIndex t_);
40 | void SerializeRequire(lua_State* L_);
41 | } // namespace tools
42 |
--------------------------------------------------------------------------------
/src/tracker.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ===============================================================================
3 |
4 | Copyright (C) 2024 Benoit Germain
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
24 | ===============================================================================
25 | */
26 | #include "_pch.hpp"
27 | #include "tracker.hpp"
28 |
29 | #include "lane.hpp"
30 |
31 | // #################################################################################################
32 |
33 | /*
34 | * Add the lane to tracking chain; the ones still running at the end of the
35 | * whole process will be cancelled.
36 | */
37 | void LaneTracker::tracking_add(Lane* lane_)
38 | {
39 | if (!isActive()) {
40 | return;
41 | }
42 | std::lock_guard _guard{ trackingMutex };
43 | assert(lane_->tracking_next == nullptr);
44 |
45 | lane_->tracking_next = trackingFirst;
46 | trackingFirst = lane_;
47 | }
48 |
49 | // #################################################################################################
50 |
51 | /*
52 | * A free-running lane has ended; remove it from tracking chain
53 | */
54 | [[nodiscard]]
55 | bool LaneTracker::tracking_remove(Lane* lane_)
56 | {
57 | if (!isActive()) {
58 | return false;
59 | }
60 |
61 | bool _found{ false };
62 | std::lock_guard _guard{ trackingMutex };
63 | // Make sure (within the MUTEX) that we actually are in the chain
64 | // still (at process exit they will remove us from chain and then
65 | // cancel/kill).
66 | //
67 | if (lane_->tracking_next != nullptr) {
68 | Lane** _ref = (Lane**) &trackingFirst;
69 |
70 | while (*_ref != TRACKING_END) {
71 | if (*_ref == lane_) {
72 | *_ref = lane_->tracking_next;
73 | lane_->tracking_next = nullptr;
74 | _found = true;
75 | break;
76 | }
77 | _ref = (Lane**) &((*_ref)->tracking_next);
78 | }
79 | assert(_found);
80 | }
81 | return _found;
82 | }
83 |
84 | // ################################################################################################
85 |
86 | [[nodiscard]]
87 | int LaneTracker::pushThreadsTable(lua_State* L_) const
88 | {
89 | int const _top{ lua_gettop(L_) };
90 | // List _all_ still running threads
91 | std::lock_guard _guard{ trackingMutex };
92 | if (trackingFirst && trackingFirst != TRACKING_END) {
93 | Lane* _lane{ trackingFirst };
94 | int _index{ 0 };
95 | lua_newtable(L_); // L_: {}
96 | while (_lane != TRACKING_END) {
97 | // insert a { name='', status='' } tuple, so that several lanes with the same name can't clobber each other
98 | lua_createtable(L_, 0, 2); // L_: {} {}
99 | luaG_pushstring(L_, _lane->getDebugName()); // L_: {} {} "name"
100 | lua_setfield(L_, -2, "name"); // L_: {} {}
101 | _lane->pushStatusString(L_); // L_: {} {} ""
102 | lua_setfield(L_, -2, "status"); // L_: {} {}
103 | lua_rawseti(L_, -2, ++_index); // L_: {}
104 | _lane = _lane->tracking_next;
105 | }
106 | }
107 | return lua_gettop(L_) - _top; // L_: 0 or 1
108 | }
109 |
--------------------------------------------------------------------------------
/src/tracker.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | class Lane;
4 | struct lua_State;
5 |
6 | // The chain is ended by '(Lane*)(-1)', not nullptr:
7 | // 'trackingFirst -> ... -> ... -> (-1)'
8 | #define TRACKING_END ((Lane*) (-1))
9 |
10 | class LaneTracker
11 | {
12 | private:
13 | mutable std::mutex trackingMutex;
14 | Lane* trackingFirst{ nullptr }; // will change to TRACKING_END if we want to activate tracking
15 |
16 | public:
17 | void tracking_add(Lane* lane_);
18 | [[nodiscard]]
19 | bool tracking_remove(Lane* lane_);
20 | [[nodiscard]]
21 | int pushThreadsTable(lua_State* L_) const;
22 | void activate() {
23 | trackingFirst = TRACKING_END;
24 | }
25 | [[nodiscard]]
26 | bool isActive() const {
27 | return trackingFirst != nullptr;
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/src/unique.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // #################################################################################################
4 |
5 | // A unique type generator
6 | // Marking *all* Unique<> types as [[nodiscard]] is maybe overkill, but there is no way of marking a specific instanciation
7 | template
8 | class [[nodiscard]] Unique
9 | {
10 | private:
11 | T val; // no default initialization so that std::is_trivial_v> == true
12 |
13 | public:
14 | using self = Unique;
15 | using type = T;
16 |
17 | ~Unique() = default;
18 | constexpr explicit Unique(T b_)
19 | : val{ b_ }
20 | {
21 | }
22 |
23 | // rule of 5
24 | constexpr Unique() = default;
25 | constexpr Unique(Unique const&) = default;
26 | constexpr Unique(Unique&&) = default;
27 | constexpr Unique& operator=(Unique const&) = default;
28 | constexpr Unique& operator=(Unique&&) = default;
29 |
30 | // Forbid construction with any other class, especially with types that convert naturally to T.
31 | // For instance, this prevents construction with a float when T is an integer type.
32 | // Conversion will have to be explicit and the developer will be aware of it.
33 | // However we want to keep the same-type copy constructors (including with an inherited class), hence the enable_if stuff.
34 | template >, bool> = true>
35 | Unique(AnyOtherClass const&) = delete;
36 | template >, bool> = true>
37 | Unique(AnyOtherClass&&) = delete;
38 |
39 | // can't implicitly affect from base type
40 | Unique& operator=(T const&) = delete;
41 | constexpr Unique& operator=(T&&) = delete;
42 |
43 | // cast
44 | constexpr operator T const&() const noexcept { return val; }
45 | constexpr T const& value() const noexcept { return val; }
46 |
47 | // pre-increment
48 | auto& operator++() noexcept
49 | {
50 | ++val;
51 | return *this;
52 | }
53 | // post-increment
54 | auto operator++(int) noexcept
55 | {
56 | return Unique{ std::exchange(val, val + 1) };
57 | }
58 |
59 | // pre-decrement
60 | auto& operator--() noexcept
61 | {
62 | --val;
63 | return *this;
64 | }
65 | // post-decrement
66 | auto operator--(int) noexcept
67 | {
68 | return Unique{ std::exchange(val, val - 1) };
69 | }
70 | };
71 |
72 | template
73 | // Marking *all* Unique<> types as [[nodiscard]] is maybe overkill, but there is no way of marking a specific instanciation
74 | class [[nodiscard]] Unique>>
75 | : public T
76 | {
77 | public:
78 | using self = Unique;
79 | using type = T;
80 | using T::T;
81 | constexpr explicit Unique(T const& b_)
82 | : T{ b_ }
83 | {
84 | }
85 |
86 | // rule of 5
87 | constexpr Unique() = default;
88 | constexpr Unique(Unique const&) = default;
89 | constexpr Unique(Unique&&) = default;
90 | constexpr Unique& operator=(Unique const&) = default;
91 | constexpr Unique& operator=(Unique&&) = default;
92 |
93 | // Forbid construction with any other class, especially with types that convert naturally to T.
94 | // For instance, this prevents construction with a float when T is an integer type.
95 | // Conversion will have to be explicit and the developer will be aware of it.
96 | // However we want to keep the same-type copy constructors (including with an inherited class), hence the enable_if stuff.
97 | template >, bool> = true>
98 | Unique(AnyOtherClass const&) = delete;
99 | template >, bool> = true>
100 | Unique(AnyOtherClass&&) = delete;
101 |
102 | // can't implicitly affect from base type
103 | Unique& operator=(T const&) = delete;
104 | constexpr Unique& operator=(T&&) = delete;
105 | };
106 |
107 | #define DECLARE_UNIQUE_TYPE(_name, _type) using _name = Unique<_type, class Unique_##_name##_Tag>
108 |
--------------------------------------------------------------------------------
/tests/appendud.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- APPENDUD.LUA
3 | --
4 | -- Lanes version for John Belmonte's challenge on Lua list (about finalizers):
5 | --
6 | --
7 | -- Needs Lanes >= 2.0.3
8 | --
9 | local lanes = require "lanes"
10 |
11 | local _tab = {
12 | beginupdate = function (this) print('tab.beginupdate') end;
13 | endupdate = function (this) print('tab.endupdate') end;
14 | }
15 | local _ud = {
16 | lock = function (this) print('ud.lock') end;
17 | unlock = function (this) print('ud.unlock') end;
18 | 1,2,3,4,5;
19 | }
20 |
21 | --
22 | -- This sample is with the 'finalize/guard' patch applied (new keywords):
23 | --
24 | --function appendud(tab, ud)
25 | -- tab:beginupdate() finalize tab:endupdate() end
26 | -- ud:lock() finalize ud:unlock() end
27 | -- for i = 1,#ud do
28 | -- tab[#tab+1] = ud[i]
29 | -- end
30 | --end
31 |
32 |
33 | function appendud(tab, ud)
34 | io.stderr:write "Starting "
35 | tab:beginupdate() set_finalizer( function() tab:endupdate() end )
36 | ud:lock() set_finalizer( function() ud:unlock() end )
37 | for i = 1,#ud do
38 | tab[#tab+1] = ud[i]
39 | end
40 | io.stderr:write "Ending "
41 | return tab -- need to return 'tab' since we're running in a separate thread
42 | -- ('tab' is passed over lanes by value, not by reference)
43 | end
44 |
45 | local t,err= lanes.gen( "base,io", { name = 'auto'}, appendud )( _tab, _ud ) -- create & launch a thread
46 | assert(t)
47 | assert(not err)
48 |
49 | -- test
50 | -- print("t:join()")
51 | a,b,c = t[1],t[2],t[3] -- Need to explicitly wait for the thread, since 'ipairs()' does not
52 | --r,a,b,c = t:join() -- Need to explicitly wait for the thread, since 'ipairs()' does not
53 | -- value the '__index' metamethod (wouldn't it be cool if it did..?)
54 |
55 | print(a,b,c)
56 | -- print("io.stderr:write(t[1])")
57 | -- io.stderr:write(t[1])
58 | _ = t[0]
59 | print(_)
60 |
61 |
--------------------------------------------------------------------------------
/tests/argtable.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- ARGTABLE.LUA Copyright (c) 2007, Asko Kauppi
3 | --
4 | -- Command line argument 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' arguments, and any
14 | -- "-flag[=xxx]" or "--flag[=xxx]" arguments set to { flag=xxx/true }.
15 | --
16 | -- In other words, makes handling command line arguments 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
39 |
--------------------------------------------------------------------------------
/tests/atexit.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes" -- auto configure
2 |
3 | -- create a free-running lane
4 |
5 | local linda = lanes.linda()
6 |
7 | local f = function( _linda)
8 | _linda:receive("oy")
9 | end
10 |
11 | local g = function()
12 | local cancelled
13 | repeat
14 | cancelled = cancel_test()
15 | until cancelled
16 | print "User cancellation detected!"
17 | end
18 |
19 | local genF = lanes.gen( "", {name = 'auto', globals = {threadName = "mylane"}}, f)
20 | local genG = lanes.gen( "", { name = 'auto'}, g)
21 |
22 |
23 | -- launch a good batch of free running lanes
24 | for i = 1, 10 do
25 | -- if i%50 == 0 then print( i) end
26 | local h = genF( linda)
27 | local status
28 | repeat
29 | status = h.status
30 | --print( status)
31 | until status == "waiting"
32 |
33 | -- [[
34 | local h = genG()
35 | local status
36 | repeat
37 | status = h.status
38 | --print( status)
39 | until status == "running"
40 | --]]
41 | end
42 |
43 | print "exiting"
44 |
45 | -- let process end terminate them and see what happens
--------------------------------------------------------------------------------
/tests/atomic.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- ATOMIC.LUA
3 | --
4 | -- Test program for Lua Lanes
5 | --
6 |
7 | local lanes = require "lanes"
8 |
9 | local linda= lanes.linda()
10 | local key= "$"
11 |
12 | -- TODO: test what happens when we cancel the linda
13 | local f= lanes.genatomic( linda, key, 5 )
14 |
15 | local v
16 | v= f(); print(v); assert(v==6)
17 | v= f(-0.5); print(v); assert(v==5.5)
18 |
19 | v= f(-10); print(v); assert(v==-4.5)
20 |
--------------------------------------------------------------------------------
/tests/cyclic.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- CYCLIC.LUA
3 | --
4 | -- Test program for Lua Lanes
5 | --
6 |
7 | local lanes = 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 | return true
33 | end
34 | local L1= lanes.gen( "io", { name = 'auto' }, lane1 )()
35 | -- ...running
36 |
37 | -- Getting the tables as arguments should also keep the linkage
38 | --
39 | local function lane2( aa, bb )
40 | WR( "Via arguments:", same(aa,bb[1]), same(aa[1],bb) )
41 | assert( aa[1]==bb )
42 | assert( bb[1]==aa )
43 | return true
44 | end
45 | local L2= lanes.gen( "io", { name = 'auto' }, lane2 )( a, b )
46 | -- ...running
47 |
48 | -- Really unnecessary, but let's try a directly recursive table
49 | --
50 | c= {}
51 | c.a= c
52 |
53 | local function lane3( cc )
54 | WR( "Directly recursive: ", same(cc, cc.a) )
55 | assert( cc and cc.a==cc )
56 | return true
57 | end
58 | local L3= lanes.gen("io", { name = 'auto' }, lane3)(c)
59 |
60 | -- Without a wait, exit from the main lane will close the process
61 | --
62 | -- Waiting for multiple lanes at once could be done using a Linda
63 | -- (but we're okay waiting them in order)
64 | --
65 | L1:join()
66 | L2:join()
67 | L3:join()
68 |
--------------------------------------------------------------------------------
/tests/deadlock.lua:
--------------------------------------------------------------------------------
1 | -- this script tests the fix of a bug that could cause the mutex of a keeper state to remain locked
2 | -- see https://github.com/LuaLanes/lanes/commit/0cc1c9c9dcea5955f7dab921d9a2fff78c4e1729
3 |
4 | local lanes = require "lanes"
5 |
6 | -- Lua 5.1 compatibility
7 | local table_unpack = table.unpack or unpack
8 |
9 | local SLEEP = function(...)
10 | local k, v = lanes.sleep(...)
11 | assert(k == nil and v == "timeout")
12 | end
13 |
14 | print "let's begin"
15 |
16 | local do_extra_stuff = true
17 |
18 | if do_extra_stuff then
19 | local linda = lanes.linda{name = "deadlock_linda"}
20 | -- just something to make send() succeed and receive() fail
21 | local payload = { io.flush }
22 |
23 | -- lane generator. don't initialize "io" base library so that it is not known in the lane
24 | local g = lanes.gen('base,table', function()
25 | lane_threadname( "deadlock_lane")
26 | -- wrapping inside pcall makes the Lanes module unaware that something went wrong
27 | print( "In lane 1:", table_unpack{ pcall( linda.receive, linda, 'tmp')})
28 | -- with the bug not fixed, and non-recursive mutexes, we can hang here
29 | print( "In lane 2:", table_unpack{ pcall( linda.receive, linda, 'tmp')})
30 | -- return something out of the lane
31 | return 33, 55
32 | end)
33 |
34 | -- send payload twice. succeeds because sending stores a function identification string in the linda's keeper state
35 | linda:send( 'tmp', payload, payload)
36 | -- start the lane
37 | local h = g()
38 | -- wait for lane completion
39 | local err, stack = h:join()
40 | print( 'result of lane execution', err, stack)
41 | end
42 |
43 | -- With the bug not fixed, the linda keeper's mutex is still acquired,
44 | -- and the program hangs when the internal linda used for timers attempts to acquire the same keeper (there is only one by default)
45 | print('waiting a bit')
46 | SLEEP(2)
47 | print('we should reach here')
--------------------------------------------------------------------------------
/tests/ehynes.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- Test from
3 | --
4 | local lanes = require "lanes"
5 |
6 | local SLEEP = function(...)
7 | local k, v = lanes.sleep(...)
8 | assert(k == nil and v == "timeout")
9 | end
10 |
11 | local function PRINT_FMT( fmt, ... )
12 | io.stderr:write( string.format(fmt,...).."\n" )
13 | end
14 |
15 | -- a linda for sending messages
16 | local linda = lanes.linda()
17 |
18 | -- a linda message receiver
19 | local receiver_gen = lanes.gen( 'base', 'os', 'string', 'io', { name = 'auto' },
20 | function (message_name)
21 | PRINT_FMT( 'receiver for message %s entered', message_name )
22 | local n = 1
23 | while linda:receive(message_name) do
24 | PRINT_FMT( '%s %d received', message_name, n )
25 | n = n + 1
26 | end
27 | end
28 | )
29 |
30 | -- create a receiver
31 | local receiver1 = receiver_gen('message')
32 |
33 | -- create a second receiver (a second receiver in the same linda
34 | -- appears to be needed to trigger the delays)
35 | --
36 | -- AKa 4-Aug-2008: No, with svn version it isn't. But it causes the 2nd
37 | -- message to be hanging...
38 | --
39 | local receiver2 = receiver_gen('another message')
40 |
41 | -- a function to pause and log the execution for debugging
42 | local function logf(s, f, ...)
43 | SLEEP(1)
44 | PRINT_FMT( "*** %s", s )
45 | f(...)
46 | end
47 |
48 | -- first message sent is received right away
49 | logf('first message sent', linda.send, linda, 'message', true)
50 |
51 | -- second message sent is not received immediatly
52 | logf('second message sent', linda.send, linda, 'message', true)
53 |
54 | -- third message sent triggers receipt of both second and third messages
55 | logf('third message sent', linda.send, linda, 'message', true)
56 |
57 | logf('all done', function() end)
58 |
--------------------------------------------------------------------------------
/tests/errhangtest.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes".configure{with_timers=false,strip_functions=false}
2 |
3 | local require_assert_result_1, require_assert_result_2 = require "assert" -- assert.fails()
4 | print("require_assert_result:", require_assert_result_1, require_assert_result_2)
5 |
6 | local linda = lanes.linda()
7 |
8 | -- we are not allowed to send coroutines through a lane
9 | -- however, this should raise an error, not hang the program...
10 | if true then
11 | print "#### coro set"
12 | local coro = coroutine.create(function() end)
13 | print(pcall(linda.set, linda, 'test', coro))
14 | local _count, _val = linda:get("test")
15 | assert(_count == 0 and _val == nil)
16 | print "OK"
17 | end
18 |
19 | if true then
20 | print "\n#### reserved sentinels"
21 | print(pcall(linda.set, linda, lanes.cancel_error))
22 | local _count, _val = linda:get("test")
23 | assert(_count == 0 and _val == nil)
24 | print "OK"
25 | end
26 |
27 | -- get/set a few values
28 | if true then
29 | print "\n#### set 3 -> receive batched"
30 | assert.fails(function() linda:receive_batched("some key", -1, 1) end)
31 | assert.fails(function() linda:receive_batched("some key", 2, 1) end)
32 | assert.failsnot(function() linda:receive_batched(0, "some key", 1, 3) end)
33 | local fun = function() print "function test ok" end
34 | print(pcall(linda.set, linda, 'test', true, nil, fun))
35 | -- read back the contents
36 | local k,b,n,f = linda:receive_batched('test', 3)
37 | local _count, _val = linda:get("test")
38 | assert(_count == 0 and _val == nil)
39 | -- check they are ok
40 | print(k, b, n)
41 | f()
42 | print "OK"
43 | end
44 |
45 | -- send a table that contains a function
46 | if true then
47 | print "\n#### send table with a function"
48 | local fun = function() print "function test ok" end
49 | local t_in = { [fun] = fun, fun = fun }
50 | print(pcall(linda.send, linda, 'test', t_in))
51 | local k,t_out = linda:receive('test') -- read the contents successfully sent
52 | t_out.fun()
53 | -- t_out should contain a single entry, as [fun] = fun should have been discarded because functions are not acceptable keys
54 | local count = 0
55 | for k,v in pairs(t_out) do count = count + 1 end
56 | assert(count == 1)
57 | print "OK"
58 | end
59 |
60 | -- send a string
61 | if true then
62 | print "\n#### send string"
63 | print(pcall(linda.send, linda, 'test', "string test ok"))
64 | local k,str = linda:receive('test') -- read the contents successfully sent
65 | print(str)
66 | print "OK"
67 | end
68 |
69 | -- we are not allowed to send coroutines through a lane
70 | -- however, this should raise an error, not hang the program...
71 | if true then
72 | print "\n#### coro send"
73 | local coro = coroutine.create(function() end)
74 | print(pcall(linda.send, linda, 'test', coro))
75 | local _count, _val = linda:get("test")
76 | assert(_count == 0 and _val == nil)
77 | print "OK"
78 | end
79 |
80 | -- done
81 | print "\nSUCCESS"
--------------------------------------------------------------------------------
/tests/fibonacci.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- FIBONACCI.LUA Copyright (c) 2007-08, Asko Kauppi
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 | --
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 | -- dummy function so that we don't error when fib() is launched from the master state
27 | lane_threadname = function ( ...)
28 | end
29 |
30 | --
31 | -- uint= fib( n_uint )
32 | --
33 | local function fib( n )
34 | lane_threadname( "fib(" .. n .. ")")
35 | local lanes = require"lanes"
36 | --
37 | local sum
38 | local floor= assert(math.floor)
39 |
40 | WR( "fib("..n..")" )
41 |
42 | if n <= #KNOWN then
43 | sum= KNOWN[n]
44 | else
45 | -- Splits into two; this task remains waiting for the results
46 | --
47 | local gen_f= lanes.gen( "*", { name = 'auto' }, fib)
48 |
49 | local n1=floor(n/2) +1
50 | local n2=floor(n/2) -1 + n%2
51 |
52 | WR( "splitting "..n.." -> "..n1.." "..n2 )
53 |
54 | local a= gen_f( n1 )
55 | local b= gen_f( n2 )
56 |
57 | -- children running...
58 |
59 | local a2= a[1]^2
60 | local b2= b[1]^2
61 |
62 | sum = (n%2==1) and a2+b2 or a2-b2
63 | end
64 |
65 | io.stderr:write( "fib("..n..") = "..sum.."\n" )
66 |
67 | return sum
68 | end
69 |
70 | --
71 | -- Right answers from:
72 | --
73 | local right=
74 | {
75 | 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711,
76 | 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887,
77 | 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437,
78 | 701408733, 1134903170, 1836311903, 2971215073, 4807526976, 7778742049, 12586269025, 20365011074,
79 | 32951280099, 53316291173, 86267571272, 139583862445, 225851433717, 365435296162, 591286729879,
80 | 956722026041, 1548008755920, 2504730781961, 4052739537881, 6557470319842, 10610209857723,
81 | 17167680177565, 27777890035288, 44945570212853, 72723460248141, 117669030460994, 190392490709135,
82 | 308061521170129, 498454011879264, 806515533049393, 1304969544928657, 2111485077978050,
83 | 3416454622906707, 5527939700884757, 8944394323791464, 14472334024676220, 23416728348467684,
84 | 37889062373143900, 61305790721611580, 99194853094755490, 160500643816367070, 259695496911122560,
85 | 420196140727489660, 679891637638612200, 1100087778366101900, 1779979416004714000,
86 | 2880067194370816000, 4660046610375530000, 7540113804746346000, 12200160415121877000,
87 | 19740274219868226000, 31940434634990105000, 51680708854858334000, 83621143489848440000,
88 | 135301852344706780000, 218922995834555200000
89 | }
90 | assert( #right==99 )
91 |
92 | local N= 80
93 | local res= fib(N)
94 | print( right[N], res )
95 | -- assert( res==right[N] ) -- can't guarantee such a large number will match exactly
96 |
--------------------------------------------------------------------------------
/tests/fifo.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- FIFO.LUA
3 | --
4 | -- Sample program for Lua Lanes
5 | --
6 |
7 | local lanes = require "lanes".configure{shutdown_timeout=3,with_timers=true}
8 |
9 | local atomic_linda = lanes.linda{name = "atom"}
10 | local atomic_inc= lanes.genatomic( atomic_linda, "FIFO_n")
11 |
12 | local fifo_linda = lanes.linda{name = "fifo"}
13 |
14 | -- Lua 5.1 support
15 | local table_unpack = table.unpack or unpack
16 |
17 | assert( atomic_inc()==1)
18 | assert( atomic_inc()==2)
19 |
20 | local function FIFO()
21 | local my_channel= "FIFO_"..atomic_inc()
22 |
23 | return {
24 | -- Giving explicit 'nil' timeout allows numbers to be used as 'my_channel'
25 | --
26 | send = function(self, ...)
27 | fifo_linda:send( nil, my_channel, ...)
28 | end,
29 | receive = function(self, timeout)
30 | return fifo_linda:receive( timeout, my_channel)
31 | end,
32 | channel = my_channel
33 | }
34 | end
35 |
36 | local A = FIFO()
37 | local B = FIFO()
38 |
39 | print "Sending to A.."
40 | A:send( 1,2,3,4,5)
41 |
42 | print "Sending to B.."
43 | B:send( 'a','b','c')
44 |
45 | print "Dumping linda stats.. [1]" -- count everything
46 | local stats = fifo_linda:count()
47 | for key,count in pairs(stats) do
48 | print("channel " .. key .. " contains " .. count .. " entries.")
49 | -- print(i, key_count[1], key_count[2])
50 | end
51 |
52 | print "Dumping linda stats.. [2]" -- query count for known channels one at a time
53 | print("channel " .. A.channel .. " contains " .. fifo_linda:count(A.channel) .. " entries.")
54 | print("channel " .. B.channel .. " contains " .. fifo_linda:count(B.channel) .. " entries.")
55 |
56 | print "Dumping linda stats.. [3]" -- query counts for a predefined list of keys, tossing unknown keys in the mix for good measure
57 | for key,count in pairs(fifo_linda:count("unknown key", A.channel, "unknown key", B.channel, "unknown key", "unknown key")) do
58 | print("channel " .. key .. " contains " .. count .. " entries.")
59 | -- print(i, key_count[1], key_count[2])
60 | end
61 | print "Dumping linda stats.. [4]" -- count everything
62 | for key,contents in pairs(fifo_linda:dump()) do
63 | print("channel " .. key .. ": limit=".. contents.limit, " first=" .. contents.first, " count=" .. contents.count .. " restrict=" .. contents.restrict)
64 | for k,v in pairs(contents.fifo) do
65 | print("[".. k.."] = " .. v)
66 | end
67 | end
68 |
69 | print "Reading A.."
70 | print( A:receive( 1.0))
71 | print( A:receive( 1.0))
72 | print( A:receive( 1.0))
73 | print( A:receive( 1.0))
74 | print( A:receive( 1.0))
75 |
76 | print "Reading B.."
77 | print( B:receive( 2.0))
78 | print( B:receive( 2.0))
79 | print( B:receive( 2.0))
80 |
81 | -- Note: A and B can be passed between threads, or used as upvalues
82 | -- by multiple threads (other parts will be copied but the 'linda'
83 | -- handle is shared userdata and will thus point to the single place)
84 | lanes.timer_lane:cancel() -- hard cancel, 0 timeout
85 | local status, err = lanes.timer_lane:join()
86 | assert(status == nil and err == lanes.cancel_error, "status="..tostring(status).." err="..tostring(err))
87 | print "TEST OK"
88 |
--------------------------------------------------------------------------------
/tests/finalizer.lua:
--------------------------------------------------------------------------------
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 | local lanes = require "lanes"
12 | lanes.configure{with_timers=false}
13 | local finally = lanes.finally
14 |
15 | local FN = "finalizer-test.tmp"
16 |
17 | local cleanup
18 |
19 | local function lane(error_)
20 | set_finalizer(cleanup)
21 |
22 | local st,err = pcall(finally, cleanup) -- should cause an error because called from a lane
23 | assert(not st, "finally() should have thrown an error")
24 | io.stderr:write("finally() raised error '", err, "'\n")
25 |
26 | local f,err = io.open(FN,"w")
27 | if not f then
28 | error( "Could not create "..FN..": "..err)
29 | end
30 |
31 | f:write( "Test file that should get removed." )
32 |
33 | io.stderr:write( "File "..FN.." created\n" )
34 | -- don't forget to close the file immediately, else we won't be able to delete it until f is collected
35 | f:close()
36 |
37 | if error_ then
38 | io.stderr:write("Raising ", tostring(error_), "\n")
39 | error(error_, 0) -- exception here; the value needs NOT be a string
40 | end
41 | -- no exception
42 | io.stderr:write("Lane completed\n")
43 | return true
44 | end
45 |
46 | --
47 | -- This is called at the end of the lane; whether succesful or not.
48 | -- Gets the 'error()' argument as argument ('nil' if normal return).
49 | --
50 | cleanup = function(err)
51 | io.stderr:write "------------------------ In Worker Finalizer -----------------------\n"
52 | -- An error in finalizer will override an error (or success) in the main
53 | -- chunk.
54 | --
55 | --error( "This is important!" )
56 |
57 | if err then
58 | io.stderr:write( "Cleanup after error: "..tostring(err).."\n" )
59 | else
60 | io.stderr:write( "Cleanup after normal return\n" )
61 | end
62 |
63 | local _,err2 = os.remove(FN)
64 | io.stderr:write( "file removal result: ", tostring(err2), "\n")
65 | assert(not err2) -- if this fails, it will be shown in the calling script
66 | -- as an error from the lane itself
67 |
68 | io.stderr:write( "Removed file "..FN.."\n" )
69 | end
70 |
71 | -- we need error_trace_level above "minimal" to get a stack trace out of h:join()
72 | local lgen = lanes.gen("*", { name = 'auto', error_trace_level = "basic" }, lane)
73 |
74 | local do_test = function(error_)
75 |
76 | io.stderr:write "======================== Launching the lane! =======================\n"
77 |
78 | local h = lgen(error_)
79 |
80 | local r,err,stack = h:join() -- wait for the lane (no automatic error propagation)
81 | if not r then
82 | assert(stack, "no stack trace on error, check 'error_trace_level'")
83 | io.stderr:write( "Lane error: "..tostring(err).."\n" )
84 | io.stderr:write( "\t", table.concat(stack,"\t\n"), "\n" )
85 | else
86 | io.stderr:write( "Done\n" )
87 | end
88 | end
89 |
90 | do_test(nil)
91 | -- do return end
92 | do_test("An error")
93 |
94 | local on_exit = function()
95 | finally(nil)
96 | io.stderr:write "=========================In Lanes Finalizer! =======================\n"
97 | local f = io.open(FN,"r")
98 | if f then
99 | error( "CLEANUP DID NOT WORK: "..FN.." still exists!" )
100 | else
101 | io.stderr:write(FN .. " was successfully removed\n")
102 | end
103 | io.stderr:write "Finished!\n"
104 | end
105 |
106 | -- this function is called after script exit, when the state is closed
107 | lanes.finally(on_exit)
108 |
--------------------------------------------------------------------------------
/tests/func_is_string.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes"
2 |
3 | -- Lua 5.4 specific:
4 | if _VERSION >= "Lua 5.4" then
5 | -- go through a string so that the script we are in doesn't trigger a parse error with older Lua
6 | local res = assert(load [[
7 | local lanes = require 'lanes'
8 | local r
9 | do
10 | local h = lanes.gen('*', { name = 'auto' }, lanes.sleep)(0.5)
11 | r = h
12 | end -- h is closed here
13 | return r.status
14 | ]])()
15 | -- when the do...end block is exited, the to-be-closed variable h is closed, which internally calls h:join()
16 | -- therefore the status of the lane should be "done"
17 | assert(res == "done")
18 | print("close result:", res)
19 | end
20 |
21 |
22 | local options = {globals = { b = 666 }}
23 |
24 | local gen1 = lanes.gen("*", { name = 'auto' }, "return true, error('bob')")
25 |
26 | fibLane = gen1()
27 | lanes.sleep(0.1)
28 | print(fibLane, fibLane.status)
29 | local _r, _err, _stk = fibLane:join()
30 | assert(_r == nil, "got " .. tostring(_r) .. " " .. tostring(_err) .. " " .. tostring(_stk))
31 |
32 | local gen2 = lanes.gen(options, { name = 'auto' }, "return b")
33 | local retLane1, retLane2 = gen2(), gen2()
34 |
35 | print( retLane1[1], retLane2[1])
36 | assert(retLane1[1] == 666 and retLane2[1] == 666)
37 | print "TEST OK"
38 |
--------------------------------------------------------------------------------
/tests/hangtest.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- Test case for hang on [1]s and :join()s.
3 | --
4 |
5 | local lanes = require "lanes"
6 |
7 | local function ret(b)
8 | return b
9 | end
10 | local lgen = lanes.gen("*", { name = 'auto' }, 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!"
27 |
--------------------------------------------------------------------------------
/tests/irayo_closure.lua:
--------------------------------------------------------------------------------
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 | local lanes = 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 | -- When some function dereferences a global key, the associated global in the source state
24 | -- isn't sent over the target lane
25 | -- therefore, the necessary functions must either be pulled as upvalues (hence locals)
26 | -- or the globals must exist in the target lanes because the modules have been required there
27 | --
28 | local print=print
29 | local assert=assert
30 |
31 | local function useclosurehere()
32 | assert( print )
33 | print "using the closure"
34 | end
35 |
36 | local lane= lanes.gen( "", { name = 'auto', globals = { data=1, func=useclosurehere } }, testrun )()
37 | print(lane[1])
38 |
--------------------------------------------------------------------------------
/tests/irayo_recursive.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes"
2 | --
3 | -- Bugs filed by irayo Jul-2008
4 | --
5 | --[[
6 | This code showed lack of caching 'select', 'type' etc. in 'src/lanes.lua'.
7 | ]]
8 | local function recurse()
9 | print("level "..i);
10 | if i > 10 then return "finished" end
11 |
12 | --local lanes = require "lanes"
13 |
14 | local lane = lanes.gen( "base,string,lanes_core", { name = 'auto', globals = { ["i"]= i + 1 } }, recurse ) ()
15 | return lane[1]
16 | end
17 |
18 | i = 0;
19 | recurse()
20 |
--------------------------------------------------------------------------------
/tests/lanes_as_upvalue.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes".configure{ with_timers = true, verbose_errors = true} -- with timers enabled
2 |
3 | local function foo()
4 | local lanes = lanes -- lanes as upvalue
5 | end
6 |
7 | local g = lanes.gen( "*", { name = 'auto', error_trace_level = "extended"}, foo)
8 |
9 | -- this should raise an error as lanes.timer_lane is a Lane (a non-deep full userdata)
10 | local res, err = pcall( g)
11 | print( "Generating lane yielded: ", tostring( res), tostring( err))
12 |
--------------------------------------------------------------------------------
/tests/launchtest.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- LAUNCHTEST.LUA Copyright (c) 2007-08, Asko Kauppi
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 | local lanes = require "lanes"
49 |
50 | local g= lanes.gen( LIBS, { name = 'auto' }, 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 r,rc = t[i]:join()
73 | assert( r == true and rc == i )
74 | end
75 |
76 | io.stderr:write( N.." lanes finished.\n" )
77 | end
78 |
79 |
--------------------------------------------------------------------------------
/tests/manual_register.lua:
--------------------------------------------------------------------------------
1 | local RequireAModuleThatExportsGlobalFunctions = function(type_)
2 | -- grab some module that exports C functions, this is good enough for our purpose
3 | local due = require "deep_userdata_example"
4 | -- make one of these a global
5 | GlobalFunc = due.get_deep_count
6 | -- we need to register it so that it is transferable
7 | local lanes = require "lanes"
8 | lanes.register( "GlobalFunc", GlobalFunc)
9 | print(type_, "RequireAModuleThatExportsGlobalFunctions done:", lanes.nameof(GlobalFunc))
10 | end
11 |
12 |
13 | local lanes = require "lanes".configure{on_state_create = RequireAModuleThatExportsGlobalFunctions}
14 |
15 | -- load some module that adds C functions to the global space
16 | RequireAModuleThatExportsGlobalFunctions("main")
17 |
18 | local GlobalFuncUpval = GlobalFunc
19 |
20 | -- a lane that makes use of the above global
21 | local f = function()
22 | GlobalFuncUpval("foo")
23 | print("f done:", lanes.nameof(GlobalFuncUpval))
24 | return 33
25 | end
26 |
27 |
28 | local g = lanes.gen( "*", { name = 'auto' }, f)
29 |
30 | -- generate a lane, this will transfer f, which pulls GlobalFunc.
31 | local h = g()
32 | assert(h[1] == 33)
33 |
--------------------------------------------------------------------------------
/tests/nameof.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes".configure{nb_user_keepers = 100, on_state_create = function() end}
2 |
3 | local SLEEP = function(...)
4 | local k, v = lanes.sleep(...)
5 | assert(k == nil and v == "timeout")
6 | end
7 |
8 | print("Name of table: ", lanes.nameof({}))
9 | print("Name of string.sub: ", lanes.nameof(string.sub))
10 | print("Name of print: ", lanes.nameof(print))
11 |
12 | -- a standalone function without any dependency
13 | local body = function()
14 | local n = 0
15 | for i = 1, 1e30 do
16 | n = n + 1
17 | end
18 | end
19 |
20 | -- start the lane without any library
21 | local h = lanes.gen(nil, { name = 'auto' }, body)()
22 | SLEEP(0.1)
23 | print("Name of lane: ", lanes.nameof(h), "("..h.status..")")
24 | assert(h.status == "running")
25 | -- cancel the lane
26 | h:cancel("line", 1)
27 | SLEEP(0.1)
28 | print("Name of lane: ", lanes.nameof(h), "("..h.status..")")
29 | assert(h.status == "cancelled")
30 | print "TEST OK"
31 |
--------------------------------------------------------------------------------
/tests/objects.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- OBJECTS.LUA
3 | --
4 | -- Tests that objects (metatables) can be passed between lanes.
5 | --
6 |
7 | local lanes = require "lanes"
8 |
9 | local linda= lanes.linda()
10 |
11 | local start_lane= lanes.gen("io", { name = 'auto' },
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 k, obj2= linda:receive("")
22 | assert(k == "" and 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 argument
72 |
73 | linda:send("", b ) -- 2nd object via Linda
74 |
75 | assert(h[1]==true ) -- wait for return
76 |
77 |
--------------------------------------------------------------------------------
/tests/package.lua:
--------------------------------------------------------------------------------
1 | local loaders = package.loaders or package.searchers
2 |
3 | assert(nil == loaders[5])
4 |
5 | local configure_loaders = function(type_)
6 | table.insert(loaders, 4, function() end)
7 | assert(loaders[1])
8 | assert(loaders[2])
9 | assert(loaders[3])
10 | assert(loaders[4])
11 | assert(loaders[5])
12 | print(type_, "loaders configured!")
13 | end
14 |
15 | configure_loaders("main")
16 |
17 | for k,v in pairs(loaders) do
18 | print( k, type(v))
19 | end
20 |
21 | lanes = require "lanes"
22 | lanes.configure{on_state_create = configure_loaders}
--------------------------------------------------------------------------------
/tests/parallel_os_calls.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes"
2 | print( os.date())
3 | local linda = lanes.linda()
4 | local l1 = lanes.gen("os,base", { name = 'auto' }, function() print "start sleeping" linda:receive(3, "null") print("finished_sleeping " .. os.date()) return true end)()
5 | lanes.gen("os,base", { name = 'auto' }, function() print "start sleeping" linda:receive(2, "null") print("finished_sleeping " .. os.date()) end)()
6 | lanes.gen("os,base", { name = 'auto' }, function() print "start sleeping" linda:receive(2, "null") print("finished_sleeping " .. os.date()) end)()
7 | lanes.gen("os,base", { name = 'auto' }, function() print "start sleeping" linda:receive(2, "null") print("finished_sleeping " .. os.date()) end)()
8 | -- wait, else all lanes will get hard-cancelled at stat shutdown
9 | l1:join()
10 | --[[
11 | lanes.gen("os,base", { name = 'auto' }, function() os.execute('sleep 10 && echo finished_sleeping') print( os.date()) end)()
12 | lanes.gen("os,base", { name = 'auto' }, function() os.execute('sleep 10 && echo finished_sleeping') print( os.date()) end)()
13 | lanes.gen("os,base", { name = 'auto' }, function() os.execute('sleep 10 && echo finished_sleeping') print( os.date()) end)()
14 | lanes.gen("os,base", { name = 'auto' }, function() os.execute('sleep 10 && echo finished_sleeping') print( os.date()) end)()
15 | ]]
16 |
--------------------------------------------------------------------------------
/tests/pingpong.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes"
2 | local q = lanes.linda()
3 |
4 | local pingpong = function(name, qr, qs, start)
5 | print("start " .. name, qr, qs, start)
6 | local count = 0
7 | if start then
8 | print(name .. ": sending " .. qs .. " 0")
9 | q:send(qs, 0)
10 | end
11 | while count < 10 do
12 | print(name .. ": receiving " .. qr)
13 | local key, val = q:receive(qr)
14 | if val == nil then
15 | print(name .. ": timeout")
16 | break
17 | end
18 | print(name .. ":" .. val)
19 | val = val + 1
20 | print(name .. ": sending " .. qs .. " " .. tostring(val + 1))
21 | q:send(qs, val)
22 | count = count + 1
23 | end
24 | return "ping!"
25 | end
26 |
27 | -- pingpong("L1", '0', '1', true)
28 | local t1, err1 = lanes.gen("*", { name = 'auto' }, pingpong)("L1", 'a', 'b', true)
29 | local t2, err2 = lanes.gen("*", { name = 'auto' }, pingpong)("L2", 'b', 'a', false)
30 |
31 | local r1, ret1 = t1:join()
32 | assert(r1 == true and ret1 == "ping!")
33 | local r2, ret2 = t2:join()
34 | assert(r2 == true and ret2 == "ping!")
35 | print "TEST OK"
36 |
--------------------------------------------------------------------------------
/tests/protect_allocator.lua:
--------------------------------------------------------------------------------
1 | local print = print
2 |
3 | local lanes = require "lanes".configure{ with_timers = false, allocator="protected"}
4 |
5 | local SLEEP = function(...)
6 | local k, v = lanes.sleep(...)
7 | assert(k == nil and v == "timeout")
8 | end
9 |
10 | local linda = lanes.linda()
11 |
12 | local body = function( id_)
13 | set_finalizer( function( err, stk)
14 | if err == lanes.cancel_error then
15 | -- lane cancellation is performed by throwing a special userdata as error
16 | print( "after cancel")
17 | elseif err then
18 | -- no special error: true error
19 | print( " error: "..tostring(err))
20 | else
21 | -- no error: we just got finalized
22 | print( "[" .. id_ .. "] done")
23 | end
24 | end)
25 |
26 | print( "[" .. id_ .. "] waiting for signal")
27 | -- wait for the semaphore to be available
28 | local _, cost = linda:receive( "lock")
29 | -- starting to work
30 | print( "[" .. id_ .. "] doing some allocations")
31 | for i = 1, cost * (1 + id_ * 0.1) do
32 | local t = { "some", "table", "with", "a", "few", "fields"}
33 | end
34 | linda:send( "key", "done")
35 | end
36 |
37 | -- start threads
38 | local COUNT = 4
39 | local gen = lanes.gen( "*", { name = 'auto' }, body)
40 | for i = 1, COUNT do
41 | gen( i)
42 | end
43 |
44 | -- wait a bit
45 | print "waiting a bit ..."
46 | SLEEP(2)
47 | -- tell lanes to start running
48 | print "GO!"
49 | for i = 1, COUNT do
50 | linda:send( "lock", 300000)
51 | end
52 |
53 | -- wait for completion
54 | print "wait for completion"
55 | linda:receive_batched("key", COUNT)
56 | print "waiting a bit more ..."
57 | SLEEP(1)
58 | print "SUCCESS"
59 |
--------------------------------------------------------------------------------
/tests/protectproxy.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes".configure{ with_timers = false}
2 |
3 | local body = function( param)
4 | print ( "lane body: " .. param)
5 | return 1
6 | end
7 |
8 | local gen = lanes.gen( "*", { name = 'auto' }, body)
9 |
10 | local mylane = gen( "hello")
11 |
12 | local result = mylane[1]
13 |
14 | -- make sure we have properly protected the lane
15 |
16 | -- can't access the metatable
17 | print( "metatable:" .. tostring( getmetatable( mylane)))
18 |
19 | -- can't write to the userdata
20 | print( "lane result: " .. mylane[1])
21 |
22 | -- read nonexistent values -> nil
23 | print "reading nonexistent return value"
24 | a = mylane[2]
25 |
26 | print "writing to the lane -> error"
27 | mylane[4] = true
28 |
--------------------------------------------------------------------------------
/tests/recursive.lua:
--------------------------------------------------------------------------------
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 <= 0 then
11 | return "done!"
12 | end
13 |
14 | local lanes = require "lanes"
15 | local lane = lanes.gen("*", { name = 'auto' }, func)( depth-1 )
16 | return lane[1]
17 | end
18 |
19 | local v= func(100)
20 | assert(v=="done!")
21 | io.stderr:write("TEST OK\n")
22 |
--------------------------------------------------------------------------------
/tests/require.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- REQUIRE.LUA
3 | --
4 | -- Test that 'require' works from sublanes
5 | --
6 | lanes = require "lanes"
7 | lanes.configure{with_timers = false}
8 |
9 | local function a_lane()
10 | print "IN A LANE"
11 | -- To require 'math' we still actually need to have it initialized for
12 | -- the lane.
13 | --
14 | require "math"
15 | assert( math and math.sqrt )
16 | assert( math.sqrt(4)==2 )
17 |
18 | assert( lanes==nil )
19 | local lanes = require "lanes"
20 | assert( lanes and lanes.gen )
21 |
22 | local h= lanes.gen( { name = 'auto' }, function() return 42 end ) ()
23 | local v= h[1]
24 |
25 | return v==42
26 | end
27 |
28 | -- string and table for Lanes itself, package to be able to require in the lane, math for the actual work
29 | local gen= lanes.gen( "math,package,string,table", { name = 'auto', package={} },a_lane )
30 |
31 | local h= gen()
32 | local ret= h[1]
33 | assert( ret==true )
34 | print "TEST OK"
--------------------------------------------------------------------------------
/tests/rupval.lua:
--------------------------------------------------------------------------------
1 | lanes = require "lanes".configure{with_timers=false}
2 |
3 | -- make sure we can copy functions with interdependant upvalues
4 |
5 | local x = 33
6 | local y = 44
7 | local z = 55
8 |
9 | local b
10 | local a = function( n)
11 | print( "a", n)
12 | return n <= 0 and x or b( n-1)
13 | end
14 |
15 | local c
16 | b = function( n)
17 | print( "b", n)
18 | return n <= 0 and y or c( n-1)
19 | end
20 |
21 | c = function( n)
22 | print( "c", n)
23 | return n <= 0 and z or a( n-1)
24 | end
25 |
26 | local g = lanes.gen( "base", { name = 'auto' }, a)
27 |
28 | local l = g(7)
29 | local _, r = l:join()
30 | assert(r == y)
31 | print(r)
32 |
33 | local l = g(8)
34 | local _, r = l:join()
35 | assert(r == z)
36 | print(r)
37 |
38 | local l = g(9)
39 | local _, r = l:join()
40 | assert(r == x)
41 | print(r)
42 |
43 | print "TEST OK"
44 |
--------------------------------------------------------------------------------
/tests/timer.lua:
--------------------------------------------------------------------------------
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 | local lanes = require "lanes".configure{with_timers=true}
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 | local PING_DURATION = 20
22 |
23 | local step= {}
24 |
25 | lanes.timer( linda, T1, 1.0, 1.0 )
26 | step[T1]= 1.0
27 |
28 | PRINT( "\n*** Starting 1s Timer (not synced to wall clock) ***\n" )
29 |
30 | local v_first
31 | local v_last= {} -- { [channel]= num }
32 | local T2_first_round= true
33 |
34 | local caught= {} -- { [T1]= bool, [T2]= bool }
35 |
36 | while true do
37 | io.stderr:write("waiting...\t")
38 | local channel, v = linda:receive( 6.0, T1,T2 )
39 | assert( channel==T1 or channel==T2 )
40 | caught[channel]= true
41 |
42 | io.stderr:write( ((channel==T1) and "" or "\t\t").. string.format("%.3f",v),"\n" )
43 | assert( type(v)=="number" )
44 |
45 | if v_last[channel] then
46 | if channel==T2 and T2_first_round then
47 | -- do not make measurements, first round is not 5secs due to wall clock adjustment
48 | T2_first_round= false
49 | else
50 | local dt = math.abs(v-v_last[channel]- step[channel])
51 | assert( dt < 0.02, "channel " .. channel .. " is late: " .. dt)
52 | end
53 | end
54 |
55 | if not v_first then
56 | v_first= v
57 | elseif v-v_first > 3.0 and (not step[T2]) then
58 | PRINT( "\n*** Starting 5s timer (synced to wall clock) ***\n" )
59 |
60 | -- The first event can be in the past (just cut seconds down to 5s)
61 | --
62 | local date= os.date("*t")
63 | date.sec = date.sec - date.sec%5
64 |
65 | lanes.timer( linda, T2, date, 5.0 )
66 | step[T2]= 5.0
67 |
68 | elseif v-v_first > PING_DURATION then -- exit condition
69 | break
70 | end
71 | v_last[channel]= v
72 | end
73 |
74 | -- Windows version had a bug where T2 timers were not coming through, at all.
75 | -- AKa 24-Jan-2009
76 | --
77 | assert( caught[T1] )
78 | assert( caught[T2] )
79 |
80 |
81 | PRINT( "\n*** Listing timers ***\n" )
82 | local r = lanes.timers() -- list of {linda, key, {}}
83 | for _,t in ipairs( r) do
84 | local linda, key, timer = t[1], t[2], t[3]
85 | print( tostring( linda), key, timer[1], timer[2])
86 | end
87 |
88 |
89 | PRINT( "\n*** Clearing timers ***\n" )
90 |
91 | lanes.timer( linda, T1, 0 ) -- reset; no reoccuring ticks
92 | lanes.timer( linda, T2, 0 )
93 |
94 | linda:receive( 0, T1 ) -- clear out; there could be one tick left
95 | linda:receive( 0, T2 )
96 |
97 | local _count, _val = linda:get(T1)
98 | assert(_count == 0 and _val == nil)
99 |
100 | local _count, _val = linda:get(T2)
101 | assert(_count == 0 and _val == nil)
102 |
103 | PRINT "...making sure no ticks are coming..."
104 |
105 | local k,v= linda:receive( 10, T1,T2 ) -- should not get any
106 | assert(k==nil and v == "timeout")
107 |
108 | lanes.timer_lane:cancel() -- hard cancel, 0 timeout
109 | print (lanes.timer_lane[1], lanes.timer_lane[2])
--------------------------------------------------------------------------------
/tests/tobeclosed.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- TOBECLOSED.LUA Copyright (C) 2024 benoit Germain
3 | --
4 |
5 | local require_lanes_result_1, require_lanes_result_2 = require "lanes"
6 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
7 | local lanes = require_lanes_result_1
8 |
9 | local WR = function(...)
10 | local str=""
11 | for i=1,select('#',...) do
12 | local v = select(i,...)
13 | if type(v) == "function" then
14 | local infos = debug.getinfo(v)
15 | --[[for k,v in pairs(infos) do
16 | print(k,v)
17 | end]]
18 | v = infos.source..":"..infos.linedefined
19 | end
20 | str= str..tostring(v).."\t"
21 | end
22 | if io then
23 | io.stderr:write(str.."\n")
24 | end
25 | end
26 |
27 | -- #################################################################################################
28 | -- test that table and function handlers work fine, even with upvalues
29 | WR "================================================================================================"
30 | WR "Basic to-be-closed"
31 | do
32 | local closed_by_f = false
33 | local closed_by_t = false
34 | do
35 | local close_handler_f = function(linda_, err_)
36 | WR("f closing ", linda_)
37 | closed_by_f = true
38 | end
39 | local lf = lanes.linda{name = "closed by f", close_handler = close_handler_f}
40 |
41 | local close_handler_t = setmetatable({},
42 | {
43 | __call = function(self_, linda_)
44 | WR("t closing ", linda_)
45 | closed_by_t = true
46 | end
47 | }
48 | )
49 | local lt = lanes.linda{name = "closed by t", close_handler = close_handler_t}
50 | end
51 | assert(closed_by_f == true)
52 | assert(closed_by_t == true)
53 | end
54 |
55 |
56 | -- #################################################################################################
57 | -- test that linda transfer still works even when they contain a close handler
58 | WR "================================================================================================"
59 | WR "Through Linda"
60 | do
61 | local l = lanes.linda{name = "channel"}
62 |
63 | local close_handler_f = function(linda_, err_)
64 | WR("f closing ", linda_)
65 | linda_:set("closed", true)
66 | end
67 | local l_in = lanes.linda{name = "voyager", close_handler = close_handler_f}
68 | l:set("trip", l_in)
69 |
70 | do
71 |
72 | local _, l_out = l:get("trip")
73 | end
74 | local _count, _closed = l_in:get("closed")
75 | assert(_count == 1 and _closed == true)
76 | end
77 |
78 | -- #################################################################################################
79 | -- test that lane closing works
80 | WR "================================================================================================"
81 | WR "Lane closing"
82 | do
83 | local lane_body = function()
84 | WR "In lane body"
85 | lanes.sleep(1)
86 | return "success"
87 | end
88 |
89 | local h = lanes.gen("*", { name = 'auto' }, lane_body)()
90 | do
91 | local tobeclosed = h
92 | end
93 | assert(h.status == "done")
94 | return "success"
95 | end
96 |
97 | -- #################################################################################################
98 | -- test that linda with a close handler can still be transferred in a Lane
99 | WR "================================================================================================"
100 | WR "Linda closing through Lane"
101 | do
102 | local l = lanes.linda{name = "channel"}
103 | local lane_body = function(l_arg_)
104 | WR "In lane body"
105 | -- linda obtained through a linda
106 | local _count, l_out = l:get("trip")
107 | -- linda from arguments
108 | local l_arg = l_arg_
109 | return "done"
110 | end
111 |
112 | local close_handler_f = function(linda_, err_)
113 | WR("f closing ", linda_)
114 | local _count, _closed = linda_:get("closed")
115 | linda_:set("closed", (_closed or 0) + 1)
116 | end
117 | local l_in = lanes.linda{name = "voyager", close_handler = close_handler_f}
118 | l:set("trip", l_in)
119 |
120 | do
121 | local r, ret = lanes.gen("*", { name = 'auto' }, lane_body)(l_in):join()
122 | assert(r == true and ret == "done")
123 | end
124 | local _count, _closed = l_in:get("closed")
125 | assert(_count == 1 and _closed == 2)
126 | end
127 |
128 | WR "================================================================================================"
129 | WR "TEST OK"
130 |
--------------------------------------------------------------------------------
/tests/track_lanes.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes" .configure{ with_timers = false, track_lanes = true}
2 |
3 | local SLEEP = function(...)
4 | local k, v = lanes.sleep(...)
5 | assert(k == nil and v == "timeout")
6 | end
7 |
8 | print "hello"
9 |
10 | local track = function( title_, expected_count_)
11 | print( title_)
12 | local count = 0
13 | local threads = lanes.threads()
14 | for k, v in pairs(threads)
15 | do
16 | print( k, v.name, v.status)
17 | count = count + 1
18 | end
19 | assert(count == expected_count_, "unexpected active lane count " .. count .. " ~= " .. expected_count_)
20 | print( "\n")
21 | return threads
22 | end
23 |
24 | local sleeper = function( name_, seconds_)
25 | -- no set_finalizer in main thread
26 | if set_finalizer
27 | then
28 | set_finalizer(function() print("Finalizing '" .. name_ .. "'") end)
29 | end
30 | -- print( "entering '" .. name_ .. "'")
31 | local lanes = require "lanes"
32 | -- no lane_threadname in main thread
33 | if lane_threadname
34 | then
35 | -- print( "lane_threadname('" .. name_ .. "')")
36 | lane_threadname( name_)
37 | end
38 | -- suspend the lane for the specified duration
39 | lanes.sleep(seconds_)
40 | -- print( "exiting '" .. name_ .. "'")
41 | end
42 |
43 | -- sleeper( "main", 1)
44 |
45 | -- the generator
46 | local g = lanes.gen( "*", { name = 'auto' }, sleeper)
47 |
48 | -- start a forever-waiting lane (nil timeout)
49 | local forever = g( "forever", 'indefinitely')
50 |
51 | -- start a lane that will last 2 seconds
52 | local ephemeral1 = g( "two_seconds", 2)
53 |
54 | -- give a bit of time to reach the linda waiting call
55 | SLEEP(0.1)
56 |
57 | -- list the known lanes (both should be living lanes)
58 | local threads = track( "============= START", 2)
59 | -- two_seconds forever
60 | assert(threads[1].status == 'waiting' and threads[2].status == 'waiting')
61 |
62 | -- wait until ephemeral1 has completed, should take about 2 seconds
63 | repeat
64 | SLEEP(0.1)
65 | until ephemeral1.status == "done"
66 |
67 | local threads = track( "============= two_seconds dead", 2)
68 | -- two_seconds forever
69 | assert(threads[1].status == 'done' and threads[2].status == 'waiting')
70 |
71 | -- start another lane that will last 2 seconds, with the same name
72 | local ephemeral2 = g( "two_seconds", 2)
73 |
74 | -- give a bit of time to reach the linda waiting call
75 | SLEEP( 0.1)
76 |
77 | -- list the known lanes
78 | -- we should have 3 lanes
79 | local threads = track( "============= ANOTHER", 3)
80 | -- two_seconds #2 two_seconds #1 forever
81 | assert(threads[1].status == 'waiting' and threads[2].status == 'done' and threads[3].status == 'waiting')
82 |
83 | -- this will collect all lane handles.
84 | -- since ephemeral1 has completed, it is no longer tracked, but the other 2 should still be
85 | forever = nil
86 | ephemeral1 = nil
87 | ephemeral2 = nil
88 | collectgarbage()
89 |
90 | -- list the known lanes
91 | local threads = track( "============= AFTER COLLECTGARBAGE", 2)
92 | -- two_seconds #2 forever
93 | assert(threads[1].status == 'waiting' and threads[2].status == 'waiting')
94 |
95 | -- wait a bit more for ephemeral2 to exit, so that we get to have a free-running lane terminate itself before shutdown
96 | SLEEP(3)
97 |
98 | print "============= AFTER WAITING"
99 | -- this is printed after Universe shutdown terminates the only remaining lane, 'forever'
100 | lanes.finally(function() print "TEST OK" end)
101 |
--------------------------------------------------------------------------------
/unit_tests/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
2 | *.gch
3 | *.map
4 |
--------------------------------------------------------------------------------
/unit_tests/UnitTests.makefile:
--------------------------------------------------------------------------------
1 | #
2 | # Lanes/unit_tests/UnitTests.makefile
3 | #
4 |
5 | include ../Shared.makefile
6 |
7 | _TARGET := UnitTests$(_LUAEXT)
8 |
9 | _SRC := $(wildcard *.cpp) ../src/deep.cpp ../src/compat.cpp
10 |
11 | _OBJ := $(_SRC:.cpp=.o)
12 |
13 | #---
14 | all: $(_TARGET)
15 | $(info CC: $(CC))
16 | $(info _TARGET: $(_TARGET))
17 | $(info _SRC: $(_SRC))
18 |
19 | _pch.hpp.gch: _pch.hpp
20 | $(CC) -I "../.." $(CFLAGS) -x c++-header _pch.hpp -o _pch.hpp.gch
21 |
22 | %.o: %.cpp _pch.hpp.gch *.h *.hpp UnitTests.makefile
23 | $(CC) -I "../.." $(CFLAGS) -c $< -o $@
24 |
25 | # Note: Don't put $(LUA_LIBS) ahead of $^; MSYS will not like that (I think)
26 | #
27 | $(_TARGET): $(_OBJ)
28 | $(CC) $^ $(LIBS) $(LUA_LIBS) -o $@
29 |
30 | clean:
31 | -rm -rf $(_TARGET) *.o *.map *.gch
32 |
33 | .PHONY: all clean
34 |
--------------------------------------------------------------------------------
/unit_tests/UnitTests.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Lanes
12 |
13 |
14 |
15 |
16 | Lanes
17 |
18 |
19 | Catch2
20 |
21 |
22 |
23 |
24 |
25 |
26 | Lanes
27 |
28 |
29 | Lanes
30 |
31 |
32 | Catch2
33 |
34 |
35 |
36 |
37 | {06101607-f449-40ce-970a-55fc8387aa97}
38 |
39 |
40 | {309f0ee2-05fe-42f8-9011-7d896e58b1d3}
41 |
42 |
43 | {dc01ee0b-82d1-4a12-9bcd-d9caee1b064c}
44 |
45 |
46 | {fb19e619-8de3-4d82-84d0-21e80d63331d}
47 |
48 |
49 | {3cf4f618-1863-4de0-8761-5fca21e6b07c}
50 |
51 |
52 | {2e1bf85c-7722-42ba-86f8-ac0f5a494ac5}
53 |
54 |
55 | {c62af5d9-9161-4ca1-9b58-6837e2907e35}
56 |
57 |
58 |
59 |
60 | Scripts\linda
61 |
62 |
63 | Scripts\linda
64 |
65 |
66 | Scripts\lane
67 |
68 |
69 | Scripts\lane
70 |
71 |
72 | Scripts\lane
73 |
74 |
75 | Scripts
76 |
77 |
78 | Scripts
79 |
80 |
81 | Scripts\lane
82 |
83 |
84 | Scripts\lane
85 |
86 |
87 | Scripts\lane
88 |
89 |
90 | Scripts\lane
91 |
92 |
93 | Scripts\lane
94 |
95 |
96 | Scripts\lane
97 |
98 |
99 | Scripts\coro
100 |
101 |
102 | Scripts\coro
103 |
104 |
105 | Scripts\lane
106 |
107 |
108 | Scripts\linda
109 |
110 |
111 | Make
112 |
113 |
114 | Catch2
115 |
116 |
117 | Scripts\lane
118 |
119 |
120 | Scripts\linda
121 |
122 |
123 |
--------------------------------------------------------------------------------
/unit_tests/_pch.cpp:
--------------------------------------------------------------------------------
1 | #include "_pch.hpp"
2 |
--------------------------------------------------------------------------------
/unit_tests/_pch.hpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "catch_amalgamated.hpp"
10 |
11 | #ifdef __cplusplus
12 | extern "C"
13 | {
14 | #endif // __cplusplus
15 | #include "lua.h"
16 | #include "lualib.h"
17 | #include "lauxlib.h"
18 | #ifdef __cplusplus
19 | }
20 | #endif // __cplusplus
21 |
22 | #include "lanes/src/allocator.hpp"
23 | #include "lanes/src/compat.hpp"
24 | #include "lanes/src/universe.hpp"
25 |
--------------------------------------------------------------------------------
/unit_tests/deep_tests.cpp:
--------------------------------------------------------------------------------
1 | #include "_pch.hpp"
2 | #include "shared.h"
3 |
4 | // yeah it's dirty, I will do better someday
5 | #include "../deep_userdata_example/deep_userdata_example.cpp"
6 |
7 |
8 | // #################################################################################################
9 | // #################################################################################################
10 |
11 | TEST_CASE("misc.deep_userdata.example")
12 | {
13 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } };
14 | S.requireSuccess(
15 | " lanes = require 'lanes'.configure()"
16 | " fixture = require 'fixture'"
17 | " due = require 'deep_userdata_example'"
18 | );
19 |
20 | SECTION("garbage collection collects")
21 | {
22 | S.requireSuccess("assert(true)");
23 | S.requireFailure("assert(false)");
24 | if constexpr (LUA_VERSION_NUM >= 503) { // Lua < 5.3 only supports a table uservalue
25 | S.requireSuccess(
26 | // create a deep userdata object without referencing it. First uservalue is a function, and should be called on __gc
27 | " due.new_deep(1):setuv(1, function() collected = collected and collected + 1 or 1 end)"
28 | " due.new_deep(1):setuv(1, function() collected = collected and collected + 1 or 1 end)"
29 | " collectgarbage()" // and collect it
30 | " assert(collected == 2)"
31 | );
32 | }
33 | }
34 |
35 | // ---------------------------------------------------------------------------------------------
36 |
37 | SECTION("reference counting")
38 | {
39 | S.requireSuccess(
40 | " d = due.new_deep(1)" // create a deep userdata object
41 | " d:set(42)" // set some value
42 | " assert(d:refcount() == 1)"
43 | );
44 | S.requireSuccess(
45 | " l = lanes.linda()"
46 | " b, s = l:set('k', d, d)" // store it twice in the linda
47 | " assert(b == false and s == 'under')" // no waking, under capacity
48 | " assert(d:refcount() == 2)" // 1 ref here, 1 in the keeper (even if we sent it twice)
49 | );
50 | S.requireSuccess(
51 | " n, d = l:get('k')" // pull it out of the linda
52 | " assert(n == 1 and type(d) == 'userdata')" // got 1 item out of the linda
53 | " assert(d:get() == 42 and d:refcount() == 2)" // 1 ref here, 1 in the keeper (even if we sent it twice)
54 | );
55 | S.requireSuccess(
56 | " l = nil"
57 | " collectgarbage()" // clears the linda, removes its storage from the keeper
58 | " lanes.collectgarbage()" // collect garbage inside the keepers too, to finish cleanup
59 | " assert(d:refcount() == 1)" // 1 ref here
60 | );
61 | if constexpr (LUA_VERSION_NUM >= 503) { // Lua < 5.3 only supports a table uservalue
62 | S.requireSuccess(
63 | " d:setuv(1, function() collected = collected and collected + 1 or 1 end)"
64 | " d = nil" // clear last reference
65 | " collectgarbage()" // force collection
66 | " assert(collected == 1)" // we should see it
67 | );
68 | }
69 | }
70 |
71 | // ---------------------------------------------------------------------------------------------
72 |
73 | SECTION("collection from inside a Linda")
74 | {
75 | S.requireSuccess(
76 | " d = due.new_deep(1)" // create a deep userdata object
77 | " d:set(42)" // set some value
78 | " assert(d:refcount() == 1)"
79 | );
80 | S.requireSuccess(
81 | " l = lanes.linda()"
82 | " b, s = l:set('k', d, d)" // store it twice in the linda
83 | " assert(b == false and s == 'under')" // no waking, under capacity
84 | " assert(d:refcount() == 2)" // 1 ref here, 1 in the keeper (even if we sent it twice)
85 | );
86 | S.requireSuccess(
87 | " d = nil"
88 | " collectgarbage()" // force collection
89 | " l = nil"
90 | " collectgarbage()" // clears the linda, removes its storage from the keeper
91 | " lanes.collectgarbage()" // collect garbage inside the keepers too, to finish cleanup
92 | " assert(due.get_deep_count() == 0)"
93 | );
94 | }
95 | }
--------------------------------------------------------------------------------
/unit_tests/legacy_tests.cpp:
--------------------------------------------------------------------------------
1 | #include "_pch.hpp"
2 | #include "shared.h"
3 |
4 | #define RUN_LEGACY_TESTS 1
5 | #if RUN_LEGACY_TESTS
6 |
7 | // #################################################################################################
8 | // #################################################################################################
9 |
10 | // unfortunately, VS Test adapter does not list individual sections,
11 | // so let's create a separate test case for each file with an ugly macro...
12 |
13 | #define MAKE_TEST_CASE(FILE) \
14 | TEST_CASE("scripted_tests.legacy." #FILE) \
15 | { \
16 | FileRunner _runner(R"(.\tests\)"); \
17 | _runner.performTest(FileRunnerParam{ #FILE, TestType::AssertNoLuaError }); \
18 | }
19 |
20 | MAKE_TEST_CASE(appendud)
21 | MAKE_TEST_CASE(atexit)
22 | MAKE_TEST_CASE(atomic)
23 | MAKE_TEST_CASE(basic)
24 | MAKE_TEST_CASE(cancel)
25 | MAKE_TEST_CASE(cyclic)
26 | MAKE_TEST_CASE(deadlock)
27 | MAKE_TEST_CASE(errhangtest)
28 | MAKE_TEST_CASE(error)
29 | MAKE_TEST_CASE(fibonacci)
30 | MAKE_TEST_CASE(fifo)
31 | MAKE_TEST_CASE(finalizer)
32 | MAKE_TEST_CASE(func_is_string)
33 | MAKE_TEST_CASE(irayo_closure)
34 | MAKE_TEST_CASE(irayo_recursive)
35 | MAKE_TEST_CASE(keeper)
36 | //MAKE_TEST_CASE(linda_perf)
37 | MAKE_TEST_CASE(manual_register)
38 | MAKE_TEST_CASE(nameof)
39 | MAKE_TEST_CASE(objects)
40 | MAKE_TEST_CASE(package)
41 | MAKE_TEST_CASE(pingpong)
42 | MAKE_TEST_CASE(recursive)
43 | MAKE_TEST_CASE(require)
44 | MAKE_TEST_CASE(rupval)
45 | MAKE_TEST_CASE(timer)
46 | #if LUA_VERSION_NUM == 504
47 | MAKE_TEST_CASE(tobeclosed)
48 | #endif // LUA_VERSION_NUM
49 | MAKE_TEST_CASE(track_lanes)
50 |
51 | /*
52 | TEST_CASE("lanes.legacy_scripted_tests")
53 | {
54 | auto const& _testParam = GENERATE(
55 | FileRunnerParam{ "appendud", TestType::AssertNoLuaError } // 0
56 | , FileRunnerParam{ "atexit", TestType::AssertNoLuaError } // 1
57 | , FileRunnerParam{ "atomic", TestType::AssertNoLuaError } // 2
58 | , FileRunnerParam{ "basic", TestType::AssertNoLuaError } // 3
59 | , FileRunnerParam{ "cancel", TestType::AssertNoLuaError } // 4
60 | , FileRunnerParam{ "cyclic", TestType::AssertNoLuaError } // 5
61 | , FileRunnerParam{ "deadlock", TestType::AssertNoLuaError } // 6
62 | , FileRunnerParam{ "errhangtest", TestType::AssertNoLuaError } // 7
63 | , FileRunnerParam{ "error", TestType::AssertNoLuaError } // 8
64 | , FileRunnerParam{ "fibonacci", TestType::AssertNoLuaError } // 9
65 | , FileRunnerParam{ "fifo", TestType::AssertNoLuaError } // 10
66 | , FileRunnerParam{ "finalizer", TestType::AssertNoLuaError } // 11
67 | , FileRunnerParam{ "func_is_string", TestType::AssertNoLuaError } // 12
68 | , FileRunnerParam{ "irayo_closure", TestType::AssertNoLuaError } // 13
69 | , FileRunnerParam{ "irayo_recursive", TestType::AssertNoLuaError } // 14
70 | , FileRunnerParam{ "keeper", TestType::AssertNoLuaError } // 15
71 | //, FileRunnerParam{ "linda_perf", TestType::AssertNoLuaError }
72 | , FileRunnerParam{ LUA54_ONLY("manual_register"), TestType::AssertNoLuaError } // 16: uses lfs module, currently not available for non-5.4 flavors
73 | , FileRunnerParam{ "nameof", TestType::AssertNoLuaError } // 17
74 | , FileRunnerParam{ "objects", TestType::AssertNoLuaError } // 18
75 | , FileRunnerParam{ "package", TestType::AssertNoLuaError } // 19
76 | , FileRunnerParam{ "pingpong", TestType::AssertNoLuaError } // 20
77 | , FileRunnerParam{ "recursive", TestType::AssertNoLuaError } // 21
78 | , FileRunnerParam{ "require", TestType::AssertNoLuaError } // 22
79 | , FileRunnerParam{ "rupval", TestType::AssertNoLuaError } // 23
80 | , FileRunnerParam{ "timer", TestType::AssertNoLuaError } // 24
81 | , FileRunnerParam{ LUA54_ONLY("tobeclosed"), TestType::AssertNoLuaError } // 25
82 | , FileRunnerParam{ "track_lanes", TestType::AssertNoLuaError } // 26
83 | );
84 |
85 | FileRunner _runner(R"(.\tests\)");
86 | _runner.performTest(_testParam);
87 | }
88 | */
89 |
90 | #endif // RUN_LEGACY_TESTS
--------------------------------------------------------------------------------
/unit_tests/scripts/_utils.lua:
--------------------------------------------------------------------------------
1 | local io = assert(io)
2 | local pairs = assert(pairs)
3 | local select = assert(select)
4 | local string_format = assert(string.format)
5 | local tostring = assert(tostring)
6 | local type = assert(type)
7 |
8 | local print_id = 0
9 | local P = function(whence_, ...)
10 | if not io then return end
11 | print_id = print_id + 1
12 | local _str = string_format("%s: %02d.\t", whence_, print_id)
13 | for _i = 1, select('#', ...) do
14 | _str = _str .. tostring(select(_i, ...)) .. "\t"
15 | end
16 | if io then
17 | io.stderr:write(_str .. "\n")
18 | end
19 | end
20 |
21 | local MAKE_PRINT = function()
22 | local _whence = lane_threadname and lane_threadname() or "main"
23 | return function(...)
24 | P(_whence, ...)
25 | end
26 | end
27 |
28 | local tables_match
29 |
30 | -- true if 'a' is a subtable of 'b'
31 | --
32 | local function subtable(a, b)
33 | --
34 | assert(type(a)=="table" and type(b)=="table")
35 |
36 | for k,v in pairs(b) do
37 | if type(v)~=type(a[k]) then
38 | return false -- not subtable (different types, or missing key)
39 | elseif type(v)=="table" then
40 | if not tables_match(v,a[k]) then return false end
41 | else
42 | if a[k] ~= v then return false end
43 | end
44 | end
45 | return true -- is a subtable
46 | end
47 |
48 | -- true when contents of 'a' and 'b' are identical
49 | --
50 | tables_match = function(a, b)
51 | return subtable(a, b) and subtable(b, a)
52 | end
53 |
54 | local function dump_error_stack(error_reporting_mode_, stack)
55 | local PRINT = MAKE_PRINT()
56 | if error_reporting_mode_ == "minimal" then
57 | assert(stack == nil, table.concat{"stack is a ", type(stack)})
58 | elseif error_reporting_mode_ == "basic" then -- each stack line is a string in basic mode
59 | PRINT("STACK:\n", table.concat(stack,"\n\t"));
60 | else -- each stack line is a table in extended mode
61 | PRINT "STACK:"
62 | for line, details in pairs(stack) do
63 | PRINT("\t", line);
64 | for detail, value in pairs(details) do
65 | PRINT("\t\t", detail, ": ", value)
66 | end
67 | end
68 | end
69 | end
70 |
71 | return {
72 | MAKE_PRINT = MAKE_PRINT,
73 | tables_match = tables_match,
74 | dump_error_stack = dump_error_stack
75 | }
76 |
--------------------------------------------------------------------------------
/unit_tests/scripts/coro/basics.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes"
2 |
3 | local fixture = require "fixture"
4 | lanes.finally(fixture.throwing_finalizer)
5 |
6 | local utils = lanes.require "_utils"
7 | local PRINT = utils.MAKE_PRINT()
8 |
9 | if true then
10 | -- a lane body that just returns some value
11 | local lane = function(msg_)
12 | local utils = lanes.require "_utils"
13 | local PRINT = utils.MAKE_PRINT()
14 | PRINT "In lane"
15 | assert(msg_ == "hi")
16 | return "bye"
17 | end
18 |
19 | -- the generator
20 | local g1 = lanes.coro("*", {name = "auto"}, lane)
21 |
22 | -- launch lane
23 | local h1 = g1("hi")
24 |
25 | local r = h1[1]
26 | assert(r == "bye")
27 | end
28 |
29 | -- a lane coroutine that yields back what got in, one element at a time
30 | local yielder = function(...)
31 | local utils = lanes.require "_utils"
32 | local PRINT = utils.MAKE_PRINT()
33 | PRINT "In lane"
34 | for _i = 1, select('#', ...) do
35 | local _val = select(_i, ...)
36 | PRINT("yielding #", _i, _val)
37 | local _ack = coroutine.yield(_val)
38 | assert(_ack == _i)
39 | end
40 | return "done!"
41 | end
42 |
43 | if true then
44 | -- if we start a non-coroutine lane with a yielding function, we should get an error, right?
45 | local fun_g = lanes.gen("*", { name = 'auto' }, yielder)
46 | local h = fun_g("hello", "world", "!")
47 | local err, status, stack = h:join()
48 | PRINT(err, status, stack)
49 | -- the actual error message is not the same for Lua 5.1
50 | -- of course, it also has to be different for LuaJIT as well
51 | -- also, LuaJIT prepends a file:line to the actual error message, which Lua5.1 does not.
52 | local msgs = {
53 | ["Lua 5.1"] = jit and "attempt to yield across C-call boundary" or "attempt to yield across metamethod/C-call boundary",
54 | ["Lua 5.2"] = "attempt to yield from outside a coroutine",
55 | ["Lua 5.3"] = "attempt to yield from outside a coroutine",
56 | ["Lua 5.4"] = "attempt to yield from outside a coroutine"
57 | }
58 | local expected_msg = msgs[_VERSION]
59 | PRINT("expected_msg = " .. expected_msg)
60 | assert(err == nil and string.find(status, expected_msg, 1, true) and stack == nil, "status = " .. status)
61 | end
62 |
63 | -- the generator
64 | local coro_g = lanes.coro("*", {name = "auto"}, yielder)
65 |
66 | if true then
67 | -- launch coroutine lane
68 | local h2 = coro_g("hello", "world", "!")
69 | -- read the yielded values, sending back the expected index
70 | assert(h2:resume(1) == "hello")
71 | assert(h2:resume(2) == "world")
72 | assert(h2:resume(3) == "!")
73 | -- the lane return value is available as usual
74 | local r = h2[1]
75 | assert(r == "done!")
76 | end
77 |
78 | if true then
79 | -- another coroutine lane
80 | local h3 = coro_g("hello", "world", "!")
81 |
82 | -- yielded values are available as regular return values
83 | assert(h3[1] == "hello" and h3.status == "suspended")
84 | -- since we consumed the returned values, they should not be here when we resume
85 | assert(h3:resume(1) == nil)
86 |
87 | -- similarly, we can get them with join()
88 | local r3, ret3 = h3:join()
89 | assert(r3 == true and ret3 == "world" and h3.status == "suspended")
90 | -- since we consumed the returned values, they should not be here when we resume
91 | assert(h3:resume(2) == nil)
92 |
93 | -- the rest should work as usual
94 | assert(h3:resume(3) == "!")
95 |
96 | -- the final return value of the lane body remains to be read
97 | local r3, ret3 = h3:join()
98 | assert(r3 == true and ret3 == "done!" and h3.status == "done")
99 | end
100 |
--------------------------------------------------------------------------------
/unit_tests/scripts/coro/error_handling.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes".configure{strip_functions = true}
2 |
3 | local fixture = require "fixture"
4 | lanes.finally(fixture.throwing_finalizer)
5 |
6 | local utils = lanes.require "_utils"
7 | local PRINT = utils.MAKE_PRINT()
8 |
9 | -- a lane coroutine that yields back what got in, one element at a time
10 | local yielder = function(...)
11 | local utils = lanes.require "_utils"
12 | local PRINT = utils.MAKE_PRINT()
13 | PRINT "In lane"
14 | for _i = 1, select('#', ...) do
15 | local _val = select(_i, ...)
16 | PRINT("yielding #", _i, _val)
17 | local _ack = coroutine.yield(_val)
18 | assert(_ack == _i, "unexpected reply ".._ack)
19 | end
20 | return "done!"
21 | end
22 |
23 | local force_error_test = function(error_trace_level_)
24 | -- the generator, minimal error handling
25 | local coro_g = lanes.coro("*", {name = "auto", error_trace_level = error_trace_level_}, yielder)
26 |
27 | -- launch coroutine lane
28 | local h = coro_g("hello", "world", "!")
29 | -- read the yielded values, sending back the expected index
30 | assert(h:resume(1) == "hello")
31 | assert(h:resume(2) == "world")
32 | -- mistake: we resume with 0 when the lane expects 3 -> assert() in the lane body!
33 | assert(h:resume(0) == "!")
34 | local a, b, c = h:join()
35 | PRINT(error_trace_level_ .. ":", a, b, c)
36 | local expected_c_type = error_trace_level_ == "minimal" and "nil" or "table"
37 | assert(h.status == "error" and string.find(b, "unexpected reply 0", 1, true) and type(c) == expected_c_type, "error message is " .. b)
38 | utils.dump_error_stack(error_trace_level_, c)
39 | end
40 |
41 | if false then
42 | force_error_test("minimal")
43 | end
44 |
45 | if false then
46 | force_error_test("basic")
47 | end
48 |
49 | if false then
50 | force_error_test("extended")
51 | end
52 |
53 | if true then
54 | -- start a coroutine lane that ends with a non-string error
55 | local non_string_thrower = function()
56 | error({"string in table"})
57 | end
58 | local coro_g = lanes.coro("*", {name = "auto"}, non_string_thrower)
59 | local h = coro_g()
60 | local a, b, c = h:join()
61 | -- we get the expected error back
62 | PRINT("non_string_thrower:", a, b, c)
63 | assert(a == nil and type(b) == "table" and b[1] == "string in table" and c == nil, "a=" .. tostring(a) .. " b=" .. tostring(b))
64 | end
65 |
--------------------------------------------------------------------------------
/unit_tests/scripts/lane/cooperative_shutdown.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes".configure{on_state_create = require "fixture".on_state_create}
2 |
3 | -- launch lanes that cooperate properly with cancellation request
4 |
5 | local lane1 = function()
6 | lane_threadname("lane1")
7 | -- loop breaks on soft cancellation request
8 | repeat
9 | lanes.sleep(0)
10 | until cancel_test()
11 | print "lane1 cancelled"
12 | end
13 |
14 | local lane2 = function(linda_)
15 | lane_threadname("lane2")
16 | -- loop breaks on lane/linda cancellation
17 | repeat
18 | local k, v = linda_:receive('k')
19 | until v == lanes.cancel_error
20 | print "lane2 cancelled"
21 | end
22 |
23 | local lane3 = function()
24 | lane_threadname("lane3")
25 | -- this one cooperates too, because of the hook cancellation modes that Lanes will be using
26 | local fixture = require "fixture"
27 | repeat until fixture.give_me_back(false)
28 | end
29 |
30 |
31 |
32 | -- the generators
33 | local g1 = lanes.gen("*", { name = 'auto' }, lane1)
34 | local g2 = lanes.gen("*", { name = 'auto' }, lane2)
35 | local g3 = lanes.gen("*", { name = 'auto' }, lane3)
36 |
37 | -- launch lanes
38 | local h1 = g1()
39 |
40 | local linda = lanes.linda()
41 | local h2 = g2(linda)
42 |
43 | local h3 = g3()
44 |
45 | lanes.sleep(0.1)
46 |
47 | local is_running = function(lane_h)
48 | local status = lane_h.status
49 | return status == "running" or status == "waiting"
50 | end
51 |
52 | -- wait until they are all started
53 | repeat until is_running(h1) and is_running(h2) and is_running(h3)
54 |
55 | -- let the script terminate, Lanes should not crash at shutdown
56 |
--------------------------------------------------------------------------------
/unit_tests/scripts/lane/stdlib_naming.lua:
--------------------------------------------------------------------------------
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3 | local lanes = require_lanes_result_1
4 |
5 | local require_assert_result_1, require_assert_result_2 = require "_assert"
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7 |
8 | local utils = lanes.require "_utils"
9 | local PRINT = utils.MAKE_PRINT()
10 |
11 | local lanes_gen = assert(lanes.gen)
12 | local lanes_linda = assert(lanes.linda)
13 |
14 | -- ##################################################################################################
15 | -- ##################################################################################################
16 | -- ##################################################################################################
17 |
18 | local function task(a, b, c)
19 | lane_threadname("task("..a..","..b..","..c..")")
20 | --error "111" -- testing error messages
21 | assert(hey)
22 | local v=0
23 | for i=a,b,c do
24 | v= v+i
25 | end
26 | return v, hey
27 | end
28 |
29 | local gc_cb = function(name_, status_)
30 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
31 | end
32 |
33 | -- ##################################################################################################
34 | -- ##################################################################################################
35 | -- ##################################################################################################
36 |
37 | PRINT("\n\n", "---=== Stdlib naming ===---", "\n\n")
38 |
39 | local function dump_g(_x)
40 | lane_threadname "dump_g"
41 | assert(print)
42 | print("### dumping _G for '" .. _x .. "'")
43 | for k, v in pairs(_G) do
44 | print("\t" .. k .. ": " .. type(v))
45 | end
46 | return true
47 | end
48 |
49 | local function io_os_f(_x)
50 | lane_threadname "io_os_f"
51 | assert(print)
52 | print("### checking io and os libs existence for '" .. _x .. "'")
53 | assert(io)
54 | assert(os)
55 | return true
56 | end
57 |
58 | local function coro_f(_x)
59 | lane_threadname "coro_f"
60 | assert(print)
61 | print("### checking coroutine lib existence for '" .. _x .. "'")
62 | assert(coroutine)
63 | return true
64 | end
65 |
66 | assert.fails(function() lanes_gen("xxx", { name = 'auto', gc_cb = gc_cb }, io_os_f) end)
67 |
68 | local stdlib_naming_tests =
69 | {
70 | -- { "", dump_g},
71 | -- { "coroutine", dump_g},
72 | -- { "io", dump_g},
73 | -- { "bit32", dump_g},
74 | { "coroutine?", coro_f}, -- in Lua 5.1, the coroutine base library doesn't exist (coroutine table is created when 'base' is opened)
75 | { "*", io_os_f},
76 | { "io,os", io_os_f},
77 | { "io+os", io_os_f},
78 | { "/io;os[base{", io_os_f}, -- use unconventional name separators to check that everything works fine
79 | }
80 |
81 | for _, t in ipairs(stdlib_naming_tests) do
82 | local f= lanes_gen(t[1], {gc_cb = gc_cb}, t[2]) -- any delimiter will do
83 | assert(f(t[1])[1])
84 | end
85 |
86 | PRINT("collectgarbage")
87 | collectgarbage()
88 |
--------------------------------------------------------------------------------
/unit_tests/scripts/lane/tasking_basic.lua:
--------------------------------------------------------------------------------
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3 | local lanes = require_lanes_result_1
4 |
5 | local require_assert_result_1, require_assert_result_2 = require "_assert"
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7 |
8 | local utils = lanes.require "_utils"
9 | local PRINT = utils.MAKE_PRINT()
10 |
11 | local lanes_gen = assert(lanes.gen)
12 | local lanes_linda = assert(lanes.linda)
13 |
14 | -- ##################################################################################################
15 | -- ##################################################################################################
16 | -- ##################################################################################################
17 |
18 | local function task(a, b, c)
19 | local new_name = "task("..a..","..b..","..c..")"
20 | -- test lane naming change
21 | lane_threadname(new_name)
22 | assert(lane_threadname() == new_name)
23 | --error "111" -- testing error messages
24 | assert(hey)
25 | local v=0
26 | for i=a,b,c do
27 | v= v+i
28 | end
29 | return v, hey
30 | end
31 |
32 | local gc_cb = function(name_, status_)
33 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
34 | end
35 |
36 | -- ##################################################################################################
37 | -- ##################################################################################################
38 | -- ##################################################################################################
39 |
40 | PRINT("\n\n", "---=== Tasking (basic) ===---", "\n\n")
41 |
42 | local task_launch = lanes_gen("", { name = 'auto', globals={hey=true}, gc_cb = gc_cb }, task)
43 | -- base stdlibs, normal priority
44 |
45 | -- 'task_launch' is a factory of multithreaded tasks, we can launch several:
46 |
47 | local lane1 = task_launch(100,200,3)
48 | assert.fails(function() print(lane1[lane1]) end) -- indexing the lane with anything other than a string or a number should fail
49 | lanes.sleep(0.1) -- give some time so that the lane can set its name
50 | assert(lane1:get_threadname() == "task(100,200,3)", "Lane name is " .. lane1:get_threadname())
51 |
52 | local lane2= task_launch(200,300,4)
53 |
54 | -- At this stage, states may be "pending", "running" or "done"
55 |
56 | local st1,st2= lane1.status, lane2.status
57 | PRINT(st1,st2)
58 | assert(st1=="pending" or st1=="running" or st1=="done")
59 | assert(st2=="pending" or st2=="running" or st2=="done")
60 |
61 | -- Accessing results ([1..N]) pends until they are available
62 | --
63 | PRINT("waiting...")
64 | local v1, v1_hey= lane1[1], lane1[2]
65 | local v2, v2_hey= lane2[1], lane2[2]
66 |
67 | PRINT(v1, v1_hey)
68 | assert(v1_hey == true)
69 |
70 | PRINT(v2, v2_hey)
71 | assert(v2_hey == true)
72 |
73 | assert(lane1.status == "done")
74 | assert(lane1.status == "done")
75 |
--------------------------------------------------------------------------------
/unit_tests/scripts/lane/tasking_cancelling.lua:
--------------------------------------------------------------------------------
1 | local require_fixture_result_1, require_fixture_result_2 = require "fixture"
2 | local fixture = assert(require_fixture_result_1)
3 |
4 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{on_state_create = fixture.on_state_create}.configure()
5 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
6 | local lanes = require_lanes_result_1
7 |
8 | local require_assert_result_1, require_assert_result_2 = require "_assert"
9 | print("require_assert_result:", require_assert_result_1, require_assert_result_2)
10 |
11 | local utils = lanes.require "_utils"
12 | local PRINT = utils.MAKE_PRINT()
13 |
14 | local lanes_gen = assert(lanes.gen)
15 | local lanes_linda = assert(lanes.linda)
16 |
17 | -- ##################################################################################################
18 | -- ##################################################################################################
19 | -- ##################################################################################################
20 |
21 | -- cancellation of cooperating lanes
22 | local cooperative = function()
23 | local fixture = assert(require "fixture")
24 | local which_cancel
25 | repeat
26 | fixture.block_for(0.2)
27 | which_cancel = cancel_test()
28 | until which_cancel
29 | return which_cancel
30 | end
31 | -- soft and hard are behaviorally equivalent when no blocking linda operation is involved
32 | local cooperative_lane_soft = lanes_gen("*", { name = 'auto' }, cooperative)()
33 | local a, b = cooperative_lane_soft:cancel("soft", 0) -- issue request, do not wait for lane to terminate
34 | assert(a == false and b == "timeout", "got " .. tostring(a) .. " " .. tostring(b))
35 | assert(cooperative_lane_soft[1] == "soft") -- return value of the lane body is the value returned by cancel_test()
36 | local cooperative_lane_hard = lanes_gen("*", { name = 'auto' }, cooperative)()
37 | local c, d = cooperative_lane_hard:cancel("hard", 0) -- issue request, do not wait for lane to terminate
38 | assert(a == false and b == "timeout", "got " .. tostring(c) .. " " .. tostring(d))
39 | assert(cooperative_lane_hard[1] == "hard") -- return value of the lane body is the value returned by cancel_test()
40 |
41 | -- ##################################################################################################
42 |
43 | -- cancellation of lanes waiting on a linda
44 | local limited = lanes_linda{name = "limited"}
45 | assert.fails(function() limited:limit("key", -1) end)
46 | assert.failsnot(function() limited:limit("key", 1) end)
47 | -- [[################################################
48 | limited:send("key", "hello") -- saturate linda, so that subsequent sends will block
49 | for k, v in pairs(limited:dump()) do
50 | PRINT("limited[" .. tostring(k) .. "] = " .. tostring(v))
51 | end
52 | local wait_send = function()
53 | local a,b
54 | set_finalizer(function() print("wait_send", a, b) end)
55 | a,b = limited:send("key", "bybye") -- infinite timeout, returns only when lane is cancelled
56 | end
57 |
58 | local wait_send_lane = lanes_gen("*", { name = 'auto' }, wait_send)()
59 | repeat
60 | io.stderr:write('!')
61 | -- currently mingw64 builds can deadlock if we cancel the lane too early (before the linda blocks, at it causes the linda condvar not to be signalled)
62 | lanes.sleep(0.1)
63 | until wait_send_lane.status == "waiting"
64 | PRINT "wait_send_lane is waiting"
65 | wait_send_lane:cancel() -- hard cancel, 0 timeout
66 | repeat until wait_send_lane.status == "cancelled"
67 | PRINT "wait_send_lane is cancelled"
68 | --################################################]]
69 | local wait_receive = function()
70 | local k, v
71 | set_finalizer(function() print("wait_receive", k, v) end)
72 | k, v = limited:receive("dummy") -- infinite timeout, returns only when lane is cancelled
73 | end
74 |
75 | local wait_receive_lane = lanes_gen("*", { name = 'auto' }, wait_receive)()
76 | repeat
77 | io.stderr:write('!')
78 | -- currently mingw64 builds can deadlock if we cancel the lane too early (before the linda blocks, at it causes the linda condvar not to be signalled)
79 | lanes.sleep(0.1)
80 | until wait_receive_lane.status == "waiting"
81 | PRINT "wait_receive_lane is waiting"
82 | wait_receive_lane:cancel() -- hard cancel, 0 timeout
83 | repeat until wait_receive_lane.status == "cancelled"
84 | PRINT "wait_receive_lane is cancelled"
85 | --################################################]]
86 | local wait_receive_batched = function()
87 | local k, v1, v2
88 | set_finalizer(function() print("wait_receive_batched", k, v1, v2) end)
89 | k, v1, v2 = limited:receive_batched("dummy", 2) -- infinite timeout, returns only when lane is cancelled
90 | end
91 |
92 | local wait_receive_batched_lane = lanes_gen("*", { name = 'auto' }, wait_receive_batched)()
93 | repeat
94 | io.stderr:write('!')
95 | -- currently mingw64 builds can deadlock if we cancel the lane too early (before the linda blocks, at it causes the linda condvar not to be signalled)
96 | lanes.sleep(0.1)
97 | until wait_receive_batched_lane.status == "waiting"
98 | PRINT "wait_receive_batched_lane is waiting"
99 | wait_receive_batched_lane:cancel() -- hard cancel, 0 timeout
100 | repeat until wait_receive_batched_lane.status == "cancelled"
101 | PRINT "wait_receive_batched_lane is cancelled"
102 | --################################################]]
103 |
--------------------------------------------------------------------------------
/unit_tests/scripts/lane/tasking_cancelling_with_hook.lua:
--------------------------------------------------------------------------------
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3 | local lanes = require_lanes_result_1
4 |
5 | local require_assert_result_1, require_assert_result_2 = require "_assert"
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7 |
8 | local utils = lanes.require "_utils"
9 | local PRINT = utils.MAKE_PRINT()
10 |
11 | -- ##################################################################################################
12 | -- ##################################################################################################
13 | -- ##################################################################################################
14 |
15 | local function task(a, b, c)
16 | lane_threadname("task("..a..","..b..","..c..")")
17 | --error "111" -- testing error messages
18 | assert(hey)
19 | local v=0
20 | for i=a,b,c do
21 | v= v+i
22 | end
23 | return v, hey
24 | end
25 |
26 | local gc_cb = function(name_, status_)
27 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
28 | end
29 |
30 | -- ##################################################################################################
31 | -- ##################################################################################################
32 | -- ##################################################################################################
33 |
34 | local generator = lanes.gen("", { name = 'auto', globals={hey=true}, gc_cb = gc_cb }, task)
35 |
36 | local N = 999999999
37 | local lane_h = generator(1,N,1) -- huuuuuuge...
38 |
39 | -- Wait until state changes "pending"->"running"
40 | --
41 | local st
42 | local t0 = os.time()
43 | while os.time()-t0 < 5 do
44 | st = lane_h.status
45 | io.stderr:write((i==1) and st.." " or '.')
46 | if st~="pending" then break end
47 | end
48 | PRINT(" "..st)
49 |
50 | if st == "error" then
51 | local _ = lane_h[0] -- propagate the error here
52 | end
53 | if st == "done" then
54 | error("Looping to "..N.." was not long enough (cannot test cancellation)")
55 | end
56 | assert(st == "running", "st == " .. st)
57 |
58 | -- when running under luajit, the function is JIT-ed, and the instruction count isn't hit, so we need a different hook
59 | lane_h:cancel(jit and "line" or "count", 100) -- 0 timeout, hook triggers cancelslation when reaching the specified count
60 |
61 | local t0 = os.time()
62 | while os.time()-t0 < 5 do
63 | st = lane_h.status
64 | io.stderr:write((i==1) and st.." " or '.')
65 | if st~="running" then break end
66 | end
67 | PRINT(" "..st)
68 | assert(st == "cancelled", "st is '" .. st .. "' instead of 'cancelled'")
69 |
--------------------------------------------------------------------------------
/unit_tests/scripts/lane/tasking_comms_criss_cross.lua:
--------------------------------------------------------------------------------
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3 | local lanes = require_lanes_result_1
4 |
5 | local require_assert_result_1, require_assert_result_2 = require "_assert"
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7 |
8 | local utils = lanes.require "_utils"
9 | local PRINT = utils.MAKE_PRINT()
10 |
11 | local lanes_gen = assert(lanes.gen)
12 | local lanes_linda = assert(lanes.linda)
13 |
14 | -- ##################################################################################################
15 | -- ##################################################################################################
16 | -- ##################################################################################################
17 |
18 | local gc_cb = function(name_, status_)
19 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
20 | end
21 |
22 | -- ##################################################################################################
23 | -- ##################################################################################################
24 | -- ##################################################################################################
25 |
26 | PRINT("\n\n", "---=== Comms criss cross ===---", "\n\n")
27 |
28 | -- We make two identical lanes, which are using the same Linda channel.
29 | --
30 | local tc = lanes_gen("io", { name = 'auto', gc_cb = gc_cb },
31 | function(linda, ch_in, ch_out)
32 | lane_threadname("criss cross " .. ch_in .. " -> " .. ch_out)
33 | local function STAGE(str)
34 | io.stderr:write(ch_in..": "..str.."\n")
35 | linda:send(nil, ch_out, str)
36 | local k,v= linda:receive(nil, ch_in)
37 | assert(v==str)
38 | end
39 | STAGE("Hello")
40 | STAGE("I was here first!")
41 | STAGE("So what?")
42 | end
43 | )
44 |
45 | local linda= lanes_linda{name = "criss cross"}
46 |
47 | local a,b= tc(linda, "A","B"), tc(linda, "B","A") -- launching two lanes, twisted comms
48 |
49 | local _= a[0],b[0] -- waits until they are both ready
50 |
51 | PRINT("collectgarbage")
52 | a, b = nil
53 | collectgarbage()
54 |
--------------------------------------------------------------------------------
/unit_tests/scripts/lane/tasking_error.lua:
--------------------------------------------------------------------------------
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3 | local lanes = require_lanes_result_1
4 |
5 | local require_assert_result_1, require_assert_result_2 = require "_assert"
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7 |
8 | local utils = lanes.require "_utils"
9 | local PRINT = utils.MAKE_PRINT()
10 |
11 | local lanes_gen = assert(lanes.gen)
12 | local lanes_linda = assert(lanes.linda)
13 |
14 | -- ##################################################################################################
15 | -- ##################################################################################################
16 | -- ##################################################################################################
17 |
18 | local gc_cb = function(name_, status_)
19 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
20 | end
21 |
22 | PRINT("---=== Tasking (error) ===---", "\n\n")
23 |
24 | -- a lane that throws immediately the error value it received
25 | local g = lanes_gen("", { name = 'auto', gc_cb = gc_cb }, error)
26 | local errmsg = "ERROR!"
27 |
28 | if true then
29 | -- if you index an errored lane, it should throw the error again
30 | local lane = g(errmsg)
31 | assert.fails(function() return lane[1] end)
32 | assert(lane.status == "error")
33 | -- even after indexing, joining a lane in error should give nil,
34 | local a,b = lane:join()
35 | assert(a == nil and string.find(b, errmsg))
36 | end
37 |
38 | if true then
39 | local lane = g(errmsg)
40 | -- after indexing, joining a lane in error should give nil,
41 | local a, b = lane:join()
42 | assert(lane.status == "error")
43 | assert(a == nil and string.find(b, errmsg))
44 | -- even after joining, indexing should raise an error
45 | assert.fails(function() return lane[1] end)
46 | -- unless we index with a negative value to get the error message
47 | local c = lane[-1]
48 | assert(c == b)
49 | end
50 |
--------------------------------------------------------------------------------
/unit_tests/scripts/lane/tasking_join_test.lua:
--------------------------------------------------------------------------------
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3 | local lanes = require_lanes_result_1
4 |
5 | local require_assert_result_1, require_assert_result_2 = require "_assert"
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7 |
8 | local utils = lanes.require "_utils"
9 | local PRINT = utils.MAKE_PRINT()
10 |
11 | local lanes_gen = assert(lanes.gen)
12 | local lanes_linda = assert(lanes.linda)
13 |
14 | -- ##################################################################################################
15 | -- ##################################################################################################
16 | -- ##################################################################################################
17 |
18 | local gc_cb = function(name_, status_)
19 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
20 | end
21 |
22 | -- ##################################################################################################
23 | -- ##################################################################################################
24 | -- ##################################################################################################
25 |
26 | local SLEEP = function(...)
27 | local k, v = lanes.sleep(...)
28 | assert(k == nil and v == "timeout")
29 | end
30 |
31 | PRINT("---=== :join test ===---", "\n\n")
32 |
33 | -- a lane body that returns nothing is successfully joined with true, nil
34 | local r, ret = lanes_gen(function() end)():join()
35 | assert(r == true and ret == nil)
36 |
37 | -- NOTE: 'unpack()' cannot be used on the lane handle; it will always return nil
38 | -- (unless [1..n] has been read earlier, in which case it would seemingly
39 | -- work).
40 |
41 | local S = lanes_gen("table", { name = 'auto', gc_cb = gc_cb },
42 | function(arg)
43 | lane_threadname "join test lane"
44 | set_finalizer(function() end)
45 | -- take arg table, reverse its contents in aux, then return the unpacked result
46 | local aux= {}
47 | for i, v in ipairs(arg) do
48 | table.insert(aux, 1, v)
49 | end
50 | -- unpack was renamed table.unpack in Lua 5.2: cater for both!
51 | return (unpack or table.unpack)(aux)
52 | end)
53 |
54 | local h = S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values
55 | -- wait a bit so that the lane has a chance to set its debug name
56 | SLEEP(0.5)
57 | print("joining with '" .. h:get_threadname() .. "'")
58 | local r, a, b, c, d = h:join()
59 | if h.status == "error" then
60 | print(h:get_threadname(), "error: " , r, a, b, c, d)
61 | else
62 | print(h:get_threadname(), r, a, b, c, d)
63 | assert(r==true, "r == " .. tostring(r))
64 | assert(a==14, "a == " .. tostring(a))
65 | assert(b==13, "b == " .. tostring(b))
66 | assert(c==12, "c == " .. tostring(c))
67 | assert(d==nil, "d == " .. tostring(d))
68 | end
69 |
70 | local nameof_type, nameof_name = lanes.nameof(print)
71 | PRINT("name of " .. nameof_type .. " print = '" .. nameof_name .. "'")
72 |
--------------------------------------------------------------------------------
/unit_tests/scripts/lane/tasking_send_receive_code.lua:
--------------------------------------------------------------------------------
1 | local config = { with_timers = false, strip_functions = false, internal_allocator = "libc"}
2 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
3 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
4 | local lanes = require_lanes_result_1
5 |
6 | local require_assert_result_1, require_assert_result_2 = require "_assert"
7 | print("require_assert_result:", require_assert_result_1, require_assert_result_2)
8 |
9 | local utils = lanes.require "_utils"
10 | local PRINT = utils.MAKE_PRINT()
11 |
12 | local lanes_gen = assert(lanes.gen)
13 | local lanes_linda = assert(lanes.linda)
14 |
15 | -- ##################################################################################################
16 | -- ##################################################################################################
17 | -- ##################################################################################################
18 |
19 | local gc_cb = function(name_, status_)
20 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
21 | end
22 |
23 | -- ##################################################################################################
24 | -- ##################################################################################################
25 | -- ##################################################################################################
26 |
27 | PRINT("---=== Receive & send of code ===---", "\n")
28 |
29 | local upvalue = "123"
30 | local tostring = tostring
31 |
32 | local function chunk2(linda)
33 | local utils = require "_utils"
34 | local PRINT = utils.MAKE_PRINT()
35 | PRINT("here")
36 | assert(upvalue == "123") -- even when running as separate thread
37 | -- function name & line number should be there even as separate thread
38 | --
39 | local info= debug.getinfo(1) -- 1 = us
40 | --
41 | PRINT("linda named-> '" ..tostring(linda).."'")
42 | PRINT "debug.getinfo->"
43 | for k,v in pairs(info) do PRINT(k,v) end
44 |
45 | -- some assertions are adjusted depending on config.strip_functions, because it changes what we get out of debug.getinfo
46 | assert(info.nups == (_VERSION == "Lua 5.1" and 3 or 4), "bad nups " .. info.nups) -- upvalue + config + tostring + _ENV (Lua > 5.2 only)
47 | assert(info.what == "Lua", "bad what")
48 | --assert(info.name == "chunk2") -- name does not seem to come through
49 | assert(config.strip_functions and info.source=="=?" or string.match(info.source, "^@.*tasking_send_receive_code.lua$"), "bad info.source " .. info.source)
50 | assert(config.strip_functions and info.short_src=="?" or string.match(info.short_src, "^.*tasking_send_receive_code.lua$"), "bad info.short_src " .. info.short_src)
51 | -- These vary so let's not be picky (they're there..)
52 | --
53 | assert(info.linedefined == 32, "bad linedefined") -- start of 'chunk2'
54 | assert(config.strip_functions and info.currentline==-1 or info.currentline > info.linedefined, "bad currentline") -- line of 'debug.getinfo'
55 | assert(info.lastlinedefined > info.currentline, "bad lastlinedefined") -- end of 'chunk2'
56 | local k,func= linda:receive("down")
57 | assert(type(func)=="function", "not a function")
58 | assert(k=="down")
59 |
60 | func(linda)
61 |
62 | local k,str= linda:receive("down")
63 | assert(str=="ok", "bad receive result")
64 |
65 | linda:send("up", function() return ":)" end, "ok2")
66 | end
67 |
68 | local linda = lanes_linda{name = "auto"}
69 | local t2= lanes_gen("debug,package,string,io", { name = 'auto', gc_cb = gc_cb }, chunk2)(linda) -- prepare & launch
70 | linda:send("down", function(linda) linda:send("up", "ready!") end,
71 | "ok")
72 | -- wait to see if the tiny function gets executed
73 | --
74 | local k,s= linda:receive(1, "up")
75 | if t2.status == "error" then
76 | PRINT("t2 error: " , t2:join())
77 | assert(false)
78 | end
79 | PRINT(s)
80 | assert(s=="ready!", s .. " is not 'ready!'")
81 |
82 | -- returns of the 'chunk2' itself
83 | --
84 | local k,f= linda:receive("up")
85 | assert(type(f)=="function")
86 |
87 | local s2= f()
88 | assert(s2==":)")
89 |
90 | local k,ok2= linda:receive("up")
91 | assert(ok2 == "ok2")
92 |
--------------------------------------------------------------------------------
/unit_tests/scripts/lane/uncooperative_shutdown.lua:
--------------------------------------------------------------------------------
1 | if not package.preload.fixture then
2 | error "can't be run outside of UnitTest framework"
3 | end
4 | local fixture = require "fixture"
5 |
6 | local lanes = require "lanes".configure{shutdown_timeout = 0.001, on_state_create = fixture.on_state_create}
7 |
8 | -- launch lanes that blocks forever
9 | local lane = function()
10 | local fixture = require "fixture"
11 | fixture.block_for()
12 | end
13 |
14 | -- the generator
15 | local g1 = lanes.gen("*", { name = 'auto' }, lane)
16 |
17 | -- launch lane
18 | local h1 = g1()
19 |
20 | -- wait until the lane is running
21 | repeat until h1.status == "running"
22 |
23 | -- this finalizer returns an error string that telling Universe::__gc will use to raise an error when it detects the uncooperative lane
24 | lanes.finally(fixture.throwing_finalizer)
25 |
--------------------------------------------------------------------------------
/unit_tests/scripts/linda/multiple_keepers.lua:
--------------------------------------------------------------------------------
1 | -- 3 keepers in addition to the one reserved for the timer linda
2 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{nb_user_keepers = 3, keepers_gc_threshold = 500}
3 | local lanes = require_lanes_result_1
4 |
5 | local a = lanes.linda{name = "A", group = 1}
6 | local b = lanes.linda{name = "B", group = 2}
7 | local c = lanes.linda{name = "C", group = 3}
8 |
9 | -- store each linda in the other 2
10 | do
11 | a:set("kA", a, b, c)
12 | local nA, rA, rB, rC = a:get("kA", 3)
13 | assert(nA == 3 and rA == a and rB == b and rC == c)
14 | end
15 |
16 | do
17 | b:set("kB", a, b, c)
18 | local nB, rA, rB, rC = b:get("kB", 3)
19 | assert(nB == 3 and rA == a and rB == b and rC == c)
20 | end
21 |
22 | do
23 | c:set("kC", a, b, c)
24 | local nC, rA, rB, rC = c:get("kC", 3)
25 | assert(nC == 3 and rA == a and rB == b and rC == c)
26 | end
27 |
--------------------------------------------------------------------------------
/unit_tests/scripts/linda/send_receive.lua:
--------------------------------------------------------------------------------
1 | local lanes = require "lanes"
2 |
3 | -- a newly created linda doesn't contain anything
4 | local l = lanes.linda()
5 | assert(l.null == lanes.null)
6 | assert(l:dump() == nil)
7 |
8 | -- read something with 0 timeout, should fail
9 | local k,v = l:receive(0, "k")
10 | assert(k == nil and v == 'timeout')
11 |
12 | -- send some value
13 | assert(l:send("k1", 99) == true)
14 | -- make sure the contents look as expected
15 | local t = l:dump()
16 | assert(type(t) == 'table')
17 | local tk1 = t.k1
18 | assert(type(tk1) == 'table')
19 | assert(tk1.first == 1 and tk1.count == 1 and tk1.limit == 'unlimited' and tk1.restrict == 'none')
20 | assert(#tk1.fifo == tk1.count, #tk1.fifo .. " ~= " .. tk1.count)
21 | assert(tk1.fifo[1] == 99, tk1.fifo[1] .. " ~= " .. 99)
22 |
23 | -- read the value, should succeed
24 | local k,v = l:receive("k1")
25 | assert(k == "k1" and v == 99)
26 | -- after reading, the data is no longer available (even if the key itself still exists within the linda)
27 | local t = l:dump()
28 | assert(type(t) == 'table')
29 | local tk1 = t.k1
30 | assert(type(tk1) == 'table')
31 | assert(tk1.first == 1 and tk1.count == 0 and tk1.limit == 'unlimited' and tk1.restrict == 'none')
32 | assert(#tk1.fifo == tk1.count and tk1.fifo[1] == nil)
33 | -- read again, should fail
34 | local k,v = l:receive(0, "k1")
35 | assert(k == nil and v == 'timeout')
36 |
37 | -- send null, read nil
38 | l:send("k", l.null)
39 | local k,v = l:receive(0, "k")
40 | assert(k == "k" and v == nil)
41 |
42 | -- using a deep userdata (such as another linda) as key, should work
43 | local l2 = lanes.linda()
44 | l:send(nil, l2, 99)
45 | local k,v = l:receive(0, l2)
46 | assert(k == l2 and v == 99)
47 |
--------------------------------------------------------------------------------
/unit_tests/scripts/linda/send_registered_userdata.lua:
--------------------------------------------------------------------------------
1 | local lanes = require 'lanes'.configure{with_timers = false}
2 | local l = lanes.linda{name = 'gleh'}
3 | l:set('yo', io.stdin)
4 | local n, stdin_out = l:get('yo')
5 | assert(n == 1 and stdin_out == io.stdin, tostring(stdin_out) .. " ~= " .. tostring(io.stdin))
6 |
--------------------------------------------------------------------------------
/unit_tests/scripts/linda/wake_period.lua:
--------------------------------------------------------------------------------
1 | -- default wake period is 0.5 seconds
2 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{linda_wake_period = 0.5}
3 | local lanes = require_lanes_result_1
4 |
5 | -- a lane that performs a blocking operation for 2 seconds
6 | local body = function(linda_)
7 | -- a blocking read that lasts longer than the tested wake_period values
8 | linda_:receive(2, "empty_slot")
9 | return "done"
10 | end
11 |
12 | -- if we don't cancel the lane, we should wait the whole duration
13 | local function check_wake_duration(linda_, expected_, do_cancel_)
14 | local h = lanes.gen(body)(linda_)
15 | -- wait until the linda is blocked
16 | repeat until h.status == "waiting"
17 | local t0 = lanes.now_secs()
18 | -- soft cancel, no timeout, no waking
19 | if do_cancel_ then
20 | local result, reason = h:cancel('soft', 0, false)
21 | -- should say there was a timeout, since the lane didn't actually cancel (normal since we did not wake it)
22 | assert(result == false and reason == 'timeout', "unexpected cancel result")
23 | end
24 | -- this should wait until the linda wakes by itself before the actual receive timeout and sees the cancel request
25 | local r, ret = h:join()
26 | assert(r == true and ret == "done")
27 | local t1 = lanes.now_secs()
28 | local delta = t1 - t0
29 | -- the linda should check for cancellation at about the expected period, not earlier
30 | assert(delta >= expected_, tostring(linda_) .. " woke too early:" .. delta)
31 | -- the lane shouldn't terminate too long after cancellation was processed
32 | assert(delta <= expected_ * 1.1, tostring(linda_) .. " woke too late: " .. delta)
33 | end
34 |
35 | -- legacy behavior: linda waits until operation timeout
36 | check_wake_duration(lanes.linda{name = "A", wake_period = 'never'}, 2, true)
37 | -- early wake behavior: linda wakes after the expected time, sees a cancellation requests, and aborts the operation early
38 | check_wake_duration(lanes.linda{name = "B", wake_period = 0.25}, 0.25, true)
39 | check_wake_duration(lanes.linda{name = "C"}, 0.5, true) -- wake_period defaults to 0.5 (see above)
40 | check_wake_duration(lanes.linda{name = "D", wake_period = 1}, 1, true)
41 | -- even when there is a wake_period, the operation should reach full timeout if not cancelled early
42 | check_wake_duration(lanes.linda{name = "E", wake_period = 0.1}, 2, false)
43 |
--------------------------------------------------------------------------------
/unit_tests/shared.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // #################################################################################################
4 |
5 | class LuaState
6 | {
7 | public:
8 |
9 | DECLARE_UNIQUE_TYPE(WithBaseLibs, bool);
10 | DECLARE_UNIQUE_TYPE(WithFixture, bool);
11 | lua_State* L{ luaL_newstate() };
12 | bool finalizerWasCalled{};
13 | STACK_CHECK_START_REL(L, 0);
14 |
15 | ~LuaState()
16 | {
17 | close();
18 | }
19 | LuaState(WithBaseLibs withBaseLibs_, WithFixture withFixture_);
20 |
21 | LuaState(LuaState const&) = delete;
22 | LuaState(LuaState&& rhs_) noexcept
23 | : L{ std::exchange(rhs_.L, nullptr) }
24 | {
25 | }
26 | LuaState& operator=(LuaState const&) = delete;
27 | LuaState& operator=(LuaState&& rhs_) noexcept {
28 | L = std::exchange(rhs_.L, nullptr);
29 | return *this;
30 | }
31 |
32 | public:
33 |
34 | operator lua_State*() const { return L; }
35 |
36 | void stackCheck(int delta_) { STACK_CHECK(L, delta_); }
37 | void close();
38 |
39 | // all these methods leave a single item on the stack: an error string on failure, or a single value that depends on what we do
40 | [[nodiscard]]
41 | LuaError doString(std::string_view const& str_) const;
42 | std::string_view doStringAndRet(std::string_view const& str_) const;
43 | [[nodiscard]]
44 | LuaError doFile(std::filesystem::path const& root_, std::string_view const& str_) const;
45 | [[nodiscard]]
46 | LuaError loadString(std::string_view const& str_) const;
47 | [[nodiscard]]
48 | LuaError loadFile(std::filesystem::path const& root_, std::string_view const& str_) const;
49 | void requireFailure(std::string_view const& script_);
50 | void requireNotReturnedString(std::string_view const& script_, std::string_view const& unexpected_);
51 | void requireReturnedString(std::string_view const& script_, std::string_view const& expected_);
52 | void requireSuccess(std::string_view const& script_);
53 | void requireSuccess(std::filesystem::path const& root_, std::string_view const& path_);
54 | [[nodiscard]]
55 | LuaError runChunk() const;
56 |
57 | friend std::ostream& operator<<(std::ostream& os_, LuaState const& s_)
58 | {
59 | os_ << luaG_tostring(s_.L, kIdxTop);
60 | return os_;
61 | }
62 | };
63 |
64 | // #################################################################################################
65 |
66 | enum class [[nodiscard]] TestType
67 | {
68 | AssertNoLuaError,
69 | #if LUA_VERSION_NUM >= 504 // warnings are a Lua 5.4 feature
70 | AssertWarns,
71 | #endif // LUA_VERSION_NUM
72 | };
73 |
74 | struct FileRunnerParam
75 | {
76 | std::string_view script;
77 | TestType test;
78 | };
79 |
80 | // Define a specialization for FileRunnerParam in Catch::Detail::stringify
81 | namespace Catch {
82 | namespace Detail {
83 | template <>
84 | inline std::string stringify(FileRunnerParam const& param_)
85 | {
86 | return std::string{ param_.script };
87 | }
88 | } // namespace Detail
89 | } // namespace Catch
90 |
91 | class FileRunner : private LuaState
92 | {
93 | private:
94 |
95 | std::string root;
96 |
97 | public:
98 |
99 | FileRunner(std::string_view const& where_);
100 |
101 | void performTest(FileRunnerParam const& testParam_);
102 | };
103 |
104 | // #################################################################################################
105 |
106 | // Can't #ifdef stuff away inside a macro expansion
107 | #if LUA_VERSION_NUM == 501
108 | #define LUA51_ONLY(a) a
109 | #else
110 | #define LUA51_ONLY(a) ""
111 | #endif
112 |
113 | #if LUA_VERSION_NUM == 504
114 | #define LUA54_ONLY(a) a
115 | #else
116 | #define LUA54_ONLY(a) ""
117 | #endif
118 |
119 | #if LUAJIT_FLAVOR() == 0
120 | #define PUC_LUA_ONLY(a) a
121 | #define JIT_LUA_ONLY(a) ""
122 | #else
123 | #define PUC_LUA_ONLY(a) ""
124 | #define JIT_LUA_ONLY(a) a
125 | #endif
126 |
--------------------------------------------------------------------------------