From 13f7f505375f7c1afd3a7e479a64cc147501b01d Mon Sep 17 00:00:00 2001
From: Benoit Germain
Date: Tue, 7 May 2024 17:56:10 +0200
Subject: Linda API changes
* timeout clarifications (negative values are no longer accepted, use nil instead)
* linda(send, linda.null, key, ...) removed, if you want to send a nil, just do it as usual
---
docs/index.html | 51 +++++----
src/cancel.cpp | 35 +++---
src/cancel.h | 2 +-
src/lanes.cpp | 46 ++++----
src/lanes.lua | 17 +--
src/lanes_private.h | 2 +-
src/linda.cpp | 25 ++---
src/lindafactory.cpp | 1 +
src/universe.cpp | 3 +-
tests/basic.lua | 296 ++++++++++++++++++++++++++-------------------------
10 files changed, 250 insertions(+), 228 deletions(-)
diff --git a/docs/index.html b/docs/index.html
index e811074..3c9cbcf 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -953,26 +953,29 @@
- Waits until the lane finishes, or timeout seconds have passed. Returns nil, "timeout" on timeout, nil,err,stack_tbl if the lane hit an error, nil, "killed" if forcefully killed, or the return values of the lane.
+ timeout is an optional number >= 0 (the default if unspecified).
+
+ Waits until the lane finishes, or timeout seconds have passed.
+
+ Returns nil, "timeout" on timeout, nil,err,stack_tbl if the lane hit an error, nil, "killed" if forcefully killed, or the return values of the lane.
+
Unlike in reading the results in table fashion, errors are not propagated.
stack_tbl is a table describing where the error was thrown.
- In "extended" mode, stack_tbl is an array of tables containing info gathered with lua_getinfo() ("source","currentline","name","namewhat","what").
+ In "extended" mode, stack_tbl is an array of tables containing info gathered with lua_getinfo() ("source","currentline","name","namewhat","what").
- In "basic mode", stack_tbl is an array of "<filename>:<line>" strings. Use table.concat() to format it to your liking (or just ignore it).
+ In "basic" mode, stack_tbl is an array of "<filename>:<line>" strings. Use table.concat() to format it to your liking (or just ignore it).
- If you use :join, make sure your lane main function returns a non-nil value so you can tell timeout and error cases apart from succesful return (using the .status property may be risky, since it might change between a timed out join and the moment you read it).
+ If you use :join(), make sure your lane main function returns a non-nil value so you can tell timeout and error cases apart from succesful return (using the .status property may be risky, since it might change between a timed out join and the moment you read it).
-
-
- require "lanes".configure()
+ local lanes = require "lanes".configure()
f = lanes.gen(function() error "!!!" end)
a = f(1)
@@ -990,7 +993,7 @@
- require "lanes".configure()
+ local lanes = require "lanes".configure()
local sync_linda = lanes.linda()
f = lanes.gen(function() dostuff() sync_linda:send("done", true) end)
@@ -1012,7 +1015,10 @@
|
- cancel() sends a cancellation request to the lane.
+ timeout is an optional number >= 0. Defaults to 0 if left unspecified or nil.
+
+ cancel() sends a cancellation request to the lane.
+
First argument is a mode can be one of "hard", "soft", "call", "ret", "line", "count".
If mode is not specified, it defaults to "hard".
If wake_lane is true, the lane is also signalled so that execution returns from any pending linda operation. Linda operations detecting the cancellation request return lanes.cancel_error.
@@ -1056,17 +1062,17 @@
|
- The error call is used for throwing exceptions in Lua. What Lua does not offer, however, is scoped finalizers
+ The regular Lua error function is usable in lanes for throwing exceptions. What Lua does not offer, however, is scoped finalizers
that would get called when a certain block of instructions gets exited, whether through peaceful return or abrupt error.
- Since 2.0.3, Lanes registers a function set_finalizer in the lane's Lua state for doing this.
+ Lanes registers a function set_finalizer in the lane's Lua state for doing this.
Any functions given to it will be called in the lane Lua state, just prior to closing it. It is possible to set more than one finalizer. They are not called in any particular order.
- An error in a finalizer itself overrides the state of the regular chunk (in practise, it would be highly preferable not to have errors in finalizers). If one finalizer errors, the others may not get called.
+ An error in a finalizer itself overrides the state of the regular chunk (in practice, it would be highly preferable not to have errors in finalizers). If one finalizer errors, the others may not get called.
If a finalizer error occurs after an error in the lane body, then this new error replaces the previous one (including the full stack trace).
@@ -1103,18 +1109,18 @@
- require "lanes".configure()
+ local lanes = require "lanes".configure()
- local linda = lanes.linda()
+ local linda = lanes.linda("my linda")
local function loop(max)
for i = 1, max do
print("sending: " .. i)
- linda:send("x", i) -- linda as upvalue
+ linda:send("x", i) -- linda as upvalue of loop()
end
end
- a = lanes.gen("", loop)(10000)
+ lane_h = lanes.gen("", loop)(10000)
while true do
local key, val = linda:receive(3.0, "x") -- timeout in seconds
@@ -1124,6 +1130,8 @@
end
print(tostring(linda) .. " received: " .. val)
end
+
+ lane_h:join()
|
@@ -1147,7 +1155,7 @@
h = lanes.linda([opt_name, [opt_group]])
- [true|lanes.cancel_error] = h:send([timeout_secs,] [h.null,] key, ...)
+ [true|lanes.cancel_error] = h:send([timeout_secs,] key, ...)
[key, val]|[lanes.cancel_error] = h:receive([timeout_secs,] key [, ...])
@@ -1157,10 +1165,11 @@
|
- The send() and receive() methods use Linda keys as FIFO stacks (first in, first out). Timeouts are given in seconds (millisecond accuracy). If using numbers as the first Linda key, one must explicitly give nil as the timeout parameter to avoid ambiguities.
+ Timeouts are given in seconds (>= 0, millisecond accuracy) or nil. Timeout can be omitted only if the first key is not a number (then it's equivalent to an infinite duration).
+ The send() and receive() methods use Linda keys as FIFO stacks (first in, first out).
By default, stack sizes are unlimited but limits can be enforced using the limit() method. This can be useful to balance execution speeds in a producer/consumer scenario. Any negative value removes the limit.
A limit of 0 is allowed to block everything.
@@ -1181,7 +1190,7 @@
send() returns lanes.cancel_error if interrupted by a soft cancel request.
- If no data is provided after the key, send() raises an error. If provided with linda.null or lanes.null before the actual key and there is no data to send, send() sends a single nil.
+ If no data is provided after the key, send() raises an error.
Also, if linda.null or lanes.null is sent as data in a linda, it will be read as a nil.
@@ -1395,7 +1404,7 @@ events to a common Linda, but... :).
- void = lanes.sleep(['indefinitely'|seconds|false])
+ void = lanes.sleep(['indefinitely'|seconds|nil])
|
@@ -1775,7 +1784,7 @@ static MyDeepFactory g_MyDeepFactory;
- Data passing (parameters, upvalues, Linda messages) is generally fast, doing two binary state-to-state copies (from source state to hidden state, hidden state to target state). Remember that not only the function you specify but also its upvalues, their upvalues, etc. etc. will get copied.
- Lane startup is fast (1000's of lanes a second), depending on the number of standard libraries initialized. Initializing all standard libraries is about 3-4 times slower than having no standard libraries at all. If you throw in a lot of lanes per second, make sure you give them minimal necessary set of libraries.
- - Waiting Lindas are woken up (and execute some hidden Lua code) each time any key in the Lindas they are waiting for are changed. This may give essential slow-down (not measured, just a gut feeling) if a lot of Linda keys are used. Using separate Linda objects for logically separate issues will help (which is good practise anyhow).
+ - Waiting Lindas are woken up (and execute some hidden Lua code) each time any key in the Lindas they are waiting for are changed. This may give essential slow-down (not measured, just a gut feeling) if a lot of Linda keys are used. Using separate Linda objects for logically separate issues will help (which is good practice anyhow).
- Linda objects are light. The memory footprint is two OS-level signalling objects (HANDLE or pthread_cond_t) for each, plus one C pointer for the proxies per each Lua state using the Linda. Barely nothing.
- Timers are light. You can probably expect timers up to 0.01 second resolution to be useful, but that is very system specific. All timers are merged into one main timer state (see timer.lua); no OS side timers are utilized.
- If you are using a lot of Linda objects, it may be useful to try having more of these keeper states. By default, only one is used (see lanes.configure()).
diff --git a/src/cancel.cpp b/src/cancel.cpp
index dd848a7..fe1623b 100644
--- a/src/cancel.cpp
+++ b/src/cancel.cpp
@@ -107,7 +107,7 @@ LUAG_FUNC(cancel_test)
// #################################################################################################
-[[nodiscard]] static CancelResult thread_cancel_soft(Lane* lane_, lua_Duration duration_, bool wakeLane_)
+[[nodiscard]] static CancelResult thread_cancel_soft(Lane* lane_, std::chrono::time_point until_, bool wakeLane_)
{
lane_->cancelRequest = CancelRequest::Soft; // it's now signaled to stop
// negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own
@@ -118,12 +118,12 @@ LUAG_FUNC(cancel_test)
}
}
- return lane_->waitForCompletion(duration_) ? CancelResult::Cancelled : CancelResult::Timeout;
+ return lane_->waitForCompletion(until_) ? CancelResult::Cancelled : CancelResult::Timeout;
}
// #################################################################################################
-[[nodiscard]] static CancelResult thread_cancel_hard(Lane* lane_, lua_Duration duration_, bool wakeLane_)
+[[nodiscard]] static CancelResult thread_cancel_hard(Lane* lane_, std::chrono::time_point until_, bool wakeLane_)
{
lane_->cancelRequest = CancelRequest::Hard; // it's now signaled to stop
// lane_->thread.get_stop_source().request_stop();
@@ -134,13 +134,13 @@ LUAG_FUNC(cancel_test)
}
}
- CancelResult result{ lane_->waitForCompletion(duration_) ? CancelResult::Cancelled : CancelResult::Timeout };
+ CancelResult result{ lane_->waitForCompletion(until_) ? CancelResult::Cancelled : CancelResult::Timeout };
return result;
}
// #################################################################################################
-CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hookCount_, lua_Duration duration_, bool wakeLane_)
+CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hookCount_, std::chrono::time_point until_, bool wakeLane_)
{
// remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here
// We can read 'lane_->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN)
@@ -152,12 +152,12 @@ CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hookCount_, lua_Durati
// signal the linda the wake up the thread so that it can react to the cancel query
// let us hope we never land here with a pointer on a linda that has been destroyed...
if (op_ == CancelOp::Soft) {
- return thread_cancel_soft(lane_, duration_, wakeLane_);
+ return thread_cancel_soft(lane_, until_, wakeLane_);
} else if (static_cast(op_) > static_cast(CancelOp::Soft)) {
lua_sethook(lane_->L, cancel_hook, static_cast(op_), hookCount_);
}
- return thread_cancel_hard(lane_, duration_, wakeLane_);
+ return thread_cancel_hard(lane_, until_, wakeLane_);
}
// #################################################################################################
@@ -200,7 +200,7 @@ CancelOp which_cancel_op(char const* opString_)
// #################################################################################################
-// bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, wake_lindas])
+// bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, wake_lane])
LUAG_FUNC(thread_cancel)
{
Lane* const lane{ ToLane(L_, 1) };
@@ -215,14 +215,19 @@ LUAG_FUNC(thread_cancel)
}
}
- lua_Duration wait_timeout{ 0.0 };
- if (lua_type(L_, 2) == LUA_TNUMBER) {
- wait_timeout = lua_Duration{ lua_tonumber(L_, 2) };
- lua_remove(L_, 2); // argument is processed, remove it
- if (wait_timeout.count() < 0.0) {
- raise_luaL_error(L_, "cancel timeout cannot be < 0");
+ std::chrono::time_point until{ std::chrono::time_point::max() };
+ if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion
+ lua_Duration const duration{ lua_tonumber(L_, 2) };
+ if (duration.count() >= 0.0) {
+ until = std::chrono::steady_clock::now() + std::chrono::duration_cast(duration);
+ } else {
+ raise_luaL_argerror(L_, 2, "duration cannot be < 0");
}
+ lua_remove(L_, 2); // argument is processed, remove it
+ } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key
+ lua_remove(L_, 2); // argument is processed, remove it
}
+
// we wake by default in "hard" mode (remember that hook is hard too), but this can be turned off if desired
bool wake_lane{ op != CancelOp::Soft };
if (lua_gettop(L_) >= 2) {
@@ -233,7 +238,7 @@ LUAG_FUNC(thread_cancel)
lua_remove(L_, 2); // argument is processed, remove it
}
STACK_CHECK_START_REL(L_, 0);
- switch (thread_cancel(lane, op, hook_count, wait_timeout, wake_lane)) {
+ switch (thread_cancel(lane, op, hook_count, until, wake_lane)) {
default: // should never happen unless we added a case and forgot to handle it
LUA_ASSERT(L_, false);
break;
diff --git a/src/cancel.h b/src/cancel.h
index 3df5252..1918df3 100644
--- a/src/cancel.h
+++ b/src/cancel.h
@@ -49,7 +49,7 @@ enum class CancelOp
static constexpr UniqueKey kCancelError{ 0x0630345FEF912746ull, "lanes.cancel_error" }; // 'raise_cancel_error' sentinel
[[nodiscard]] CancelOp which_cancel_op(char const* opString_);
-[[nodiscard]] CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hookCount_, lua_Duration secs_, bool wakeLane_);
+[[nodiscard]] CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hookCount_, std::chrono::time_point until_, bool wakeLane_);
[[noreturn]] static inline void raise_cancel_error(lua_State* L_)
{
diff --git a/src/lanes.cpp b/src/lanes.cpp
index 90f0f9f..d211b6a 100644
--- a/src/lanes.cpp
+++ b/src/lanes.cpp
@@ -169,17 +169,12 @@ Lane::Lane(Universe* U_, lua_State* L_)
// #################################################################################################
-bool Lane::waitForCompletion(lua_Duration duration_)
+bool Lane::waitForCompletion(std::chrono::time_point until_)
{
- std::chrono::time_point until{ std::chrono::time_point::max() };
- if (duration_.count() >= 0.0) {
- until = std::chrono::steady_clock::now() + std::chrono::duration_cast(duration_);
- }
-
std::unique_lock lock{ doneMutex };
// std::stop_token token{ thread.get_stop_token() };
// return doneCondVar.wait_until(lock, token, secs_, [this](){ return status >= Lane::Done; });
- return doneCondVar.wait_until(lock, until, [this]() { return status >= Lane::Done; });
+ return doneCondVar.wait_until(lock, until_, [this]() { return status >= Lane::Done; });
}
// #################################################################################################
@@ -1209,22 +1204,33 @@ void Lane::pushThreadStatus(lua_State* L_)
LUAG_FUNC(thread_join)
{
Lane* const lane{ ToLane(L_, 1) };
- lua_Duration const duration{ luaL_optnumber(L_, 2, -1.0) };
lua_State* const L2{ lane->L };
- bool const done{ !lane->thread.joinable() || lane->waitForCompletion(duration) };
+ std::chrono::time_point until{ std::chrono::time_point::max() };
+ if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion
+ lua_Duration const duration{ lua_tonumber(L_, 2) };
+ if (duration.count() >= 0.0) {
+ until = std::chrono::steady_clock::now() + std::chrono::duration_cast(duration);
+ } else {
+ raise_luaL_argerror(L_, 2, "duration cannot be < 0");
+ }
+
+ } else if (!lua_isnoneornil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key
+ raise_luaL_argerror(L_, 2, "incorrect duration type");
+ }
+
+ bool const done{ !lane->thread.joinable() || lane->waitForCompletion(until) };
+ lua_settop(L_, 1); // L_: lane
if (!done || !L2) {
- STACK_GROW(L_, 2);
- lua_pushnil(L_); // L_: lane timeout? nil
- lua_pushliteral(L_, "timeout"); // L_: lane timeout? nil "timeout"
+ lua_pushnil(L_); // L_: lane nil
+ lua_pushliteral(L_, "timeout"); // L_: lane nil "timeout"
return 2;
}
- STACK_CHECK_START_REL(L_, 0);
+ STACK_CHECK_START_REL(L_, 0); // L_: lane
// Thread is Done/Error/Cancelled; all ours now
int ret{ 0 };
- Universe* const U{ lane->U };
// debugName is a pointer to string possibly interned in the lane's state, that no longer exists when the state is closed
// so store it in the userdata uservalue at a key that can't possibly collide
lane->securizeDebugName(L_);
@@ -1234,8 +1240,8 @@ LUAG_FUNC(thread_join)
int const n{ lua_gettop(L2) }; // whole L2 stack
if (
(n > 0) &&
- (InterCopyContext{ U, DestState{ L_ }, SourceState{ L2 }, {}, {}, {}, {}, {} }.inter_move(n) != InterCopyResult::Success)
- ) { // L_: lane timeout? results L2:
+ (InterCopyContext{ lane->U, DestState{ L_ }, SourceState{ L2 }, {}, {}, {}, {}, {} }.inter_move(n) != InterCopyResult::Success)
+ ) { // L_: lane results L2:
raise_luaL_error(L_, "tried to copy unsupported types");
}
ret = n;
@@ -1244,12 +1250,12 @@ LUAG_FUNC(thread_join)
case Lane::Error:
{
- int const n{ lua_gettop(L2) }; // L_: lane timeout? L2: "err" [trace]
+ int const n{ lua_gettop(L2) }; // L_: lane L2: "err" [trace]
STACK_GROW(L_, 3);
- lua_pushnil(L_); // L_: lane timeout? nil
+ lua_pushnil(L_); // L_: lane nil
// even when ERROR_FULL_STACK, if the error is not LUA_ERRRUN, the handler wasn't called, and we only have 1 error message on the stack ...
- InterCopyContext c{ U, DestState{ L_ }, SourceState{ L2 }, {}, {}, {}, {}, {} };
- if (c.inter_move(n) != InterCopyResult::Success) { // L_: lane timeout? nil "err" [trace] L2:
+ InterCopyContext c{ lane->U, DestState{ L_ }, SourceState{ L2 }, {}, {}, {}, {}, {} };
+ if (c.inter_move(n) != InterCopyResult::Success) { // L_: lane nil "err" [trace] L2:
raise_luaL_error(L_, "tried to copy unsupported types: %s", lua_tostring(L_, -n));
}
ret = 1 + n;
diff --git a/src/lanes.lua b/src/lanes.lua
index caa8818..0ab6661 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -51,16 +51,17 @@ local lanes = setmetatable({}, lanesMeta)
-- and 'table' visible.
--
local assert = assert(assert)
+local error = assert(error)
local io = assert(io)
+local pairs = assert(pairs)
local string_gmatch = assert(string.gmatch)
local string_format = assert(string.format)
local select = assert(select)
local setmetatable = assert(setmetatable)
local table_insert = assert(table.insert)
-local type = assert(type)
-local pairs = assert(pairs)
+local tonumber = assert(tonumber)
local tostring = assert(tostring)
-local error = assert(error)
+local type = assert(type)
-- #################################################################################################
@@ -625,10 +626,12 @@ end
--
-- PUBLIC LANES API
local sleep = function(seconds_)
- seconds_ = seconds_ or 0.0 -- this causes false and nil to be a valid input, equivalent to 0.0, but that's ok
- if seconds_ == 'indefinitely' then
- seconds_ = nil
- elseif type(seconds_) ~= "number" then
+ local type = type(seconds_)
+ if type == "string" then
+ seconds_ = (seconds_ ~= 'indefinitely') and tonumber(seconds_) or nil
+ elseif type == "nil" then
+ seconds_ = 0
+ elseif type ~= "number" then
error("invalid duration " .. string_format("%q", tostring(seconds_)))
end
-- receive data on a channel no-one ever sends anything, thus blocking for the specified duration
diff --git a/src/lanes_private.h b/src/lanes_private.h
index 196a346..a756c42 100644
--- a/src/lanes_private.h
+++ b/src/lanes_private.h
@@ -94,7 +94,7 @@ class Lane
Lane(Universe* U_, lua_State* L_);
~Lane();
- [[nodiscard]] bool waitForCompletion(lua_Duration duration_);
+ [[nodiscard]] bool waitForCompletion(std::chrono::time_point until_);
void startThread(int priority_);
void pushThreadStatus(lua_State* L_);
void changeDebugName(int nameIdx_);
diff --git a/src/linda.cpp b/src/linda.cpp
index bbfbd69..40ef6c7 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -180,7 +180,7 @@ int Linda::ProtectedCall(lua_State* L_, lua_CFunction f_)
// #################################################################################################
/*
- * bool= linda_send( linda_ud, [timeout_secs=-1,] [linda.null,] key_num|str|bool|lightuserdata, ... )
+ * bool= linda:linda_send([timeout_secs=nil,] key_num|str|bool|lightuserdata, ...)
*
* Send one or more values to a Linda. If there is a limit, all values must fit.
*
@@ -192,25 +192,21 @@ LUAG_FUNC(linda_send)
{
auto send = [](lua_State* L_) {
Linda* const linda{ ToLinda(L_, 1) };
- std::chrono::time_point until{ std::chrono::time_point::max() };
int key_i{ 2 }; // index of first key, if timeout not there
+ std::chrono::time_point until{ std::chrono::time_point::max() };
if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion
lua_Duration const duration{ lua_tonumber(L_, 2) };
if (duration.count() >= 0.0) {
until = std::chrono::steady_clock::now() + std::chrono::duration_cast(duration);
+ } else {
+ raise_luaL_argerror(L_, 2, "duration cannot be < 0");
}
++key_i;
} else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key
++key_i;
}
- bool const as_nil_sentinel{ kNilSentinel.equals(L_, key_i) }; // if not nullptr, send() will silently send a single nil if nothing is provided
- if (as_nil_sentinel) {
- // the real key to send data to is after the kNilSentinel marker
- ++key_i;
- }
-
// make sure the key is of a valid type
check_key_types(L_, key_i, key_i);
@@ -218,12 +214,7 @@ LUAG_FUNC(linda_send)
// make sure there is something to send
if (lua_gettop(L_) == key_i) {
- if (as_nil_sentinel) {
- // send a single nil if nothing is provided
- kNilSentinel.pushKey(L_);
- } else {
- raise_luaL_error(L_, "no data to send");
- }
+ raise_luaL_error(L_, "no data to send");
}
// convert nils to some special non-nil sentinel in sent values
@@ -322,7 +313,7 @@ LUAG_FUNC(linda_send)
/*
* 2 modes of operation
- * [val, key]= linda_receive( linda_ud, [timeout_secs_num=-1], key_num|str|bool|lightuserdata [, ...] )
+ * [val, key]= linda_receive( linda_ud, [timeout_secs_num=nil], key_num|str|bool|lightuserdata [, ...] )
* Consumes a single value from the Linda, in any key.
* Returns: received value (which is consumed from the slot), and the key which had it
@@ -335,13 +326,15 @@ LUAG_FUNC(linda_receive)
{
auto receive = [](lua_State* L_) {
Linda* const linda{ ToLinda(L_, 1) };
- std::chrono::time_point until{ std::chrono::time_point::max() };
int key_i{ 2 }; // index of first key, if timeout not there
+ std::chrono::time_point until{ std::chrono::time_point::max() };
if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion
lua_Duration const duration{ lua_tonumber(L_, 2) };
if (duration.count() >= 0.0) {
until = std::chrono::steady_clock::now() + std::chrono::duration_cast(duration);
+ } else {
+ raise_luaL_argerror(L_, 2, "duration cannot be < 0");
}
++key_i;
} else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key
diff --git a/src/lindafactory.cpp b/src/lindafactory.cpp
index 0ec5a0a..917d949 100644
--- a/src/lindafactory.cpp
+++ b/src/lindafactory.cpp
@@ -32,6 +32,7 @@ THE SOFTWARE.
#include "lindafactory.h"
+#include "lanes_private.h"
#include "linda.h"
// must be a #define instead of a constexpr to work with lua_pushliteral (until I templatize it)
diff --git a/src/universe.cpp b/src/universe.cpp
index 6adc314..becffdd 100644
--- a/src/universe.cpp
+++ b/src/universe.cpp
@@ -89,13 +89,12 @@ void Universe::terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTim
{
std::lock_guard guard{ selfdestructMutex };
Lane* lane{ selfdestructFirst };
- lua_Duration timeout{ 1us };
while (lane != SELFDESTRUCT_END) {
// attempt the requested cancel with a small timeout.
// if waiting on a linda, they will raise a cancel_error.
// if a cancellation hook is desired, it will be installed to try to raise an error
if (lane->thread.joinable()) {
- std::ignore = thread_cancel(lane, op_, 1, timeout, true);
+ std::ignore = thread_cancel(lane, op_, 1, std::chrono::steady_clock::now() + 1us, true);
}
lane = lane->selfdestruct_next;
}
diff --git a/tests/basic.lua b/tests/basic.lua
index 1cf37e6..85a9889 100644
--- a/tests/basic.lua
+++ b/tests/basic.lua
@@ -8,16 +8,16 @@
--
local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{ with_timers = false, internal_allocator = "libc"}
-print( "require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
+print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
local lanes = require_lanes_result_1
local require_assert_result_1, require_assert_result_2 = require "assert" -- assert.fails()
-print( "require_assert_result:", require_assert_result_1, require_assert_result_2)
+print("require_assert_result:", require_assert_result_1, require_assert_result_2)
-local lanes_gen= assert( lanes.gen )
-local lanes_linda= assert( lanes.linda )
+local lanes_gen= assert(lanes.gen)
+local lanes_linda= assert(lanes.linda)
-local tostring= assert( tostring )
+local tostring= assert(tostring)
local function PRINT(...)
local str=""
@@ -29,8 +29,8 @@ local function PRINT(...)
end
end
-local gc_cb = function( name_, status_)
- PRINT( " ---> lane '" .. name_ .. "' collected with status " .. status_)
+local gc_cb = function(name_, status_)
+ PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
end
--gc_cb = nil
@@ -41,9 +41,9 @@ local tables_match
-- true if 'a' is a subtable of 'b'
--
-local function subtable( a, b )
+local function subtable(a, b)
--
- assert( type(a)=="table" and type(b)=="table" )
+ assert(type(a)=="table" and type(b)=="table")
for k,v in pairs(b) do
if type(v)~=type(a[k]) then
@@ -59,18 +59,18 @@ end
-- true when contents of 'a' and 'b' are identical
--
-tables_match= function( a, b )
- return subtable( a, b ) and subtable( b, a )
+tables_match= function(a, b)
+ return subtable(a, b) and subtable(b, a)
end
-- ##################################################################################################
-- ##################################################################################################
-- ##################################################################################################
-PRINT( "\n\n", "---=== Tasking (basic) ===---", "\n\n")
+PRINT("\n\n", "---=== Tasking (basic) ===---", "\n\n")
-local function task( a, b, c )
- set_debug_threadname( "task("..a..","..b..","..c..")")
+local function task(a, b, c)
+ set_debug_threadname("task("..a..","..b..","..c..")")
--error "111" -- testing error messages
assert(hey)
local v=0
@@ -80,20 +80,20 @@ local function task( a, b, c )
return v, hey
end
-local task_launch= lanes_gen( "", { globals={hey=true}, gc_cb = gc_cb}, task )
+local task_launch= lanes_gen("", { globals={hey=true}, gc_cb = gc_cb}, task)
-- base stdlibs, normal priority
-- 'task_launch' is a factory of multithreaded tasks, we can launch several:
-local lane1= task_launch( 100,200,3 )
-local lane2= task_launch( 200,300,4 )
+local lane1= task_launch(100,200,3)
+local lane2= task_launch(200,300,4)
-- At this stage, states may be "pending", "running" or "done"
local st1,st2= lane1.status, lane2.status
PRINT(st1,st2)
-assert( st1=="pending" or st1=="running" or st1=="done" )
-assert( st2=="pending" or st2=="running" or st2=="done" )
+assert(st1=="pending" or st1=="running" or st1=="done")
+assert(st2=="pending" or st2=="running" or st2=="done")
-- Accessing results ([1..N]) pends until they are available
--
@@ -101,14 +101,14 @@ PRINT("waiting...")
local v1, v1_hey= lane1[1], lane1[2]
local v2, v2_hey= lane2[1], lane2[2]
-PRINT( v1, v1_hey )
-assert( v1_hey == true )
+PRINT(v1, v1_hey)
+assert(v1_hey == true)
-PRINT( v2, v2_hey )
-assert( v2_hey == true )
+PRINT(v2, v2_hey)
+assert(v2_hey == true)
-assert( lane1.status == "done" )
-assert( lane1.status == "done" )
+assert(lane1.status == "done")
+assert(lane1.status == "done")
lane1, lane2 = nil
collectgarbage()
@@ -116,9 +116,9 @@ collectgarbage()
-- ##################################################################################################
-- ##################################################################################################
-PRINT( "\n\n", "---=== Tasking (cancelling) ===---", "\n\n")
+PRINT("\n\n", "---=== Tasking (cancelling) ===---", "\n\n")
-local task_launch2= lanes_gen( "", { globals={hey=true}, gc_cb = gc_cb}, task )
+local task_launch2= lanes_gen("", { globals={hey=true}, gc_cb = gc_cb}, task)
local N=999999999
local lane9= task_launch2(1,N,1) -- huuuuuuge...
@@ -129,7 +129,7 @@ local st
local t0= os.time()
while os.time()-t0 < 5 do
st= lane9.status
- io.stderr:write( (i==1) and st.." " or '.' )
+ io.stderr:write((i==1) and st.." " or '.')
if st~="pending" then break end
end
PRINT(" "..st)
@@ -138,36 +138,36 @@ if st=="error" then
local _= lane9[0] -- propagate the error here
end
if st=="done" then
- error( "Looping to "..N.." was not long enough (cannot test cancellation)" )
+ error("Looping to "..N.." was not long enough (cannot test cancellation)")
end
-assert( st=="running" )
+assert(st=="running")
-lane9:cancel( "count", 100) -- 0 timeout, 100 instructions count hook
+lane9:cancel("count", 100) -- 0 timeout, 100 instructions count hook
local t0= os.time()
while os.time()-t0 < 5 do
st= lane9.status
- io.stderr:write( (i==1) and st.." " or '.' )
+ io.stderr:write((i==1) and st.." " or '.')
if st~="running" then break end
end
PRINT(" "..st)
-assert( st == "cancelled" )
+assert(st == "cancelled")
-- cancellation of lanes waiting on a linda
local limited = lanes.linda("limited")
-limited:limit( "key", 1)
+limited:limit("key", 1)
-- [[################################################
-limited:send( "key", "hello") -- saturate linda
-for k, v in pairs( limited:dump()) do
- PRINT("limited[" .. tostring( k) .. "] = " .. tostring( v))
+limited:send("key", "hello") -- saturate linda
+for k, v in pairs(limited:dump()) do
+ PRINT("limited[" .. tostring(k) .. "] = " .. tostring(v))
end
local wait_send = function()
local a,b
- set_finalizer( function() print( "wait_send", a, b) end)
- a,b = limited:send( "key", "bybye") -- infinite timeout, returns only when lane is cancelled
+ set_finalizer(function() print("wait_send", a, b) end)
+ a,b = limited:send("key", "bybye") -- infinite timeout, returns only when lane is cancelled
end
-local wait_send_lane = lanes.gen( "*", wait_send)()
+local wait_send_lane = lanes.gen("*", wait_send)()
repeat until wait_send_lane.status == "waiting"
print "wait_send_lane is waiting"
wait_send_lane:cancel() -- hard cancel, 0 timeout
@@ -176,11 +176,11 @@ print "wait_send_lane is cancelled"
--################################################]]
local wait_receive = function()
local k, v
- set_finalizer( function() print( "wait_receive", k, v) end)
- k, v = limited:receive( "dummy") -- infinite timeout, returns only when lane is cancelled
+ set_finalizer(function() print("wait_receive", k, v) end)
+ k, v = limited:receive("dummy") -- infinite timeout, returns only when lane is cancelled
end
-local wait_receive_lane = lanes.gen( "*", wait_receive)()
+local wait_receive_lane = lanes.gen("*", wait_receive)()
repeat until wait_receive_lane.status == "waiting"
print "wait_receive_lane is waiting"
wait_receive_lane:cancel() -- hard cancel, 0 timeout
@@ -189,11 +189,11 @@ print "wait_receive_lane is cancelled"
--################################################]]
local wait_receive_batched = function()
local k, v1, v2
- set_finalizer( function() print( "wait_receive_batched", k, v1, v2) end)
- k, v1, v2 = limited:receive( limited.batched, "dummy", 2) -- infinite timeout, returns only when lane is cancelled
+ set_finalizer(function() print("wait_receive_batched", k, v1, v2) end)
+ k, v1, v2 = limited:receive(limited.batched, "dummy", 2) -- infinite timeout, returns only when lane is cancelled
end
-local wait_receive_batched_lane = lanes.gen( "*", wait_receive_batched)()
+local wait_receive_batched_lane = lanes.gen("*", wait_receive_batched)()
repeat until wait_receive_batched_lane.status == "waiting"
print "wait_receive_batched_lane is waiting"
wait_receive_batched_lane:cancel() -- hard cancel, 0 timeout
@@ -205,120 +205,126 @@ print "wait_receive_batched_lane is cancelled"
-- ##################################################################################################
-- ##################################################################################################
-PRINT( "\n\n", "---=== Communications ===---", "\n\n")
+PRINT("\n\n", "---=== Communications ===---", "\n\n")
local function WR(...) io.stderr:write(...) end
-local chunk= function( linda )
- set_debug_threadname "chunk"
- local function receive() return linda:receive( "->" ) end
- local function send(...) linda:send( "<-", ... ) end
+local chunk= function(linda)
+ local function receive() return linda:receive("->") end
+ local function send(...) linda:send("<-", ...) end
- WR( "Lane starts!\n" )
+ WR("Lane starts!\n")
local k,v
- k,v=receive(); WR( v.." received\n" ); assert( v==1 )
- k,v=receive(); WR( v.." received\n" ); assert( v==2 )
- k,v=receive(); WR( v.." received\n" ); assert( v==3 )
+ k,v=receive(); WR(v.." received\n"); assert(v==1)
+ k,v=receive(); WR(v.." received\n"); assert(v==2)
+ k,v=receive(); WR(v.." received\n"); assert(v==3)
+ k,v=receive(); WR(tostring(v).." received\n"); assert(v==nil)
- send( 1,2,3 ); WR( "1,2,3 sent\n" )
- send 'a'; WR( "'a' sent\n" )
- send { 'a', 'b', 'c', d=10 }; WR( "{'a','b','c',d=10} sent\n" )
+ send(1,2,3); WR("1,2,3 sent\n")
+ send 'a'; WR("'a' sent\n")
+ send(nil); WR("nil sent\n")
+ send { 'a', 'b', 'c', d=10 }; WR("{'a','b','c',d=10} sent\n")
- k,v=receive(); WR( v.." received\n" ); assert( v==4 )
+ k,v=receive(); WR(v.." received\n"); assert(v==4)
local subT1 = { "subT1"}
local subT2 = { "subT2"}
- send { subT1, subT2, subT1, subT2}; WR( "{ subT1, subT2, subT1, subT2} sent\n" )
+ send { subT1, subT2, subT1, subT2}; WR("{ subT1, subT2, subT1, subT2} sent\n")
- WR( "Lane ends!\n" )
+ WR("Lane ends!\n")
end
-local linda= lanes_linda("communications")
-assert( type(linda) == "userdata" )
+local linda = lanes_linda("communications")
+assert(type(linda) == "userdata" and tostring(linda) == "Linda: communications")
--
-- ["->"] master -> slave
-- ["<-"] slave <- master
local function PEEK() return linda:get("<-") end
-local function SEND(...) linda:send( "->", ... ) end
-local function RECEIVE() local k,v = linda:receive( 1, "<-" ) return v end
+local function SEND(...) linda:send("->", ...) end
+local function RECEIVE() local k,v = linda:receive(1, "<-") return v end
-local t= lanes_gen("io", {gc_cb = gc_cb}, chunk)(linda) -- prepare & launch
+local comms_lane = lanes_gen("io", {gc_cb = gc_cb, name = "auto"}, chunk)(linda) -- prepare & launch
-SEND(1); WR( "1 sent\n" )
-SEND(2); WR( "2 sent\n" )
+SEND(1); WR("1 sent\n")
+SEND(2); WR("2 sent\n")
+SEND(3); WR("3 sent\n")
for i=1,100 do
WR "."
- assert( PEEK() == nil ) -- nothing coming in, yet
+ lanes.sleep(0.0001)
+ assert(PEEK() == nil) -- nothing coming in, yet
end
-SEND(3); WR( "3 sent\n" )
+SEND(nil); WR("\nnil sent\n")
-local a,b,c= RECEIVE(), RECEIVE(), RECEIVE()
+local a,b,c = RECEIVE(), RECEIVE(), RECEIVE()
-print( "lane status: " .. t.status)
-if t.status == "error" then
- print( t:join())
+print("lane status: " .. comms_lane.status)
+if comms_lane.status == "error" then
+ print(comms_lane:join())
else
- WR( a..", "..b..", "..c.." received\n" )
+ WR(a..", "..b..", "..c.." received\n")
end
-assert( a==1 and b==2 and c==3 )
+assert(a==1 and b==2 and c==3)
-local a= RECEIVE(); WR( a.." received\n" )
-assert( a=='a' )
+local a = RECEIVE(); WR(a.." received\n")
+assert(a=='a')
-local a= RECEIVE(); WR( type(a).." received\n" )
-assert( tables_match( a, {'a','b','c',d=10} ) )
+local null = RECEIVE(); WR(tostring(null).." received\n")
+assert(null==nil)
-assert( PEEK() == nil )
+local out_t = RECEIVE(); WR(type(out_t).." received\n")
+assert(tables_match(out_t, {'a','b','c',d=10}))
+
+assert(PEEK() == nil)
SEND(4)
-local complex_table = RECEIVE(); WR( type(complex_table).." received\n" )
-assert( complex_table[1] == complex_table[3] and complex_table[2] == complex_table[4])
-WR( table.concat( {complex_table[1][1],complex_table[2][1],complex_table[3][1],complex_table[4][1]},", "))
+local complex_table = RECEIVE(); WR(type(complex_table).." received\n")
+assert(complex_table[1] == complex_table[3] and complex_table[2] == complex_table[4])
+WR(table.concat({complex_table[1][1],complex_table[2][1],complex_table[3][1],complex_table[4][1]},", "))
WR("collectgarbage")
-t = nil
+comms_lane = nil
collectgarbage()
-- wait
WR("waiting 1s")
-linda:receive( 1, "wait")
+lanes.sleep(1)
-- ##################################################################################################
-- ##################################################################################################
-- ##################################################################################################
-PRINT( "\n\n", "---=== Stdlib naming ===---", "\n\n")
+PRINT("\n\n", "---=== Stdlib naming ===---", "\n\n")
-local function dump_g( _x)
+local function dump_g(_x)
set_debug_threadname "dump_g"
assert(print)
- print( "### dumping _G for '" .. _x .. "'")
- for k, v in pairs( _G) do
- print( "\t" .. k .. ": " .. type( v))
+ print("### dumping _G for '" .. _x .. "'")
+ for k, v in pairs(_G) do
+ print("\t" .. k .. ": " .. type(v))
end
return true
end
-local function io_os_f( _x)
+local function io_os_f(_x)
set_debug_threadname "io_os_f"
assert(print)
- print( "### checking io and os libs existence for '" .. _x .. "'")
+ print("### checking io and os libs existence for '" .. _x .. "'")
assert(io)
assert(os)
return true
end
-local function coro_f( _x)
+local function coro_f(_x)
set_debug_threadname "coro_f"
assert(print)
- print( "### checking coroutine lib existence for '" .. _x .. "'")
+ print("### checking coroutine lib existence for '" .. _x .. "'")
assert(coroutine)
return true
end
-assert.fails( function() lanes_gen( "xxx", {gc_cb = gc_cb}, io_os_f ) end )
+assert.fails(function() lanes_gen("xxx", {gc_cb = gc_cb}, io_os_f) end)
local stdlib_naming_tests =
{
@@ -333,9 +339,9 @@ local stdlib_naming_tests =
{ "io,os,base", io_os_f},
}
-for _, t in ipairs( stdlib_naming_tests) do
- local f= lanes_gen( t[1], {gc_cb = gc_cb}, t[2]) -- any delimiter will do
- assert( f(t[1])[1] )
+for _, t in ipairs(stdlib_naming_tests) do
+ local f= lanes_gen(t[1], {gc_cb = gc_cb}, t[2]) -- any delimiter will do
+ assert(f(t[1])[1])
end
WR("collectgarbage")
@@ -345,17 +351,17 @@ collectgarbage()
-- ##################################################################################################
-- ##################################################################################################
-PRINT( "\n\n", "---=== Comms criss cross ===---", "\n\n")
+PRINT("\n\n", "---=== Comms criss cross ===---", "\n\n")
-- We make two identical lanes, which are using the same Linda channel.
--
-local tc= lanes_gen( "io", {gc_cb = gc_cb},
- function( linda, ch_in, ch_out )
- set_debug_threadname( "criss cross " .. ch_in .. " -> " .. ch_out)
+local tc= lanes_gen("io", {gc_cb = gc_cb},
+ function(linda, ch_in, ch_out)
+ set_debug_threadname("criss cross " .. ch_in .. " -> " .. ch_out)
local function STAGE(str)
- io.stderr:write( ch_in..": "..str.."\n" )
- linda:send( nil, ch_out, str )
- local k,v= linda:receive( nil, ch_in )
+ io.stderr:write(ch_in..": "..str.."\n")
+ linda:send(nil, ch_out, str)
+ local k,v= linda:receive(nil, ch_in)
assert(v==str)
end
STAGE("Hello")
@@ -378,103 +384,103 @@ collectgarbage()
-- ##################################################################################################
-- ##################################################################################################
-PRINT( "\n\n", "---=== Receive & send of code ===---", "\n\n")
+PRINT("\n\n", "---=== Receive & send of code ===---", "\n\n")
local upvalue="123"
-local function chunk2( linda )
- assert( upvalue=="123" ) -- even when running as separate thread
+local function chunk2(linda)
+ assert(upvalue=="123") -- even when running as separate thread
-- function name & line number should be there even as separate thread
--
local info= debug.getinfo(1) -- 1 = us
--
for k,v in pairs(info) do PRINT(k,v) end
- assert( info.nups == (_VERSION == "Lua 5.1" and 2 or 3) ) -- one upvalue + PRINT + _ENV (Lua 5.2 only)
- assert( info.what == "Lua" )
- --assert( info.name == "chunk2" ) -- name does not seem to come through
- assert( string.match( info.source, "^@.*basic.lua$" ) )
- assert( string.match( info.short_src, "^.*basic.lua$" ) )
+ assert(info.nups == (_VERSION == "Lua 5.1" and 2 or 3)) -- one upvalue + PRINT + _ENV (Lua 5.2 only)
+ assert(info.what == "Lua")
+ --assert(info.name == "chunk2") -- name does not seem to come through
+ assert(string.match(info.source, "^@.*basic.lua$"))
+ assert(string.match(info.short_src, "^.*basic.lua$"))
-- These vary so let's not be picky (they're there..)
--
- assert( info.linedefined > 200 ) -- start of 'chunk2'
- assert( info.currentline > info.linedefined ) -- line of 'debug.getinfo'
- assert( info.lastlinedefined > info.currentline ) -- end of 'chunk2'
- local k,func= linda:receive( "down" )
- assert( type(func)=="function" )
- assert( k=="down" )
+ assert(info.linedefined > 200) -- start of 'chunk2'
+ assert(info.currentline > info.linedefined) -- line of 'debug.getinfo'
+ assert(info.lastlinedefined > info.currentline) -- end of 'chunk2'
+ local k,func= linda:receive("down")
+ assert(type(func)=="function")
+ assert(k=="down")
func(linda)
- local k,str= linda:receive( "down" )
- assert( str=="ok" )
+ local k,str= linda:receive("down")
+ assert(str=="ok")
- linda:send( "up", function() return ":)" end, "ok2" )
+ linda:send("up", function() return ":)" end, "ok2")
end
local linda= lanes.linda("linda")
-local t2= lanes_gen( "debug,string,io", {gc_cb = gc_cb}, chunk2 )(linda) -- prepare & launch
-linda:send( "down", function(linda) linda:send( "up", "ready!" ) end,
- "ok" )
+local t2= lanes_gen("debug,string,io", {gc_cb = gc_cb}, chunk2)(linda) -- prepare & launch
+linda:send("down", function(linda) linda:send("up", "ready!") end,
+ "ok")
-- wait to see if the tiny function gets executed
--
-local k,s= linda:receive( 1, "up" )
+local k,s= linda:receive(1, "up")
if t2.status == "error" then
- print( "t2 error: " , t2:join())
+ print("t2 error: " , t2:join())
end
PRINT(s)
-assert( s=="ready!" )
+assert(s=="ready!")
-- returns of the 'chunk2' itself
--
-local k,f= linda:receive( "up" )
-assert( type(f)=="function" )
+local k,f= linda:receive("up")
+assert(type(f)=="function")
local s2= f()
-assert( s2==":)" )
+assert(s2==":)")
-local k,ok2= linda:receive( "up" )
-assert( ok2 == "ok2" )
+local k,ok2= linda:receive("up")
+assert(ok2 == "ok2")
-- ##################################################################################################
-- ##################################################################################################
-- ##################################################################################################
-PRINT( "\n\n", "---=== :join test ===---", "\n\n")
+PRINT("\n\n", "---=== :join test ===---", "\n\n")
-- 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", {gc_cb = gc_cb},
+local S= lanes_gen("table", {gc_cb = gc_cb},
function(arg)
set_debug_threadname "join test lane"
- set_finalizer( function() end)
+ set_finalizer(function() end)
aux= {}
for i, v in ipairs(arg) do
table.insert (aux, 1, v)
end
-- unpack was renamed table.unpack in Lua 5.2: cater for both!
return (unpack or table.unpack)(aux)
-end )
+end)
h= S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values
--- wait a bit so that the lane hasa chance to set its debug name
-linda:receive(0.5, "gloupti")
-print( "joining with '" .. h:get_debug_threadname() .. "'")
+-- wait a bit so that the lane has a chance to set its debug name
+lanes.sleep(0.5)
+print("joining with '" .. h:get_debug_threadname() .. "'")
local a,b,c,d= h:join()
if h.status == "error" then
- print( h:get_debug_threadname(), "error: " , a, b, c, d)
+ print(h:get_debug_threadname(), "error: " , a, b, c, d)
else
- print( h:get_debug_threadname(), a,b,c,d)
+ print(h:get_debug_threadname(), a,b,c,d)
assert(a==14)
assert(b==13)
assert(c==12)
assert(d==nil)
end
-local nameof_type, nameof_name = lanes.nameof( print)
-PRINT( "name of " .. nameof_type .. " print = '" .. nameof_name .. "'")
+local nameof_type, nameof_name = lanes.nameof(print)
+PRINT("name of " .. nameof_type .. " print = '" .. nameof_name .. "'")
--
io.stderr:write "Done! :)\n"
--
cgit v1.2.3-55-g6feb