aboutsummaryrefslogtreecommitdiff
path: root/src/keeper.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/keeper.cpp884
1 files changed, 884 insertions, 0 deletions
diff --git a/src/keeper.cpp b/src/keeper.cpp
new file mode 100644
index 0000000..f56c50c
--- /dev/null
+++ b/src/keeper.cpp
@@ -0,0 +1,884 @@
1/*
2 --
3 -- KEEPER.CPP
4 --
5 -- Keeper state logic
6 --
7 -- This code is read in for each "keeper state", which are the hidden, inter-
8 -- mediate data stores used by Lanes inter-state communication objects.
9 --
10 -- Author: Benoit Germain <bnt.germain@gmail.com>
11 --
12 -- C implementation replacement of the original keeper.lua
13 --
14 --[[
15 ===============================================================================
16
17 Copyright (C) 2011-2024 Benoit Germain <bnt.germain@gmail.com>
18
19 Permission is hereby granted, free of charge, to any person obtaining a copy
20 of this software and associated documentation files (the "Software"), to deal
21 in the Software without restriction, including without limitation the rights
22 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23 copies of the Software, and to permit persons to whom the Software is
24 furnished to do so, subject to the following conditions:
25
26 The above copyright notice and this permission notice shall be included in
27 all copies or substantial portions of the Software.
28
29 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
35 THE SOFTWARE.
36
37 ===============================================================================
38 ]]--
39 */
40#include "keeper.h"
41
42#include "compat.h"
43#include "state.h"
44#include "tools.h"
45#include "uniquekey.h"
46#include "universe.h"
47
48#include <algorithm>
49#include <cassert>
50
51// ###################################################################################
52// Keeper implementation
53// ###################################################################################
54
55class keeper_fifo
56{
57 public:
58
59 int first{ 1 };
60 int count{ 0 };
61 int limit{ -1 };
62
63 // a fifo full userdata has one uservalue, the table that holds the actual fifo contents
64 [[nodiscard]] static void* operator new([[maybe_unused]] size_t size_, lua_State* L) noexcept { return lua_newuserdatauv<keeper_fifo>(L, 1); }
65 // always embedded somewhere else or "in-place constructed" as a full userdata
66 // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception
67 static void operator delete([[maybe_unused]] void* p_, lua_State* L) { ASSERT_L(!"should never be called") };
68
69 [[nodiscard]] static keeper_fifo* getPtr(lua_State* L, int idx_)
70 {
71 return lua_tofulluserdata<keeper_fifo>(L, idx_);
72 }
73};
74
75static constexpr int CONTENTS_TABLE{ 1 };
76
77// ##################################################################################################
78
79// replaces the fifo ud by its uservalue on the stack
80[[nodiscard]] static keeper_fifo* prepare_fifo_access(lua_State* L, int idx_)
81{
82 keeper_fifo* const fifo{ keeper_fifo::getPtr(L, idx_) };
83 if (fifo != nullptr)
84 {
85 idx_ = lua_absindex(L, idx_);
86 STACK_GROW(L, 1);
87 // we can replace the fifo userdata in the stack without fear of it being GCed, there are other references around
88 lua_getiuservalue(L, idx_, CONTENTS_TABLE);
89 lua_replace(L, idx_);
90 }
91 return fifo;
92}
93
94// ##################################################################################################
95
96// in: nothing
97// out: { first = 1, count = 0, limit = -1}
98[[nodiscard]] static keeper_fifo* fifo_new(lua_State* L)
99{
100 STACK_GROW(L, 2);
101 STACK_CHECK_START_REL(L, 0);
102 keeper_fifo* const fifo{ new (L) keeper_fifo{} };
103 STACK_CHECK(L, 1);
104 lua_newtable(L);
105 lua_setiuservalue(L, -2, CONTENTS_TABLE);
106 STACK_CHECK(L, 1);
107 return fifo;
108}
109
110// ##################################################################################################
111
112// in: expect fifo ... on top of the stack
113// out: nothing, removes all pushed values from the stack
114static void fifo_push(lua_State* L, keeper_fifo* fifo_, int count_)
115{
116 int const idx{ lua_gettop(L) - count_ };
117 int const start{ fifo_->first + fifo_->count - 1 };
118 // pop all additional arguments, storing them in the fifo
119 for (int i = count_; i >= 1; --i)
120 {
121 // store in the fifo the value at the top of the stack at the specified index, popping it from the stack
122 lua_rawseti(L, idx, start + i);
123 }
124 fifo_->count += count_;
125}
126
127// ##################################################################################################
128
129// in: fifo
130// out: ...|nothing
131// expects exactly 1 value on the stack!
132// currently only called with a count of 1, but this may change in the future
133// function assumes that there is enough data in the fifo to satisfy the request
134static void fifo_peek(lua_State* L, keeper_fifo* fifo_, int count_)
135{
136 STACK_GROW(L, count_);
137 for (int i = 0; i < count_; ++i)
138 {
139 lua_rawgeti(L, 1, (fifo_->first + i));
140 }
141}
142
143// ##################################################################################################
144
145// in: fifo
146// out: remove the fifo from the stack, push as many items as required on the stack (function assumes they exist in sufficient number)
147static void fifo_pop( lua_State* L, keeper_fifo* fifo_, int count_)
148{
149 ASSERT_L(lua_istable(L, -1));
150 int const fifo_idx{ lua_gettop(L) }; // ... fifotbl
151 // each iteration pushes a value on the stack!
152 STACK_GROW(L, count_ + 2);
153 // skip first item, we will push it last
154 for (int i = 1; i < count_; ++i)
155 {
156 int const at{ fifo_->first + i };
157 // push item on the stack
158 lua_rawgeti(L, fifo_idx, at); // ... fifotbl val
159 // remove item from the fifo
160 lua_pushnil(L); // ... fifotbl val nil
161 lua_rawseti(L, fifo_idx, at); // ... fifotbl val
162 }
163 // now process first item
164 {
165 int const at{ fifo_->first };
166 lua_rawgeti(L, fifo_idx, at); // ... fifotbl vals val
167 lua_pushnil(L); // ... fifotbl vals val nil
168 lua_rawseti(L, fifo_idx, at); // ... fifotbl vals val
169 lua_replace(L, fifo_idx); // ... vals
170 }
171
172 // avoid ever-growing indexes by resetting each time we detect the fifo is empty
173 {
174 int const new_count{ fifo_->count - count_ };
175 fifo_->first = (new_count == 0) ? 1 : (fifo_->first + count_);
176 fifo_->count = new_count;
177 }
178}
179
180// ##################################################################################################
181
182// in: linda_ud expected at stack slot idx
183// out: fifos[ud]
184// crc64/we of string "FIFOS_KEY" generated at http://www.nitrxgen.net/hashgen/
185static constexpr UniqueKey FIFOS_KEY{ 0xdce50bbc351cd465ull };
186static void push_table(lua_State* L, int idx_)
187{
188 STACK_GROW(L, 5);
189 STACK_CHECK_START_REL(L, 0);
190 idx_ = lua_absindex(L, idx_);
191 FIFOS_KEY.pushValue(L); // ud fifos
192 lua_pushvalue(L, idx_); // ud fifos ud
193 lua_rawget(L, -2); // ud fifos fifos[ud]
194 STACK_CHECK(L, 2);
195 if (lua_isnil(L, -1))
196 {
197 lua_pop(L, 1); // ud fifos
198 // add a new fifos table for this linda
199 lua_newtable(L); // ud fifos fifos[ud]
200 lua_pushvalue(L, idx_); // ud fifos fifos[ud] ud
201 lua_pushvalue(L, -2); // ud fifos fifos[ud] ud fifos[ud]
202 lua_rawset(L, -4); // ud fifos fifos[ud]
203 }
204 lua_remove(L, -2); // ud fifos[ud]
205 STACK_CHECK(L, 1);
206}
207
208// ##################################################################################################
209
210int keeper_push_linda_storage(Universe* U, Dest L, void* ptr_, uintptr_t magic_)
211{
212 Keeper* const K{ which_keeper(U->keepers, magic_) };
213 Source const KL{ K ? K->L : nullptr };
214 if (KL == nullptr)
215 return 0;
216 STACK_GROW(KL, 4); // KEEPER MAIN
217 STACK_CHECK_START_REL(KL, 0);
218 FIFOS_KEY.pushValue(KL); // fifos
219 lua_pushlightuserdata(KL, ptr_); // fifos ud
220 lua_rawget(KL, -2); // fifos storage
221 lua_remove(KL, -2); // storage
222 if (!lua_istable(KL, -1))
223 {
224 lua_pop(KL, 1); //
225 STACK_CHECK(KL, 0);
226 return 0;
227 }
228 // move data from keeper to destination state
229 lua_pushnil(KL); // storage nil
230 STACK_GROW(L, 5);
231 STACK_CHECK_START_REL(L, 0);
232 lua_newtable(L); // out
233 while (lua_next(KL, -2)) // storage key fifo
234 {
235 keeper_fifo* fifo = prepare_fifo_access(KL, -1); // storage key fifotbl
236 lua_pushvalue(KL, -2); // storage key fifotbl key
237 std::ignore = luaG_inter_move(U, KL, L, 1, LookupMode::FromKeeper); // storage key fifotbl // out key
238 STACK_CHECK(L, 2);
239 lua_newtable(L); // out key keyout
240 std::ignore = luaG_inter_move(U, KL, L, 1, LookupMode::FromKeeper); // storage key // out key keyout fifotbl
241 lua_pushinteger(L, fifo->first); // out key keyout fifotbl first
242 STACK_CHECK(L, 5);
243 lua_setfield(L, -3, "first"); // out key keyout fifotbl
244 lua_pushinteger(L, fifo->count); // out key keyout fifobtl count
245 STACK_CHECK(L, 5);
246 lua_setfield(L, -3, "count"); // out key keyout fifotbl
247 lua_pushinteger(L, fifo->limit); // out key keyout fifotbl limit
248 STACK_CHECK(L, 5);
249 lua_setfield(L, -3, "limit"); // out key keyout fifotbl
250 lua_setfield(L, -2, "fifo"); // out key keyout
251 lua_rawset(L, -3); // out
252 STACK_CHECK(L, 1);
253 }
254 STACK_CHECK(L, 1);
255 lua_pop(KL, 1); //
256 STACK_CHECK(KL, 0);
257 return 1;
258}
259
260// ##################################################################################################
261
262// in: linda_ud
263int keepercall_clear(lua_State* L)
264{
265 STACK_GROW(L, 3);
266 STACK_CHECK_START_REL(L, 0);
267 FIFOS_KEY.pushValue(L); // ud fifos
268 lua_pushvalue(L, 1); // ud fifos ud
269 lua_pushnil(L); // ud fifos ud nil
270 lua_rawset(L, -3); // ud fifos
271 lua_pop(L, 1); // ud
272 STACK_CHECK(L, 0);
273 return 0;
274}
275
276// ##################################################################################################
277
278// in: linda_ud, key, ...
279// out: true|false
280int keepercall_send(lua_State* L)
281{
282 int const n{ lua_gettop(L) - 2 };
283 push_table(L, 1); // ud key ... fifos
284 // get the fifo associated to this key in this linda, create it if it doesn't exist
285 lua_pushvalue(L, 2); // ud key ... fifos key
286 lua_rawget(L, -2); // ud key ... fifos fifo
287 if( lua_isnil(L, -1))
288 {
289 lua_pop(L, 1); // ud key ... fifos
290 std::ignore = fifo_new(L); // ud key ... fifos fifo
291 lua_pushvalue(L, 2); // ud key ... fifos fifo key
292 lua_pushvalue(L, -2); // ud key ... fifos fifo key fifo
293 lua_rawset(L, -4); // ud key ... fifos fifo
294 }
295 lua_remove(L, -2); // ud key ... fifo
296 keeper_fifo* fifo{ keeper_fifo::getPtr(L, -1) };
297 if (fifo->limit >= 0 && fifo->count + n > fifo->limit)
298 {
299 lua_settop(L, 0); //
300 lua_pushboolean(L, 0); // false
301 }
302 else
303 {
304 fifo = prepare_fifo_access(L, -1); // ud fifotbl
305 lua_replace(L, 2); // ud fifotbl ...
306 fifo_push(L, fifo, n); // ud fifotbl
307 lua_settop(L, 0); //
308 lua_pushboolean(L, 1); // true
309 }
310 return 1;
311}
312
313// ##################################################################################################
314
315// in: linda_ud, key [, key]?
316// out: (key, val) or nothing
317int keepercall_receive(lua_State* L)
318{
319 int const top{ lua_gettop(L) };
320 push_table(L, 1); // ud keys fifos
321 lua_replace(L, 1); // fifos keys
322 for (int i = 2; i <= top; ++i)
323 {
324 lua_pushvalue(L, i); // fifos keys key[i]
325 lua_rawget(L, 1); // fifos keys fifo
326 keeper_fifo* const fifo{ prepare_fifo_access(L, -1) }; // fifos keys fifotbl
327 if (fifo != nullptr && fifo->count > 0)
328 {
329 fifo_pop(L, fifo, 1); // fifos keys val
330 if (!lua_isnil(L, -1))
331 {
332 lua_replace(L, 1); // val keys
333 lua_settop(L, i); // val keys key[i]
334 if (i != 2)
335 {
336 lua_replace(L, 2); // val key keys
337 lua_settop(L, 2); // val key
338 }
339 lua_insert(L, 1); // key, val
340 return 2;
341 }
342 }
343 lua_settop(L, top); // data keys
344 }
345 // nothing to receive
346 return 0;
347}
348
349// ##################################################################################################
350
351// in: linda_ud key mincount [maxcount]
352int keepercall_receive_batched(lua_State* L)
353{
354 int const min_count{ static_cast<int>(lua_tointeger(L, 3)) };
355 if( min_count > 0)
356 {
357 int const max_count{ static_cast<int>(luaL_optinteger(L, 4, min_count)) };
358 lua_settop(L, 2); // ud key
359 lua_insert(L, 1); // key ud
360 push_table(L, 2); // key ud fifos
361 lua_remove(L, 2); // key fifos
362 lua_pushvalue(L, 1); // key fifos key
363 lua_rawget(L, 2); // key fifos fifo
364 lua_remove(L, 2); // key fifo
365 keeper_fifo* const fifo{ prepare_fifo_access(L, 2) }; // key fifotbl
366 if( fifo != nullptr && fifo->count >= min_count)
367 {
368 fifo_pop(L, fifo, std::min( max_count, fifo->count)); // key ...
369 }
370 else
371 {
372 lua_settop(L, 0); //
373 }
374 return lua_gettop(L);
375 }
376 else
377 {
378 return 0;
379 }
380}
381
382// ##################################################################################################
383
384// in: linda_ud key n
385// out: true or nil
386int keepercall_limit(lua_State* L)
387{
388 int const limit{ static_cast<int>(lua_tointeger(L, 3)) };
389 push_table(L, 1); // ud key n fifos
390 lua_replace(L, 1); // fifos key n
391 lua_pop(L, 1); // fifos key
392 lua_pushvalue(L, -1); // fifos key key
393 lua_rawget(L, -3); // fifos key fifo|nil
394 keeper_fifo* fifo{ keeper_fifo::getPtr(L, -1) };
395 if (fifo == nullptr)
396 { // fifos key nil
397 lua_pop(L, 1); // fifos key
398 fifo = fifo_new(L); // fifos key fifo
399 lua_rawset(L, -3); // fifos
400 }
401 // remove any clutter on the stack
402 lua_settop(L, 0);
403 // return true if we decide that blocked threads waiting to write on that key should be awakened
404 // this is the case if we detect the key was full but it is no longer the case
405 if(
406 ((fifo->limit >= 0) && (fifo->count >= fifo->limit)) // the key was full if limited and count exceeded the previous limit
407 && ((limit < 0) || (fifo->count < limit)) // the key is not full if unlimited or count is lower than the new limit
408 )
409 {
410 lua_pushboolean(L, 1); // true
411 }
412 // set the new limit
413 fifo->limit = limit;
414 // return 0 or 1 value
415 return lua_gettop(L);
416}
417
418// ##################################################################################################
419
420// in: linda_ud key [[val] ...]
421//out: true or nil
422int keepercall_set(lua_State* L)
423{
424 bool should_wake_writers{ false };
425 STACK_GROW(L, 6);
426
427 // retrieve fifos associated with the linda
428 push_table(L, 1); // ud key [val [, ...]] fifos
429 lua_replace(L, 1); // fifos key [val [, ...]]
430
431 // make sure we have a value on the stack
432 if (lua_gettop(L) == 2) // fifos key
433 {
434 lua_pushvalue(L, -1); // fifos key key
435 lua_rawget(L, 1); // fifos key fifo|nil
436 // empty the fifo for the specified key: replace uservalue with a virgin table, reset counters, but leave limit unchanged!
437 keeper_fifo* const fifo{ keeper_fifo::getPtr(L, -1) };
438 if (fifo != nullptr) // might be nullptr if we set a nonexistent key to nil
439 { // fifos key fifo
440 if (fifo->limit < 0) // fifo limit value is the default (unlimited): we can totally remove it
441 {
442 lua_pop(L, 1); // fifos key
443 lua_pushnil(L); // fifos key nil
444 lua_rawset(L, -3); // fifos
445 }
446 else
447 {
448 // we create room if the fifo was full but it is no longer the case
449 should_wake_writers = (fifo->limit > 0) && (fifo->count >= fifo->limit);
450 lua_remove(L, -2); // fifos fifo
451 lua_newtable(L); // fifos fifo {}
452 lua_setiuservalue(L, -2, CONTENTS_TABLE); // fifos fifo
453 fifo->first = 1;
454 fifo->count = 0;
455 }
456 }
457 }
458 else // set/replace contents stored at the specified key?
459 {
460 int const count{ lua_gettop(L) - 2 }; // number of items we want to store
461 lua_pushvalue(L, 2); // fifos key [val [, ...]] key
462 lua_rawget(L, 1); // fifos key [val [, ...]] fifo|nil
463 keeper_fifo* fifo{ keeper_fifo::getPtr(L, -1) };
464 if( fifo == nullptr) // can be nullptr if we store a value at a new key
465 { // fifos key [val [, ...]] nil
466 // no need to wake writers in that case, because a writer can't wait on an inexistent key
467 lua_pop(L, 1); // fifos key [val [, ...]]
468 std::ignore = fifo_new(L); // fifos key [val [, ...]] fifo
469 lua_pushvalue(L, 2); // fifos key [val [, ...]] fifo key
470 lua_pushvalue(L, -2); // fifos key [val [, ...]] fifo key fifo
471 lua_rawset(L, 1); // fifos key [val [, ...]] fifo
472 }
473 else // the fifo exists, we just want to update its contents
474 { // fifos key [val [, ...]] fifo
475 // we create room if the fifo was full but it is no longer the case
476 should_wake_writers = (fifo->limit > 0) && (fifo->count >= fifo->limit) && (count < fifo->limit);
477 // empty the fifo for the specified key: replace uservalue with a virgin table, reset counters, but leave limit unchanged!
478 lua_newtable(L); // fifos key [val [, ...]] fifo {}
479 lua_setiuservalue(L, -2, CONTENTS_TABLE); // fifos key [val [, ...]] fifo
480 fifo->first = 1;
481 fifo->count = 0;
482 }
483 fifo = prepare_fifo_access(L, -1); // fifos key [val [, ...]] fifotbl
484 // move the fifo below the values we want to store
485 lua_insert(L, 3); // fifos key fifotbl [val [, ...]]
486 fifo_push(L, fifo, count); // fifos key fifotbl
487 }
488 return should_wake_writers ? (lua_pushboolean(L, 1), 1) : 0;
489}
490
491// ##################################################################################################
492
493// in: linda_ud key [count]
494// out: at most <count> values
495int keepercall_get(lua_State* L)
496{
497 int count{ 1 };
498 if (lua_gettop(L) == 3) // ud key count
499 {
500 count = static_cast<int>(lua_tointeger(L, 3));
501 lua_pop(L, 1); // ud key
502 }
503 push_table(L, 1); // ud key fifos
504 lua_replace(L, 1); // fifos key
505 lua_rawget(L, 1); // fifos fifo
506 keeper_fifo* const fifo{ prepare_fifo_access(L, -1) }; // fifos fifotbl
507 if (fifo != nullptr && fifo->count > 0)
508 {
509 lua_remove(L, 1); // fifotbl
510 count = std::min(count, fifo->count);
511 // read <count> value off the fifo
512 fifo_peek(L, fifo, count); // fifotbl ...
513 return count;
514 }
515 // no fifo was ever registered for this key, or it is empty
516 return 0;
517}
518
519// ##################################################################################################
520
521// in: linda_ud [, key [, ...]]
522int keepercall_count(lua_State* L)
523{
524 push_table(L, 1); // ud keys fifos
525 switch (lua_gettop(L))
526 {
527 // no key is specified: return a table giving the count of all known keys
528 case 2: // ud fifos
529 lua_newtable(L); // ud fifos out
530 lua_replace(L, 1); // out fifos
531 lua_pushnil(L); // out fifos nil
532 while (lua_next(L, 2)) // out fifos key fifo
533 {
534 keeper_fifo* const fifo{ keeper_fifo::getPtr(L, -1) };
535 lua_pop(L, 1); // out fifos key
536 lua_pushvalue(L, -1); // out fifos key key
537 lua_pushinteger(L, fifo->count); // out fifos key key count
538 lua_rawset(L, -5); // out fifos key
539 }
540 lua_pop(L, 1); // out
541 break;
542
543 // 1 key is specified: return its count
544 case 3: // ud key fifos
545 lua_replace(L, 1); // fifos key
546 lua_rawget(L, -2); // fifos fifo|nil
547 if (lua_isnil(L, -1)) // the key is unknown
548 { // fifos nil
549 lua_remove(L, -2); // nil
550 }
551 else // the key is known
552 { // fifos fifo
553 keeper_fifo* const fifo{ keeper_fifo::getPtr(L, -1) };
554 lua_pushinteger(L, fifo->count); // fifos fifo count
555 lua_replace(L, -3); // count fifo
556 lua_pop(L, 1); // count
557 }
558 break;
559
560 // a variable number of keys is specified: return a table of their counts
561 default: // ud keys fifos
562 lua_newtable(L); // ud keys... fifos out
563 lua_replace(L, 1); // out keys... fifos
564 // shifts all keys up in the stack. potentially slow if there are a lot of them, but then it should be bearable
565 lua_insert(L, 2); // out fifos keys...
566 while (lua_gettop(L) > 2)
567 {
568 lua_pushvalue(L, -1); // out fifos keys... key
569 lua_rawget(L, 2); // out fifos keys... fifo|nil
570 keeper_fifo* const fifo{ keeper_fifo::getPtr(L, -1) };
571 lua_pop(L, 1); // out fifos keys...
572 if (fifo != nullptr) // the key is known
573 {
574 lua_pushinteger(L, fifo->count); // out fifos keys... count
575 lua_rawset(L, 1); // out fifos keys...
576 }
577 else // the key is unknown
578 {
579 lua_pop(L, 1); // out fifos keys...
580 }
581 } // all keys are exhausted // out fifos
582 lua_pop(L, 1); // out
583 }
584 ASSERT_L(lua_gettop(L) == 1);
585 return 1;
586}
587
588//###################################################################################
589// Keeper API, accessed from linda methods
590//###################################################################################
591
592/*---=== Keeper states ===---
593*/
594
595/*
596* Pool of keeper states
597*
598* Access to keeper states is locked (only one OS thread at a time) so the
599* bigger the pool, the less chances of unnecessary waits. Lindas map to the
600* keepers randomly, by a hash.
601*/
602
603// called as __gc for the keepers array userdata
604void close_keepers(Universe* U)
605{
606 if (U->keepers != nullptr)
607 {
608 int nbKeepers = U->keepers->nb_keepers;
609 // NOTE: imagine some keeper state N+1 currently holds a linda that uses another keeper N, and a _gc that will make use of it
610 // when keeper N+1 is closed, object is GCed, linda operation is called, which attempts to acquire keeper N, whose Lua state no longer exists
611 // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success
612 // which is early-outed with a U->keepers->nbKeepers null-check
613 U->keepers->nb_keepers = 0;
614 for (int i = 0; i < nbKeepers; ++i)
615 {
616 lua_State* K = U->keepers->keeper_array[i].L;
617 U->keepers->keeper_array[i].L = nullptr;
618 if (K != nullptr)
619 {
620 lua_close(K);
621 }
622 else
623 {
624 // detected partial init: destroy only the mutexes that got initialized properly
625 nbKeepers = i;
626 }
627 }
628 for (int i = 0; i < nbKeepers; ++i)
629 {
630 U->keepers->keeper_array[i].~Keeper();
631 }
632 // free the keeper bookkeeping structure
633 U->internal_allocator.free(U->keepers, sizeof(Keepers) + (nbKeepers - 1) * sizeof(Keeper));
634 U->keepers = nullptr;
635 }
636}
637
638// ##################################################################################################
639
640/*
641 * Initialize keeper states
642 *
643 * If there is a problem, returns nullptr and pushes the error message on the stack
644 * else returns the keepers bookkeeping structure.
645 *
646 * Note: Any problems would be design flaws; the created Lua state is left
647 * unclosed, because it does not really matter. In production code, this
648 * function never fails.
649 * settings table is at position 1 on the stack
650 */
651void init_keepers(Universe* U, lua_State* L)
652{
653 STACK_CHECK_START_REL(L, 0); // L K
654 lua_getfield(L, 1, "nb_keepers"); // nb_keepers
655 int const nb_keepers{ static_cast<int>(lua_tointeger(L, -1)) };
656 lua_pop(L, 1); //
657 if (nb_keepers < 1)
658 {
659 luaL_error(L, "Bad number of keepers (%d)", nb_keepers); // doesn't return
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);
667
668 // Keepers contains an array of 1 Keeper, adjust for the actual number of keeper states
669 {
670 size_t const bytes = sizeof(Keepers) + (nb_keepers - 1) * sizeof(Keeper);
671 U->keepers = static_cast<Keepers*>(U->internal_allocator.alloc(bytes));
672 if (U->keepers == nullptr)
673 {
674 luaL_error(L, "init_keepers() failed while creating keeper array; out of memory"); // doesn't return
675 }
676 U->keepers->Keepers::Keepers();
677 U->keepers->gc_threshold = keepers_gc_threshold;
678 U->keepers->nb_keepers = nb_keepers;
679
680 for (int i = 0; i < nb_keepers; ++i)
681 {
682 U->keepers->keeper_array[i].Keeper::Keeper();
683 }
684 }
685 for (int i = 0; i < nb_keepers; ++i) // keepersUD
686 {
687 // note that we will leak K if we raise an error later
688 lua_State* const K{ create_state(U, L) };
689 if (K == nullptr)
690 {
691 luaL_error(L, "init_keepers() failed while creating keeper states; out of memory"); // doesn't return
692 }
693
694 U->keepers->keeper_array[i].L = K;
695
696 if (U->keepers->gc_threshold >= 0)
697 {
698 lua_gc(K, LUA_GCSTOP, 0);
699 }
700
701 STACK_CHECK_START_ABS(K, 0);
702
703 // copy the universe pointer in the keeper itself
704 universe_store(K, U);
705 STACK_CHECK(K, 0);
706
707 // make sure 'package' is initialized in keeper states, so that we have require()
708 // this because this is needed when transferring deep userdata object
709 luaL_requiref(K, "package", luaopen_package, 1); // package
710 lua_pop(K, 1); //
711 STACK_CHECK(K, 0);
712 serialize_require(DEBUGSPEW_PARAM_COMMA(U) K);
713 STACK_CHECK(K, 0);
714
715 // copy package.path and package.cpath from the source state
716 lua_getglobal(L, "package"); // "..." keepersUD package
717 if (!lua_isnil(L, -1))
718 {
719 // when copying with mode LookupMode::ToKeeper, error message is pushed at the top of the stack, not raised immediately
720 if (luaG_inter_copy_package(U, Source{ L }, Dest{ K }, -1, LookupMode::ToKeeper) != InterCopyResult::Success)
721 {
722 // if something went wrong, the error message is at the top of the stack
723 lua_remove(L, -2); // error_msg
724 raise_lua_error(L);
725 }
726 }
727 lua_pop(L, 1); //
728 STACK_CHECK(L, 0);
729
730 // attempt to call on_state_create(), if we have one and it is a C function
731 // (only support a C function because we can't transfer executable Lua code in keepers)
732 // will raise an error in L in case of problem
733 call_on_state_create(U, K, L, LookupMode::ToKeeper);
734
735 // to see VM name in Decoda debugger
736 lua_pushfstring(K, "Keeper #%d", i + 1); // "Keeper #n"
737 lua_setglobal(K, "decoda_name"); //
738 // create the fifos table in the keeper state
739 FIFOS_KEY.setValue(K, [](lua_State* L) { lua_newtable(L); });
740 STACK_CHECK(K, 0);
741 }
742 STACK_CHECK(L, 0);
743}
744
745// ##################################################################################################
746
747// should be called only when inside a keeper_acquire/keeper_release pair (see linda_protected_call)
748Keeper* which_keeper(Keepers* keepers_, uintptr_t magic_)
749{
750 int const nbKeepers{ keepers_->nb_keepers };
751 if (nbKeepers)
752 {
753 unsigned int i = (unsigned int) ((magic_ >> KEEPER_MAGIC_SHIFT) % nbKeepers);
754 return &keepers_->keeper_array[i];
755 }
756 return nullptr;
757}
758
759// ##################################################################################################
760
761Keeper* keeper_acquire(Keepers* keepers_, uintptr_t magic_)
762{
763 int const nbKeepers{ keepers_->nb_keepers };
764 // can be 0 if this happens during main state shutdown (lanes is being GC'ed -> no keepers)
765 if (nbKeepers)
766 {
767 /*
768 * Any hashing will do that maps pointers to 0..GNbKeepers-1
769 * consistently.
770 *
771 * Pointers are often aligned by 8 or so - ignore the low order bits
772 * have to cast to unsigned long to avoid compilation warnings about loss of data when converting pointer-to-integer
773 */
774 unsigned int i = (unsigned int)((magic_ >> KEEPER_MAGIC_SHIFT) % nbKeepers);
775 Keeper* K = &keepers_->keeper_array[i];
776 K->m_mutex.lock();
777 //++ K->count;
778 return K;
779 }
780 return nullptr;
781}
782
783// ##################################################################################################
784
785void keeper_release(Keeper* K)
786{
787 //-- K->count;
788 if (K)
789 {
790 K->m_mutex.unlock();
791 }
792}
793
794// ##################################################################################################
795
796void keeper_toggle_nil_sentinels(lua_State* L, int val_i_, LookupMode const mode_)
797{
798 int const n{ lua_gettop(L) };
799 for (int i = val_i_; i <= n; ++i)
800 {
801 if (mode_ == LookupMode::ToKeeper)
802 {
803 if (lua_isnil(L, i))
804 {
805 NIL_SENTINEL.pushKey(L);
806 lua_replace(L, i);
807 }
808 }
809 else
810 {
811 if (NIL_SENTINEL.equals(L, i))
812 {
813 lua_pushnil(L);
814 lua_replace(L, i);
815 }
816 }
817 }
818}
819
820// ##################################################################################################
821
822/*
823* Call a function ('func_name') in the keeper state, and pass on the returned
824* values to 'L'.
825*
826* 'linda': deep Linda pointer (used only as a unique table key, first parameter)
827* 'starting_index': first of the rest of parameters (none if 0)
828*
829* Returns: number of return values (pushed to 'L') or -1 in case of error
830*/
831int keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, void* linda, int starting_index)
832{
833 int const args{ starting_index ? (lua_gettop(L) - starting_index + 1) : 0 };
834 int const Ktos{ lua_gettop(K) };
835 int retvals = -1;
836
837 STACK_GROW(K, 2);
838
839 PUSH_KEEPER_FUNC(K, func_);
840
841 lua_pushlightuserdata(K, linda);
842
843 if ((args == 0) || luaG_inter_copy(U, Source{ L }, Dest{ K }, args, LookupMode::ToKeeper) == InterCopyResult::Success) // L->K
844 {
845 lua_call(K, 1 + args, LUA_MULTRET);
846 retvals = lua_gettop(K) - Ktos;
847 // note that this can raise a luaL_error while the keeper state (and its mutex) is acquired
848 // this may interrupt a lane, causing the destruction of the underlying OS thread
849 // after this, another lane making use of this keeper can get an error code from the mutex-locking function
850 // when attempting to grab the mutex again (WINVER <= 0x400 does this, but locks just fine, I don't know about pthread)
851 if ((retvals > 0) && luaG_inter_move(U, Source{ K }, Dest{ L }, retvals, LookupMode::FromKeeper) != InterCopyResult::Success) // K->L
852 {
853 retvals = -1;
854 }
855 }
856 // whatever happens, restore the stack to where it was at the origin
857 lua_settop(K, Ktos);
858
859 // 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
860 if (func_ != KEEPER_API(clear)) [[unlikely]]
861 {
862 // since keeper state GC is stopped, let's run a step once in a while if required
863 int const gc_threshold{ U->keepers->gc_threshold };
864 if (gc_threshold == 0) [[unlikely]]
865 {
866 lua_gc(K, LUA_GCSTEP, 0);
867 }
868 else if (gc_threshold > 0) [[likely]]
869 {
870 int const gc_usage{ lua_gc(K, LUA_GCCOUNT, 0) };
871 if (gc_usage >= gc_threshold)
872 {
873 lua_gc(K, LUA_GCCOLLECT, 0);
874 int const gc_usage_after{ lua_gc(K, LUA_GCCOUNT, 0) };
875 if (gc_usage_after > gc_threshold) [[unlikely]]
876 {
877 luaL_error(L, "Keeper GC threshold is too low, need at least %d", gc_usage_after);
878 }
879 }
880 }
881 }
882
883 return retvals;
884}