aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/index.html19
-rw-r--r--src/cancel.cpp18
-rw-r--r--tests/atexit.lua2
-rw-r--r--tests/cancel.lua2
-rw-r--r--unit_tests/scripts/_utils.lua2
-rw-r--r--unit_tests/scripts/_utils54.lua2
-rw-r--r--unit_tests/scripts/lane/cooperative_shutdown.lua2
-rw-r--r--unit_tests/scripts/lane/tasking_cancelling.lua16
8 files changed, 43 insertions, 20 deletions
diff --git a/docs/index.html b/docs/index.html
index 3dc2b61..11ed3eb 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -65,13 +65,13 @@
65 <font size="-1"> 65 <font size="-1">
66 <p> 66 <p>
67 <br /> 67 <br />
68 <i>Copyright &copy; 2007-25 Asko Kauppi, Benoit Germain. All rights reserved.</i> 68 <i>Copyright &copy; 2007-26 Asko Kauppi, Benoit Germain. All rights reserved.</i>
69 <br /> 69 <br />
70 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, 5.4 and 5.5. 70 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, 5.4 and 5.5.
71 </p> 71 </p>
72 72
73 <p> 73 <p>
74 This document was revised on 29-Sep-2025, and applies to version <tt>4.0.0</tt>. 74 This document was revised on 26-Feb-2026, and applies to version <tt>4.0.0</tt>.
75 </p> 75 </p>
76 </font> 76 </font>
77 </center> 77 </center>
@@ -1200,7 +1200,7 @@
1200 <li><tt>"extended"</tt>: <tt>stack_tbl</tt> is an array of tables containing info gathered with <tt>lua_getinfo()</tt> (<tt>"source"</tt>,<tt>"currentline"</tt>,<tt>"name"</tt>,<tt>"namewhat"</tt>,<tt>"what"</tt>).</li> 1200 <li><tt>"extended"</tt>: <tt>stack_tbl</tt> is an array of tables containing info gathered with <tt>lua_getinfo()</tt> (<tt>"source"</tt>,<tt>"currentline"</tt>,<tt>"name"</tt>,<tt>"namewhat"</tt>,<tt>"what"</tt>).</li>
1201 </ul> 1201 </ul>
1202 </li> 1202 </li>
1203 <li><tt>nil, "killed"</tt> if forcefully killed.</li> 1203 <li><tt>nil, lanes.cancel_error</tt> if <tt>"hard"</tt>-cancelled during a <a href="#lindas">linda</a> operation (which is nothing more than a special case of the above).</li>
1204 <li><tt>true [, returned-values]</tt>: The return values of the lane function.</li> 1204 <li><tt>true [, returned-values]</tt>: The return values of the lane function.</li>
1205 </ul> 1205 </ul>
1206 If the lane handle obtained from <tt>lanes.gen()</tt> is to-be-closed, closing the value will cause a call to <tt>join()</tt>. 1206 If the lane handle obtained from <tt>lanes.gen()</tt> is to-be-closed, closing the value will cause a call to <tt>join()</tt>.
@@ -1262,14 +1262,14 @@
1262 <li> 1262 <li>
1263 <tt>"hard"</tt>: waits for the request to be processed, or a timeout to occur. <a href="#lindas">linda</a> operations detecting the cancellation request will raise a special cancellation error (meaning they won't return in that case). 1263 <tt>"hard"</tt>: waits for the request to be processed, or a timeout to occur. <a href="#lindas">linda</a> operations detecting the cancellation request will raise a special cancellation error (meaning they won't return in that case).
1264 <br /> 1264 <br />
1265 If the lane isn't actually waiting on a <a href="#lindas">linda</a> when the request is issued, a lane calling <tt>cancel_test()</tt> will see it return <tt>"hard"</tt>. 1265 If the lane isn't actually waiting on a <a href="#lindas">linda</a> when the request is issued, a lane calling <tt>cancel_test()</tt> will also raise <tt>lanes.cancel_error</tt>, unless <tt>cancel_test(true)</tt> is used, in which case it returns <tt>"hard"</tt> instead.
1266 <br /> 1266 <br />
1267 <tt>wake_lane</tt> defaults to <tt>true</tt>, and <tt>timeout</tt> defaults to 0 if not specified. 1267 <tt>wake_lane</tt> defaults to <tt>true</tt>, and <tt>timeout</tt> defaults to infinite if not specified.
1268 </li> 1268 </li>
1269 <li> 1269 <li>
1270 <tt>"call"</tt>, <tt>"ret"</tt>, <tt>"line"</tt>, <tt>"count"</tt>: Asynchronously install the corresponding hook, then behave as <tt>"hard"</tt>. 1270 <tt>"call"</tt>, <tt>"ret"</tt>, <tt>"line"</tt>, <tt>"count"</tt>: Asynchronously install the corresponding hook, then behave as <tt>"hard"</tt>.
1271 <br /> 1271 <br />
1272 If the lane has the opportunity to call <tt>cancel_test()</tt> before the hook is invoked, calling <tt>cancel_test()</tt> will see it return <tt>"hard"</tt>. 1272 If the lane has the opportunity to call <tt>cancel_test()</tt> before the hook is invoked, it will also raise <tt>lanes.cancel_error</tt> (or return <tt>"hard"</tt> if <tt>cancel_test(true)</tt> is used).
1273 </li> 1273 </li>
1274 <li> 1274 <li>
1275 <tt>"all"</tt>: Installs all hooks in one shot, just to be sure. 1275 <tt>"all"</tt>: Installs all hooks in one shot, just to be sure.
@@ -1297,10 +1297,15 @@
1297</p> 1297</p>
1298 1298
1299 <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1299 <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1300 false|"soft"|"hard" = cancel_test() -- inside the lane 1300 false|"soft" = cancel_test() -- raises cancel_error on hard cancel
1301 false|"soft"|"hard" = cancel_test(true) -- returns "hard" instead of raising
1301</pre></td></tr></table> 1302</pre></td></tr></table>
1302<p> 1303<p>
1303 Lanes installs the function <tt>cancel_test()</tt> in each created lane to manually test for cancel requests. 1304 Lanes installs the function <tt>cancel_test()</tt> in each created lane to manually test for cancel requests.
1305 It returns <tt>false</tt> when no cancel is pending, <tt>"soft"</tt> on a soft cancel request, and raises
1306 <tt>lanes.cancel_error</tt> on a hard cancel request. Passing <tt>true</tt> as the optional argument suppresses
1307 the raise and returns <tt>"hard"</tt> instead, which is useful when the lane needs to distinguish the cancel
1308 mode before deciding how to react.
1304</p> 1309</p>
1305 1310
1306<!-- finalizers +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> 1311<!-- finalizers +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
diff --git a/src/cancel.cpp b/src/cancel.cpp
index 6812b0d..245065d 100644
--- a/src/cancel.cpp
+++ b/src/cancel.cpp
@@ -112,18 +112,28 @@ CancelRequest CheckCancelRequest(lua_State* const L_)
112// ################################################################################################# 112// #################################################################################################
113 113
114//--- 114//---
115// bool = cancel_test() 115// false|"soft" = cancel_test([return_hard])
116// 116//
117// Available inside the global namespace of a lane 117// Available inside the global namespace of a lane.
118// returns a boolean saying if a cancel request is pending 118// Returns false when no cancel request is pending.
119// Returns "soft" when a soft cancel request is pending.
120// Raises cancel_error when a hard cancel request is pending,
121// unless the optional boolean argument is true, in which case it returns "hard" instead.
119// 122//
120LUAG_FUNC(cancel_test) 123LUAG_FUNC(cancel_test)
121{ 124{
122 CancelRequest const _test{ CheckCancelRequest(L_) }; 125 CancelRequest const _test{ CheckCancelRequest(L_) };
123 if (_test == CancelRequest::None) { 126 if (_test == CancelRequest::None) {
124 lua_pushboolean(L_, 0); 127 lua_pushboolean(L_, 0);
128 } else if (_test == CancelRequest::Soft) {
129 luaW_pushstring(L_, "soft");
125 } else { 130 } else {
126 luaW_pushstring(L_, (_test == CancelRequest::Soft) ? "soft" : "hard"); 131 // Hard cancel: raise by default, return "hard" if the optional argument is true
132 if (lua_toboolean(L_, 1)) {
133 luaW_pushstring(L_, "hard");
134 } else {
135 raise_cancel_error(L_); // doesn't return
136 }
127 } 137 }
128 return 1; 138 return 1;
129} 139}
diff --git a/tests/atexit.lua b/tests/atexit.lua
index ba10d3b..744b705 100644
--- a/tests/atexit.lua
+++ b/tests/atexit.lua
@@ -11,7 +11,7 @@ end
11local g = function() 11local g = function()
12 local cancelled 12 local cancelled
13 repeat 13 repeat
14 cancelled = cancel_test() 14 cancelled = cancel_test(true)
15 until cancelled 15 until cancelled
16 print "User cancellation detected!" 16 print "User cancellation detected!"
17end 17end
diff --git a/tests/cancel.lua b/tests/cancel.lua
index 66957c3..c5ff0e6 100644
--- a/tests/cancel.lua
+++ b/tests/cancel.lua
@@ -130,7 +130,7 @@ local laneBody = function(mode_, payload_)
130 else 130 else
131 error "no mode: raise an error" 131 error "no mode: raise an error"
132 end 132 end
133 until cancel_test() -- soft cancel self test 133 until cancel_test(true) -- soft cancel self test
134 print " lane shutting down after breaking out of loop" 134 print " lane shutting down after breaking out of loop"
135end 135end
136 136
diff --git a/unit_tests/scripts/_utils.lua b/unit_tests/scripts/_utils.lua
index 9f46237..365fd10 100644
--- a/unit_tests/scripts/_utils.lua
+++ b/unit_tests/scripts/_utils.lua
@@ -76,7 +76,7 @@ local yield_one_by_one = function(...)
76 local _val = select(_i, ...) 76 local _val = select(_i, ...)
77 PRINT("yielding #", _i, _val) 77 PRINT("yielding #", _i, _val)
78 local _ack = coroutine.yield(_val) 78 local _ack = coroutine.yield(_val)
79 if cancel_test and cancel_test() then -- cancel_test does not exist when run immediately (not in a Lane) 79 if cancel_test and cancel_test(true) then -- cancel_test does not exist when run immediately (not in a Lane)
80 return "cancelled!" 80 return "cancelled!"
81 end 81 end
82 -- of course, if we are cancelled, we were not resumed, and yield() didn't return what we expect 82 -- of course, if we are cancelled, we were not resumed, and yield() didn't return what we expect
diff --git a/unit_tests/scripts/_utils54.lua b/unit_tests/scripts/_utils54.lua
index a511563..629f5c5 100644
--- a/unit_tests/scripts/_utils54.lua
+++ b/unit_tests/scripts/_utils54.lua
@@ -20,7 +20,7 @@ utils.yielder_with_to_be_closed = function(out_linda_, wait_)
20 local n = 1 20 local n = 1
21 while true do 21 while true do
22 coroutine.yield("I yield!", n) 22 coroutine.yield("I yield!", n)
23 if cancel_test and cancel_test() then -- cancel_test does not exist when run immediately (not in a Lane) 23 if cancel_test and cancel_test(true) then -- cancel_test does not exist when run immediately (not in a Lane)
24 return "I am cancelled" 24 return "I am cancelled"
25 end 25 end
26 n = n + 1 26 n = n + 1
diff --git a/unit_tests/scripts/lane/cooperative_shutdown.lua b/unit_tests/scripts/lane/cooperative_shutdown.lua
index 0a0943e..3329e9d 100644
--- a/unit_tests/scripts/lane/cooperative_shutdown.lua
+++ b/unit_tests/scripts/lane/cooperative_shutdown.lua
@@ -7,7 +7,7 @@ local lane1 = function()
7 -- loop breaks on soft cancellation request 7 -- loop breaks on soft cancellation request
8 repeat 8 repeat
9 lanes.sleep(0) 9 lanes.sleep(0)
10 until cancel_test() 10 until cancel_test(true)
11 print "lane1 cancelled" 11 print "lane1 cancelled"
12end 12end
13 13
diff --git a/unit_tests/scripts/lane/tasking_cancelling.lua b/unit_tests/scripts/lane/tasking_cancelling.lua
index 395bee5..f7ad729 100644
--- a/unit_tests/scripts/lane/tasking_cancelling.lua
+++ b/unit_tests/scripts/lane/tasking_cancelling.lua
@@ -29,24 +29,32 @@ local no_cancel_result = no_cancel_lane[1]
29assert(no_cancel_result == false, "cancel_test() should return boolean false, got " .. type(no_cancel_result) .. ": " .. tostring(no_cancel_result)) 29assert(no_cancel_result == false, "cancel_test() should return boolean false, got " .. type(no_cancel_result) .. ": " .. tostring(no_cancel_result))
30 30
31-- cancellation of cooperating lanes 31-- cancellation of cooperating lanes
32local cooperative = function() 32-- query_only: if true, pass true to cancel_test() so that hard cancel returns "hard" instead of raising
33local cooperative = function(query_only_)
33 local fixture = assert(require "fixture") 34 local fixture = assert(require "fixture")
34 local which_cancel 35 local which_cancel
35 repeat 36 repeat
36 fixture.block_for(0.2) 37 fixture.block_for(0.2)
37 which_cancel = cancel_test() 38 which_cancel = cancel_test(query_only_)
38 until which_cancel 39 until which_cancel
39 return which_cancel 40 return which_cancel
40end 41end
41-- soft and hard are behaviorally equivalent when no blocking linda operation is involved 42-- for soft cancel, cancel_test() returns "soft" so the lane exits the loop and returns it
42local cooperative_lane_soft = lanes_gen("*", { name = 'auto' }, cooperative)() 43local cooperative_lane_soft = lanes_gen("*", { name = 'auto' }, cooperative)()
43local a, b = cooperative_lane_soft:cancel("soft", 0) -- issue request, do not wait for lane to terminate 44local a, b = cooperative_lane_soft:cancel("soft", 0) -- issue request, do not wait for lane to terminate
44assert(a == false and b == "timeout", "got " .. tostring(a) .. " " .. tostring(b)) 45assert(a == false and b == "timeout", "got " .. tostring(a) .. " " .. tostring(b))
45assert(cooperative_lane_soft[1] == "soft", "cancel_test() should return \"soft\", got " .. type(cooperative_lane_soft[1]) .. ": " .. tostring(cooperative_lane_soft[1])) -- return value of the lane body is the value returned by cancel_test() 46assert(cooperative_lane_soft[1] == "soft", "cancel_test() should return \"soft\", got " .. type(cooperative_lane_soft[1]) .. ": " .. tostring(cooperative_lane_soft[1])) -- return value of the lane body is the value returned by cancel_test()
47-- for hard cancel, cancel_test() raises cancel_error, so the lane ends up in the cancelled state
46local cooperative_lane_hard = lanes_gen("*", { name = 'auto' }, cooperative)() 48local cooperative_lane_hard = lanes_gen("*", { name = 'auto' }, cooperative)()
47local c, d = cooperative_lane_hard:cancel("hard", 0) -- issue request, do not wait for lane to terminate 49local c, d = cooperative_lane_hard:cancel("hard", 0) -- issue request, do not wait for lane to terminate
48assert(c == false and d == "timeout", "got " .. tostring(c) .. " " .. tostring(d)) 50assert(c == false and d == "timeout", "got " .. tostring(c) .. " " .. tostring(d))
49assert(cooperative_lane_hard[1] == "hard", "cancel_test() should return \"hard\", got " .. type(cooperative_lane_hard[1]) .. ": " .. tostring(cooperative_lane_hard[1])) -- return value of the lane body is the value returned by cancel_test() 51assert(cooperative_lane_hard[1] == nil, "cancelled lane first result should be nil, got " .. type(cooperative_lane_hard[1]) .. ": " .. tostring(cooperative_lane_hard[1]))
52assert(cooperative_lane_hard[2] == lanes.cancel_error, "cancelled lane second result should be cancel_error")
53-- for hard cancel with query_only=true, cancel_test(true) returns "hard" so the lane exits the loop and returns it
54local cooperative_lane_hard_query = lanes_gen("*", { name = 'auto' }, cooperative)(true)
55local e, f = cooperative_lane_hard_query:cancel("hard", 0) -- issue request, do not wait for lane to terminate
56assert(e == false and f == "timeout", "got " .. tostring(e) .. " " .. tostring(f))
57assert(cooperative_lane_hard_query[1] == "hard", "cancel_test(true) should return \"hard\", got " .. type(cooperative_lane_hard_query[1]) .. ": " .. tostring(cooperative_lane_hard_query[1]))
50 58
51-- ################################################################################################## 59-- ##################################################################################################
52 60