From f7a38b5681ebf7429828fca9a9f7c109df853d54 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Fri, 31 May 2024 17:04:17 +0200 Subject: Some API changes * lanes.timers() can return nil, cancel_error if interrupted * linda:receive() always return a k,v, where k is nil when v is "timeout" or cancel_error * lanes.sleep returns nil, "timeout" during normal operations --- docs/index.html | 18 ++++++++++-------- src/lanes.lua | 38 +++++++++++++++++++++++--------------- src/linda.cpp | 19 ++++++++++++++++--- tests/basic.lua | 11 ++++++++--- tests/cancel.lua | 23 ++++++++++++++--------- tests/deadlock.lua | 7 ++++++- tests/ehynes.lua | 7 ++++++- tests/errhangtest.lua | 4 +++- tests/nameof.lua | 9 +++++++-- tests/objects.lua | 34 +++++++++++++++++----------------- tests/protect_allocator.lua | 9 +++++++-- tests/timer.lua | 2 +- tests/track_lanes.lua | 12 ++++++++---- 13 files changed, 126 insertions(+), 67 deletions(-) diff --git a/docs/index.html b/docs/index.html index 0d97c29..98c2abb 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1198,9 +1198,9 @@ [true|lanes.cancel_error] = h:send([timeout_secs,] key, ...) - [key, val]|[lanes.cancel_error] = h:receive([timeout_secs,] key [, ...]) + key, val = h:receive([timeout_secs,] key [, key...]) - [key, val [, ...]]|[lanes.cancel_error] = h:receive(timeout, h.batched, key, n_uint_min[, n_uint_max]) + key, val [, val...] = h:receive(timeout, h.batched, key, n_uint_min[, n_uint_max]) [true|lanes.cancel_error] = h:limit(key, n_uint) @@ -1235,9 +1235,9 @@

- Equally, receive() returns a key and the value extracted from it, or nothing for timeout. Note that nils can be sent and received; the key value will tell it apart from a timeout. -
- receive() returns lanes.cancel_error if interrupted by a soft cancel request. + Equally, receive() returns a key and the value extracted from it. Note that nils can be sent and received; the key value will tell it apart from a timeout.
+ receive() returns nil, lanes.cancel_error if interrupted by a hard cancel request.
+ receive() returns nil, "timeout" if nothing was available.

@@ -1439,16 +1439,18 @@ On the other side, you need to use a common Linda for waiting for multiple keys.

- The full list of active timers can be obtained. Obviously, this is a snapshot, and non-repeating timers might no longer exist by the time the results are inspected. + The full list of active timers can be obtained. Obviously, this is a snapshot, and non-repeating timers might no longer exist by the time the results are inspected.
+ Can return nil, "timeout" or nil, lanes.cancel_error in case of interruption.

-	void = lanes.sleep(['indefinitely'|seconds|nil])
+	nil, "timeout" = lanes.sleep(['indefinitely'|seconds|nil])
 

A very simple way of sleeping when nothing else is available. Is implemented by attempting to read some data in an unused channel of the internal linda used for timers (this linda exists even when timers aren't enabled). - Default duration is 0, which should only cause a thread context switch. + Default duration is 0, which should only cause a thread context switch.
+ Return values should always be nil, "timeout" (or nil, lanes.cancel_error in case of interruption).

diff --git a/src/lanes.lua b/src/lanes.lua
index d5a04e5..f5e81d4 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -573,20 +573,22 @@ local configure_timers = function()
                     secs =  next_wakeup - now_secs()
                     if secs < 0 then secs = 0 end
                 end
-                local key, what = timerLinda:receive(secs, TGW_KEY, TGW_QUERY)
+                -- poll both TGW_KEY and TGW_QUERY at the same time
+                local _timerKey, _what = timerLinda:receive(secs, TGW_KEY, TGW_QUERY)
 
-                if key == TGW_KEY then
-                    assert(getmetatable(what) == "Linda") -- 'what' should be a linda on which the client sets a timer
+                if _timerKey == TGW_KEY then
+                    assert(getmetatable(_what) == "Linda") -- '_what' should be a linda on which the client sets a timer
                     local _, key, wakeup_at, period = timerLinda:receive(0, timer_gateway_batched, TGW_KEY, 3)
                     assert(key)
-                    set_timer(what, key, wakeup_at, period and period > 0 and period or nil)
-                elseif key == TGW_QUERY then
-                    if what == "get_timers" then
+                    set_timer(_what, key, wakeup_at, period and period > 0 and period or nil)
+                elseif _timerKey == TGW_QUERY then
+                    if _what == "get_timers" then
                         timerLinda:send(TGW_REPLY, get_timers())
                     else
-                        timerLinda:send(TGW_REPLY, "unknown query " .. what)
+                        timerLinda:send(TGW_REPLY, "unknown query " .. _what)
                     end
-                --elseif secs == nil then -- got no value while block-waiting?
+                else -- got no value while block-waiting
+                    assert(_what == cancel_error or _what == "timeout")
                 --	WR("timer lane: no linda, aborted?")
                 end
             end
@@ -630,8 +632,14 @@ local configure_timers = function()
     -- PUBLIC LANES API
     timers = function()
         timerLinda:send(TGW_QUERY, "get_timers")
-        local _, r = timerLinda:receive(TGW_REPLY)
-        return r
+        -- can be nil,  in case of cancellation or timeout
+        local _k, _t = timerLinda:receive(TGW_REPLY)
+        -- success: return the table
+        if _k then
+            return _t
+        end
+        -- error: return everything we got
+        return _k, _t
     end -- timers()
 end -- configure_timers()
 
@@ -639,7 +647,7 @@ end -- configure_timers()
 -- ###################################### lanes.sleep() ############################################
 -- #################################################################################################
 
---  = sleep([seconds_])
+-- nil, "timeout" = sleep([seconds_])
 --
 -- PUBLIC LANES API
 local sleep = function(seconds_)
@@ -699,9 +707,9 @@ local genlock = function(linda_, key_, N)
             -- 'nil' timeout allows 'key_' to be numeric
             return linda_:send(timeout, key_, true)    -- suspends until been able to push them
         else
-            local k = linda_:receive(nil, key_)
+            local _k, _v = linda_:receive(nil, key_)
             -- propagate cancel_error if we got it, else return true or false
-            return k and ((k ~= cancel_error) and true or k) or false
+            return (_v == cancel_error and _v) or (_k and true or false)
         end
     end
     or
@@ -711,9 +719,9 @@ local genlock = function(linda_, key_, N)
             -- 'nil' timeout allows 'key_' to be numeric
             return linda_:send(timeout, key_, trues(M_))    -- suspends until been able to push them
         else
-            local k = linda_:receive(nil, linda_.batched, key_, -M_)
+            local _k, _v = linda_:receive(nil, linda_.batched, key_, -M_)
             -- propagate cancel_error if we got it, else return true or false
-            return k and ((k ~= cancel_error) and true or k) or false
+            return (_v == cancel_error and _v) or (_k and true or false)
         end
     end
 end -- genlock
diff --git a/src/linda.cpp b/src/linda.cpp
index 4ab89ab..2a31799 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -553,17 +553,30 @@ LUAG_FUNC(linda_receive)
         }
 
         switch (_cancel) {
+        case CancelRequest::None:
+            {
+                int const _nbPushed{ _pushed.value() };
+                if (_nbPushed == 0) {
+                    // not enough data in the linda slot to fulfill the request, return nil, "timeout"
+                    lua_pushnil(L_);
+                    std::ignore = lua_pushstringview(L_, "timeout");
+                    return 2;
+                }
+                return _nbPushed;
+            }
+
         case CancelRequest::Soft:
-            // if user wants to soft-cancel, the call returns kCancelError
+            // if user wants to soft-cancel, the call returns nil, kCancelError
+            lua_pushnil(L_);
             kCancelError.pushKey(L_);
-            return 1;
+            return 2;
 
         case CancelRequest::Hard:
             // raise an error interrupting execution only in case of hard cancel
             raise_cancel_error(L_); // raises an error and doesn't return
 
         default:
-            return _pushed.value();
+            raise_luaL_error(L_, "internal error: unknown cancel request");
         }
     };
     return Linda::ProtectedCall(L_, _receive);
diff --git a/tests/basic.lua b/tests/basic.lua
index ab8a080..da7d756 100644
--- a/tests/basic.lua
+++ b/tests/basic.lua
@@ -29,6 +29,11 @@ local function PRINT(...)
     end
 end
 
+local SLEEP = function(...)
+    local k, v = lanes.sleep(...)
+    assert(k == nil and v == "timeout")
+end
+
 local gc_cb = function(name_, status_)
     PRINT("				---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
 end
@@ -256,7 +261,7 @@ SEND(3);  WR("main ", "3 sent\n")
 SEND(setmetatable({"should be ignored"},{__lanesignore=true})); WR("main ", "__lanesignore table sent\n")
 for i=1,100 do
     WR "."
-    lanes.sleep(0.0001)
+    SLEEP(0.0001)
     assert(PEEK() == nil)    -- nothing coming in, yet
 end
 SEND(nil);  WR("\nmain ", "nil sent\n")
@@ -293,7 +298,7 @@ comms_lane = nil
 collectgarbage()
 -- wait
 WR("waiting 1s")
-lanes.sleep(1)
+SLEEP(1)
 
 -- ##################################################################################################
 -- ##################################################################################################
@@ -470,7 +475,7 @@ end)
 
 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
-lanes.sleep(0.5)
+SLEEP(0.5)
 print("joining with '" .. h:get_debug_threadname() .. "'")
 local a,b,c,d= h:join()
 if h.status == "error" then
diff --git a/tests/cancel.lua b/tests/cancel.lua
index e39ad98..b75d085 100644
--- a/tests/cancel.lua
+++ b/tests/cancel.lua
@@ -9,6 +9,11 @@ end
 
 local lanes = require "lanes" .configure{ with_timers = false}
 
+local SLEEP = function(...)
+	local k, v = lanes.sleep(...)
+	assert(k == nil and v == "timeout")
+end
+
 local linda = lanes.linda()
 -- a numeric value to read
 linda:set( "val", 33.0)
@@ -51,7 +56,7 @@ local waitCancellation = function( h, expected_status)
 	if expected_status ~= "running" then
 		repeat
 			-- print( "lane status:", h.status)
-			l:receive( 0.1, "yeah") -- wait a bit
+			SLEEP(0.1) -- wait a bit
 		until h.status ~= "running"
 	end
 	print( "lane status:", h.status)
@@ -80,8 +85,8 @@ local laneBody = function( mode_, payload_)
 			-- linda mode
 			io.stdout:write( "			lane calling receive() ... ")
 			local key, val = linda:receive( payload_, "boob")
-			print( lanes.cancel_error == key and "cancel_error" or tostring( key), tostring( val))
-			if key == lanes.cancel_error then
+			print(tostring(key), val == lanes.cancel_error and "cancel_error" or tostring(val))
+			if val == lanes.cancel_error then
 				break -- gracefully abort loop
 			end
 		elseif mode_ == "get" then
@@ -138,9 +143,9 @@ if not next(which_tests) or which_tests.linda then
 	h = lanes.gen( "*", laneBody)( "receive", nil) -- start an infinite wait on the linda
 
 	print "wait 1s"
-	linda:receive( 1, "yeah")
+	SLEEP(1)
 
-	-- linda cancel: linda:receive() returns cancel_error immediately
+	-- linda cancel: linda:receive() returns nil,cancel_error immediately
 	print "cancelling"
 	linda:cancel( "both")
 
@@ -159,7 +164,7 @@ if not next(which_tests) or which_tests.soft then
 	h = lanes.gen( "*", protectedBody)( "receive") -- start an infinite wait on the linda
 
 	print "wait 1s"
-	linda:receive( 1, "yeah")
+	SLEEP(1)
 
 	-- soft cancel, no awakening of waiting linda operations, should timeout
 	local a, b = h:cancel( "soft", 1, false)
@@ -182,7 +187,7 @@ if not next(which_tests) or which_tests.hook then
 	print "\n\n####################################################################\nbegin hook cancel test\n"
 	h = lanes.gen( "*", protectedBody)( "get", 300000)
 	print "wait 2s"
-	linda:receive( 2, "yeah")
+	SLEEP(2)
 
 	-- count hook cancel after some instruction instructions
 	print "cancelling"
@@ -201,7 +206,7 @@ if not next(which_tests) or which_tests.hard then
 
 	-- wait 2s before cancelling the lane
 	print "wait 2s"
-	linda:receive( 2, "yeah")
+	SLEEP(2)
 
 	-- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it
 	print "cancelling"
@@ -220,7 +225,7 @@ if not next(which_tests) or which_tests.hard_unprotected then
 
 	-- wait 2s before cancelling the lane
 	print "wait 2s"
-	linda:receive( 2, "yeah")
+	SLEEP(2)
 
 	-- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it
 	print "cancelling"
diff --git a/tests/deadlock.lua b/tests/deadlock.lua
index c85d99f..c38ca13 100644
--- a/tests/deadlock.lua
+++ b/tests/deadlock.lua
@@ -4,6 +4,11 @@
 local lanes = require('lanes').configure{with_timers=false}
 local linda = lanes.linda "deadlock_linda"
 
+local SLEEP = function(...)
+    local k, v = lanes.sleep(...)
+    assert(k == nil and v == "timeout")
+end
+
 print "let's begin"
 
 local do_extra_stuff = true
@@ -35,5 +40,5 @@ end
 -- With the bug not fixed, the linda keeper's mutex is still acquired,
 -- and the program hangs when the internal linda used for timers attempts to acquire the same keeper (there is only one by default)
 print('waiting a bit')
-lanes.sleep(2)
+SLEEP(2)
 print('we should reach here')
\ No newline at end of file
diff --git a/tests/ehynes.lua b/tests/ehynes.lua
index e203a12..9436c7d 100644
--- a/tests/ehynes.lua
+++ b/tests/ehynes.lua
@@ -4,6 +4,11 @@
 local lanes = require "lanes"
 lanes.configure()
 
+local SLEEP = function(...)
+    local k, v = lanes.sleep(...)
+    assert(k == nil and v == "timeout")
+end
+
 local function PRINT_FMT( fmt, ... )
     io.stderr:write( string.format(fmt,...).."\n" )
 end
@@ -36,7 +41,7 @@ local receiver2 = receiver_gen('another message')
 
 -- a function to pause and log the execution for debugging
 local function logf(s, f, ...)
-    linda:receive(1, "dummy") -- wait 1s
+    SLEEP(1)
     PRINT_FMT( "*** %s", s )
     f(...)
 end
diff --git a/tests/errhangtest.lua b/tests/errhangtest.lua
index 84de8c6..99f44b2 100644
--- a/tests/errhangtest.lua
+++ b/tests/errhangtest.lua
@@ -24,8 +24,10 @@ if true then
 	print "\n#### set 3 -> receive batched"
 	local fun = function() print "function test ok" end
 	print(pcall(linda.set, linda, 'test', true, nil, fun))
-	local k,b,n,f = linda:receive(linda.batched, 'test', 3) -- read back the contents
+	-- read back the contents
+	local k,b,n,f = linda:receive(linda.batched, 'test', 3)
 	assert(linda:get("test") == nil)
+	-- check they are ok
 	print(k, b, n)
 	f()
 	print "OK"
diff --git a/tests/nameof.lua b/tests/nameof.lua
index 58df3e2..f48a971 100644
--- a/tests/nameof.lua
+++ b/tests/nameof.lua
@@ -1,5 +1,10 @@
 local lanes = require "lanes".configure{nb_user_keepers = 100, on_state_create = function() end}
 
+local SLEEP = function(...)
+    local k, v = lanes.sleep(...)
+    assert(k == nil and v == "timeout")
+end
+
 print("Name of table: ", lanes.nameof({}))
 print("Name of string.sub: ", lanes.nameof(string.sub))
 print("Name of print: ", lanes.nameof(print))
@@ -14,12 +19,12 @@ end
 
 -- start the lane without any library
 local h = lanes.gen(nil, body)()
-lanes.sleep(0.1)
+SLEEP(0.1)
 print("Name of lane: ", lanes.nameof(h), "("..h.status..")")
 assert(h.status == "running")
 -- cancel the lane
 h:cancel("line", 1)
-lanes.sleep(0.1)
+SLEEP(0.1)
 print("Name of lane: ", lanes.nameof(h), "("..h.status..")")
 assert(h.status == "cancelled")
 print "TEST OK"
diff --git a/tests/objects.lua b/tests/objects.lua
index eed02bf..7668e45 100644
--- a/tests/objects.lua
+++ b/tests/objects.lua
@@ -9,27 +9,27 @@ lanes.configure()
 
 local linda= lanes.linda()
 
-local start_lane= lanes.gen( "io", 
-    function( obj1 )
+local start_lane= lanes.gen("io", 
+    function(obj1 )
 
-        assert( obj1.v )
-        assert( obj1.print )
+        assert(obj1.v )
+        assert(obj1.print )
 
-        assert( obj1 )
+        assert(obj1 )
         local mt1= getmetatable(obj1)
         assert(mt1)
     
         local k, obj2= linda:receive("")
-        assert( obj2 )
+        assert(k == "" and obj2 )
         local mt2= getmetatable(obj2)
         assert(mt2)
-        assert( mt1==mt2 )
+        assert(mt1==mt2 )
         
         local v= obj1:print()
-        assert( v=="aaa" )
+        assert(v=="aaa" )
     
-        v= obj2:print()    
-        assert( v=="bbb" )
+        v = obj2:print()
+        assert(v=="bbb" )
     
         return true
     end
@@ -37,7 +37,7 @@ local start_lane= lanes.gen( "io",
 
 
 local WR= function(str)
-    io.stderr:write( tostring(str).."\n")
+    io.stderr:write(tostring(str).."\n")
 end
 
 
@@ -47,7 +47,7 @@ end
 -- having the methods 'fixed' in the object tables themselves.
 --
 local o_mt= {
-    __index= function( me, key )
+    __index= function(me, key )
         if key=="print" then
             return function() WR(me.v); return me.v end
         end
@@ -56,22 +56,22 @@ local o_mt= {
 
 local function obj_gen(v)
     local o= { v=v }
-    setmetatable( o, o_mt )
+    setmetatable(o, o_mt )
     return o
 end
 
 local a= obj_gen("aaa")
 local b= obj_gen("bbb")
 
-assert( a and b )
+assert(a and b )
 
 local mt_a= getmetatable(a)
 local mt_b= getmetatable(b)
-assert( mt_a and mt_a==mt_b )
+assert(mt_a and mt_a==mt_b )
 
 local h= start_lane(a)  -- 1st object as parameter
 
-linda:send( "", b )    -- 2nd object via Linda
+linda:send("", b )    -- 2nd object via Linda
 
-assert( h[1]==true )    -- wait for return
+assert(h[1]==true )    -- wait for return
 
diff --git a/tests/protect_allocator.lua b/tests/protect_allocator.lua
index 5cbb1d8..994cb39 100644
--- a/tests/protect_allocator.lua
+++ b/tests/protect_allocator.lua
@@ -2,6 +2,11 @@ local print = print
 
 local lanes = require "lanes".configure{ with_timers = false, allocator="protected"}
 
+local SLEEP = function(...)
+	local k, v = lanes.sleep(...)
+	assert(k == nil and v == "timeout")
+end
+
 local linda = lanes.linda()
 
 local body = function( id_)
@@ -38,7 +43,7 @@ end
 
 -- wait a bit
 print "waiting a bit ..."
-linda:receive( 2, "foo")
+SLEEP(2)
 -- tell lanes to start running
 print "GO!"
 for i = 1, COUNT do
@@ -49,5 +54,5 @@ end
 print "wait for completion"
 linda:receive( linda.batched, "key", COUNT)
 print "waiting a bit more ..."
-linda:receive( 1, "foo")
+SLEEP(1)
 print "SUCCESS"
diff --git a/tests/timer.lua b/tests/timer.lua
index a633286..4da1a50 100644
--- a/tests/timer.lua
+++ b/tests/timer.lua
@@ -100,7 +100,7 @@ assert( linda:get(T2) == nil )
 PRINT "...making sure no ticks are coming..."
 
 local k,v= linda:receive( 10, T1,T2 )    -- should not get any
-assert(v==nil)
+assert(k==nil and v == "timeout")
 
 lanes.timer_lane:cancel() -- hard cancel, 0 timeout
 print (lanes.timer_lane[1], lanes.timer_lane[2])
\ No newline at end of file
diff --git a/tests/track_lanes.lua b/tests/track_lanes.lua
index daaa94c..5ea9a4e 100644
--- a/tests/track_lanes.lua
+++ b/tests/track_lanes.lua
@@ -1,5 +1,9 @@
 local lanes = require "lanes" .configure{ with_timers = false, track_lanes = true}
-local wait = lanes.sleep
+
+local SLEEP = function(...)
+    local k, v = lanes.sleep(...)
+    assert(k == nil and v == "timeout")
+end
 
 print "hello"
 
@@ -43,7 +47,7 @@ g( "forever", 'indefinitely')
 g( "two_seconds", 2)
  
 -- give a bit of time to reach the linda waiting call
-wait( 0.1)
+SLEEP(0.1)
 
 -- list the known lanes (should be living lanes)
 local threads = track( "============= START", 2)
@@ -51,7 +55,7 @@ local threads = track( "============= START", 2)
 assert(threads[1].status == 'waiting' and threads[2].status == 'waiting')
 
 -- wait until "two_seconds has completed"
-wait(2.1)
+SLEEP(2.1)
 
 local threads = track( "============= two_seconds dead", 2)
 --     two_seconds                        forever
@@ -61,7 +65,7 @@ assert(threads[1].status == 'done' and threads[2].status == 'waiting')
 g( "two_seconds", 2)
  
 -- give a bit of time to reach the linda waiting call
-wait( 0.1)
+SLEEP( 0.1)
 
 -- list the known lanes
 -- unless garbage collector cleaned it, we should have 3 lanes
-- 
cgit v1.2.3-55-g6feb