aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-04-05 08:49:48 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2024-04-05 08:51:09 +0200
commitc64f9dcd61c1ad7bef3dbf5b7647a2a2da23ac0f (patch)
tree349aa02b2367d11d252468af983d719f20a00a42
parent2cb54d2ac028d648deab6d49a051f53a0bb67fb2 (diff)
downloadlanes-c64f9dcd61c1ad7bef3dbf5b7647a2a2da23ac0f.tar.gz
lanes-c64f9dcd61c1ad7bef3dbf5b7647a2a2da23ac0f.tar.bz2
lanes-c64f9dcd61c1ad7bef3dbf5b7647a2a2da23ac0f.zip
Enable manual control of GC inside keeper states
Diffstat (limited to '')
-rw-r--r--Makefile22
-rw-r--r--docs/index.html14
-rw-r--r--src/keeper.cpp54
-rw-r--r--src/keeper.h1
-rw-r--r--src/lanes.lua5
-rw-r--r--src/linda.cpp17
-rw-r--r--tests/keeper.lua15
-rw-r--r--tests/linda_perf.lua64
8 files changed, 119 insertions, 73 deletions
diff --git a/Makefile b/Makefile
index 868c481..0750401 100644
--- a/Makefile
+++ b/Makefile
@@ -72,25 +72,25 @@ rock:
72#--- Testing --- 72#--- Testing ---
73# 73#
74test: 74test:
75 $(MAKE) errhangtest 75 $(MAKE) atexit
76 $(MAKE) irayo_recursive 76 $(MAKE) atomic
77 $(MAKE) irayo_closure
78 $(MAKE) basic 77 $(MAKE) basic
79 $(MAKE) cancel 78 $(MAKE) cancel
80 $(MAKE) fifo
81 $(MAKE) keeper
82 $(MAKE) timer
83 $(MAKE) atomic
84 $(MAKE) cyclic 79 $(MAKE) cyclic
85 $(MAKE) objects 80 $(MAKE) errhangtest
86 $(MAKE) fibonacci 81 $(MAKE) fibonacci
87 $(MAKE) recursive 82 $(MAKE) fifo
88 $(MAKE) func_is_string 83 $(MAKE) func_is_string
89 $(MAKE) atexit 84 $(MAKE) irayo_recursive
85 $(MAKE) irayo_closure
86 $(MAKE) keeper
90 $(MAKE) linda_perf 87 $(MAKE) linda_perf
91 $(MAKE) rupval 88 $(MAKE) objects
92 $(MAKE) package 89 $(MAKE) package
93 $(MAKE) pingpong 90 $(MAKE) pingpong
91 $(MAKE) recursive
92 $(MAKE) rupval
93 $(MAKE) timer
94 94
95basic: tests/basic.lua $(_TARGET_SO) 95basic: tests/basic.lua $(_TARGET_SO)
96 $(_PREFIX) $(LUA) $< 96 $(_PREFIX) $(LUA) $<
diff --git a/docs/index.html b/docs/index.html
index 24fa4ef..ee5acfa 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -291,6 +291,18 @@
291 </tr> 291 </tr>
292 292
293 <tr valign=top> 293 <tr valign=top>
294 <td id="keepers_gc_threshold">
295 <code>.keepers_gc_threshold</code>
296 </td>
297 <td>integer</td>
298 <td>
299 If &lt;0, GC runs automatically. This is the default.<br/>
300 If 0, GC runs after *every* keeper operation.<br/>
301 If &gt;0, Keepers run GC manually with <tt>lua_gc(LUA_GCCOLLECT)</tt> whenever memory usage reported by <tt>lua_gc(LUA_GCCOUNT)</tt> reaches this threshold. Check is made after every keeper operation (see <a href="#lindas">below</a>). If memory usage remains above threshold after the GC cycle, an error is raised.
302 </td>
303 </tr>
304
305 <tr valign=top>
294 <td id="with_timers"> 306 <td id="with_timers">
295 <code>.with_timers</code> 307 <code>.with_timers</code>
296 </td> 308 </td>
@@ -1784,4 +1796,4 @@ int luaD_new_clonable( lua_State* L)
1784</p> 1796</p>
1785 1797
1786</body> 1798</body>
1787</html></pre> \ No newline at end of file 1799</html>
diff --git a/src/keeper.cpp b/src/keeper.cpp
index 244cb6a..937d190 100644
--- a/src/keeper.cpp
+++ b/src/keeper.cpp
@@ -652,12 +652,18 @@ void init_keepers(Universe* U, lua_State* L)
652{ 652{
653 STACK_CHECK_START_REL(L, 0); // L K 653 STACK_CHECK_START_REL(L, 0); // L K
654 lua_getfield(L, 1, "nb_keepers"); // nb_keepers 654 lua_getfield(L, 1, "nb_keepers"); // nb_keepers
655 int nb_keepers{ static_cast<int>(lua_tointeger(L, -1)) }; 655 int const nb_keepers{ static_cast<int>(lua_tointeger(L, -1)) };
656 lua_pop(L, 1); // 656 lua_pop(L, 1); //
657 if (nb_keepers < 1) 657 if (nb_keepers < 1)
658 { 658 {
659 std::ignore = luaL_error(L, "Bad number of keepers (%d)", nb_keepers); 659 std::ignore = luaL_error(L, "Bad number of keepers (%d)", nb_keepers);
660 } 660 }
661 STACK_CHECK(L, 0);
662
663 lua_getfield(L, 1, "keepers_gc_threshold"); // keepers_gc_threshold
664 int const keepers_gc_threshold{ static_cast<int>(lua_tointeger(L, -1)) };
665 lua_pop(L, 1); //
666 STACK_CHECK(L, 0);
661 667
662 // Keepers contains an array of 1 Keeper, adjust for the actual number of keeper states 668 // Keepers contains an array of 1 Keeper, adjust for the actual number of keeper states
663 { 669 {
@@ -668,6 +674,7 @@ void init_keepers(Universe* U, lua_State* L)
668 std::ignore = luaL_error(L, "init_keepers() failed while creating keeper array; out of memory"); 674 std::ignore = luaL_error(L, "init_keepers() failed while creating keeper array; out of memory");
669 } 675 }
670 memset(U->keepers, 0, bytes); 676 memset(U->keepers, 0, bytes);
677 U->keepers->gc_threshold = keepers_gc_threshold;
671 U->keepers->nb_keepers = nb_keepers; 678 U->keepers->nb_keepers = nb_keepers;
672 } 679 }
673 for (int i = 0; i < nb_keepers; ++i) // keepersUD 680 for (int i = 0; i < nb_keepers; ++i) // keepersUD
@@ -685,6 +692,11 @@ void init_keepers(Universe* U, lua_State* L)
685 // therefore, we need a recursive mutex. 692 // therefore, we need a recursive mutex.
686 MUTEX_RECURSIVE_INIT(&U->keepers->keeper_array[i].keeper_cs); 693 MUTEX_RECURSIVE_INIT(&U->keepers->keeper_array[i].keeper_cs);
687 694
695 if (U->keepers->gc_threshold >= 0)
696 {
697 lua_gc(K, LUA_GCSTOP, 0);
698 }
699
688 STACK_CHECK_START_ABS(K, 0); 700 STACK_CHECK_START_ABS(K, 0);
689 701
690 // copy the universe pointer in the keeper itself 702 // copy the universe pointer in the keeper itself
@@ -735,8 +747,12 @@ void init_keepers(Universe* U, lua_State* L)
735Keeper* which_keeper(Keepers* keepers_, uintptr_t magic_) 747Keeper* which_keeper(Keepers* keepers_, uintptr_t magic_)
736{ 748{
737 int const nbKeepers{ keepers_->nb_keepers }; 749 int const nbKeepers{ keepers_->nb_keepers };
738 unsigned int i = (unsigned int)((magic_ >> KEEPER_MAGIC_SHIFT) % nbKeepers); 750 if (nbKeepers)
739 return &keepers_->keeper_array[i]; 751 {
752 unsigned int i = (unsigned int) ((magic_ >> KEEPER_MAGIC_SHIFT) % nbKeepers);
753 return &keepers_->keeper_array[i];
754 }
755 return nullptr;
740} 756}
741 757
742// ################################################################################################## 758// ##################################################################################################
@@ -745,11 +761,7 @@ Keeper* keeper_acquire(Keepers* keepers_, uintptr_t magic_)
745{ 761{
746 int const nbKeepers{ keepers_->nb_keepers }; 762 int const nbKeepers{ keepers_->nb_keepers };
747 // can be 0 if this happens during main state shutdown (lanes is being GC'ed -> no keepers) 763 // can be 0 if this happens during main state shutdown (lanes is being GC'ed -> no keepers)
748 if( nbKeepers == 0) 764 if (nbKeepers)
749 {
750 return nullptr;
751 }
752 else
753 { 765 {
754 /* 766 /*
755 * Any hashing will do that maps pointers to 0..GNbKeepers-1 767 * Any hashing will do that maps pointers to 0..GNbKeepers-1
@@ -765,6 +777,7 @@ Keeper* keeper_acquire(Keepers* keepers_, uintptr_t magic_)
765 //++ K->count; 777 //++ K->count;
766 return K; 778 return K;
767 } 779 }
780 return nullptr;
768} 781}
769 782
770// ################################################################################################## 783// ##################################################################################################
@@ -843,5 +856,30 @@ int keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, voi
843 } 856 }
844 // whatever happens, restore the stack to where it was at the origin 857 // whatever happens, restore the stack to where it was at the origin
845 lua_settop(K, Ktos); 858 lua_settop(K, Ktos);
859
860 // don't do this for this particular function, as it is only called during Linda destruction, and we don't want to raise an error, ever
861 if (func_ != KEEPER_API(clear)) [[unlikely]]
862 {
863 // since keeper state GC is stopped, let's run a step once in a while if required
864 int const gc_threshold{ U->keepers->gc_threshold };
865 if (gc_threshold == 0) [[unlikely]]
866 {
867 lua_gc(K, LUA_GCSTEP, 0);
868 }
869 else if (gc_threshold > 0) [[likely]]
870 {
871 int const gc_usage{ lua_gc(K, LUA_GCCOUNT, 0) };
872 if (gc_usage >= gc_threshold)
873 {
874 lua_gc(K, LUA_GCCOLLECT, 0);
875 int const gc_usage_after{ lua_gc(K, LUA_GCCOUNT, 0) };
876 if (gc_usage_after > gc_threshold) [[unlikely]]
877 {
878 luaL_error(L, "Keeper GC threshold is too low, need at least %d", gc_usage_after);
879 }
880 }
881 }
882 }
883
846 return retvals; 884 return retvals;
847} 885}
diff --git a/src/keeper.h b/src/keeper.h
index e081bea..f7e3951 100644
--- a/src/keeper.h
+++ b/src/keeper.h
@@ -24,6 +24,7 @@ struct Keeper
24 24
25struct Keepers 25struct Keepers
26{ 26{
27 int gc_threshold{ 0 };
27 int nb_keepers; 28 int nb_keepers;
28 Keeper keeper_array[1]; 29 Keeper keeper_array[1];
29}; 30};
diff --git a/src/lanes.lua b/src/lanes.lua
index b4c0070..6af286a 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -70,6 +70,7 @@ lanes.configure = function( settings_)
70 local default_params = 70 local default_params =
71 { 71 {
72 nb_keepers = 1, 72 nb_keepers = 1,
73 keepers_gc_threshold = -1,
73 on_state_create = nil, 74 on_state_create = nil,
74 shutdown_timeout = 0.25, 75 shutdown_timeout = 0.25,
75 with_timers = true, 76 with_timers = true,
@@ -91,6 +92,10 @@ lanes.configure = function( settings_)
91 -- nb_keepers should be a number > 0 92 -- nb_keepers should be a number > 0
92 return type( val_) == "number" and val_ > 0 93 return type( val_) == "number" and val_ > 0
93 end, 94 end,
95 keepers_gc_threshold = function( val_)
96 -- keepers_gc_threshold should be a number
97 return type( val_) == "number"
98 end,
94 with_timers = boolean_param_checker, 99 with_timers = boolean_param_checker,
95 allocator = function( val_) 100 allocator = function( val_)
96 -- can be nil, "protected", or a function 101 -- can be nil, "protected", or a function
diff --git a/src/linda.cpp b/src/linda.cpp
index 37a74b0..5ee4768 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -885,15 +885,22 @@ static void* linda_id( lua_State* L, DeepOp op_)
885 { 885 {
886 Linda* const linda{ lua_tolightuserdata<Linda>(L, 1) }; 886 Linda* const linda{ lua_tolightuserdata<Linda>(L, 1) };
887 ASSERT_L(linda); 887 ASSERT_L(linda);
888 888 Keeper* const myK{ which_keeper(linda->U->keepers, linda->hashSeed()) };
889 // Clean associated structures in the keeper state. 889 // if collected after the universe, keepers are already destroyed, and there is nothing to clear
890 Keeper* const K{ keeper_acquire(linda->U->keepers, linda->hashSeed()) }; 890 if (myK)
891 if (K && K->L) // can be nullptr if this happens during main state shutdown (lanes is GC'ed -> no keepers -> no need to cleanup)
892 { 891 {
892 // if collected from my own keeper, we can't acquire/release it
893 // because we are already inside a protected area, and trying to do so would deadlock!
894 bool const need_acquire_release{ myK->L != L };
895 // Clean associated structures in the keeper state.
896 Keeper* const K{ need_acquire_release ? keeper_acquire(linda->U->keepers, linda->hashSeed()) : myK };
893 // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex... 897 // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex...
894 keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0); 898 keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0);
899 if (need_acquire_release)
900 {
901 keeper_release(K);
902 }
895 } 903 }
896 keeper_release(K);
897 904
898 delete linda; // operator delete overload ensures things go as expected 905 delete linda; // operator delete overload ensures things go as expected
899 return nullptr; 906 return nullptr;
diff --git a/tests/keeper.lua b/tests/keeper.lua
index 9b38f02..6dbbd15 100644
--- a/tests/keeper.lua
+++ b/tests/keeper.lua
@@ -4,7 +4,7 @@
4-- Test program for Lua Lanes 4-- Test program for Lua Lanes
5-- 5--
6 6
7local lanes = require "lanes".configure{ with_timers = false, nb_keepers = 200} 7local lanes = require "lanes".configure{ with_timers = false, nb_keepers = 1, keepers_gc_threshold = 500}
8 8
9do 9do
10 print "Linda names test:" 10 print "Linda names test:"
@@ -12,7 +12,20 @@ do
12 local unnamedLinda2 = lanes.linda("") 12 local unnamedLinda2 = lanes.linda("")
13 local veeeerrrryyyylooongNamedLinda= lanes.linda( "veeeerrrryyyylooongNamedLinda", 1) 13 local veeeerrrryyyylooongNamedLinda= lanes.linda( "veeeerrrryyyylooongNamedLinda", 1)
14 print(unnamedLinda, unnamedLinda2, veeeerrrryyyylooongNamedLinda) 14 print(unnamedLinda, unnamedLinda2, veeeerrrryyyylooongNamedLinda)
15 print "GC deadlock test start"
16 -- store a linda in another linda (-> in a keeper)
17 unnamedLinda:set("here", lanes.linda("temporary linda"))
18 -- repeatedly add and remove stuff in the linda so that a GC happens during the keeper operation
19 for i = 1, 1000 do
20 for j = 1, 1000 do -- send 1000 tables
21 unnamedLinda:send("here", {"a", "table", "with", "some", "stuff"})
22 end
23 unnamedLinda:set("here") -- clear everything
24 end
15end 25end
26print "collecting garbage"
27collectgarbage()
28print "GC deadlock test done"
16 29
17local print_id = 0 30local print_id = 0
18local PRINT = function(...) 31local PRINT = function(...)
diff --git a/tests/linda_perf.lua b/tests/linda_perf.lua
index 9177852..c736428 100644
--- a/tests/linda_perf.lua
+++ b/tests/linda_perf.lua
@@ -1,5 +1,5 @@
1local lanes = require "lanes" 1local lanes = require "lanes"
2lanes.configure{ with_timers = false } 2lanes.configure{ with_timers = false, keepers_gc_threshold=20000 }
3 3
4-- set TEST1, PREFILL1, FILL1, TEST2, PREFILL2, FILL2 from the command line 4-- set TEST1, PREFILL1, FILL1, TEST2, PREFILL2, FILL2 from the command line
5 5
@@ -17,6 +17,8 @@ local finalizer = function(err, stk)
17 end 17 end
18end 18end
19 19
20--##################################################################################################
21
20-- this lane eats items in the linda one by one 22-- this lane eats items in the linda one by one
21local eater = function( l, loop) 23local eater = function( l, loop)
22 set_finalizer(finalizer) 24 set_finalizer(finalizer)
@@ -32,6 +34,8 @@ local eater = function( l, loop)
32 print("eater: done ("..val..")") 34 print("eater: done ("..val..")")
33end 35end
34 36
37--##################################################################################################
38
35-- this lane eats items in the linda in batches 39-- this lane eats items in the linda in batches
36local gobbler = function( l, loop, batch) 40local gobbler = function( l, loop, batch)
37 set_finalizer(finalizer) 41 set_finalizer(finalizer)
@@ -47,9 +51,13 @@ local gobbler = function( l, loop, batch)
47 print("gobbler: done ("..val..")") 51 print("gobbler: done ("..val..")")
48end 52end
49 53
54--##################################################################################################
55
50local lane_eater_gen = lanes.gen( "*", {priority = 3}, eater) 56local lane_eater_gen = lanes.gen( "*", {priority = 3}, eater)
51local lane_gobbler_gen = lanes.gen( "*", {priority = 3}, gobbler) 57local lane_gobbler_gen = lanes.gen( "*", {priority = 3}, gobbler)
52 58
59--##################################################################################################
60
53-- main thread writes data while a lane reads it 61-- main thread writes data while a lane reads it
54local function ziva( preloop, loop, batch) 62local function ziva( preloop, loop, batch)
55 -- prefill the linda a bit to increase fifo stress 63 -- prefill the linda a bit to increase fifo stress
@@ -94,6 +102,8 @@ local function ziva( preloop, loop, batch)
94 return lanes.now_secs() - t1 102 return lanes.now_secs() - t1
95end 103end
96 104
105--##################################################################################################
106
97TEST1 = TEST1 or 1000 107TEST1 = TEST1 or 1000
98PREFILL1 = PREFILL1 or 10000 108PREFILL1 = PREFILL1 or 10000
99FILL1 = FILL1 or 2000000 109FILL1 = FILL1 or 2000000
@@ -109,6 +119,7 @@ local tests1 =
109 { PREFILL1, FILL1, 13}, 119 { PREFILL1, FILL1, 13},
110 { PREFILL1, FILL1, 21}, 120 { PREFILL1, FILL1, 21},
111 { PREFILL1, FILL1, 44}, 121 { PREFILL1, FILL1, 44},
122 { PREFILL1, FILL1, 65},
112} 123}
113print "############################################ tests #1" 124print "############################################ tests #1"
114for i, v in ipairs( tests1) do 125for i, v in ipairs( tests1) do
@@ -119,38 +130,7 @@ for i, v in ipairs( tests1) do
119 print("DURATION = " .. ziva( pre, loop, batch) .. "\n") 130 print("DURATION = " .. ziva( pre, loop, batch) .. "\n")
120end 131end
121 132
122--[[ 133--##################################################################################################
123 V 2.1.0:
124 ziva( 20000, 0) -> 4s ziva( 10000, 20000) -> 3s
125 ziva( 30000, 0) -> 8s ziva( 20000, 30000) -> 7s
126 ziva( 40000, 0) -> 15s ziva( 30000, 40000) -> 15s
127 ziva( 50000, 0) -> 24s ziva( 40000, 50000) -> 23s
128 ziva( 60000, 0) -> 34s ziva( 50000, 60000) -> 33s
129
130 SIMPLIFIED:
131 ziva( 20000, 0) -> 4s ziva( 10000, 20000) -> 3s
132 ziva( 30000, 0) -> 9s ziva( 20000, 30000) -> 8s
133 ziva( 40000, 0) -> 15s ziva( 30000, 40000) -> 15s
134 ziva( 50000, 0) -> 25s ziva( 40000, 50000) -> 24s
135 ziva( 60000, 0) -> 35s ziva( 50000, 60000) -> 35s
136
137 FIFO:
138 ziva( 2000000, 0) -> 9s ziva( 1000000, 2000000) -> 33s
139 ziva( 3000000, 0) -> 14s ziva( 2000000, 3000000) -> 40s
140 ziva( 4000000, 0) -> 20s ziva( 3000000, 4000000) -> 27s
141 ziva( 5000000, 0) -> 24s ziva( 4000000, 5000000) -> 42s
142 ziva( 6000000, 0) -> 29s ziva( 5000000, 6000000) -> 55s
143
144 FIFO BATCHED:
145 ziva( 4000000, 0, 1) -> 20s
146 ziva( 4000000, 0, 2) -> 11s
147 ziva( 4000000, 0, 3) -> 7s
148 ziva( 4000000, 0, 5) -> 5s
149 ziva( 4000000, 0, 8) -> 3s
150 ziva( 4000000, 0, 13) -> 3s
151 ziva( 4000000, 0, 21) -> 3s
152 ziva( 4000000, 0, 44) -> 2s
153]]
154 134
155-- sequential write/read (no parallelization involved) 135-- sequential write/read (no parallelization involved)
156local function ziva2( preloop, loop, batch) 136local function ziva2( preloop, loop, batch)
@@ -183,7 +163,7 @@ local function ziva2( preloop, loop, batch)
183 for i = 1, preloop, step do 163 for i = 1, preloop, step do
184 batch_send() 164 batch_send()
185 end 165 end
186 print( "stored " .. (l:count( "key") or 0) .. " items in the linda before starting consumer lane") 166 print( "stored " .. (l:count( "key") or 0) .. " items in the linda before starting the alternating reads and writes")
187 -- loop that alternatively sends and reads data off the linda 167 -- loop that alternatively sends and reads data off the linda
188 if loop > preloop then 168 if loop > preloop then
189 for i = preloop + 1, loop, step do 169 for i = preloop + 1, loop, step do
@@ -198,25 +178,14 @@ local function ziva2( preloop, loop, batch)
198 return lanes.now_secs() - t1 178 return lanes.now_secs() - t1
199end 179end
200 180
181--##################################################################################################
182
201TEST2 = TEST2 or 1000 183TEST2 = TEST2 or 1000
202PREFILL2 = PREFILL2 or 0 184PREFILL2 = PREFILL2 or 0
203FILL2 = FILL2 or 4000000 185FILL2 = FILL2 or 4000000
204 186
205local tests2 = 187local tests2 =
206{ 188{
207 -- prefill, then consume everything
208 --[[
209 { 4000000, 0},
210 { 4000000, 0, 1},
211 { 4000000, 0, 2},
212 { 4000000, 0, 3},
213 { 4000000, 0, 5},
214 { 4000000, 0, 8},
215 { 4000000, 0, 13},
216 { 4000000, 0, 21},
217 { 4000000, 0, 44},
218 --]]
219 -- alternatively fill and consume
220 { PREFILL2, FILL2}, 189 { PREFILL2, FILL2},
221 { PREFILL2, FILL2, 1}, 190 { PREFILL2, FILL2, 1},
222 { PREFILL2, FILL2, 2}, 191 { PREFILL2, FILL2, 2},
@@ -226,6 +195,7 @@ local tests2 =
226 { PREFILL2, FILL2, 13}, 195 { PREFILL2, FILL2, 13},
227 { PREFILL2, FILL2, 21}, 196 { PREFILL2, FILL2, 21},
228 { PREFILL2, FILL2, 44}, 197 { PREFILL2, FILL2, 44},
198 { PREFILL2, FILL2, 65},
229} 199}
230 200
231print "############################################ tests #2" 201print "############################################ tests #2"