aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2025-04-18 10:26:19 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2025-04-18 10:26:19 +0200
commit022e40cc71beda874d0bad2cec227e472d5dd4af (patch)
tree7f0e03815217dd2e329a2ade1ed346247d5db884
parent1bff784b474261212a996ac9fc59389d53a69590 (diff)
downloadlanes-022e40cc71beda874d0bad2cec227e472d5dd4af.tar.gz
lanes-022e40cc71beda874d0bad2cec227e472d5dd4af.tar.bz2
lanes-022e40cc71beda874d0bad2cec227e472d5dd4af.zip
New feature: Linda periodical cancellation checks
* lanes.linda() api change: takes all settings in a single table argument * new linda creation argument wake_period * new lanes.configure setting linda_wake_period * adjusted all unit tests (one TODO test fails on purpose)
Diffstat (limited to '')
-rw-r--r--docs/index.html46
-rw-r--r--src/lanes.lua14
-rw-r--r--src/linda.cpp152
-rw-r--r--src/linda.hpp10
-rw-r--r--src/lindafactory.cpp9
-rw-r--r--src/universe.cpp8
-rw-r--r--src/universe.hpp2
-rw-r--r--tests/basic.lua8
-rw-r--r--tests/cancel.lua2
-rw-r--r--tests/deadlock.lua2
-rw-r--r--tests/error.lua4
-rw-r--r--tests/fifo.lua4
-rw-r--r--tests/keeper.lua36
-rw-r--r--tests/linda_perf.lua6
-rw-r--r--tests/tobeclosed.lua12
-rw-r--r--unit_tests/embedded_tests.cpp4
-rw-r--r--unit_tests/init_and_shutdown.cpp533
-rw-r--r--unit_tests/linda_tests.cpp185
-rw-r--r--unit_tests/scripts/lane/tasking_cancelling.lua2
-rw-r--r--unit_tests/scripts/lane/tasking_comms_criss_cross.lua2
-rw-r--r--unit_tests/scripts/lane/tasking_communications.lua2
-rw-r--r--unit_tests/scripts/lane/tasking_send_receive_code.lua2
-rw-r--r--unit_tests/scripts/linda/multiple_keepers.lua6
-rw-r--r--unit_tests/scripts/linda/send_registered_userdata.lua2
24 files changed, 627 insertions, 426 deletions
diff --git a/docs/index.html b/docs/index.html
index 4dd5848..116fc0a 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -64,13 +64,13 @@
64 <font size="-1"> 64 <font size="-1">
65 <p> 65 <p>
66 <br /> 66 <br />
67 <i>Copyright &copy; 2007-24 Asko Kauppi, Benoit Germain. All rights reserved.</i> 67 <i>Copyright &copy; 2007-25 Asko Kauppi, Benoit Germain. All rights reserved.</i>
68 <br /> 68 <br />
69 Lua Lanes is published under the same <a href="http://en.wikipedia.org/wiki/MIT_License">MIT license</a> as Lua 5.1, 5.2, 5.3 and 5.4. 69 Lua Lanes is published under the same <a href="http://en.wikipedia.org/wiki/MIT_License">MIT license</a> as Lua 5.1, 5.2, 5.3 and 5.4.
70 </p> 70 </p>
71 71
72 <p> 72 <p>
73 This document was revised on 17-Mar-25, and applies to version <tt>4.0.0</tt>. 73 This document was revised on 18-Apr-25, and applies to version <tt>4.0.0</tt>.
74 </p> 74 </p>
75 </font> 75 </font>
76 </center> 76 </center>
@@ -271,7 +271,7 @@
271</p> 271</p>
272 272
273<p> 273<p>
274 <tt>lanes.configure</tt> accepts an optional options table as sole argument. 274 <tt>lanes.configure</tt> accepts an optional table as sole argument.
275 <table border="1" cellpadding="10" style="width:100%"> 275 <table border="1" cellpadding="10" style="width:100%">
276 <tr> 276 <tr>
277 <th style="width:15%">name</th> 277 <th style="width:15%">name</th>
@@ -337,6 +337,22 @@
337 </tr> 337 </tr>
338 338
339 <tr valign=top> 339 <tr valign=top>
340 <td id="linda_wake_period">
341 <code>.linda_wake_period</code>
342 </td>
343 <td>
344 number > 0
345 </td>
346 <td>
347 Sets the default period in seconds a linda will wake by itself during blocked operations. Default is never.<br />
348 When a Linda enters a blocking call (<tt>send()</tt>, <tt>receive()</tt>, <tt>receive_batched()</tt>, <tt>sleep()</tt>), it normally sleeps either until the operation completes
349 or the specified timeout expires. With this setting, the default behavior can be changed to wake periodically. This can help for example with timing issues where a lane is signalled
350 for cancellation, but a linda inside the lane was in the middle of processing an operation but did not actually start the wait. This can result in the signal to be ignored, thus
351 causing the Linda to wait out the full operation timeout before cancellation is processed.
352 </td>
353 </tr>
354
355 <tr valign=top>
340 <td id="nb_user_keepers"> 356 <td id="nb_user_keepers">
341 <code>.nb_user_keepers</code> 357 <code>.nb_user_keepers</code>
342 </td> 358 </td>
@@ -1222,7 +1238,7 @@
1222<table border="1" bgcolor="#FFFFE0" cellpadding="10" style="width:50%"><tr><td><pre> 1238<table border="1" bgcolor="#FFFFE0" cellpadding="10" style="width:50%"><tr><td><pre>
1223 local lanes = require "lanes" 1239 local lanes = require "lanes"
1224 1240
1225 local linda = lanes.linda("my linda") 1241 local linda = lanes.linda{name = "my linda"}
1226 1242
1227 local function loop(max) 1243 local function loop(max)
1228 for i = 1, max do 1244 for i = 1, max do
@@ -1276,16 +1292,24 @@
1276</p> 1292</p>
1277 1293
1278<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1294<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1279 h = lanes.linda([name],[group],[close_handler]) 1295 h = lanes.linda(table|nil)
1280</pre></td></tr></table> 1296</pre></td></tr></table>
1281 1297
1282<p> 1298<p>
1283 Arguments to <tt>lanes.linda()</tt> can be provided in any order, as long as there is a single string, a single number, and a single callable value (all are optional).<br /> 1299 Argument to <tt>lanes.linda()</tt> is either <tt>nil</tt> or a single table. The table may contain the following entries:
1284 Converting the linda to a string will yield the provided name prefixed by <tt>"Linda: "</tt>.<br /> 1300 <ul>
1285 If <tt>opt_name</tt> is omitted, it will evaluate to an hexadecimal number uniquely representing that linda when the linda is converted to a string. The value is the same as returned by <tt>linda:deep()</tt>.<br /> 1301 <li><tt>close_handler</tt>: a callable object (function or table/userdata with <tt>__call</tt> metamethod). If provided, and the linda is to-be-closed (Lua 5.4+), it will be called with all the provided arguments. For older Lua versions, its presence is ignored.</li>
1286 If <tt>opt_name</tt> is <tt>"auto"</tt>, Lanes will try to construct a name from the source location that called <tt>lanes.linda()</tt>. If that fails, the linda name will be <tt>"&lt;unresolved&gt;"</tt>.<br /> 1302 <li><tt>group</tt>: an integer between 0 and the number of Keeper states. Mandatory if Lanes is configured with more than one Keeper state. Group 0 is used by the internal timer linda.</li>
1287 If Lanes is configured with more than one Keeper state, <tt>group</tt> is mandatory.<br /> 1303 <li>
1288 If the linda is to-be-closed (Lua 5.4+), and a <tt>close_handler</tt> is provided, it will be called with all the provided arguments. For older Lua versions, its presence will cause an error. 1304 <tt>name</tt>: a string. Converting the linda to a string will yield the provided name prefixed by <tt>"Linda: "</tt>.
1305 If omitted or empty, it will evaluate to the string representation of a hexadecimal number uniquely representing that linda when the linda is converted to a string. The numeric value is the same as returned by <tt>linda:deep()</tt>.<br />
1306 If <tt>"auto"</tt>, Lanes will try to construct a name from the source location that called <tt>lanes.linda()</tt>. If that fails, the linda name will be <tt>"&lt;unresolved&gt;"</tt>.
1307 </li>
1308 <li>
1309 <tt>wake_period</tt>: a number > 0 (unit: seconds). If provided, overrides <a href="#linda_wake_period"><tt>linda_wake_period</tt></a> provided to <a href="#initialization"><tt>lanes.configure()</tt></a>.
1310 </li>
1311 </ul>
1312 Unknown fields are silently ignored.
1289</p> 1313</p>
1290 1314
1291<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1315<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
diff --git a/src/lanes.lua b/src/lanes.lua
index bd94a14..3ee959c 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -96,6 +96,7 @@ local default_params =
96 -- it looks also like LuaJIT allocator may not appreciate direct use of its allocator for other purposes than the VM operation 96 -- it looks also like LuaJIT allocator may not appreciate direct use of its allocator for other purposes than the VM operation
97 internal_allocator = isLuaJIT and "libc" or "allocator", 97 internal_allocator = isLuaJIT and "libc" or "allocator",
98 keepers_gc_threshold = -1, 98 keepers_gc_threshold = -1,
99 linda_wake_period = 'never',
99 nb_user_keepers = 0, 100 nb_user_keepers = 0,
100 on_state_create = nil, 101 on_state_create = nil,
101 shutdown_timeout = 0.25, 102 shutdown_timeout = 0.25,
@@ -141,6 +142,19 @@ local param_checkers =
141 end 142 end
142 return true 143 return true
143 end, 144 end,
145 linda_wake_period = function(val_)
146 -- linda_wake_period should be a number > 0, or the string 'never'
147 if val_ == 'never' then
148 return true
149 end
150 if type(val_) ~= "number" then
151 return nil, "not a number"
152 end
153 if val_ <= 0 then
154 return nil, "value out of range"
155 end
156 return true
157 end,
144 nb_user_keepers = function(val_) 158 nb_user_keepers = function(val_)
145 -- nb_user_keepers should be a number in [0,100] (so that nobody tries to run OOM by specifying a huge amount) 159 -- nb_user_keepers should be a number in [0,100] (so that nobody tries to run OOM by specifying a huge amount)
146 if type(val_) ~= "number" then 160 if type(val_) ~= "number" then
diff --git a/src/linda.cpp b/src/linda.cpp
index 0cdacfa..a9ae61c 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -237,11 +237,23 @@ namespace {
237 _lane->waiting_on = &_linda->writeHappened; 237 _lane->waiting_on = &_linda->writeHappened;
238 _lane->status.store(Lane::Waiting, std::memory_order_release); 238 _lane->status.store(Lane::Waiting, std::memory_order_release);
239 } 239 }
240
241 // wait until the final target date by small increments, interrupting regularly so that we can check for cancel requests,
242 // in case some timing issue caused a cancel request to be issued, and the condvar signalled, before we actually wait for it
243 auto const [_forceTryAgain, _until_check_cancel] = std::invoke([_until, wakePeriod = _linda->getWakePeriod()] {
244 auto _until_check_cancel{ std::chrono::time_point<std::chrono::steady_clock>::max() };
245 if (wakePeriod.count() > 0.0f) {
246 _until_check_cancel = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(wakePeriod);
247 }
248 bool const _forceTryAgain{ _until_check_cancel < _until };
249 return std::make_tuple(_forceTryAgain, _forceTryAgain ? _until_check_cancel : _until);
250 });
251
240 // not enough data to read: wakeup when data was sent, or when timeout is reached 252 // not enough data to read: wakeup when data was sent, or when timeout is reached
241 std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock }; 253 std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock };
242 std::cv_status const _status{ _linda->writeHappened.wait_until(_guard, _until) }; 254 std::cv_status const _status{ _linda->writeHappened.wait_until(_guard, _until_check_cancel) };
243 _guard.release(); // we don't want to unlock the mutex on exit! 255 _guard.release(); // we don't want to unlock the mutex on exit!
244 _try_again = (_status == std::cv_status::no_timeout); // detect spurious wakeups 256 _try_again = _forceTryAgain || (_status == std::cv_status::no_timeout); // detect spurious wakeups
245 if (_lane != nullptr) { 257 if (_lane != nullptr) {
246 _lane->waiting_on = nullptr; 258 _lane->waiting_on = nullptr;
247 _lane->status.store(_prev_status, std::memory_order_release); 259 _lane->status.store(_prev_status, std::memory_order_release);
@@ -296,9 +308,10 @@ LUAG_FUNC(linda);
296// ################################################################################################# 308// #################################################################################################
297// ################################################################################################# 309// #################################################################################################
298 310
299Linda::Linda(Universe* const U_, LindaGroup const group_, std::string_view const& name_) 311Linda::Linda(Universe* const U_, std::string_view const& name_, lua_Duration const wake_period_, LindaGroup const group_)
300: DeepPrelude{ LindaFactory::Instance } 312: DeepPrelude{ LindaFactory::Instance }
301, U{ U_ } 313, U{ U_ }
314, wakePeriod{ wake_period_ }
302, keeperIndex{ group_ % U_->keepers.getNbKeepers() } 315, keeperIndex{ group_ % U_->keepers.getNbKeepers() }
303{ 316{
304 setName(name_); 317 setName(name_);
@@ -330,9 +343,13 @@ Linda* Linda::CreateTimerLinda(lua_State* const L_)
330 STACK_CHECK_START_REL(L_, 0); // L_: 343 STACK_CHECK_START_REL(L_, 0); // L_:
331 // Initialize 'timerLinda'; a common Linda object shared by all states 344 // Initialize 'timerLinda'; a common Linda object shared by all states
332 lua_pushcfunction(L_, LG_linda); // L_: lanes.linda 345 lua_pushcfunction(L_, LG_linda); // L_: lanes.linda
333 luaG_pushstring(L_, "lanes-timer"); // L_: lanes.linda "lanes-timer" 346 lua_createtable(L_, 0, 3); // L_: lanes.linda {}
334 lua_pushinteger(L_, 0); // L_: lanes.linda "lanes-timer" 0 347 luaG_pushstring(L_, "lanes-timer"); // L_: lanes.linda {} "lanes-timer"
335 lua_call(L_, 2, 1); // L_: linda 348 luaG_setfield(L_, StackIndex{ -2 }, std::string_view{ "name" }); // L_: lanes.linda { .name="lanes-timer" }
349 lua_pushinteger(L_, 0); // L_: lanes.linda { .name="lanes-timer" } 0
350 luaG_setfield(L_, StackIndex{ -2 }, std::string_view{ "group" }); // L_: lanes.linda { .name="lanes-timer" .group = 0 }
351 // note that wake_period is not set (will default to the value in the universe)
352 lua_call(L_, 1, 1); // L_: linda
336 STACK_CHECK(L_, 1); 353 STACK_CHECK(L_, 1);
337 354
338 // Proxy userdata contents is only a 'DeepPrelude*' pointer 355 // Proxy userdata contents is only a 'DeepPrelude*' pointer
@@ -941,11 +958,23 @@ LUAG_FUNC(linda_send)
941 _lane->waiting_on = &_linda->readHappened; 958 _lane->waiting_on = &_linda->readHappened;
942 _lane->status.store(Lane::Waiting, std::memory_order_release); 959 _lane->status.store(Lane::Waiting, std::memory_order_release);
943 } 960 }
961
962 // wait until the final target date by small increments, interrupting regularly so that we can check for cancel requests,
963 // in case some timing issue caused a cancel request to be issued, and the condvar signalled, before we actually wait for it
964 auto const [_forceTryAgain, _until_check_cancel] = std::invoke([_until, wakePeriod = _linda->getWakePeriod()] {
965 auto _until_check_cancel{ std::chrono::time_point<std::chrono::steady_clock>::max() };
966 if (wakePeriod.count() > 0.0f) {
967 _until_check_cancel = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(wakePeriod);
968 }
969 bool const _forceTryAgain{ _until_check_cancel < _until };
970 return std::make_tuple(_forceTryAgain, _forceTryAgain ? _until_check_cancel : _until);
971 });
972
944 // could not send because no room: wait until some data was read before trying again, or until timeout is reached 973 // could not send because no room: wait until some data was read before trying again, or until timeout is reached
945 std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock }; 974 std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock };
946 std::cv_status const status{ _linda->readHappened.wait_until(_guard, _until) }; 975 std::cv_status const status{ _linda->readHappened.wait_until(_guard, _until_check_cancel) };
947 _guard.release(); // we don't want to unlock the mutex on exit! 976 _guard.release(); // we don't want to unlock the mutex on exit!
948 _try_again = (status == std::cv_status::no_timeout); // detect spurious wakeups 977 _try_again = _forceTryAgain || (status == std::cv_status::no_timeout); // detect spurious wakeups
949 if (_lane != nullptr) { 978 if (_lane != nullptr) {
950 _lane->waiting_on = nullptr; 979 _lane->waiting_on = nullptr;
951 _lane->status.store(_prev_status, std::memory_order_release); 980 _lane->status.store(_prev_status, std::memory_order_release);
@@ -1129,88 +1158,69 @@ namespace {
1129// ################################################################################################# 1158// #################################################################################################
1130 1159
1131/* 1160/*
1132 * ud = lanes.linda( [name[,group[,close_handler]]]) 1161 * ud = lanes.linda{.name = <string>, .group = <number>, .close_handler = <callable>, .wake_period = <number>}
1133 * 1162 *
1134 * returns a linda object, or raises an error if creation failed 1163 * returns a linda object, or raises an error if creation failed
1135 */ 1164 */
1136LUAG_FUNC(linda) 1165LUAG_FUNC(linda)
1137{ 1166{
1138 static constexpr StackIndex kLastArg{ LUA_VERSION_NUM >= 504 ? 3 : 2 }; 1167 // unpack the received table on the stack, putting name wake_period group close_handler in that order
1139 StackIndex const _top{ lua_gettop(L_) }; 1168 StackIndex const _top{ lua_gettop(L_) };
1140 luaL_argcheck(L_, _top <= kLastArg, _top, "too many arguments"); 1169 luaL_argcheck(L_, _top <= 1, _top, "too many arguments");
1141 StackIndex _closeHandlerIdx{}; 1170 if (_top == 0) {
1142 StackIndex _nameIdx{}; 1171 lua_settop(L_, 3); // L_: nil nil nil
1143 StackIndex _groupIdx{}; 1172 }
1144 for (StackIndex const _i : std::ranges::iota_view{ StackIndex{ 1 }, StackIndex{ _top + 1 }}) { 1173 else if (!lua_istable(L_, kIdxTop)) {
1145 switch (luaG_type(L_, _i)) { 1174 luaL_argerror(L_, 1, "expecting a table");
1175 } else {
1176 auto* const _U{ Universe::Get(L_) };
1177 lua_getfield(L_, 1, "wake_period"); // L_: {} wake_period
1178 if (lua_isnil(L_, kIdxTop)) {
1179 lua_pop(L_, 1);
1180 lua_pushnumber(L_, _U->lindaWakePeriod.count());
1181 } else {
1182 luaL_argcheck(L_, luaL_optnumber(L_, 2, 0) > 0, 1, "wake_period must be > 0");
1183 }
1184
1185 lua_getfield(L_, 1, "group"); // L_: {} wake_period group
1186 int const _nbKeepers{ _U->keepers.getNbKeepers() };
1187 if (lua_isnil(L_, kIdxTop)) {
1188 luaL_argcheck(L_, _nbKeepers < 2, 0, "Group is mandatory in multiple Keeper scenarios");
1189 } else {
1190 int const _group{ static_cast<int>(lua_tointeger(L_, kIdxTop)) };
1191 luaL_argcheck(L_, _group >= 0 && _group < _nbKeepers, 1, "group out of range");
1192 }
1193
1146#if LUA_VERSION_NUM >= 504 // to-be-closed support starts with Lua 5.4 1194#if LUA_VERSION_NUM >= 504 // to-be-closed support starts with Lua 5.4
1147 case LuaType::FUNCTION: 1195 lua_getfield(L_, 1, "close_handler"); // L_: {} wake_period group close_handler
1148 luaL_argcheck(L_, _closeHandlerIdx == 0, _i, "More than one __close handler"); 1196 LuaType const _handlerType{ luaG_type(L_, kIdxTop) };
1149 _closeHandlerIdx = _i; 1197 if (_handlerType == LuaType::NIL) {
1150 break; 1198 lua_pop(L_, 1); // L_: {} wake_period group
1151 1199 } else if (_handlerType == LuaType::USERDATA || _handlerType == LuaType::TABLE) {
1152 case LuaType::USERDATA: 1200 luaL_argcheck(L_, luaL_getmetafield(L_, kIdxTop, "__call") != 0, 1, "__close handler is not callable");
1153 case LuaType::TABLE:
1154 luaL_argcheck(L_, _closeHandlerIdx == 0, _i, "More than one __close handler");
1155 luaL_argcheck(L_, luaL_getmetafield(L_, _i, "__call") != 0, _i, "__close handler is not callable");
1156 lua_pop(L_, 1); // luaL_getmetafield() pushed the field, we need to pop it 1201 lua_pop(L_, 1); // luaL_getmetafield() pushed the field, we need to pop it
1157 _closeHandlerIdx = _i; 1202 } else {
1158 break; 1203 luaL_argcheck(L_, _handlerType == LuaType::FUNCTION, 1, "__close handler is not a function");
1204 }
1159#endif // LUA_VERSION_NUM >= 504 1205#endif // LUA_VERSION_NUM >= 504
1160 1206
1161 case LuaType::STRING: 1207 auto const _nameType{ luaG_getfield(L_, StackIndex{ 1 }, "name") }; // L_: {} wake_period group [close_handler] name
1162 luaL_argcheck(L_, _nameIdx == 0, _i, "More than one name"); 1208 luaL_argcheck(L_, _nameType == LuaType::NIL || _nameType == LuaType::STRING, 1, "name is not a string");
1163 _nameIdx = _i; 1209 lua_replace(L_, 1); // L_: name wake_period group [close_handler]
1164 break;
1165
1166 case LuaType::NUMBER:
1167 luaL_argcheck(L_, _groupIdx == 0, _i, "More than one group");
1168 _groupIdx = _i;
1169 break;
1170
1171 default:
1172 luaL_argcheck(L_, false, _i, "Bad argument type (should be a string, a number, or a callable type)");
1173 }
1174 }
1175
1176 int const _nbKeepers{ Universe::Get(L_)->keepers.getNbKeepers() };
1177 if (!_groupIdx) {
1178 luaL_argcheck(L_, _nbKeepers < 2, 0, "Group is mandatory in multiple Keeper scenarios");
1179 } else {
1180 int const _group{ static_cast<int>(lua_tointeger(L_, _groupIdx)) };
1181 luaL_argcheck(L_, _group >= 0 && _group < _nbKeepers, _groupIdx, "Group out of range");
1182 } 1210 }
1183 1211
1184 // done with argument checking, let's proceed 1212 // done with argument checking, let's proceed
1185 if constexpr (LUA_VERSION_NUM >= 504) { 1213 if (lua_gettop(L_) == 4) {
1186 // make sure we have kMaxArgs arguments on the stack for processing, with name, group, and handler, in that order
1187 lua_settop(L_, kLastArg); // L_: a b c
1188 // If either index is 0, lua_settop() adjusted the stack with a nil in slot kLastArg
1189 lua_pushvalue(L_, _closeHandlerIdx ? _closeHandlerIdx : kLastArg); // L_: a b c close_handler
1190 lua_pushvalue(L_, _groupIdx ? _groupIdx : kLastArg); // L_: a b c close_handler group
1191 lua_pushvalue(L_, _nameIdx ? _nameIdx : kLastArg); // L_: a b c close_handler group name
1192 lua_replace(L_, 1); // L_: name b c close_handler group
1193 lua_replace(L_, 2); // L_: name group c close_handler
1194 lua_replace(L_, 3); // L_: name group close_handler
1195
1196 // if we have a __close handler, we need a uservalue slot to store it 1214 // if we have a __close handler, we need a uservalue slot to store it
1197 UserValueCount const _nuv{ _closeHandlerIdx ? 1 : 0 }; 1215 LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, UserValueCount{ 1 }); // L_: name wake_period group [close_handler] linda
1198 LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, _nuv); // L_: name group close_handler linda 1216 lua_replace(L_, 3); // L_: name wake_period linda close_handler
1199 if (_closeHandlerIdx != 0) { 1217 lua_setiuservalue(L_, StackIndex{ 3 }, UserValueIndex{ 1 }); // L_: name wake_period linda
1200 lua_replace(L_, 2); // L_: name linda close_handler
1201 lua_setiuservalue(L_, StackIndex{ 2 }, UserValueIndex{ 1 }); // L_: name linda
1202 }
1203 // depending on whether we have a handler or not, the stack is not in the same state at this point 1218 // depending on whether we have a handler or not, the stack is not in the same state at this point
1204 // just make sure we have our Linda at the top 1219 // just make sure we have our Linda at the top
1205 LUA_ASSERT(L_, ToLinda<true>(L_, kIdxTop)); 1220 LUA_ASSERT(L_, ToLinda<true>(L_, kIdxTop));
1206 return 1; 1221 return 1;
1207 } else { // no to-be-closed support 1222 } else { // no to-be-closed support
1208 // ensure we have name, group in that order on the stack 1223 LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, UserValueCount{ 0 }); // L_: name wake_period group linda
1209 if (_nameIdx > _groupIdx) {
1210 lua_insert(L_, 1); // L_: name group
1211 }
1212 LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, UserValueCount{ 0 }); // L_: name group linda
1213 return 1; 1224 return 1;
1214 } 1225 }
1215
1216} 1226}
diff --git a/src/linda.hpp b/src/linda.hpp
index 2d5c9dc..7874db3 100644
--- a/src/linda.hpp
+++ b/src/linda.hpp
@@ -42,19 +42,21 @@ class Linda final
42 }; 42 };
43 using enum Status; 43 using enum Status;
44 44
45 private: 45 public:
46 Universe* const U{ nullptr }; // the universe this linda belongs to
46 47
48 private:
47 static constexpr size_t kEmbeddedNameLength = 24; 49 static constexpr size_t kEmbeddedNameLength = 24;
48 using EmbeddedName = std::array<char, kEmbeddedNameLength>; 50 using EmbeddedName = std::array<char, kEmbeddedNameLength>;
49 // depending on the name length, it is either embedded inside the Linda, or allocated separately 51 // depending on the name length, it is either embedded inside the Linda, or allocated separately
50 std::variant<std::string_view, EmbeddedName> nameVariant{}; 52 std::variant<std::string_view, EmbeddedName> nameVariant{};
51 // counts the keeper operations in progress 53 // counts the keeper operations in progress
52 std::atomic<int> keeperOperationCount{}; 54 std::atomic<int> keeperOperationCount{};
55 lua_Duration wakePeriod{};
53 56
54 public: 57 public:
55 std::condition_variable readHappened{}; 58 std::condition_variable readHappened{};
56 std::condition_variable writeHappened{}; 59 std::condition_variable writeHappened{};
57 Universe* const U{ nullptr }; // the universe this linda belongs to
58 KeeperIndex const keeperIndex{ -1 }; // the keeper associated to this linda 60 KeeperIndex const keeperIndex{ -1 }; // the keeper associated to this linda
59 Status cancelStatus{ Status::Active }; 61 Status cancelStatus{ Status::Active };
60 62
@@ -68,7 +70,7 @@ class Linda final
68 static void operator delete(void* p_) { static_cast<Linda*>(p_)->U->internalAllocator.free(p_, sizeof(Linda)); } 70 static void operator delete(void* p_) { static_cast<Linda*>(p_)->U->internalAllocator.free(p_, sizeof(Linda)); }
69 71
70 ~Linda(); 72 ~Linda();
71 Linda(Universe* U_, LindaGroup group_, std::string_view const& name_); 73 Linda(Universe* U_, std::string_view const& name_, lua_Duration wake_period_, LindaGroup group_);
72 Linda() = delete; 74 Linda() = delete;
73 // non-copyable, non-movable 75 // non-copyable, non-movable
74 Linda(Linda const&) = delete; 76 Linda(Linda const&) = delete;
@@ -92,6 +94,8 @@ class Linda final
92 [[nodiscard]] 94 [[nodiscard]]
93 std::string_view getName() const; 95 std::string_view getName() const;
94 [[nodiscard]] 96 [[nodiscard]]
97 auto getWakePeriod() const { return wakePeriod; }
98 [[nodiscard]]
95 bool inKeeperOperation() const { return keeperOperationCount.load(std::memory_order_seq_cst) != 0; } 99 bool inKeeperOperation() const { return keeperOperationCount.load(std::memory_order_seq_cst) != 0; }
96 template <typename T = uintptr_t> 100 template <typename T = uintptr_t>
97 [[nodiscard]] 101 [[nodiscard]]
diff --git a/src/lindafactory.cpp b/src/lindafactory.cpp
index 42d0984..4eab0c1 100644
--- a/src/lindafactory.cpp
+++ b/src/lindafactory.cpp
@@ -108,9 +108,11 @@ std::string_view LindaFactory::moduleName() const
108 108
109DeepPrelude* LindaFactory::newDeepObjectInternal(lua_State* const L_) const 109DeepPrelude* LindaFactory::newDeepObjectInternal(lua_State* const L_) const
110{ 110{
111 // we always expect name and group at the bottom of the stack (either can be nil). any extra stuff we ignore and keep unmodified 111 STACK_CHECK_START_REL(L_, 0);
112 // we always expect name, wake_period, group at the bottom of the stack (either can be nil). any extra stuff we ignore and keep unmodified
112 std::string_view _linda_name{ luaG_tostring(L_, StackIndex{ 1 }) }; 113 std::string_view _linda_name{ luaG_tostring(L_, StackIndex{ 1 }) };
113 LindaGroup _linda_group{ static_cast<int>(lua_tointeger(L_, 2)) }; 114 auto const _wake_period{ static_cast<lua_Duration>(lua_tonumber(L_, 2)) };
115 LindaGroup const _linda_group{ static_cast<int>(lua_tointeger(L_, 3)) };
114 116
115 // store in the linda the location of the script that created it 117 // store in the linda the location of the script that created it
116 if (_linda_name == "auto") { 118 if (_linda_name == "auto") {
@@ -129,6 +131,7 @@ DeepPrelude* LindaFactory::newDeepObjectInternal(lua_State* const L_) const
129 // The deep data is allocated separately of Lua stack; we might no longer be around when last reference to it is being released. 131 // The deep data is allocated separately of Lua stack; we might no longer be around when last reference to it is being released.
130 // One can use any memory allocation scheme. Just don't use L's allocF because we don't know which state will get the honor of GCing the linda 132 // One can use any memory allocation scheme. Just don't use L's allocF because we don't know which state will get the honor of GCing the linda
131 Universe* const _U{ Universe::Get(L_) }; 133 Universe* const _U{ Universe::Get(L_) };
132 Linda* const _linda{ new (_U) Linda{ _U, _linda_group, _linda_name } }; 134 Linda* const _linda{ new (_U) Linda{ _U, _linda_name, _wake_period, _linda_group } };
135 STACK_CHECK(L_, 0);
133 return _linda; 136 return _linda;
134} 137}
diff --git a/src/universe.cpp b/src/universe.cpp
index 89ad02a..335f056 100644
--- a/src/universe.cpp
+++ b/src/universe.cpp
@@ -153,6 +153,14 @@ Universe* Universe::Create(lua_State* const L_)
153 lua_setmetatable(L_, -2); // L_: settings universe 153 lua_setmetatable(L_, -2); // L_: settings universe
154 lua_pop(L_, 1); // L_: settings 154 lua_pop(L_, 1); // L_: settings
155 155
156 std::ignore = luaG_getfield(L_, kIdxSettings, "linda_wake_period"); // L_: settings linda_wake_period
157 if (luaG_type(L_, kIdxTop) == LuaType::NUMBER) {
158 _U->lindaWakePeriod = lua_Duration{ lua_tonumber(L_, kIdxTop) };
159 } else {
160 LUA_ASSERT(L_, luaG_tostring(L_, kIdxTop) == "never");
161 }
162 lua_pop(L_, 1); // L_: settings
163
156 std::ignore = luaG_getfield(L_, kIdxSettings, "strip_functions"); // L_: settings strip_functions 164 std::ignore = luaG_getfield(L_, kIdxSettings, "strip_functions"); // L_: settings strip_functions
157 _U->stripFunctions = lua_toboolean(L_, -1) ? true : false; 165 _U->stripFunctions = lua_toboolean(L_, -1) ? true : false;
158 lua_pop(L_, 1); // L_: settings 166 lua_pop(L_, 1); // L_: settings
diff --git a/src/universe.hpp b/src/universe.hpp
index 42a3d83..0c5e659 100644
--- a/src/universe.hpp
+++ b/src/universe.hpp
@@ -99,6 +99,8 @@ class Universe final
99 99
100 Keepers keepers; 100 Keepers keepers;
101 101
102 lua_Duration lindaWakePeriod{};
103
102 // Initialized by 'init_once_LOCKED()': the deep userdata Linda object 104 // Initialized by 'init_once_LOCKED()': the deep userdata Linda object
103 // used for timers (each lane will get a proxy to this) 105 // used for timers (each lane will get a proxy to this)
104 Linda* timerLinda{ nullptr }; 106 Linda* timerLinda{ nullptr };
diff --git a/tests/basic.lua b/tests/basic.lua
index a9c85cc..9aaad97 100644
--- a/tests/basic.lua
+++ b/tests/basic.lua
@@ -163,7 +163,7 @@ PRINT(" "..st)
163assert(st == "cancelled", "st is '" .. st .. "' instead of 'cancelled'") 163assert(st == "cancelled", "st is '" .. st .. "' instead of 'cancelled'")
164 164
165-- cancellation of lanes waiting on a linda 165-- cancellation of lanes waiting on a linda
166local limited = lanes_linda("limited") 166local limited = lanes_linda{name = "limited"}
167assert.fails(function() limited:limit("key", -1) end) 167assert.fails(function() limited:limit("key", -1) end)
168assert.failsnot(function() limited:limit("key", 1) end) 168assert.failsnot(function() limited:limit("key", 1) end)
169-- [[################################################ 169-- [[################################################
@@ -255,7 +255,7 @@ local chunk= function(linda)
255 WR("chunk ", "Lane ends!\n") 255 WR("chunk ", "Lane ends!\n")
256end 256end
257 257
258local linda = lanes_linda("communications") 258local linda = lanes_linda{name = "communications"}
259assert(type(linda) == "userdata" and tostring(linda) == "Linda: communications") 259assert(type(linda) == "userdata" and tostring(linda) == "Linda: communications")
260 -- 260 --
261 -- ["->"] master -> slave 261 -- ["->"] master -> slave
@@ -410,7 +410,7 @@ local tc = lanes.gen("io", { name = 'auto', gc_cb = gc_cb },
410 end 410 end
411) 411)
412 412
413local linda= lanes_linda("criss cross") 413local linda= lanes_linda{name = "criss cross"}
414 414
415local a,b= tc(linda, "A","B"), tc(linda, "B","A") -- launching two lanes, twisted comms 415local a,b= tc(linda, "A","B"), tc(linda, "B","A") -- launching two lanes, twisted comms
416 416
@@ -461,7 +461,7 @@ local function chunk2(linda)
461 linda:send("up", function() return ":)" end, "ok2") 461 linda:send("up", function() return ":)" end, "ok2")
462end 462end
463 463
464local linda = lanes_linda("auto") 464local linda = lanes_linda{name = "auto"}
465local t2 = lanes.gen("debug,string,io", { name = 'auto', gc_cb = gc_cb }, chunk2)(linda) -- prepare & launch 465local t2 = lanes.gen("debug,string,io", { name = 'auto', gc_cb = gc_cb }, chunk2)(linda) -- prepare & launch
466linda:send("down", function(linda) linda:send("up", "ready!") end, 466linda:send("down", function(linda) linda:send("up", "ready!") end,
467 "ok") 467 "ok")
diff --git a/tests/cancel.lua b/tests/cancel.lua
index 80e6c6a..66957c3 100644
--- a/tests/cancel.lua
+++ b/tests/cancel.lua
@@ -148,7 +148,7 @@ local protectedBody = function(...)
148 local paramLessClosure = function() laneBody(unpack(params)) end 148 local paramLessClosure = function() laneBody(unpack(params)) end
149 local status, message = xpcall(paramLessClosure, errorHandler) 149 local status, message = xpcall(paramLessClosure, errorHandler)
150 if status == false then 150 if status == false then
151 print(" error handler rethrowing '" .. (ce == message and "cancel_error"or tostring(message)) .. "'") 151 print(" protectedBody rethrowing '" .. (ce == message and "cancel_error" or tostring(message)) .. "'")
152 -- if the error isn't rethrown, the lane's finalizer won't get it 152 -- if the error isn't rethrown, the lane's finalizer won't get it
153 error(message) 153 error(message)
154 end 154 end
diff --git a/tests/deadlock.lua b/tests/deadlock.lua
index d028e83..9b93e3b 100644
--- a/tests/deadlock.lua
+++ b/tests/deadlock.lua
@@ -16,7 +16,7 @@ print "let's begin"
16local do_extra_stuff = true 16local do_extra_stuff = true
17 17
18if do_extra_stuff then 18if do_extra_stuff then
19 local linda = lanes.linda "deadlock_linda" 19 local linda = lanes.linda{name = "deadlock_linda"}
20 -- just something to make send() succeed and receive() fail 20 -- just something to make send() succeed and receive() fail
21 local payload = { io.flush } 21 local payload = { io.flush }
22 22
diff --git a/tests/error.lua b/tests/error.lua
index 306c51d..28cfff1 100644
--- a/tests/error.lua
+++ b/tests/error.lua
@@ -106,11 +106,11 @@ end
106 106
107local lane_error_as_string = "'lane error as string'" 107local lane_error_as_string = "'lane error as string'"
108local lane_error_as_table = setmetatable({"lane error as table"}, make_table_error_mt()) 108local lane_error_as_table = setmetatable({"lane error as table"}, make_table_error_mt())
109local lane_error_as_linda = lanes.linda("'lane error'") 109local lane_error_as_linda = lanes.linda{name = "'lane error'"}
110 110
111local finalizer_error_as_string = "'finalizer error as string'" 111local finalizer_error_as_string = "'finalizer error as string'"
112local finalizer_error_as_table = setmetatable({"finalizer error as table"}, make_table_error_mt()) 112local finalizer_error_as_table = setmetatable({"finalizer error as table"}, make_table_error_mt())
113local finalizer_error_as_linda = lanes.linda("'finalizer error'") 113local finalizer_error_as_linda = lanes.linda{name = "'finalizer error'"}
114 114
115local test_settings = {} 115local test_settings = {}
116local configure_tests = function() 116local configure_tests = function()
diff --git a/tests/fifo.lua b/tests/fifo.lua
index 9efcbd9..1317a9f 100644
--- a/tests/fifo.lua
+++ b/tests/fifo.lua
@@ -6,10 +6,10 @@
6 6
7local lanes = require "lanes".configure{shutdown_timeout=3,with_timers=true} 7local lanes = require "lanes".configure{shutdown_timeout=3,with_timers=true}
8 8
9local atomic_linda = lanes.linda( "atom") 9local atomic_linda = lanes.linda{name = "atom"}
10local atomic_inc= lanes.genatomic( atomic_linda, "FIFO_n") 10local atomic_inc= lanes.genatomic( atomic_linda, "FIFO_n")
11 11
12local fifo_linda = lanes.linda( "fifo") 12local fifo_linda = lanes.linda{name = "fifo"}
13 13
14-- Lua 5.1 support 14-- Lua 5.1 support
15local table_unpack = table.unpack or unpack 15local table_unpack = table.unpack or unpack
diff --git a/tests/keeper.lua b/tests/keeper.lua
index f566927..4742732 100644
--- a/tests/keeper.lua
+++ b/tests/keeper.lua
@@ -40,14 +40,14 @@ if true then
40 end 40 end
41 41
42 -- should succeed 42 -- should succeed
43 assert.failsnot(function() createLinda("zero", 0) end) 43 assert.failsnot(function() createLinda{name = "zero", group = 0} end)
44 assert.failsnot(function() createLinda("one", 1) end) 44 assert.failsnot(function() createLinda{name = "one", group = 1} end)
45 assert.failsnot(function() createLinda("two", 2) end) 45 assert.failsnot(function() createLinda{name = "two", group = 2} end)
46 assert.failsnot(function() createLinda("three", 3) end) 46 assert.failsnot(function() createLinda{name = "three", group = 3} end)
47 -- should fail (and not create the lindas) 47 -- should fail (and not create the lindas)
48 assert.fails(function() createLinda("minus 1", -1) end) 48 assert.fails(function() createLinda{name = "minus 1", group = -1} end)
49 assert.fails(function() createLinda("none") end) 49 assert.fails(function() createLinda{name = "none"} end)
50 assert.fails(function() createLinda("four", 4) end) 50 assert.fails(function() createLinda{name = "four", group = 4} end)
51 51
52end 52end
53-- should only collect the 4 successfully created lindas 53-- should only collect the 4 successfully created lindas
@@ -58,11 +58,11 @@ DONE()
58if true then 58if true then
59 PRINT "=========================================================================================" 59 PRINT "========================================================================================="
60 PRINT "Linda names test:" 60 PRINT "Linda names test:"
61 local unnamedLinda1 = lanes.linda(1) 61 local unnamedLinda1 = lanes.linda{group = 1}
62 local unnamedLinda2 = lanes.linda("", 2) 62 local unnamedLinda2 = lanes.linda{name = "", group = 2}
63 local veeeerrrryyyylooongNamedLinda3 = lanes.linda( "veeeerrrryyyylooongNamedLinda", 3) 63 local veeeerrrryyyylooongNamedLinda3 = lanes.linda{ name = "veeeerrrryyyylooongNamedLinda", group = 3}
64 assert(tostring(veeeerrrryyyylooongNamedLinda3) == "Linda: veeeerrrryyyylooongNamedLinda") 64 assert(tostring(veeeerrrryyyylooongNamedLinda3) == "Linda: veeeerrrryyyylooongNamedLinda")
65 local shortNamedLinda0 = lanes.linda( "short", 0) 65 local shortNamedLinda0 = lanes.linda{name = "short", group = 0}
66 assert(tostring(shortNamedLinda0) == "Linda: short") 66 assert(tostring(shortNamedLinda0) == "Linda: short")
67 PRINT(shortNamedLinda0, unnamedLinda1, unnamedLinda2, veeeerrrryyyylooongNamedLinda3) 67 PRINT(shortNamedLinda0, unnamedLinda1, unnamedLinda2, veeeerrrryyyylooongNamedLinda3)
68end 68end
@@ -74,12 +74,12 @@ DONE()
74if true then 74if true then
75 PRINT "=========================================================================================" 75 PRINT "========================================================================================="
76 PRINT "Linda GC test:" 76 PRINT "Linda GC test:"
77 local a = lanes.linda("A", 1) 77 local a = lanes.linda{name = "A", group = 1}
78 local b = lanes.linda("B", 2) 78 local b = lanes.linda{name = "B", group = 2}
79 local c = lanes.linda("C", 3) 79 local c = lanes.linda{name = "C", group = 3}
80 80
81 -- store lindas in each other and in themselves 81 -- store lindas in each other and in themselves
82 a:set("here", lanes.linda("temporary linda", 0)) 82 a:set("here", lanes.linda{name = "temporary linda", group = 0})
83 b:set("here", a, b, c) 83 b:set("here", a, b, c)
84 c:set("here", a, b, c) 84 c:set("here", a, b, c)
85 85
@@ -120,13 +120,13 @@ if true then
120 end 120 end
121 121
122 -- 122 --
123 local lindaA= lanes.linda( "A", 1) 123 local lindaA= lanes.linda{name = "A", group = 1}
124 local A= keeper( lindaA ) 124 local A= keeper( lindaA )
125 125
126 local lindaB= lanes.linda( "B", 2) 126 local lindaB= lanes.linda{name = "B", group = 2}
127 local B= keeper( lindaB ) 127 local B= keeper( lindaB )
128 128
129 local lindaC= lanes.linda( "C", 3) 129 local lindaC= lanes.linda{name = "C", group = 3}
130 local C= keeper( lindaC ) 130 local C= keeper( lindaC )
131 PRINT("Created", lindaA, lindaB, lindaC) 131 PRINT("Created", lindaA, lindaB, lindaC)
132 132
diff --git a/tests/linda_perf.lua b/tests/linda_perf.lua
index 107fd25..83b8921 100644
--- a/tests/linda_perf.lua
+++ b/tests/linda_perf.lua
@@ -22,7 +22,7 @@ if true then
22 do 22 do
23 print "############################################ tests get/set" 23 print "############################################ tests get/set"
24 -- linda:get throughput 24 -- linda:get throughput
25 local l = lanes.linda("get/set", 1) 25 local l = lanes.linda{name = "get/set", group = 1}
26 local batch = {} 26 local batch = {}
27 for i = 1,1000 do 27 for i = 1,1000 do
28 table.insert(batch, i) 28 table.insert(batch, i)
@@ -90,7 +90,7 @@ local group_uid = 1
90local function ziva1( preloop, loop, batch) 90local function ziva1( preloop, loop, batch)
91 -- prefill the linda a bit to increase fifo stress 91 -- prefill the linda a bit to increase fifo stress
92 local top = math.max( preloop, loop) 92 local top = math.max( preloop, loop)
93 local l = lanes.linda("ziva1("..preloop..":"..loop..":"..batch..")", group_uid) 93 local l = lanes.linda{name = "ziva1("..preloop..":"..loop..":"..batch..")", group = group_uid}
94 group_uid = (group_uid % config.nb_user_keepers) + 1 94 group_uid = (group_uid % config.nb_user_keepers) + 1
95 local t1 = lanes.now_secs() 95 local t1 = lanes.now_secs()
96 for i = 1, preloop do 96 for i = 1, preloop do
@@ -165,7 +165,7 @@ end
165 165
166-- sequential write/read (no parallelization involved) 166-- sequential write/read (no parallelization involved)
167local function ziva2( preloop, loop, batch) 167local function ziva2( preloop, loop, batch)
168 local l = lanes.linda("ziva2("..preloop..":"..loop..":"..tostring(batch)..")", group_uid) 168 local l = lanes.linda{name = "ziva2("..preloop..":"..loop..":"..tostring(batch)..")", group = group_uid}
169 group_uid = (group_uid % config.nb_user_keepers) + 1 169 group_uid = (group_uid % config.nb_user_keepers) + 1
170 -- prefill the linda a bit to increase fifo stress 170 -- prefill the linda a bit to increase fifo stress
171 local top, step = math.max( preloop, loop), batch or 1 171 local top, step = math.max( preloop, loop), batch or 1
diff --git a/tests/tobeclosed.lua b/tests/tobeclosed.lua
index ef09df3..447b936 100644
--- a/tests/tobeclosed.lua
+++ b/tests/tobeclosed.lua
@@ -36,7 +36,7 @@ do
36 WR("f closing ", linda_) 36 WR("f closing ", linda_)
37 closed_by_f = true 37 closed_by_f = true
38 end 38 end
39 local lf <close> = lanes.linda("closed by f", close_handler_f) 39 local lf <close> = lanes.linda{name = "closed by f", close_handler = close_handler_f}
40 40
41 local close_handler_t = setmetatable({}, 41 local close_handler_t = setmetatable({},
42 { 42 {
@@ -46,7 +46,7 @@ do
46 end 46 end
47 } 47 }
48 ) 48 )
49 local lt <close> = lanes.linda("closed by t", close_handler_t) 49 local lt <close> = lanes.linda{name = "closed by t", close_handler = close_handler_t}
50 end 50 end
51 assert(closed_by_f == true) 51 assert(closed_by_f == true)
52 assert(closed_by_t == true) 52 assert(closed_by_t == true)
@@ -58,13 +58,13 @@ end
58WR "================================================================================================" 58WR "================================================================================================"
59WR "Through Linda" 59WR "Through Linda"
60do 60do
61 local l = lanes.linda("channel") 61 local l = lanes.linda{name = "channel"}
62 62
63 local close_handler_f = function(linda_, err_) 63 local close_handler_f = function(linda_, err_)
64 WR("f closing ", linda_) 64 WR("f closing ", linda_)
65 linda_:set("closed", true) 65 linda_:set("closed", true)
66 end 66 end
67 local l_in = lanes.linda("voyager", close_handler_f) 67 local l_in = lanes.linda{name = "voyager", close_handler = close_handler_f}
68 l:set("trip", l_in) 68 l:set("trip", l_in)
69 69
70 do 70 do
@@ -99,7 +99,7 @@ end
99WR "================================================================================================" 99WR "================================================================================================"
100WR "Linda closing through Lane" 100WR "Linda closing through Lane"
101do 101do
102 local l = lanes.linda("channel") 102 local l = lanes.linda{name = "channel"}
103 local lane_body = function(l_arg_) 103 local lane_body = function(l_arg_)
104 WR "In lane body" 104 WR "In lane body"
105 -- linda obtained through a linda 105 -- linda obtained through a linda
@@ -114,7 +114,7 @@ do
114 local _count, _closed = linda_:get("closed") 114 local _count, _closed = linda_:get("closed")
115 linda_:set("closed", (_closed or 0) + 1) 115 linda_:set("closed", (_closed or 0) + 1)
116 end 116 end
117 local l_in = lanes.linda("voyager", close_handler_f) 117 local l_in = lanes.linda{name = "voyager", close_handler = close_handler_f}
118 l:set("trip", l_in) 118 l:set("trip", l_in)
119 119
120 do 120 do
diff --git a/unit_tests/embedded_tests.cpp b/unit_tests/embedded_tests.cpp
index 0991a4c..a0a7bb2 100644
--- a/unit_tests/embedded_tests.cpp
+++ b/unit_tests/embedded_tests.cpp
@@ -157,7 +157,7 @@ TEST_CASE("lanes.embedding.with default allocator")
157 // function with an upvalue 157 // function with an upvalue
158 std::string_view const _script{ 158 std::string_view const _script{
159 " local lanes = require 'lanes'.configure{with_timers = false}" 159 " local lanes = require 'lanes'.configure{with_timers = false}"
160 " local l = lanes.linda'gleh'" 160 " local l = lanes.linda{name = 'gleh'}"
161 " local upvalue = 'oeauaoeuoeuaoeuaoeujaoefubycfjbycfybcfjybcfjybcfjbcf'" 161 " local upvalue = 'oeauaoeuoeuaoeuaoeujaoefubycfjbycfybcfjybcfjybcfjbcf'"
162 " local upvalued = function()" 162 " local upvalued = function()"
163 " return upvalue" 163 " return upvalue"
@@ -183,7 +183,7 @@ TEST_CASE("lanes.embedding.with default allocator")
183 // try to send io.open into a linda, which fails if io base library is not loaded 183 // try to send io.open into a linda, which fails if io base library is not loaded
184 std::string_view const _script{ 184 std::string_view const _script{
185 " local lanes = require 'lanes'" 185 " local lanes = require 'lanes'"
186 " local l = lanes.linda'gleh'" 186 " local l = lanes.linda{name = 'gleh'}"
187 " l:set('yo', io.open)" 187 " l:set('yo', io.open)"
188 " return 'SUCCESS'" 188 " return 'SUCCESS'"
189 }; 189 };
diff --git a/unit_tests/init_and_shutdown.cpp b/unit_tests/init_and_shutdown.cpp
index 384af43..bd72157 100644
--- a/unit_tests/init_and_shutdown.cpp
+++ b/unit_tests/init_and_shutdown.cpp
@@ -289,6 +289,62 @@ TEST_CASE("lanes.configure.keepers_gc_threshold")
289 289
290// ################################################################################################# 290// #################################################################################################
291 291
292TEST_CASE("lanes.configure.linda_wake_period")
293{
294 LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
295
296 // linda_wake_period should be a number > 0, or 'never'
297
298 SECTION("linda_wake_period = <table>")
299 {
300 L.requireFailure("require 'lanes'.configure{linda_wake_period = {}}");
301 }
302
303 // ---------------------------------------------------------------------------------------------
304
305 SECTION("linda_wake_period = <string>")
306 {
307 L.requireFailure("require 'lanes'.configure{linda_wake_period = 'gluh'}");
308 }
309
310 // ---------------------------------------------------------------------------------------------
311
312 SECTION("linda_wake_period = 'never'")
313 {
314 L.requireSuccess("require 'lanes'.configure{linda_wake_period = 'never'}");
315 }
316
317 // ---------------------------------------------------------------------------------------------
318
319 SECTION("linda_wake_period = <negative number>")
320 {
321 L.requireFailure("require 'lanes'.configure{linda_wake_period = -0.001}");
322 }
323
324 // ---------------------------------------------------------------------------------------------
325
326 SECTION("linda_wake_period = 0")
327 {
328 L.requireFailure("require 'lanes'.configure{linda_wake_period = 0}");
329 }
330
331 // ---------------------------------------------------------------------------------------------
332
333 SECTION("linda_wake_period = 0.0001s")
334 {
335 L.requireSuccess("require 'lanes'.configure{linda_wake_period = 0.0001}");
336 }
337
338 // ---------------------------------------------------------------------------------------------
339
340 SECTION("linda_wake_period = 1e30")
341 {
342 L.requireSuccess("require 'lanes'.configure{linda_wake_period = 1e30}");
343 }
344}
345
346// #################################################################################################
347
292TEST_CASE("lanes.configure.nb_user_keepers") 348TEST_CASE("lanes.configure.nb_user_keepers")
293{ 349{
294 LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; 350 LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
@@ -300,35 +356,35 @@ TEST_CASE("lanes.configure.nb_user_keepers")
300 L.requireFailure("require 'lanes'.configure{nb_user_keepers = {}}"); 356 L.requireFailure("require 'lanes'.configure{nb_user_keepers = {}}");
301 } 357 }
302 358
303 // ----------------------------------------------------------------------------------------- 359 // ---------------------------------------------------------------------------------------------
304 360
305 SECTION("nb_user_keepers = <string>") 361 SECTION("nb_user_keepers = <string>")
306 { 362 {
307 L.requireFailure("require 'lanes'.configure{nb_user_keepers = 'gluh'}"); 363 L.requireFailure("require 'lanes'.configure{nb_user_keepers = 'gluh'}");
308 } 364 }
309 365
310 // ----------------------------------------------------------------------------------------- 366 // ---------------------------------------------------------------------------------------------
311 367
312 SECTION("nb_user_keepers = -1") 368 SECTION("nb_user_keepers = -1")
313 { 369 {
314 L.requireFailure("require 'lanes'.configure{nb_user_keepers = -1}"); 370 L.requireFailure("require 'lanes'.configure{nb_user_keepers = -1}");
315 } 371 }
316 372
317 // ----------------------------------------------------------------------------------------- 373 // ---------------------------------------------------------------------------------------------
318 374
319 SECTION("nb_user_keepers = 0") 375 SECTION("nb_user_keepers = 0")
320 { 376 {
321 L.requireSuccess("require 'lanes'.configure{nb_user_keepers = 0}"); 377 L.requireSuccess("require 'lanes'.configure{nb_user_keepers = 0}");
322 } 378 }
323 379
324 // ----------------------------------------------------------------------------------------- 380 // ---------------------------------------------------------------------------------------------
325 381
326 SECTION("nb_user_keepers = 1") 382 SECTION("nb_user_keepers = 1")
327 { 383 {
328 L.requireSuccess("require 'lanes'.configure{nb_user_keepers = 1}"); 384 L.requireSuccess("require 'lanes'.configure{nb_user_keepers = 1}");
329 } 385 }
330 386
331 // ----------------------------------------------------------------------------------------- 387 // ---------------------------------------------------------------------------------------------
332 388
333 SECTION("nb_user_keepers = 100") 389 SECTION("nb_user_keepers = 100")
334 { 390 {
@@ -345,340 +401,355 @@ TEST_CASE("lanes.configure.nb_user_keepers")
345 401
346// ################################################################################################# 402// #################################################################################################
347 403
348TEST_CASE("lanes.configure.the rest") 404TEST_CASE("lanes.configure.on_state_create")
349{ 405{
350 LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; 406 LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
351 407
352 // on_state_create should be a function, either C or Lua, without upvalues 408 // on_state_create should be a function, either C or Lua, without upvalues
353 409
354 SECTION("on_state_create") 410 SECTION("on_state_create = <table>")
355 { 411 {
356 SECTION("on_state_create = <table>") 412 L.requireFailure("require 'lanes'.configure{on_state_create = {}}");
357 { 413 }
358 L.requireFailure("require 'lanes'.configure{on_state_create = {}}");
359 }
360 414
361 // ----------------------------------------------------------------------------------------- 415 // ---------------------------------------------------------------------------------------------
362 416
363 SECTION("on_state_create = <string>") 417 SECTION("on_state_create = <string>")
364 { 418 {
365 L.requireFailure("require 'lanes'.configure{on_state_create = 'gluh'}"); 419 L.requireFailure("require 'lanes'.configure{on_state_create = 'gluh'}");
366 } 420 }
367 421
368 // ----------------------------------------------------------------------------------------- 422 // ---------------------------------------------------------------------------------------------
369 423
370 SECTION("on_state_create = <number>") 424 SECTION("on_state_create = <number>")
371 { 425 {
372 L.requireFailure("require 'lanes'.configure{on_state_create = 1}"); 426 L.requireFailure("require 'lanes'.configure{on_state_create = 1}");
373 } 427 }
374 428
375 // ----------------------------------------------------------------------------------------- 429 // ---------------------------------------------------------------------------------------------
376 430
377 SECTION("on_state_create = false") 431 SECTION("on_state_create = false")
378 { 432 {
379 L.requireFailure("require 'lanes'.configure{on_state_create = false}"); 433 L.requireFailure("require 'lanes'.configure{on_state_create = false}");
380 } 434 }
381 435
382 // ----------------------------------------------------------------------------------------- 436 // ---------------------------------------------------------------------------------------------
383 437
384 SECTION("on_state_create = true") 438 SECTION("on_state_create = true")
385 { 439 {
386 L.requireFailure("require 'lanes'.configure{on_state_create = true}"); 440 L.requireFailure("require 'lanes'.configure{on_state_create = true}");
387 } 441 }
388 442
389 // ----------------------------------------------------------------------------------------- 443 // ---------------------------------------------------------------------------------------------
390 444
391 SECTION("on_state_create = <Lua function>") 445 SECTION("on_state_create = <Lua function>")
392 { 446 {
393 // on_state_create isn't called inside a Keeper state if it's a Lua function (which is good as print() doesn't exist there!) 447 // on_state_create isn't called inside a Keeper state if it's a Lua function (which is good as print() doesn't exist there!)
394 L.requireSuccess("local print = print; require 'lanes'.configure{on_state_create = function() print 'hello' end}"); 448 L.requireSuccess("local print = print; require 'lanes'.configure{on_state_create = function() print 'hello' end}");
395 } 449 }
396 450
397 // ----------------------------------------------------------------------------------------- 451 // ---------------------------------------------------------------------------------------------
398 452
399 SECTION("on_state_create = <C function>") 453 SECTION("on_state_create = <C function>")
400 { 454 {
401 // funnily enough, in Lua 5.3, print() uses global tostring(), that doesn't exist in a keeper since we didn't open libs -> "attempt to call a nil value" 455 // funnily enough, in Lua 5.3, print() uses global tostring(), that doesn't exist in a keeper since we didn't open libs -> "attempt to call a nil value"
402 // conclusion, don't use print() as a fake on_state_create() callback! 456 // conclusion, don't use print() as a fake on_state_create() callback!
403 // assert() should be fine since we pass a non-false argument to on_state_create 457 // assert() should be fine since we pass a non-false argument to on_state_create
404 L.requireSuccess("require 'lanes'.configure{on_state_create = assert}"); 458 L.requireSuccess("require 'lanes'.configure{on_state_create = assert}");
405 }
406 } 459 }
460}
461
462// #################################################################################################
463
464TEST_CASE("lanes.configure.shutdown_timeout")
465{
466 LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
407 467
408 // ---------------------------------------------------------------------------------------------
409 // shutdown_timeout should be a number in [0,3600] 468 // shutdown_timeout should be a number in [0,3600]
410 469
411 SECTION("shutdown_timeout") 470 SECTION("shutdown_timeout = <table>")
412 { 471 {
413 SECTION("shutdown_timeout = <table>") 472 L.requireFailure("require 'lanes'.configure{shutdown_timeout = {}}");
414 { 473 }
415 L.requireFailure("require 'lanes'.configure{shutdown_timeout = {}}");
416 }
417 474
418 // ----------------------------------------------------------------------------------------- 475 // ---------------------------------------------------------------------------------------------
419 476
420 SECTION("shutdown_timeout = <string>") 477 SECTION("shutdown_timeout = <string>")
421 { 478 {
422 L.requireFailure("require 'lanes'.configure{shutdown_timeout = 'gluh'}"); 479 L.requireFailure("require 'lanes'.configure{shutdown_timeout = 'gluh'}");
423 } 480 }
424 481
425 // ----------------------------------------------------------------------------------------- 482 // ---------------------------------------------------------------------------------------------
426 483
427 SECTION("shutdown_timeout = <negative number>") 484 SECTION("shutdown_timeout = <negative number>")
428 { 485 {
429 L.requireFailure("require 'lanes'.configure{shutdown_timeout = -0.001}"); 486 L.requireFailure("require 'lanes'.configure{shutdown_timeout = -0.001}");
430 } 487 }
431 488
432 // ----------------------------------------------------------------------------------------- 489 // ---------------------------------------------------------------------------------------------
433 490
434 SECTION("shutdown_timeout = 0") 491 SECTION("shutdown_timeout = 0")
435 { 492 {
436 L.requireSuccess("require 'lanes'.configure{shutdown_timeout = 0}"); 493 L.requireSuccess("require 'lanes'.configure{shutdown_timeout = 0}");
437 } 494 }
438 495
439 // ----------------------------------------------------------------------------------------- 496 // ---------------------------------------------------------------------------------------------
440 497
441 SECTION("shutdown_timeout = 1s") 498 SECTION("shutdown_timeout = 1s")
442 { 499 {
443 L.requireSuccess("require 'lanes'.configure{shutdown_timeout = 1}"); 500 L.requireSuccess("require 'lanes'.configure{shutdown_timeout = 1}");
444 } 501 }
445 502
446 // ----------------------------------------------------------------------------------------- 503 // ---------------------------------------------------------------------------------------------
447 504
448 SECTION("shutdown_timeout = 3600s") 505 SECTION("shutdown_timeout = 3600s")
449 { 506 {
450 L.requireSuccess("require 'lanes'.configure{shutdown_timeout = 3600}"); 507 L.requireSuccess("require 'lanes'.configure{shutdown_timeout = 3600}");
451 } 508 }
452 509
453 // ----------------------------------------------------------------------------------------- 510 // ---------------------------------------------------------------------------------------------
454 511
455 SECTION("shutdown_timeout = <too long>") 512 SECTION("shutdown_timeout = <too long>")
456 { 513 {
457 L.requireFailure("require 'lanes'.configure{shutdown_timeout = 3600.001}"); 514 L.requireFailure("require 'lanes'.configure{shutdown_timeout = 3600.001}");
458 }
459 } 515 }
516}
517
518// #################################################################################################
519
520TEST_CASE("lanes.configure.strip_functions")
521{
522 LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
460 523
461 // ---------------------------------------------------------------------------------------------
462 // strip_functions should be a boolean 524 // strip_functions should be a boolean
463 525
464 SECTION("strip_functions") 526 SECTION("strip_functions = <table>")
465 { 527 {
466 SECTION("strip_functions = <table>") 528 L.requireFailure("require 'lanes'.configure{strip_functions = {}}");
467 { 529 }
468 L.requireFailure("require 'lanes'.configure{strip_functions = {}}");
469 }
470 530
471 // ----------------------------------------------------------------------------------------- 531 // ---------------------------------------------------------------------------------------------
472 532
473 SECTION("strip_functions = <string>") 533 SECTION("strip_functions = <string>")
474 { 534 {
475 L.requireFailure("require 'lanes'.configure{strip_functions = 'gluh'}"); 535 L.requireFailure("require 'lanes'.configure{strip_functions = 'gluh'}");
476 } 536 }
477 537
478 // ----------------------------------------------------------------------------------------- 538 // ---------------------------------------------------------------------------------------------
479 539
480 SECTION("strip_functions = <number>") 540 SECTION("strip_functions = <number>")
481 { 541 {
482 L.requireFailure("require 'lanes'.configure{strip_functions = 1}"); 542 L.requireFailure("require 'lanes'.configure{strip_functions = 1}");
483 } 543 }
484 544
485 // ----------------------------------------------------------------------------------------- 545 // ---------------------------------------------------------------------------------------------
486 546
487 SECTION("strip_functions = <C function>") 547 SECTION("strip_functions = <C function>")
488 { 548 {
489 L.requireFailure("require 'lanes'.configure{strip_functions = print}"); 549 L.requireFailure("require 'lanes'.configure{strip_functions = print}");
490 } 550 }
491 551
492 // ----------------------------------------------------------------------------------------- 552 // ---------------------------------------------------------------------------------------------
493 553
494 SECTION("strip_functions = false") 554 SECTION("strip_functions = false")
495 { 555 {
496 L.requireSuccess("require 'lanes'.configure{strip_functions = false}"); 556 L.requireSuccess("require 'lanes'.configure{strip_functions = false}");
497 } 557 }
498 558
499 // ----------------------------------------------------------------------------------------- 559 // ---------------------------------------------------------------------------------------------
500 560
501 SECTION("strip_functions = true") 561 SECTION("strip_functions = true")
502 { 562 {
503 L.requireSuccess("require 'lanes'.configure{strip_functions = true}"); 563 L.requireSuccess("require 'lanes'.configure{strip_functions = true}");
504 }
505 } 564 }
565}
566
567// #################################################################################################
568
569TEST_CASE("lanes.configure.track_lanes")
570{
571 LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
506 572
507 // ---------------------------------------------------------------------------------------------
508 // track_lanes should be a boolean 573 // track_lanes should be a boolean
509 574
510 SECTION("track_lanes") 575 SECTION("track_lanes = <table>")
511 { 576 {
512 SECTION("track_lanes = <table>") 577 L.requireFailure("require 'lanes'.configure{track_lanes = {}}");
513 { 578 }
514 L.requireFailure("require 'lanes'.configure{track_lanes = {}}");
515 }
516 579
517 // ----------------------------------------------------------------------------------------- 580 // ---------------------------------------------------------------------------------------------
518 581
519 SECTION("track_lanes = <string>") 582 SECTION("track_lanes = <string>")
520 { 583 {
521 L.requireFailure("require 'lanes'.configure{track_lanes = 'gluh'}"); 584 L.requireFailure("require 'lanes'.configure{track_lanes = 'gluh'}");
522 } 585 }
523 586
524 // ----------------------------------------------------------------------------------------- 587 // ---------------------------------------------------------------------------------------------
525 588
526 SECTION("track_lanes = <number>") 589 SECTION("track_lanes = <number>")
527 { 590 {
528 L.requireFailure("require 'lanes'.configure{track_lanes = 1}"); 591 L.requireFailure("require 'lanes'.configure{track_lanes = 1}");
529 } 592 }
530 593
531 // ----------------------------------------------------------------------------------------- 594 // ---------------------------------------------------------------------------------------------
532 595
533 SECTION("track_lanes = <C function>") 596 SECTION("track_lanes = <C function>")
534 { 597 {
535 L.requireFailure("require 'lanes'.configure{track_lanes = print}"); 598 L.requireFailure("require 'lanes'.configure{track_lanes = print}");
536 } 599 }
537 600
538 // ----------------------------------------------------------------------------------------- 601 // ---------------------------------------------------------------------------------------------
539 602
540 SECTION("track_lanes = false") 603 SECTION("track_lanes = false")
541 { 604 {
542 L.requireSuccess("require 'lanes'.configure{track_lanes = false}"); 605 L.requireSuccess("require 'lanes'.configure{track_lanes = false}");
543 } 606 }
544 607
545 // ----------------------------------------------------------------------------------------- 608 // ---------------------------------------------------------------------------------------------
546 609
547 SECTION("track_lanes = true") 610 SECTION("track_lanes = true")
548 { 611 {
549 L.requireSuccess("require 'lanes'.configure{track_lanes = true}"); 612 L.requireSuccess("require 'lanes'.configure{track_lanes = true}");
550 }
551 } 613 }
614}
615
616// #################################################################################################
617
618TEST_CASE("lanes.configure.verbose_errors")
619{
620 LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
552 621
553 // ---------------------------------------------------------------------------------------------
554 // verbose_errors should be a boolean 622 // verbose_errors should be a boolean
555 623
556 SECTION("verbose_errors") 624 SECTION("verbose_errors = <table>")
557 { 625 {
558 SECTION("verbose_errors = <table>") 626 L.requireFailure("require 'lanes'.configure{verbose_errors = {}}");
559 { 627 }
560 L.requireFailure("require 'lanes'.configure{verbose_errors = {}}");
561 }
562 628
563 // ----------------------------------------------------------------------------------------- 629 // ---------------------------------------------------------------------------------------------
564 630
565 SECTION("verbose_errors = <string>") 631 SECTION("verbose_errors = <string>")
566 { 632 {
567 L.requireFailure("require 'lanes'.configure{verbose_errors = 'gluh'}"); 633 L.requireFailure("require 'lanes'.configure{verbose_errors = 'gluh'}");
568 } 634 }
569 635
570 // ----------------------------------------------------------------------------------------- 636 // ---------------------------------------------------------------------------------------------
571 637
572 SECTION("verbose_errors = <number>") 638 SECTION("verbose_errors = <number>")
573 { 639 {
574 L.requireFailure("require 'lanes'.configure{verbose_errors = 1}"); 640 L.requireFailure("require 'lanes'.configure{verbose_errors = 1}");
575 } 641 }
576 642
577 // ----------------------------------------------------------------------------------------- 643 // ---------------------------------------------------------------------------------------------
578 644
579 SECTION("verbose_errors = <C function>") 645 SECTION("verbose_errors = <C function>")
580 { 646 {
581 L.requireFailure("require 'lanes'.configure{verbose_errors = print}"); 647 L.requireFailure("require 'lanes'.configure{verbose_errors = print}");
582 } 648 }
583 649
584 // ----------------------------------------------------------------------------------------- 650 // ---------------------------------------------------------------------------------------------
585 651
586 SECTION("verbose_errors = false") 652 SECTION("verbose_errors = false")
587 { 653 {
588 L.requireSuccess("require 'lanes'.configure{verbose_errors = false}"); 654 L.requireSuccess("require 'lanes'.configure{verbose_errors = false}");
589 } 655 }
590 656
591 // ----------------------------------------------------------------------------------------- 657 // ---------------------------------------------------------------------------------------------
592 658
593 SECTION("verbose_errors = true") 659 SECTION("verbose_errors = true")
594 { 660 {
595 L.requireSuccess("require 'lanes'.configure{verbose_errors = true}"); 661 L.requireSuccess("require 'lanes'.configure{verbose_errors = true}");
596 }
597 } 662 }
663}
664
665// #################################################################################################
666
667TEST_CASE("lanes.configure.with_timers")
668{
669 LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
598 670
599 // ---------------------------------------------------------------------------------------------
600 // with_timers should be a boolean 671 // with_timers should be a boolean
601 672
602 SECTION("with_timers") 673 SECTION("with_timers = <table>")
603 { 674 {
604 SECTION("with_timers = <table>") 675 L.requireFailure("require 'lanes'.configure{with_timers = {}}");
605 { 676 }
606 L.requireFailure("require 'lanes'.configure{with_timers = {}}");
607 }
608 677
609 // ----------------------------------------------------------------------------------------- 678 // ---------------------------------------------------------------------------------------------
610 679
611 SECTION("with_timers = <string>") 680 SECTION("with_timers = <string>")
612 { 681 {
613 L.requireFailure("require 'lanes'.configure{with_timers = 'gluh'}"); 682 L.requireFailure("require 'lanes'.configure{with_timers = 'gluh'}");
614 } 683 }
615 684
616 // ----------------------------------------------------------------------------------------- 685 // ---------------------------------------------------------------------------------------------
617 686
618 SECTION("with_timers = <number>") 687 SECTION("with_timers = <number>")
619 { 688 {
620 L.requireFailure("require 'lanes'.configure{with_timers = 1}"); 689 L.requireFailure("require 'lanes'.configure{with_timers = 1}");
621 } 690 }
622 691
623 // ----------------------------------------------------------------------------------------- 692 // ---------------------------------------------------------------------------------------------
624 693
625 SECTION("with_timers = <C function>") 694 SECTION("with_timers = <C function>")
626 { 695 {
627 L.requireFailure("require 'lanes'.configure{with_timers = print}"); 696 L.requireFailure("require 'lanes'.configure{with_timers = print}");
628 } 697 }
629 698
630 // ----------------------------------------------------------------------------------------- 699 // ---------------------------------------------------------------------------------------------
631 700
632 SECTION("with_timers = false") 701 SECTION("with_timers = false")
633 { 702 {
634 L.requireSuccess("require 'lanes'.configure{with_timers = false}"); 703 L.requireSuccess("require 'lanes'.configure{with_timers = false}");
635 } 704 }
636 705
637 // ----------------------------------------------------------------------------------------- 706 // ---------------------------------------------------------------------------------------------
638 707
639 SECTION("with_timers = true") 708 SECTION("with_timers = true")
640 { 709 {
641 L.requireSuccess("require 'lanes'.configure{with_timers = true}"); 710 L.requireSuccess("require 'lanes'.configure{with_timers = true}");
642 }
643 } 711 }
712}
713
714// #################################################################################################
715
716TEST_CASE("lanes.configure.unknown_setting")
717{
718 LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
644 719
645 // ---------------------------------------------------------------------------------------------
646 // any unknown setting should be rejected 720 // any unknown setting should be rejected
647 721
648 SECTION("unknown_setting") 722 SECTION("table setting")
649 { 723 {
650 SECTION("table setting") 724 L.requireFailure("require 'lanes'.configure{[{}] = {}}");
651 { 725 }
652 L.requireFailure("require 'lanes'.configure{[{}] = {}}");
653 }
654 726
655 // ----------------------------------------------------------------------------------------- 727 // ---------------------------------------------------------------------------------------------
656 728
657 SECTION("boolean setting") 729 SECTION("boolean setting")
658 { 730 {
659 L.requireFailure("require 'lanes'.configure{[true] = 'gluh'}"); 731 L.requireFailure("require 'lanes'.configure{[true] = 'gluh'}");
660 } 732 }
661 733
662 // ----------------------------------------------------------------------------------------- 734 // ---------------------------------------------------------------------------------------------
663 735
664 SECTION("function setting") 736 SECTION("function setting")
665 { 737 {
666 L.requireFailure("require 'lanes'.configure{[function() end] = 1}"); 738 L.requireFailure("require 'lanes'.configure{[function() end] = 1}");
667 } 739 }
668 740
669 // ----------------------------------------------------------------------------------------- 741 // ---------------------------------------------------------------------------------------------
670 742
671 SECTION("number setting") 743 SECTION("number setting")
672 { 744 {
673 L.requireFailure("require 'lanes'.configure{[1] = function() end}"); 745 L.requireFailure("require 'lanes'.configure{[1] = function() end}");
674 } 746 }
675 747
676 // ----------------------------------------------------------------------------------------- 748 // ---------------------------------------------------------------------------------------------
677 749
678 SECTION("unknown string setting") 750 SECTION("unknown string setting")
679 { 751 {
680 L.requireFailure("require 'lanes'.configure{['gluh'] = false}"); 752 L.requireFailure("require 'lanes'.configure{['gluh'] = false}");
681 }
682 } 753 }
683} 754}
684 755
diff --git a/unit_tests/linda_tests.cpp b/unit_tests/linda_tests.cpp
index f2934eb..9dbaa85 100644
--- a/unit_tests/linda_tests.cpp
+++ b/unit_tests/linda_tests.cpp
@@ -3,67 +3,132 @@
3 3
4// ################################################################################################# 4// #################################################################################################
5 5
6TEST_CASE("linda.single Keeper") 6TEST_CASE("linda.single_keeper.creation/no_argument")
7{ 7{
8 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } }; 8 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } };
9 S.requireSuccess("lanes = require 'lanes'"); 9 S.requireSuccess("lanes = require 'lanes'");
10 10
11 SECTION("Linda creation") 11 // no argument is ok
12 { 12 S.requireSuccess("lanes.linda()");
13 // no parameters is ok 13 S.requireNotReturnedString("return tostring(lanes.linda())", R"===(Linda: <not a string>)==="); // unspecified name should not result in <not a string>
14 S.requireSuccess("lanes.linda()"); 14}
15 S.requireNotReturnedString("return tostring(lanes.linda())", R"===(Linda: <not a string>)==="); // unspecified name should not result in <not a string>
16
17 // since we have only one keeper, only group 0 is authorized
18 S.requireFailure("lanes.linda(-1)");
19 S.requireSuccess("lanes.linda(0)");
20 S.requireFailure("lanes.linda(1)");
21
22 // any name is ok
23 S.requireSuccess("lanes.linda('')"); // an empty name results in a string conversion of the form "Linda: <some hex value>" that we can't test (but it works)
24 S.requireReturnedString("return tostring(lanes.linda('short name'))", R"===(Linda: short name)===");
25 S.requireReturnedString("return tostring(lanes.linda('very very very very very very long name'))", R"===(Linda: very very very very very very long name)===");
26 S.requireReturnedString("return tostring(lanes.linda('auto'))", R"===(Linda: [string "return tostring(lanes.linda('auto'))"]:1)===");
27
28 if constexpr (LUA_VERSION_NUM == 504) {
29 // a function is acceptable as a __close handler
30 S.requireSuccess("local l <close> = lanes.linda(function() end)");
31 // a callable table too (a callable full userdata as well, but I have none here)
32 S.requireSuccess("local l <close> = lanes.linda(setmetatable({}, {__call = function() end}))");
33 // if the function raises an error, we should get it
34 S.requireFailure("local l <close> = lanes.linda(function() error 'gluh' end)");
35 } else {
36 // no __close support before Lua 5.4
37 S.requireFailure("lanes.linda(function() end)");
38 S.requireFailure("lanes.linda(setmetatable({}, {__call = function() end}))");
39 }
40 15
41 // mixing parameters in any order is ok: 2 out of 3 16// #################################################################################################
42 S.requireSuccess("lanes.linda(0, 'name')");
43 S.requireSuccess("lanes.linda('name', 0)");
44 if constexpr (LUA_VERSION_NUM == 504) {
45 S.requireSuccess("lanes.linda(0, function() end)");
46 S.requireSuccess("lanes.linda(function() end, 0)");
47 S.requireSuccess("lanes.linda('name', function() end)");
48 S.requireSuccess("lanes.linda(function() end, 'name')");
49 }
50 17
51 // mixing parameters in any order is ok: 3 out of 3 18TEST_CASE("linda.single_keeper.creation/non_table_arguments")
52 if constexpr (LUA_VERSION_NUM == 504) { 19{
53 S.requireSuccess("lanes.linda(0, 'name', function() end)"); 20 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } };
54 S.requireSuccess("lanes.linda(0, function() end, 'name')"); 21 S.requireSuccess("lanes = require 'lanes'");
55 S.requireSuccess("lanes.linda('name', 0, function() end)"); 22
56 S.requireSuccess("lanes.linda('name', function() end, 0)"); 23 // any argument that is not a table is not ok
57 S.requireSuccess("lanes.linda(function() end, 0, 'name')"); 24 S.requireFailure("lanes.linda(0)");
58 S.requireSuccess("lanes.linda(function() end, 'name', 0)"); 25 S.requireFailure("lanes.linda('bob')");
59 } 26 S.requireFailure("lanes.linda(false)");
27 S.requireFailure("lanes.linda(function() end)");
28 S.requireFailure("lanes.linda(lanes.cancel_error)");
29}
30
31// #################################################################################################
32
33TEST_CASE("linda.single_keeper.creation/close_handler")
34{
35 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } };
36 S.requireSuccess("lanes = require 'lanes'");
37
38 if constexpr (LUA_VERSION_NUM == 504) {
39 // a function is acceptable as a __close handler
40 S.requireSuccess("local l <close> = lanes.linda{close_handler = function() end}");
41 // a callable table too (a callable full userdata as well, but I have none here)
42 S.requireSuccess("local l <close> = lanes.linda{close_handler = setmetatable({}, {__call = function() end})}");
43 } else {
44 // no __close support before Lua 5.4, field is ignored (therefore invalid values are accepted too!)
45 S.requireSuccess("lanes.linda{close_handler = 'a string'}");
46 S.requireSuccess("lanes.linda{close_handler = function() end}");
47 S.requireSuccess("lanes.linda{close_handler = setmetatable({}, {__call = function() end})}");
48 }
49}
50
51// #################################################################################################
52
53TEST_CASE("linda.single_keeper.creation/table_argument")
54{
55 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } };
56 S.requireSuccess("lanes = require 'lanes'");
57
58 // one table is fine
59 S.requireSuccess("lanes.linda{}");
60 // anything beyond that is not
61 S.requireFailure("lanes.linda({},{})");
62 S.requireFailure("lanes.linda({},'bob')");
63 S.requireFailure("lanes.linda({},42)");
64}
65
66// #################################################################################################
67
68TEST_CASE("linda.single_keeper.creation/group")
69{
70 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } };
71 S.requireSuccess("lanes = require 'lanes'");
72
73 // since we have only one keeper, only group 0 is authorized
74 S.requireFailure("lanes.linda{group = -1}");
75 S.requireSuccess("lanes.linda{group = 0}");
76 S.requireFailure("lanes.linda{group = 1}");
77}
78
79// #################################################################################################
80
81TEST_CASE("linda.single_keeper.creation/name")
82{
83 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } };
84 S.requireSuccess("lanes = require 'lanes'");
85
86 // any name is ok
87 S.requireSuccess("lanes.linda{name = ''}"); // an empty name results in a string conversion of the form "Linda: <some hex value>" that we can't test (but it works)
88 S.requireReturnedString("return tostring(lanes.linda{name = 'short name'})", R"===(Linda: short name)===");
89 S.requireReturnedString("return tostring(lanes.linda{name = 'very very very very very very long name'})", R"===(Linda: very very very very very very long name)===");
90 S.requireReturnedString("return tostring(lanes.linda{name = 'auto'})", R"===(Linda: [string "return tostring(lanes.linda{name = 'auto'})"]:1)===");
91}
92
93// #################################################################################################
94
95TEST_CASE("linda.single_keeper.creation/wake_period")
96{
97 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } };
98 S.requireSuccess("lanes = require 'lanes'");
60 99
61 // unsupported parameters should fail 100 // wake_period should be a number > 0
62 S.requireFailure("lanes.linda(true)"); 101 S.requireFailure("lanes.linda{wake_period = false}");
63 S.requireFailure("lanes.linda(false)"); 102 S.requireFailure("lanes.linda{wake_period = 'bob'}");
64 // uncallable table or full userdata 103 S.requireFailure("lanes.linda{wake_period = {}}");
65 S.requireFailure("lanes.linda({})"); 104 S.requireFailure("lanes.linda{wake_period = -1}");
66 S.requireFailure("lanes.linda(lanes.linda())"); 105 S.requireFailure("lanes.linda{wake_period = 0}");
106 S.requireSuccess("lanes.linda{wake_period = 0.0001}");
107}
108
109// #################################################################################################
110
111TEST_CASE("linda.single_keeper.wake_period")
112{
113 FAIL("TODO: check that wake_period works as expected");
114 // - use configure default if not provided
115 // - overrides default when provided
116 // - blocking operation wakes at the specified period
117}
118
119// #################################################################################################
120
121TEST_CASE("linda.single_keeper.the_rest")
122{
123 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } };
124 S.requireSuccess("lanes = require 'lanes'");
125
126 // ---------------------------------------------------------------------------------------------
127
128 SECTION("error in close handler is propagated")
129 {
130 // if the function raises an error, we should get it
131 S.requireFailure("local l <close> = lanes.linda{close_handler = function() error 'gluh' end}");
67 } 132 }
68 133
69 // --------------------------------------------------------------------------------------------- 134 // ---------------------------------------------------------------------------------------------
@@ -311,12 +376,12 @@ TEST_CASE("linda.multi Keeper")
311 376
312 S.requireSuccess("lanes = require 'lanes'.configure{nb_user_keepers = 3}"); 377 S.requireSuccess("lanes = require 'lanes'.configure{nb_user_keepers = 3}");
313 378
314 S.requireFailure("lanes.linda(-1)"); 379 S.requireFailure("lanes.linda{group = -1}");
315 S.requireSuccess("lanes.linda(0)"); 380 S.requireSuccess("lanes.linda{group = 0}");
316 S.requireSuccess("lanes.linda(1)"); 381 S.requireSuccess("lanes.linda{group = 1}");
317 S.requireSuccess("lanes.linda(2)"); 382 S.requireSuccess("lanes.linda{group = 2}");
318 S.requireSuccess("lanes.linda(3)"); 383 S.requireSuccess("lanes.linda{group = 3}");
319 S.requireFailure("lanes.linda(4)"); 384 S.requireFailure("lanes.linda{group = 4}");
320} 385}
321 386
322// ################################################################################################# 387// #################################################################################################
diff --git a/unit_tests/scripts/lane/tasking_cancelling.lua b/unit_tests/scripts/lane/tasking_cancelling.lua
index ea4516e..873140e 100644
--- a/unit_tests/scripts/lane/tasking_cancelling.lua
+++ b/unit_tests/scripts/lane/tasking_cancelling.lua
@@ -16,7 +16,7 @@ local lanes_linda = assert(lanes.linda)
16-- ################################################################################################## 16-- ##################################################################################################
17 17
18-- cancellation of lanes waiting on a linda 18-- cancellation of lanes waiting on a linda
19local limited = lanes_linda("limited") 19local limited = lanes_linda{name = "limited"}
20assert.fails(function() limited:limit("key", -1) end) 20assert.fails(function() limited:limit("key", -1) end)
21assert.failsnot(function() limited:limit("key", 1) end) 21assert.failsnot(function() limited:limit("key", 1) end)
22-- [[################################################ 22-- [[################################################
diff --git a/unit_tests/scripts/lane/tasking_comms_criss_cross.lua b/unit_tests/scripts/lane/tasking_comms_criss_cross.lua
index 497e81d..610da8b 100644
--- a/unit_tests/scripts/lane/tasking_comms_criss_cross.lua
+++ b/unit_tests/scripts/lane/tasking_comms_criss_cross.lua
@@ -42,7 +42,7 @@ local tc = lanes_gen("io", { name = 'auto', gc_cb = gc_cb },
42 end 42 end
43) 43)
44 44
45local linda= lanes_linda("criss cross") 45local linda= lanes_linda{name = "criss cross"}
46 46
47local a,b= tc(linda, "A","B"), tc(linda, "B","A") -- launching two lanes, twisted comms 47local a,b= tc(linda, "A","B"), tc(linda, "B","A") -- launching two lanes, twisted comms
48 48
diff --git a/unit_tests/scripts/lane/tasking_communications.lua b/unit_tests/scripts/lane/tasking_communications.lua
index 631d105..01842b4 100644
--- a/unit_tests/scripts/lane/tasking_communications.lua
+++ b/unit_tests/scripts/lane/tasking_communications.lua
@@ -72,7 +72,7 @@ local chunk= function(linda)
72 WR("chunk ", "Lane ends!\n") 72 WR("chunk ", "Lane ends!\n")
73end 73end
74 74
75local linda = lanes_linda("communications") 75local linda = lanes_linda{name = "communications"}
76assert(type(linda) == "userdata" and tostring(linda) == "Linda: communications") 76assert(type(linda) == "userdata" and tostring(linda) == "Linda: communications")
77 -- 77 --
78 -- ["->"] master -> slave 78 -- ["->"] master -> slave
diff --git a/unit_tests/scripts/lane/tasking_send_receive_code.lua b/unit_tests/scripts/lane/tasking_send_receive_code.lua
index e329a88..cb3663f 100644
--- a/unit_tests/scripts/lane/tasking_send_receive_code.lua
+++ b/unit_tests/scripts/lane/tasking_send_receive_code.lua
@@ -65,7 +65,7 @@ local function chunk2(linda)
65 linda:send("up", function() return ":)" end, "ok2") 65 linda:send("up", function() return ":)" end, "ok2")
66end 66end
67 67
68local linda = lanes_linda("auto") 68local linda = lanes_linda{name = "auto"}
69local t2= lanes_gen("debug,package,string,io", { name = 'auto', gc_cb = gc_cb }, chunk2)(linda) -- prepare & launch 69local t2= lanes_gen("debug,package,string,io", { name = 'auto', gc_cb = gc_cb }, chunk2)(linda) -- prepare & launch
70linda:send("down", function(linda) linda:send("up", "ready!") end, 70linda:send("down", function(linda) linda:send("up", "ready!") end,
71 "ok") 71 "ok")
diff --git a/unit_tests/scripts/linda/multiple_keepers.lua b/unit_tests/scripts/linda/multiple_keepers.lua
index 8733087..267d874 100644
--- a/unit_tests/scripts/linda/multiple_keepers.lua
+++ b/unit_tests/scripts/linda/multiple_keepers.lua
@@ -2,9 +2,9 @@
2local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{nb_user_keepers = 3, keepers_gc_threshold = 500} 2local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{nb_user_keepers = 3, keepers_gc_threshold = 500}
3local lanes = require_lanes_result_1 3local lanes = require_lanes_result_1
4 4
5local a = lanes.linda("A", 1) 5local a = lanes.linda{name = "A", group = 1}
6local b = lanes.linda("B", 2) 6local b = lanes.linda{name = "B", group = 2}
7local c = lanes.linda("C", 3) 7local c = lanes.linda{name = "C", group = 3}
8 8
9-- store each linda in the other 2 9-- store each linda in the other 2
10do 10do
diff --git a/unit_tests/scripts/linda/send_registered_userdata.lua b/unit_tests/scripts/linda/send_registered_userdata.lua
index 2c0195a..90c05c9 100644
--- a/unit_tests/scripts/linda/send_registered_userdata.lua
+++ b/unit_tests/scripts/linda/send_registered_userdata.lua
@@ -1,5 +1,5 @@
1local lanes = require 'lanes'.configure{with_timers = false} 1local lanes = require 'lanes'.configure{with_timers = false}
2local l = lanes.linda'gleh' 2local l = lanes.linda{name = 'gleh'}
3l:set('yo', io.stdin) 3l:set('yo', io.stdin)
4local n, stdin_out = l:get('yo') 4local n, stdin_out = l:get('yo')
5assert(n == 1 and stdin_out == io.stdin, tostring(stdin_out) .. " ~= " .. tostring(io.stdin)) 5assert(n == 1 and stdin_out == io.stdin, tostring(stdin_out) .. " ~= " .. tostring(io.stdin))