aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-05-31 11:40:28 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2024-05-31 11:40:28 +0200
commita65a84cfad4eae38e5e84b1ab11f60a62833f287 (patch)
tree3d3b6cc88c188f2c34d3283e081316d6cc05ae83
parent731556711e453a501f1d1d06a6013b8fbd53414e (diff)
downloadlanes-a65a84cfad4eae38e5e84b1ab11f60a62833f287.tar.gz
lanes-a65a84cfad4eae38e5e84b1ab11f60a62833f287.tar.bz2
lanes-a65a84cfad4eae38e5e84b1ab11f60a62833f287.zip
Improved multi-keeper tests
-rw-r--r--src/deep.cpp9
-rw-r--r--src/lanes.cpp2
-rw-r--r--src/lanes.lua37
-rw-r--r--tests/keeper.lua235
-rw-r--r--tests/nb_keepers.lua20
5 files changed, 178 insertions, 125 deletions
diff --git a/src/deep.cpp b/src/deep.cpp
index f5716bf..34cc4b4 100644
--- a/src/deep.cpp
+++ b/src/deep.cpp
@@ -78,9 +78,9 @@ namespace {
78 78
79 // can work without a universe if creating a deep userdata from some external C module when Lanes isn't loaded 79 // can work without a universe if creating a deep userdata from some external C module when Lanes isn't loaded
80 // in that case, we are not multithreaded and locking isn't necessary anyway 80 // in that case, we are not multithreaded and locking isn't necessary anyway
81 bool const isLastRef{ _p->refcount.fetch_sub(1, std::memory_order_relaxed) == 1 }; 81 bool const _isLastRef{ _p->refcount.fetch_sub(1, std::memory_order_relaxed) == 1 };
82 82
83 if (isLastRef) { 83 if (_isLastRef) {
84 // retrieve wrapped __gc, if any 84 // retrieve wrapped __gc, if any
85 lua_pushvalue(L_, lua_upvalueindex(1)); // L_: self __gc? 85 lua_pushvalue(L_, lua_upvalueindex(1)); // L_: self __gc?
86 if (!lua_isnil(L_, -1)) { 86 if (!lua_isnil(L_, -1)) {
@@ -123,6 +123,7 @@ namespace {
123// ################################################################################################# 123// #################################################################################################
124// ################################################################################################# 124// #################################################################################################
125 125
126// NEVER call deleteDeepObjectInternal by itself, ALWAYS go through DeleteDeepObject()
126void DeepFactory::DeleteDeepObject(lua_State* const L_, DeepPrelude* const o_) 127void DeepFactory::DeleteDeepObject(lua_State* const L_, DeepPrelude* const o_)
127{ 128{
128 STACK_CHECK_START_REL(L_, 0); 129 STACK_CHECK_START_REL(L_, 0);
@@ -310,7 +311,7 @@ int DeepFactory::pushDeepUserdata(DestState const L_, int const nuv_) const
310 311
311 if (_prelude->magic != kDeepVersion) { 312 if (_prelude->magic != kDeepVersion) {
312 // just in case, don't leak the newly allocated deep userdata object 313 // just in case, don't leak the newly allocated deep userdata object
313 deleteDeepObjectInternal(L_, _prelude); 314 DeleteDeepObject(L_, _prelude);
314 raise_luaL_error(L_, "Bad Deep Factory: kDeepVersion is incorrect, rebuild your implementation with the latest deep implementation"); 315 raise_luaL_error(L_, "Bad Deep Factory: kDeepVersion is incorrect, rebuild your implementation with the latest deep implementation");
315 } 316 }
316 317
@@ -319,7 +320,7 @@ int DeepFactory::pushDeepUserdata(DestState const L_, int const nuv_) const
319 320
320 if (lua_gettop(L_) - _oldtop != 0) { 321 if (lua_gettop(L_) - _oldtop != 0) {
321 // just in case, don't leak the newly allocated deep userdata object 322 // just in case, don't leak the newly allocated deep userdata object
322 deleteDeepObjectInternal(L_, _prelude); 323 DeleteDeepObject(L_, _prelude);
323 raise_luaL_error(L_, "Bad DeepFactory::newDeepObjectInternal overload: should not push anything on the stack"); 324 raise_luaL_error(L_, "Bad DeepFactory::newDeepObjectInternal overload: should not push anything on the stack");
324 } 325 }
325 326
diff --git a/src/lanes.cpp b/src/lanes.cpp
index 4b4f9a8..41450f3 100644
--- a/src/lanes.cpp
+++ b/src/lanes.cpp
@@ -664,7 +664,7 @@ LUAG_FUNC(configure)
664 664
665 STACK_CHECK(L_, 2); 665 STACK_CHECK(L_, 2);
666 DeepFactory::PushDeepProxy(DestState{ L_ }, _U->timerLinda, 0, LookupMode::LaneBody, L_); // L_: settings M timerLinda 666 DeepFactory::PushDeepProxy(DestState{ L_ }, _U->timerLinda, 0, LookupMode::LaneBody, L_); // L_: settings M timerLinda
667 lua_setfield(L_, -2, "timer_gateway"); // L_: settings M 667 lua_setfield(L_, -2, "timerLinda"); // L_: settings M
668 STACK_CHECK(L_, 2); 668 STACK_CHECK(L_, 2);
669 669
670 // prepare the metatable for threads 670 // prepare the metatable for threads
diff --git a/src/lanes.lua b/src/lanes.lua
index 1c36b46..d5a04e5 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -376,8 +376,9 @@ local timer = function() error "timers are not active" end
376local timers = timer 376local timers = timer
377local timer_lane = nil 377local timer_lane = nil
378 378
379-- timer_gateway should always exist, even when the settings disable the timers 379-- timerLinda should always exist, even when the settings disable the timers
380local timer_gateway 380-- is upvalue of timer stuff and lanes.sleep()
381local timerLinda
381 382
382local TGW_KEY = "(timer control)" -- the key does not matter, a 'weird' key may help debugging 383local TGW_KEY = "(timer control)" -- the key does not matter, a 'weird' key may help debugging
383local TGW_QUERY, TGW_REPLY = "(timer query)", "(timer reply)" 384local TGW_QUERY, TGW_REPLY = "(timer query)", "(timer reply)"
@@ -387,7 +388,7 @@ local TGW_QUERY, TGW_REPLY = "(timer query)", "(timer reply)"
387local configure_timers = function() 388local configure_timers = function()
388 -- On first 'require "lanes"', a timer lane is spawned that will maintain 389 -- On first 'require "lanes"', a timer lane is spawned that will maintain
389 -- timer tables and sleep in between the timer events. All interaction with 390 -- timer tables and sleep in between the timer events. All interaction with
390 -- the timer lane happens via a 'timer_gateway' Linda, which is common to 391 -- the timer lane happens via a 'timerLinda' Linda, which is common to
391 -- all that 'require "lanes"'. 392 -- all that 'require "lanes"'.
392 -- 393 --
393 -- Linda protocol to timer lane: 394 -- Linda protocol to timer lane:
@@ -399,8 +400,8 @@ local configure_timers = function()
399 400
400 -- Timer lane; initialize only on the first 'require "lanes"' instance (which naturally has 'table' always declared) 401 -- Timer lane; initialize only on the first 'require "lanes"' instance (which naturally has 'table' always declared)
401 local first_time_key = "first time" 402 local first_time_key = "first time"
402 local first_time = timer_gateway:get(first_time_key) == nil 403 local first_time = timerLinda:get(first_time_key) == nil
403 timer_gateway:set(first_time_key, true) 404 timerLinda:set(first_time_key, true)
404 if first_time then 405 if first_time then
405 406
406 assert(type(now_secs) == "function") 407 assert(type(now_secs) == "function")
@@ -552,7 +553,7 @@ local configure_timers = function()
552 return next_wakeup -- may be 'nil' 553 return next_wakeup -- may be 'nil'
553 end -- check_timers() 554 end -- check_timers()
554 555
555 local timer_gateway_batched = timer_gateway.batched 556 local timer_gateway_batched = timerLinda.batched
556 set_finalizer(function(err, stk) 557 set_finalizer(function(err, stk)
557 if err and type(err) ~= "userdata" then 558 if err and type(err) ~= "userdata" then
558 error("LanesTimer error: "..tostring(err)) 559 error("LanesTimer error: "..tostring(err))
@@ -572,18 +573,18 @@ local configure_timers = function()
572 secs = next_wakeup - now_secs() 573 secs = next_wakeup - now_secs()
573 if secs < 0 then secs = 0 end 574 if secs < 0 then secs = 0 end
574 end 575 end
575 local key, what = timer_gateway:receive(secs, TGW_KEY, TGW_QUERY) 576 local key, what = timerLinda:receive(secs, TGW_KEY, TGW_QUERY)
576 577
577 if key == TGW_KEY then 578 if key == TGW_KEY then
578 assert(getmetatable(what) == "Linda") -- 'what' should be a linda on which the client sets a timer 579 assert(getmetatable(what) == "Linda") -- 'what' should be a linda on which the client sets a timer
579 local _, key, wakeup_at, period = timer_gateway:receive(0, timer_gateway_batched, TGW_KEY, 3) 580 local _, key, wakeup_at, period = timerLinda:receive(0, timer_gateway_batched, TGW_KEY, 3)
580 assert(key) 581 assert(key)
581 set_timer(what, key, wakeup_at, period and period > 0 and period or nil) 582 set_timer(what, key, wakeup_at, period and period > 0 and period or nil)
582 elseif key == TGW_QUERY then 583 elseif key == TGW_QUERY then
583 if what == "get_timers" then 584 if what == "get_timers" then
584 timer_gateway:send(TGW_REPLY, get_timers()) 585 timerLinda:send(TGW_REPLY, get_timers())
585 else 586 else
586 timer_gateway:send(TGW_REPLY, "unknown query " .. what) 587 timerLinda:send(TGW_REPLY, "unknown query " .. what)
587 end 588 end
588 --elseif secs == nil then -- got no value while block-waiting? 589 --elseif secs == nil then -- got no value while block-waiting?
589 -- WR("timer lane: no linda, aborted?") 590 -- WR("timer lane: no linda, aborted?")
@@ -610,7 +611,7 @@ local configure_timers = function()
610 linda_:set(key_, now_secs()) 611 linda_:set(key_, now_secs())
611 612
612 if not period_ or period_ == 0.0 then 613 if not period_ or period_ == 0.0 then
613 timer_gateway:send(TGW_KEY, linda_, key_, nil, nil ) -- clear the timer 614 timerLinda:send(TGW_KEY, linda_, key_, nil, nil ) -- clear the timer
614 return -- nothing more to do 615 return -- nothing more to do
615 end 616 end
616 when_ = period_ 617 when_ = period_
@@ -620,7 +621,7 @@ local configure_timers = function()
620 or (when_ and now_secs()+when_ or nil) 621 or (when_ and now_secs()+when_ or nil)
621 -- queue to timer 622 -- queue to timer
622 -- 623 --
623 timer_gateway:send(TGW_KEY, linda_, key_, wakeup_at, period_) 624 timerLinda:send(TGW_KEY, linda_, key_, wakeup_at, period_)
624 end -- timer() 625 end -- timer()
625 626
626 ----- 627 -----
@@ -628,11 +629,11 @@ local configure_timers = function()
628 -- 629 --
629 -- PUBLIC LANES API 630 -- PUBLIC LANES API
630 timers = function() 631 timers = function()
631 timer_gateway:send(TGW_QUERY, "get_timers") 632 timerLinda:send(TGW_QUERY, "get_timers")
632 local _, r = timer_gateway:receive(TGW_REPLY) 633 local _, r = timerLinda:receive(TGW_REPLY)
633 return r 634 return r
634 end -- timers() 635 end -- timers()
635end 636end -- configure_timers()
636 637
637-- ################################################################################################# 638-- #################################################################################################
638-- ###################################### lanes.sleep() ############################################ 639-- ###################################### lanes.sleep() ############################################
@@ -651,8 +652,8 @@ local sleep = function(seconds_)
651 error("invalid duration " .. string_format("%q", tostring(seconds_))) 652 error("invalid duration " .. string_format("%q", tostring(seconds_)))
652 end 653 end
653 -- receive data on a channel no-one ever sends anything, thus blocking for the specified duration 654 -- receive data on a channel no-one ever sends anything, thus blocking for the specified duration
654 return timer_gateway:receive(seconds_, "ac100de1-a696-4619-b2f0-a26de9d58ab8") 655 return timerLinda:receive(seconds_, "ac100de1-a696-4619-b2f0-a26de9d58ab8")
655end -- sleep 656end -- sleep()
656 657
657-- ################################################################################################# 658-- #################################################################################################
658-- ##################################### lanes.genlock() ########################################### 659-- ##################################### lanes.genlock() ###########################################
@@ -782,7 +783,7 @@ local configure = function(settings_)
782 -- avoid pulling the whole core module as upvalue when cancel_error is enough 783 -- avoid pulling the whole core module as upvalue when cancel_error is enough
783 -- these are locals declared above, that we need to set prior to calling configure_timers() 784 -- these are locals declared above, that we need to set prior to calling configure_timers()
784 cancel_error = assert(core.cancel_error) 785 cancel_error = assert(core.cancel_error)
785 timer_gateway = assert(core.timer_gateway) 786 timerLinda = assert(core.timerLinda)
786 787
787 if settings.with_timers then 788 if settings.with_timers then
788 configure_timers(settings) 789 configure_timers(settings)
diff --git a/tests/keeper.lua b/tests/keeper.lua
index d08dd98..2d432f4 100644
--- a/tests/keeper.lua
+++ b/tests/keeper.lua
@@ -4,99 +4,170 @@
4-- Test program for Lua Lanes 4-- Test program for Lua Lanes
5-- 5--
6-- TODO: there is a random crash when nb_user_keepers > 1. Will have to investigate if it rears its ugly head again 6-- TODO: there is a random crash when nb_user_keepers > 1. Will have to investigate if it rears its ugly head again
7local lanes = require "lanes".configure{ nb_user_keepers = 3, keepers_gc_threshold = 500 } 7-- 3 keepers in addition to the one reserved for the timer linda
8local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{nb_user_keepers = 3, keepers_gc_threshold = 500}
9print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
10local lanes = require_lanes_result_1
8 11
9do 12local require_assert_result_1, require_assert_result_2 = require "assert" -- assert.fails()
10 print "Linda names test:" 13print("require_assert_result:", require_assert_result_1, require_assert_result_2)
11 local unnamedLinda = lanes.linda(1) 14
15-- #################################################################################################
16
17local print_id = 0
18local P = function(whence_, ...)
19 print_id = print_id + 1
20 print(whence_, print_id .. ".", ...)
21end
22
23local PRINT = function(...) P("main", ...) end
24
25local DONE = function()
26 PRINT "collecting garbage"
27 collectgarbage()
28 PRINT "SUCCESS\n"
29end
30
31-- #################################################################################################
32-- #################################################################################################
33
34if false then
35 PRINT "========================================================================================="
36 PRINT "Linda groups test:"
37
38 local createLinda = function(...)
39 return lanes.linda(...)
40 end
41
42 -- should succeed
43 assert.failsnot(function() createLinda("zero", 0) end)
44 assert.failsnot(function() createLinda("one", 1) end)
45 assert.failsnot(function() createLinda("two", 2) end)
46 assert.failsnot(function() createLinda("three", 3) end)
47 -- should fail (and not create the lindas)
48 assert.fails(function() createLinda("minus 1", -1) end)
49 assert.fails(function() createLinda("none") end)
50 assert.fails(function() createLinda("four", 4) end)
51
52end
53-- should only collect the 4 successfully created lindas
54DONE()
55
56-- #################################################################################################
57
58if false then
59 PRINT "========================================================================================="
60 PRINT "Linda names test:"
61 local unnamedLinda1 = lanes.linda(1)
12 local unnamedLinda2 = lanes.linda("", 2) 62 local unnamedLinda2 = lanes.linda("", 2)
13 local veeeerrrryyyylooongNamedLinda= lanes.linda( "veeeerrrryyyylooongNamedLinda", 3) 63 local veeeerrrryyyylooongNamedLinda3 = lanes.linda( "veeeerrrryyyylooongNamedLinda", 3)
14 print(unnamedLinda, unnamedLinda2, veeeerrrryyyylooongNamedLinda) 64 assert(tostring(veeeerrrryyyylooongNamedLinda3) == "Linda: veeeerrrryyyylooongNamedLinda")
15 print "GC deadlock test start" 65 local shortNamedLinda0 = lanes.linda( "short", 0)
16 -- store a linda in another linda (-> in a keeper) 66 assert(tostring(shortNamedLinda0) == "Linda: short")
17 unnamedLinda:set("here", lanes.linda("temporary linda", 0)) 67 PRINT(shortNamedLinda0, unnamedLinda1, unnamedLinda2, veeeerrrryyyylooongNamedLinda3)
18 -- repeatedly add and remove stuff in the linda so that a GC happens during the keeper operation 68end
69-- collect the 4 lindas we created
70DONE()
71
72-- #################################################################################################
73
74if true then
75 PRINT "========================================================================================="
76 PRINT "Linda GC test:"
77 local a = lanes.linda("A", 1)
78 local b = lanes.linda("B", 2)
79 local c = lanes.linda("C", 3)
80
81 -- store lindas in each other and in themselves
82 a:set("here", lanes.linda("temporary linda", 0))
83 b:set("here", a, b, c)
84 c:set("here", a, b, c)
85
86 -- repeatedly add and remove stuff in the linda 'a' so that a GC happens during the keeper operation
19 for i = 1, 100 do 87 for i = 1, 100 do
88 io.stdout:write "."
20 for j = 1, 1000 do -- send 1000 tables 89 for j = 1, 1000 do -- send 1000 tables
21 -- print("send #" .. j) 90 -- PRINT("send #" .. j)
22 unnamedLinda:send("here", {"a", "table", "with", "some", "stuff"}) 91 a:send("here", {"a", "table", "with", "some", "stuff"}, j)
23 end 92 end
24 -- print(clearing) 93 -- PRINT(clearing)
25 unnamedLinda:set("here") -- clear everything 94 a:set("here") -- clear everything, including the temporary linda for which we have no reference
26 end 95 end
96 io.stdout:write "\n"
97 b:set("here")
98 c:set("here")
27end 99end
28print "collecting garbage" 100-- should successfully collect a, b, and c and destroy the underlying Deep objects
29collectgarbage() 101DONE()
30print "GC deadlock test done"
31 102
32local print_id = 0 103-- #################################################################################################
33local PRINT = function(...)
34 print_id = print_id + 1
35 print("main", print_id .. ".", ...)
36end
37 104
38local function keeper(linda) 105if false then
39 local mt= { 106 PRINT "========================================================================================="
40 __index= function( _, key ) 107 PRINT "General test:"
41 return linda:get( key )
42 end,
43 __newindex= function( _, key, val )
44 linda:set( key, val )
45 end
46 }
47 return setmetatable( {}, mt )
48end
49 108
50-- 109 local function keeper(linda)
51local lindaA= lanes.linda( "A", 1) 110 local mt= {
52local A= keeper( lindaA ) 111 __index= function( _, key )
53 112 return linda:get( key )
54local lindaB= lanes.linda( "B", 2) 113 end,
55local B= keeper( lindaB ) 114 __newindex= function( _, key, val )
56 115 linda:set( key, val )
57local lindaC= lanes.linda( "C", 3) 116 end
58local C= keeper( lindaC ) 117 }
59print("Created", lindaA, lindaB, lindaC) 118 return setmetatable( {}, mt )
60
61A.some= 1
62PRINT("A.some == " .. A.some )
63assert( A.some==1 )
64
65B.some= "hoo"
66PRINT("B.some == " .. B.some )
67assert( B.some=="hoo" )
68assert( A.some==1 )
69assert( C.some==nil )
70
71function lane()
72 local print_id = 0
73 local PRINT = function(...)
74 print_id = print_id + 1
75 print("lane", print_id .. ".", ...)
76 end 119 end
120
121 --
122 local lindaA= lanes.linda( "A", 1)
123 local A= keeper( lindaA )
124
125 local lindaB= lanes.linda( "B", 2)
126 local B= keeper( lindaB )
127
128 local lindaC= lanes.linda( "C", 3)
129 local C= keeper( lindaC )
130 PRINT("Created", lindaA, lindaB, lindaC)
131
132 A.some= 1
133 PRINT("A.some == " .. A.some )
134 assert( A.some==1 )
135
136 B.some= "hoo"
137 PRINT("B.some == " .. B.some )
138 assert( B.some=="hoo" )
139 assert( A.some==1 )
140 assert( C.some==nil )
141
142 function lane()
143 local PRINT = function(...) P("lane", ...) end
77 144
78 local a= keeper(lindaA) 145 local a= keeper(lindaA)
79 PRINT("a.some == " .. a.some ) 146 PRINT("a.some == " .. a.some )
80 assert( a.some==1 ) 147 assert( a.some==1 )
81 a.some= 2 148 a.some= 2
82 assert( a.some==2 ) 149 assert( a.some==2 )
83 PRINT("a.some == " .. a.some ) 150 PRINT("a.some == " .. a.some )
84
85 local c = keeper(lindaC)
86 assert( c.some==nil )
87 PRINT("c.some == " .. tostring(c.some))
88 c.some= 3
89 assert( c.some==3 )
90 PRINT("c.some == " .. c.some)
91end
92 151
93PRINT("lane started") 152 local c = keeper(lindaC)
94local h= lanes.gen( "io", lane )() 153 assert( c.some==nil )
95PRINT("lane joined:", h:join()) 154 PRINT("c.some == " .. tostring(c.some))
155 c.some= 3
156 assert( c.some==3 )
157 PRINT("c.some == " .. c.some)
158 end
159
160 PRINT("lane started")
161 local h= lanes.gen( "io", lane )()
162 PRINT("lane joined:", h:join())
163
164 PRINT("A.some = " .. A.some )
165 assert( A.some==2 )
166 PRINT("C.some = " .. C.some )
167 assert( C.some==3 )
168 lindaC:set("some")
169 assert( C.some==nil )
170end
171DONE()
96 172
97PRINT("A.some = " .. A.some ) 173print "\nTEST OK" \ No newline at end of file
98assert( A.some==2 )
99PRINT("C.some = " .. C.some )
100assert( C.some==3 )
101lindaC:set("some")
102assert( C.some==nil )
diff --git a/tests/nb_keepers.lua b/tests/nb_keepers.lua
deleted file mode 100644
index 575138c..0000000
--- a/tests/nb_keepers.lua
+++ /dev/null
@@ -1,20 +0,0 @@
1-- 2 keepers in addition to the one reserved for the timer linda
2local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{nb_user_keepers = 2}
3print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
4local lanes = require_lanes_result_1
5
6local require_assert_result_1, require_assert_result_2 = require "assert" -- assert.fails()
7print("require_assert_result:", require_assert_result_1, require_assert_result_2)
8
9local createLinda = function(...)
10 return lanes.linda(...)
11end
12
13-- should succeed
14assert.failsnot(function() createLinda("one", 1) end)
15assert.failsnot(function() createLinda("two", 2) end)
16-- should fail
17assert.fails(function() createLinda("none") end)
18assert.fails(function() createLinda("zero", 0) end)
19assert.fails(function() createLinda("three", 3) end)
20print "TEST OK"