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