From bfdc7a92c4e3e99522abb6d90ef2cbb021f36fc8 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Thu, 5 Jun 2025 16:03:22 +0200 Subject: Change lane:join() return values * when no error is raised in the lane, lane:join() now precedes the lane returned values with true * lane body is no longer forced to return something when used with join() * adjusted all relevant unit tests accordingly --- CHANGES | 8 +++---- docs/index.html | 6 ++--- src/lane.cpp | 34 ++++++++++++++++----------- tests/appendud.lua | 2 +- tests/basic.lua | 17 +++++++------- tests/error.lua | 7 +++--- tests/finalizer.lua | 4 ++-- tests/func_is_string.lua | 13 +++++----- tests/launchtest.lua | 4 ++-- tests/linda_perf.lua | 7 +++--- tests/perftest.lua | 4 ++-- tests/pingpong.lua | 8 ++++--- tests/rupval.lua | 6 ++--- tests/tobeclosed.lua | 5 ++-- unit_tests/scripts/coro/basics.lua | 6 +++-- unit_tests/scripts/lane/tasking_join_test.lua | 16 +++++++++---- unit_tests/scripts/linda/wake_period.lua | 5 ++-- 17 files changed, 85 insertions(+), 67 deletions(-) diff --git a/CHANGES b/CHANGES index 65077f7..b6bf3d6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ CHANGES: -CHANGE 2: BGe 27-Nov-24 +CHANGE 2: BGe 05-Jun-25 * Internal changes - Lanes is implemented in C++20: thread, condition_variable, mutex, string_view, variant, lambdas, templates, and more! - Almost all platform-specific code is gone (only a small bit for thread priority and affinity remains). @@ -37,11 +37,9 @@ CHANGE 2: BGe 27-Nov-24 - name added. Can be used to set the name early (before the lane body calls lane_threadname()). - New generator lanes.coro() to start a lane as a coroutine. - New __close metamethod that calls join(). - - lane:join() - - Returns nil, error in case of problem. - - Forces lane function body must return a non-nil first value on success because of the above. + - lane:join() returns nil, error in case of problem, else returns true followed by the lane body return values. - lane:get_debug_threadname() renamed get_threadname(). - - cancel_test() returns "soft"/"hard" instead of true when a cancellation request is active + - cancel_test() returns "soft"/"hard" instead of true when a cancellation request is active. - Lindas: - lanes.linda() - Arguments can be provided in any order. diff --git a/docs/index.html b/docs/index.html index be8ad7f..ab8aed2 100644 --- a/docs/index.html +++ b/docs/index.html @@ -71,7 +71,7 @@

- This document was revised on 07-May-25, and applies to version 4.0.0. + This document was revised on 05-Jun-25, and applies to version 4.0.0.

@@ -1156,7 +1156,7 @@

-	[...]|[nil,err,stack_tbl]= lane_h:join([timeout])
+	[true, ...]|[nil,err,stack_tbl]= lane_h:join([timeout])
 

@@ -1174,7 +1174,7 @@

  • nil, "killed" if forcefully killed.
  • -
  • The return values of the lane function. If the first return value is nil (or there is no return value), an error is raised, to make sure you can tell timeout and error cases apart from successful return.
  • +
  • true [, returned-values]: The return values of the lane function.
  • If the lane handle obtained from lanes.gen() is to-be-closed, closing the value will cause a call to join(). Since it is implicit, the lane body isn't forced to return non-nil in that case.

    diff --git a/src/lane.cpp b/src/lane.cpp index 65a776e..cb20930 100644 --- a/src/lane.cpp +++ b/src/lane.cpp @@ -105,7 +105,7 @@ static LUAG_FUNC(lane_threadname) // ################################################################################################# //--- -// [...] | [nil, err_any, stack_tbl]= lane:join([wait_secs]) +// [true, ...] | [nil, err_any, stack_tbl]= lane:join([wait_secs]) // // timeout: returns nil // done: returns return values (0..N) @@ -147,18 +147,19 @@ static LUAG_FUNC(lane_join) switch (_lane->status.load(std::memory_order_acquire)) { case Lane::Suspended: // got yielded values case Lane::Done: // got regular return values - { - if (_stored == 0) { - raise_luaL_error(L_, _lane->L ? "First return value must be non-nil when using join()" : "Can't join() more than once or after indexing"); - } + if (_stored > 0) { lua_getiuservalue(L_, StackIndex{ 1 }, UserValueIndex{ 1 }); // L_: lane {uv} for (int _i = 2; _i <= _stored; ++_i) { lua_rawgeti(L_, 2, _i); // L_: lane {uv} results2...N } lua_rawgeti(L_, 2, 1); // L_: lane {uv} results2...N result1 lua_replace(L_, 2); // L_: lane results - _ret = _stored; } + // we precede the lane body returned values with boolean true + lua_pushboolean(L_, 1); // L_: lane results true + lua_replace(L_, 1); // L_: true results + _ret = _stored + 1; + STACK_CHECK(L_, _stored); break; case Lane::Error: @@ -174,25 +175,30 @@ static LUAG_FUNC(lane_join) lua_replace(L_, 2); // L_: lane nil } _ret = lua_gettop(L_) - 1; // 2 or 3 + STACK_CHECK(L_, _ret); } break; case Lane::Cancelled: - LUA_ASSERT(L_, _stored == 2); - lua_getiuservalue(L_, StackIndex{ 1 }, UserValueIndex{ 1 }); // L_: lane {uv} - lua_rawgeti(L_, 2, 2); // L_: lane {uv} cancel_error - lua_rawgeti(L_, 2, 1); // L_: lane {uv} cancel_error nil - lua_replace(L_, -3); // L_: lane nil cancel_error - LUA_ASSERT(L_, lua_isnil(L_, -2) && kCancelError.equals(L_, kIdxTop)); - _ret = 2; + { + LUA_ASSERT(L_, _stored == 2); + lua_getiuservalue(L_, StackIndex{ 1 }, UserValueIndex{ 1 }); // L_: lane {uv} + lua_rawgeti(L_, 2, 2); // L_: lane {uv} cancel_error + lua_rawgeti(L_, 2, 1); // L_: lane {uv} cancel_error nil + lua_replace(L_, -3); // L_: lane nil cancel_error + LUA_ASSERT(L_, lua_isnil(L_, -2) && kCancelError.equals(L_, kIdxTop)); + _ret = 2; + STACK_CHECK(L_, _ret); + } break; default: DEBUGSPEW_CODE(DebugSpew(nullptr) << "Unknown Lane status: " << static_cast(_lane->status.load(std::memory_order_relaxed)) << std::endl); LUA_ASSERT(L_, false); _ret = 0; + STACK_CHECK(L_, _ret); } - STACK_CHECK(L_, _ret); + LUA_ASSERT(L_, lua_gettop(L_) >= _ret); return _ret; } diff --git a/tests/appendud.lua b/tests/appendud.lua index f6f99c1..2a8c8ce 100644 --- a/tests/appendud.lua +++ b/tests/appendud.lua @@ -49,7 +49,7 @@ assert(not err) -- test -- print("t:join()") a,b,c = t[1],t[2],t[3] -- Need to explicitly wait for the thread, since 'ipairs()' does not ---a,b,c = t:join() -- Need to explicitly wait for the thread, since 'ipairs()' does not +--r,a,b,c = t:join() -- Need to explicitly wait for the thread, since 'ipairs()' does not -- value the '__index' metamethod (wouldn't it be cool if it did..?) print(a,b,c) diff --git a/tests/basic.lua b/tests/basic.lua index 9aaad97..f393175 100644 --- a/tests/basic.lua +++ b/tests/basic.lua @@ -507,19 +507,20 @@ local S = lanes.gen("table", { name = 'auto', gc_cb = gc_cb }, return (unpack or table.unpack)(aux) end) -h= S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values +h = S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values -- wait a bit so that the lane has a chance to set its debug name SLEEP(0.5) print("joining with '" .. h:get_threadname() .. "'") -local a,b,c,d= h:join() +local r,a,b,c,d= h:join() if h.status == "error" then - print(h:get_threadname(), "error: " , a, b, c, d) + print(h:get_threadname(), "error: " , r, a, b, c, d) else - print(h:get_threadname(), a,b,c,d) - assert(a==14) - assert(b==13) - assert(c==12) - assert(d==nil) + print(h:get_threadname(), r,a,b,c,d) + assert(r == true) + assert(a == 14) + assert(b == 13) + assert(c == 12) + assert(d == nil) end local nameof_type, nameof_name = lanes.nameof(print) diff --git a/tests/error.lua b/tests/error.lua index 28cfff1..76ceea4 100644 --- a/tests/error.lua +++ b/tests/error.lua @@ -173,8 +173,7 @@ local do_error_catching_test = function(error_reporting_mode_, error_value_, fin local h = start_lane(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) local ret,err,stack= h:join() -- wait for the lane (no automatic error propagation) WR("Processing results for {", error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_, "}") - if err then - assert(ret == nil) + if ret == nil then assert(error_reporting_mode_ == "minimal" or type(stack)=="table") -- only true if lane was configured with error_trace_level ~= "minimal" if err == error_value_ then WR("Lane regular error: ", err) @@ -198,8 +197,8 @@ local do_error_catching_test = function(error_reporting_mode_, error_value_, fin end end else -- no error - assert(ret == "success") - WR("No error in lane: ", ret) + assert(ret == true and err == "success") + WR("No error in lane: ", err, ret) end WR "TEST OK" end diff --git a/tests/finalizer.lua b/tests/finalizer.lua index ac5ce8b..9fa12dc 100644 --- a/tests/finalizer.lua +++ b/tests/finalizer.lua @@ -77,8 +77,8 @@ local do_test = function(error_) local h = lgen(error_) - local _,err,stack = h:join() -- wait for the lane (no automatic error propagation) - if err then + local r,err,stack = h:join() -- wait for the lane (no automatic error propagation) + if not r then assert(stack, "no stack trace on error, check 'error_trace_level'") io.stderr:write( "Lane error: "..tostring(err).."\n" ) io.stderr:write( "\t", table.concat(stack,"\t\n"), "\n" ) diff --git a/tests/func_is_string.lua b/tests/func_is_string.lua index 5de4c60..3c91603 100644 --- a/tests/func_is_string.lua +++ b/tests/func_is_string.lua @@ -21,16 +21,17 @@ end local options = {globals = { b = 666 }} -local gen1 = lanes.gen("*", { name = 'auto' }, "return true, dofile('fibonacci.lua')") -local gen2 = lanes.gen(options, { name = 'auto' }, "return b") +local gen1 = lanes.gen("*", { name = 'auto' }, "return true, error('bob')") fibLane = gen1() lanes.sleep(0.1) print(fibLane, fibLane.status) -local _status, _err = fibLane:join() -print(_status, _err) +local _r, _err, _stk = fibLane:join() +assert(_r == nil, "got " .. tostring(_r) .. " " .. tostring(_err) .. " " .. tostring(_stk)) -retLane1, retLane2 = gen2(), gen2() +local gen2 = lanes.gen(options, { name = 'auto' }, "return b") +local retLane1, retLane2 = gen2(), gen2() print( retLane1[1], retLane2[1]) -print "TEST OK" \ No newline at end of file +assert(retLane1[1] == 666 and retLane2[1] == 666) +print "TEST OK" diff --git a/tests/launchtest.lua b/tests/launchtest.lua index 57411e1..cdd6ffc 100644 --- a/tests/launchtest.lua +++ b/tests/launchtest.lua @@ -69,8 +69,8 @@ else io.stderr:write( N.." lanes launched.\n" ) for i=1,N do - local rc= t[i]:join() - assert( rc==i ) + local r,rc = t[i]:join() + assert( r == true and rc == i ) end io.stderr:write( N.." lanes finished.\n" ) diff --git a/tests/linda_perf.lua b/tests/linda_perf.lua index 83b8921..e68d552 100644 --- a/tests/linda_perf.lua +++ b/tests/linda_perf.lua @@ -56,7 +56,7 @@ local eater = function( l, loop) -- print "loop is over" key, val = l:receive( "done") print("eater: done ("..val..")") - return true + return "ate everything" end -- ################################################################################################# @@ -74,7 +74,7 @@ local gobbler = function( l, loop, batch) print "loop is over" key, val = l:receive( "done") print("gobbler: done ("..val..")") - return true + return "gobbled everything" end -- ################################################################################################# @@ -123,7 +123,8 @@ local function ziva1( preloop, loop, batch) end end l:send( "done" ,"are you happy?") - lane:join() + local r, ret = lane:join() + assert(r == true and type(ret) == "string", "got " .. tostring(r) .. " " .. tostring(ret)) return lanes.now_secs() - t1 end diff --git a/tests/perftest.lua b/tests/perftest.lua index fe43cca..35e164d 100644 --- a/tests/perftest.lua +++ b/tests/perftest.lua @@ -175,9 +175,9 @@ else -- Make sure all lanes finished -- for i=1,N do - local tmp= t[i]:join() + local r, tmp = t[i]:join() -- this assert will trigger if you change M to values below 1000 in order to solve C stack overflow - assert( type(tmp)=="table" and tmp[1]==2 and tmp[168]==997 ) + assert( r == true and type(tmp) == "table" and tmp[1] == 2 and tmp[168] == 997 ) end end diff --git a/tests/pingpong.lua b/tests/pingpong.lua index 06c0903..1ed5b9a 100644 --- a/tests/pingpong.lua +++ b/tests/pingpong.lua @@ -21,13 +21,15 @@ local pingpong = function(name, qr, qs, start) q:send(qs, val) count = count + 1 end - return true + return "ping!" end -- pingpong("L1", '0', '1', true) local t1, err1 = lanes.gen("*", { name = 'auto' }, pingpong)("L1", 'a', 'b', true) local t2, err2 = lanes.gen("*", { name = 'auto' }, pingpong)("L2", 'b', 'a', false) -t1:join() -t2:join() +local r1, ret1 = t1:join() +assert(r1 == true and ret1 == "ping!") +local r2, ret2 = t2:join() +assert(r2 == true and ret2 == "ping!") print "TEST OK" diff --git a/tests/rupval.lua b/tests/rupval.lua index ad5ad9d..c6743b3 100644 --- a/tests/rupval.lua +++ b/tests/rupval.lua @@ -26,17 +26,17 @@ end local g = lanes.gen( "base", { name = 'auto' }, a) local l = g(7) -local r = l:join() +local _, r = l:join() assert(r == y) print(r) local l = g(8) -local r = l:join() +local _, r = l:join() assert(r == z) print(r) local l = g(9) -local r = l:join() +local _, r = l:join() assert(r == x) print(r) diff --git a/tests/tobeclosed.lua b/tests/tobeclosed.lua index 447b936..fd157e2 100644 --- a/tests/tobeclosed.lua +++ b/tests/tobeclosed.lua @@ -106,7 +106,7 @@ do local _count, l_out = l:get("trip") -- linda from arguments local l_arg = l_arg_ - return true + return "done" end local close_handler_f = function(linda_, err_) @@ -118,7 +118,8 @@ do l:set("trip", l_in) do - lanes.gen("*", { name = 'auto' }, lane_body)(l_in):join() + local r, ret = lanes.gen("*", { name = 'auto' }, lane_body)(l_in):join() + assert(r == true and ret == "done") end local _count, _closed = l_in:get("closed") assert(_count == 1 and _closed == 2) diff --git a/unit_tests/scripts/coro/basics.lua b/unit_tests/scripts/coro/basics.lua index cd2f410..c0b7a36 100644 --- a/unit_tests/scripts/coro/basics.lua +++ b/unit_tests/scripts/coro/basics.lua @@ -85,7 +85,8 @@ if true then assert(h3:resume(1) == nil) -- similarly, we can get them with join() - assert(h3:join() == "world" and h3.status == "suspended") + local r3, ret3 = h3:join() + assert(r3 == true and ret3 == "world" and h3.status == "suspended") -- since we consumed the returned values, they should not be here when we resume assert(h3:resume(2) == nil) @@ -93,5 +94,6 @@ if true then assert(h3:resume(3) == "!") -- the final return value of the lane body remains to be read - assert(h3:join() == "done!" and h3.status == "done") + local r3, ret3 = h3:join() + assert(r3 == true and ret3 == "done!" and h3.status == "done") end diff --git a/unit_tests/scripts/lane/tasking_join_test.lua b/unit_tests/scripts/lane/tasking_join_test.lua index 2fbce6c..495a709 100644 --- a/unit_tests/scripts/lane/tasking_join_test.lua +++ b/unit_tests/scripts/lane/tasking_join_test.lua @@ -30,14 +30,19 @@ end PRINT("---=== :join test ===---", "\n\n") +-- a lane body that returns nothing is successfully joined with true, nil +local r, ret = lanes_gen(function() end)():join() +assert(r == true and ret == nil) + -- NOTE: 'unpack()' cannot be used on the lane handle; it will always return nil -- (unless [1..n] has been read earlier, in which case it would seemingly -- work). -local S= lanes_gen("table", { name = 'auto', gc_cb = gc_cb }, +local S = lanes_gen("table", { name = 'auto', gc_cb = gc_cb }, function(arg) lane_threadname "join test lane" set_finalizer(function() end) + -- take arg table, reverse its contents in aux, then return the unpacked result local aux= {} for i, v in ipairs(arg) do table.insert(aux, 1, v) @@ -46,15 +51,16 @@ local S= lanes_gen("table", { name = 'auto', gc_cb = gc_cb }, return (unpack or table.unpack)(aux) end) -h= S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values +local h = S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values -- wait a bit so that the lane has a chance to set its debug name SLEEP(0.5) print("joining with '" .. h:get_threadname() .. "'") -local a,b,c,d= h:join() +local r, a, b, c, d = h:join() if h.status == "error" then - print(h:get_threadname(), "error: " , a, b, c, d) + print(h:get_threadname(), "error: " , r, a, b, c, d) else - print(h:get_threadname(), a,b,c,d) + print(h:get_threadname(), r, a, b, c, d) + assert(r==true, "r == " .. tostring(r)) assert(a==14, "a == " .. tostring(a)) assert(b==13, "b == " .. tostring(b)) assert(c==12, "c == " .. tostring(c)) diff --git a/unit_tests/scripts/linda/wake_period.lua b/unit_tests/scripts/linda/wake_period.lua index e4a900d..d2dccc3 100644 --- a/unit_tests/scripts/linda/wake_period.lua +++ b/unit_tests/scripts/linda/wake_period.lua @@ -6,7 +6,7 @@ local lanes = require_lanes_result_1 local body = function(linda_) -- a blocking read that lasts longer than the tested wake_period values linda_:receive(2, "empty_slot") - return true + return "done" end -- if we don't cancel the lane, we should wait the whole duration @@ -22,7 +22,8 @@ local function check_wake_duration(linda_, expected_, do_cancel_) assert(result == false and reason == 'timeout', "unexpected cancel result") end -- this should wait until the linda wakes by itself before the actual receive timeout and sees the cancel request - h:join() + local r, ret = h:join() + assert(r == true and ret == "done") local t1 = lanes.now_secs() local delta = t1 - t0 -- the linda should check for cancellation at about the expected period, not earlier -- cgit v1.2.3-55-g6feb