aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2026-02-27 10:59:11 +0100
committerBenoit Germain <benoit.germain@ubisoft.com>2026-02-27 10:59:11 +0100
commit7e4b6e1526c5b2f85079df6a6ecfe08244a7dbcc (patch)
tree3efb69b5a2d40fd1c226b72f3d0f99dcc30afc83
parent68355c7dd6bef53d264eca53567df9fb9c8684b6 (diff)
downloadlanes-7e4b6e1526c5b2f85079df6a6ecfe08244a7dbcc.tar.gz
lanes-7e4b6e1526c5b2f85079df6a6ecfe08244a7dbcc.tar.bz2
lanes-7e4b6e1526c5b2f85079df6a6ecfe08244a7dbcc.zip
Unify sleep() timeout with send() and receive (nil means forever)v4.0.0
-rw-r--r--CHANGES2
-rw-r--r--docs/index.html20
-rw-r--r--src/lanes.cpp24
-rw-r--r--tests/track_lanes.lua2
-rw-r--r--unit_tests/lane_tests.cpp8
5 files changed, 28 insertions, 28 deletions
diff --git a/CHANGES b/CHANGES
index 4324035..fd45f10 100644
--- a/CHANGES
+++ b/CHANGES
@@ -12,7 +12,7 @@ CHANGE 2: BGe 26-Feb-26
12 - Lanes module: 12 - Lanes module:
13 - shared library is now lanes_core.[so|dll] instead of lanes/core.[so|dll] 13 - shared library is now lanes_core.[so|dll] instead of lanes/core.[so|dll]
14 - lanes.register() is also available as lanes_register() in the exported C API. 14 - lanes.register() is also available as lanes_register() in the exported C API.
15 - lanes.sleep() accepts a new argument "indefinitely" to block forever (until hard cancellation is received). 15 - lanes.sleep(): passing nil or no argument now blocks indefinitely (until cancellation is received).
16 - function set_debug_threadname() available inside a Lane is renamed lane_threadname(); can now both read and write the name. 16 - function set_debug_threadname() available inside a Lane is renamed lane_threadname(); can now both read and write the name.
17 - function cancel_test() raises cancel_error by default in a hard-cancelled lane. 17 - function cancel_test() raises cancel_error by default in a hard-cancelled lane.
18 - new function lanes.finally(). Installs a function that gets called at Lanes shutdown after attempting to terminate all lanes. 18 - new function lanes.finally(). Installs a function that gets called at Lanes shutdown after attempting to terminate all lanes.
diff --git a/docs/index.html b/docs/index.html
index c9a85ac..a36119e 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -218,7 +218,7 @@
218 <li> 218 <li>
219 Inside the lane 219 Inside the lane
220 <ul> 220 <ul>
221 <li><tt>cancel_test()</tt>: check for cancellation requests</li> 221 <li><tt>cancel_test()</tt>: check for <a href="#cancelling">cancellation</a> requests</li>
222 <li><tt>lane_threadname()</tt>: read or change the name of the thread</li> 222 <li><tt>lane_threadname()</tt>: read or change the name of the thread</li>
223 <li><tt>set_finalizer()</tt>: install a function called when the lane exits</li> 223 <li><tt>set_finalizer()</tt>: install a function called when the lane exits</li>
224 </ul> 224 </ul>
@@ -226,7 +226,7 @@
226 <li> 226 <li>
227 Given some <a href="#lindas">linda</a> <tt>l</tt> 227 Given some <a href="#lindas">linda</a> <tt>l</tt>
228 <ul> 228 <ul>
229 <li><tt>l:cancel()</tt>: mark a <a href="#lindas">linda</a> for cancellation</li> 229 <li><tt>l:cancel()</tt>: mark a <a href="#lindas">linda</a> for <a href="#cancelling">cancellation</a></li>
230 <li><tt>l:collectgarbage()</tt>: trigger a GC cycle in the <a href="#lindas">linda</a>'s Keeper state</li> 230 <li><tt>l:collectgarbage()</tt>: trigger a GC cycle in the <a href="#lindas">linda</a>'s Keeper state</li>
231 <li><tt>l:deep()</tt>: obtain a light userdata uniquely representing the <a href="#lindas">linda</a></li> 231 <li><tt>l:deep()</tt>: obtain a light userdata uniquely representing the <a href="#lindas">linda</a></li>
232 <li><tt>l:dump()</tt>: have information about slot contents</li> 232 <li><tt>l:dump()</tt>: have information about slot contents</li>
@@ -452,8 +452,8 @@
452 Sets the default period in seconds a <a href="#lindas">linda</a> will wake by itself during blocked operations. Default is never.<br /> 452 Sets the default period in seconds a <a href="#lindas">linda</a> will wake by itself during blocked operations. Default is never.<br />
453 When a <a href="#lindas">linda</a> 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 453 When a <a href="#lindas">linda</a> 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
454 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 454 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
455 for cancellation, but a <a href="#lindas">linda</a> 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 455 for <a href="#cancelling">cancellation</a>, but a <a href="#lindas">linda</a> 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
456 causing the <a href="#lindas">linda</a> to wait out the full operation timeout before cancellation is processed. 456 causing the <a href="#lindas">linda</a> to wait out the full operation timeout before <a href="#cancelling">cancellation</a> is processed.
457 </td> 457 </td>
458 </tr> 458 </tr>
459 459
@@ -499,7 +499,7 @@
499 </td> 499 </td>
500 <td> 500 <td>
501 Sets the duration in seconds Lanes will wait for graceful termination of running lanes at application shutdown. Default is <tt>0.25</tt>.<br /> 501 Sets the duration in seconds Lanes will wait for graceful termination of running lanes at application shutdown. Default is <tt>0.25</tt>.<br />
502 Lanes signals all lanes for cancellation with <tt>"soft"</tt>, <tt>"hard"</tt>, and <tt>"all"</tt> modes, in that order. Each attempt has <tt>shutdown_timeout</tt> seconds to succeed before the next one.<br /> 502 Lanes signals all lanes for <a href="#cancelling">cancellation</a> with <tt>"soft"</tt>, <tt>"hard"</tt>, and <tt>"all"</tt> modes, in that order. Each attempt has <tt>shutdown_timeout</tt> seconds to succeed before the next one.<br />
503 Then there is a last chance at cleanup with <a href="#finally"><tt>lanes.finally()</tt></a>. If some lanes are still running after that point, shutdown will either freeze or throw. It is YOUR responsibility to cleanup properly after yourself. 503 Then there is a last chance at cleanup with <a href="#finally"><tt>lanes.finally()</tt></a>. If some lanes are still running after that point, shutdown will either freeze or throw. It is YOUR responsibility to cleanup properly after yourself.
504 IMPORTANT: If there are still running lanes at shutdown, an error is raised, which will be propagated by Lua to the handler installed by <tt>lua_setwarnf</tt>. If the finalizer returned a value, this will be used as the error message.<br /> 504 IMPORTANT: If there are still running lanes at shutdown, an error is raised, which will be propagated by Lua to the handler installed by <tt>lua_setwarnf</tt>. If the finalizer returned a value, this will be used as the error message.<br />
505 LANES SHUTDOWN WILL NOT BE COMPLETE IN THAT CASE, AND THE SUBSEQUENT CONSEQUENCES ARE UNDEFINED! 505 LANES SHUTDOWN WILL NOT BE COMPLETE IN THAT CASE, AND THE SUBSEQUENT CONSEQUENCES ARE UNDEFINED!
@@ -1561,7 +1561,7 @@
1561<p> 1561<p>
1562 <tt>get()</tt> can read several values at once, and does not block. Return values ares: 1562 <tt>get()</tt> can read several values at once, and does not block. Return values ares:
1563 <ul> 1563 <ul>
1564 <li><tt>nil, lanes.cancel_error</tt> in case of cancellation.</li> 1564 <li><tt>nil, lanes.cancel_error</tt> in case of <a href="#cancelling">cancellation</a>.</li>
1565 <li><tt>number, val...</tt> where number is the actual count of items obtained from the linda (can be 0).</li> 1565 <li><tt>number, val...</tt> where number is the actual count of items obtained from the linda (can be 0).</li>
1566 </ul> 1566 </ul>
1567</p> 1567</p>
@@ -1768,12 +1768,12 @@
1768</p> 1768</p>
1769 1769
1770<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1770<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1771 nil, "timeout" = lanes.sleep(['indefinitely'|seconds|nil]) 1771 nil, "timeout" = lanes.sleep([seconds|nil])
1772</pre></td></tr></table> 1772</pre></td></tr></table>
1773 1773
1774<p> 1774<p>
1775 A very simple way of sleeping when nothing else is available. Is implemented by attempting to read some data in an unused channel of the internal <a href="#lindas">linda</a> used for timers (this <a href="#lindas">linda</a> exists even when timers aren't enabled). 1775 A very simple way of sleeping when nothing else is available. Is implemented by attempting to read some data in an unused channel of the internal <a href="#lindas">linda</a> used for timers (this <a href="#lindas">linda</a> exists even when timers aren't enabled).
1776 Default duration is 0, which should only cause a thread context switch.<br /> 1776 Passing <tt>nil</tt> or no argument sleeps indefinitely (until <a href="#cancelling">cancellation</a> is received). Passing a non-negative number sleeps for that many seconds.<br />
1777 Return values should always be <tt>nil, "timeout"</tt> (or <tt>nil, lanes.cancel_error</tt> in case of interruption). 1777 Return values should always be <tt>nil, "timeout"</tt> (or <tt>nil, lanes.cancel_error</tt> in case of interruption).
1778</p> 1778</p>
1779 1779
@@ -2138,13 +2138,13 @@ static MyDeepFactory g_MyDeepFactory;
2138<h3 id="cancelling_cancel">Cancelling cancel</h3> 2138<h3 id="cancelling_cancel">Cancelling cancel</h3>
2139 2139
2140<p> 2140<p>
2141 Cancellation of lanes uses the Lua error mechanism with a special lightuserdata error sentinel. 2141 <a href="#cancelling">Cancellation</a> of lanes uses the Lua error mechanism with a special lightuserdata error sentinel.
2142 If you use <tt>pcall</tt> in code that needs to be cancellable from the outside, the special error might not get through to Lanes, thus preventing the lane from being cleanly cancelled. 2142 If you use <tt>pcall</tt> in code that needs to be cancellable from the outside, the special error might not get through to Lanes, thus preventing the lane from being cleanly cancelled.
2143 You should throw any lightuserdata error further. 2143 You should throw any lightuserdata error further.
2144</p> 2144</p>
2145 2145
2146<p> 2146<p>
2147 This system can actually be used by application to detect cancel, do your own cancellation duties, and pass on the error so Lanes will get it. If it does not get a clean cancellation from a lane in due time, it may forcefully kill the lane. 2147 This system can actually be used by application to detect cancel, do your own <a href="#cancelling">cancellation</a> duties, and pass on the error so Lanes will get it. If it does not get a clean cancellation from a lane in due time, it may forcefully kill the lane.
2148</p> 2148</p>
2149 2149
2150<p> 2150<p>
diff --git a/src/lanes.cpp b/src/lanes.cpp
index ef8577b..cf227f9 100644
--- a/src/lanes.cpp
+++ b/src/lanes.cpp
@@ -169,25 +169,23 @@ LUAG_FUNC(sleep)
169 extern LUAG_FUNC(linda_receive); 169 extern LUAG_FUNC(linda_receive);
170 170
171 Universe* const _U{ Universe::Get(L_) }; 171 Universe* const _U{ Universe::Get(L_) };
172 lua_settop(L_, 1); 172 lua_settop(L_, 1); // L_: duration?
173 lua_pushcfunction(L_, LG_linda_receive); // L_: duration|nil receive() 173 lua_pushcfunction(L_, LG_linda_receive); // L_: duration? receive()
174 STACK_CHECK_START_REL(L_, 0); // we pushed the function we intend to call, now prepare the arguments 174 STACK_CHECK_START_REL(L_, 0); // we pushed the function we intend to call, now prepare the arguments
175 _U->timerLinda->push(L_); // L_: duration|nil receive() timerLinda 175 _U->timerLinda->push(L_); // L_: duration? receive() timerLinda
176 if (luaW_tostring(L_, StackIndex{ 1 }) == "indefinitely") { 176 if (lua_isnumber(L_, 1)) {
177 lua_pushnil(L_); // L_: duration? receive() timerLinda nil
178 } else if (lua_isnoneornil(L_, 1)) {
179 lua_pushnumber(L_, 0); // L_: duration? receive() timerLinda 0
180 } else if (!lua_isnumber(L_, 1)) {
181 raise_luaL_argerror(L_, StackIndex{ 1 }, "duration must be a number");
182 }
183 else {
184 auto const _n{ lua_tonumber(L_, 1) }; 177 auto const _n{ lua_tonumber(L_, 1) };
185 if (_n < 0) { 178 if (_n < 0) {
186 raise_luaL_argerror(L_, StackIndex{ 1 }, "duration must be >= 0"); 179 raise_luaL_argerror(L_, StackIndex{ 1 }, "duration must be >= 0");
187 } 180 }
188 lua_pushnumber(L_, lua_tonumber(L_, 1)); // L_: duration? receive() timerLinda duration 181 lua_pushvalue(L_, StackIndex{ 1 }); // L_: duration? receive() timerLinda timeout
182 } else if (lua_isnoneornil(L_, 1)) {
183 lua_pushnil(L_); // L_: duration? receive() timerLinda timeout
184 } else {
185 raise_luaL_argerror(L_, StackIndex{ 1 }, "duration must be a number");
189 } 186 }
190 luaW_pushstring(L_, "ac100de1-a696-4619-b2f0-a26de9d58ab8"); // L_: duration? receive() timerLinda duration key 187
188 luaW_pushstring(L_, "ac100de1-a696-4619-b2f0-a26de9d58ab8"); // L_: duration? receive() timerLinda timeout key
191 STACK_CHECK(L_, 3); // 3 arguments ready 189 STACK_CHECK(L_, 3); // 3 arguments ready
192 lua_call(L_, 3, LUA_MULTRET); // timerLinda:receive(duration,key) // L_: duration? result... 190 lua_call(L_, 3, LUA_MULTRET); // timerLinda:receive(duration,key) // L_: duration? result...
193 return lua_gettop(L_) - 1; 191 return lua_gettop(L_) - 1;
diff --git a/tests/track_lanes.lua b/tests/track_lanes.lua
index ef2ca06..27d5cf4 100644
--- a/tests/track_lanes.lua
+++ b/tests/track_lanes.lua
@@ -46,7 +46,7 @@ end
46local g = lanes.gen( "*", { name = 'auto' }, sleeper) 46local g = lanes.gen( "*", { name = 'auto' }, sleeper)
47 47
48-- start a forever-waiting lane (nil timeout) 48-- start a forever-waiting lane (nil timeout)
49local forever = g( "forever", 'indefinitely') 49local forever = g( "forever", nil)
50 50
51-- start a lane that will last 2 seconds 51-- start a lane that will last 2 seconds
52local ephemeral1 = g( "two_seconds", 2) 52local ephemeral1 = g( "two_seconds", 2)
diff --git a/unit_tests/lane_tests.cpp b/unit_tests/lane_tests.cpp
index b6fb188..7c215fa 100644
--- a/unit_tests/lane_tests.cpp
+++ b/unit_tests/lane_tests.cpp
@@ -128,9 +128,11 @@ TEST_CASE("lanes.sleep.argument_validation/numbers")
128 // negative durations are not supported 128 // negative durations are not supported
129 S.requireFailure("lanes.sleep(-1)"); 129 S.requireFailure("lanes.sleep(-1)");
130 130
131 // no duration is supported (same as 0) 131 // no duration is supported
132 S.requireSuccess("lanes.sleep()");
133 S.requireSuccess("lanes.sleep(0)"); 132 S.requireSuccess("lanes.sleep(0)");
133
134 // positive durations are supported
135 S.requireSuccess("lanes.sleep(0.1)");
134} 136}
135 137
136// ################################################################################################# 138// #################################################################################################
@@ -161,7 +163,7 @@ TEST_CASE("lanes.sleep.interactions with timers")
161 " lanes.timer(l, 'gluh', 0.1, 0.1)" 163 " lanes.timer(l, 'gluh', 0.1, 0.1)"
162 // launch a lane that is supposed to sleep forever 164 // launch a lane that is supposed to sleep forever
163 " local g = lanes.gen('*', { name = 'auto' }, lanes.sleep)" 165 " local g = lanes.gen('*', { name = 'auto' }, lanes.sleep)"
164 " local h = g('indefinitely')" 166 " local h = g(nil)"
165 // sleep 1 second (this uses the timer linda) 167 // sleep 1 second (this uses the timer linda)
166 " lanes.sleep(1)" 168 " lanes.sleep(1)"
167 // shutdown should be able to cancel the lane and stop it instantly 169 // shutdown should be able to cancel the lane and stop it instantly