diff options
| -rw-r--r-- | docs/index.html | 19 | ||||
| -rw-r--r-- | src/cancel.cpp | 18 | ||||
| -rw-r--r-- | tests/atexit.lua | 2 | ||||
| -rw-r--r-- | tests/cancel.lua | 2 | ||||
| -rw-r--r-- | unit_tests/scripts/_utils.lua | 2 | ||||
| -rw-r--r-- | unit_tests/scripts/_utils54.lua | 2 | ||||
| -rw-r--r-- | unit_tests/scripts/lane/cooperative_shutdown.lua | 2 | ||||
| -rw-r--r-- | unit_tests/scripts/lane/tasking_cancelling.lua | 16 |
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 © 2007-25 Asko Kauppi, Benoit Germain. All rights reserved.</i> | 68 | <i>Copyright © 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 | // |
| 120 | LUAG_FUNC(cancel_test) | 123 | LUAG_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 | |||
| 11 | local g = function() | 11 | local 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!" |
| 17 | end | 17 | end |
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" |
| 135 | end | 135 | end |
| 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" |
| 12 | end | 12 | end |
| 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] | |||
| 29 | assert(no_cancel_result == false, "cancel_test() should return boolean false, got " .. type(no_cancel_result) .. ": " .. tostring(no_cancel_result)) | 29 | assert(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 |
| 32 | local cooperative = function() | 32 | -- query_only: if true, pass true to cancel_test() so that hard cancel returns "hard" instead of raising |
| 33 | local 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 |
| 40 | end | 41 | end |
| 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 |
| 42 | local cooperative_lane_soft = lanes_gen("*", { name = 'auto' }, cooperative)() | 43 | local cooperative_lane_soft = lanes_gen("*", { name = 'auto' }, cooperative)() |
| 43 | local a, b = cooperative_lane_soft:cancel("soft", 0) -- issue request, do not wait for lane to terminate | 44 | local a, b = cooperative_lane_soft:cancel("soft", 0) -- issue request, do not wait for lane to terminate |
| 44 | assert(a == false and b == "timeout", "got " .. tostring(a) .. " " .. tostring(b)) | 45 | assert(a == false and b == "timeout", "got " .. tostring(a) .. " " .. tostring(b)) |
| 45 | assert(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() | 46 | assert(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 | ||
| 46 | local cooperative_lane_hard = lanes_gen("*", { name = 'auto' }, cooperative)() | 48 | local cooperative_lane_hard = lanes_gen("*", { name = 'auto' }, cooperative)() |
| 47 | local c, d = cooperative_lane_hard:cancel("hard", 0) -- issue request, do not wait for lane to terminate | 49 | local c, d = cooperative_lane_hard:cancel("hard", 0) -- issue request, do not wait for lane to terminate |
| 48 | assert(c == false and d == "timeout", "got " .. tostring(c) .. " " .. tostring(d)) | 50 | assert(c == false and d == "timeout", "got " .. tostring(c) .. " " .. tostring(d)) |
| 49 | assert(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() | 51 | assert(cooperative_lane_hard[1] == nil, "cancelled lane first result should be nil, got " .. type(cooperative_lane_hard[1]) .. ": " .. tostring(cooperative_lane_hard[1])) |
| 52 | assert(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 | ||
| 54 | local cooperative_lane_hard_query = lanes_gen("*", { name = 'auto' }, cooperative)(true) | ||
| 55 | local e, f = cooperative_lane_hard_query:cancel("hard", 0) -- issue request, do not wait for lane to terminate | ||
| 56 | assert(e == false and f == "timeout", "got " .. tostring(e) .. " " .. tostring(f)) | ||
| 57 | assert(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 | ||
