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