aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-05-30 17:57:21 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2024-05-30 17:57:21 +0200
commit731556711e453a501f1d1d06a6013b8fbd53414e (patch)
tree4c5c28cd83de320fcf4c9b4c749f2e6e8d5bef48 /src
parenta156aaeb07fada043b308409dcffcae1726eec0b (diff)
downloadlanes-731556711e453a501f1d1d06a6013b8fbd53414e.tar.gz
lanes-731556711e453a501f1d1d06a6013b8fbd53414e.tar.bz2
lanes-731556711e453a501f1d1d06a6013b8fbd53414e.zip
Keeper management modernisation and improvements
* use a std::variant to manage the distinction between one or more keeper states. Use std::unique_ptr<Keeper[]> to manage the multiple keeper case. * setting "nb_keepers" renamed "nb_user_keepers", to indicate these are in addition to internal keeper #0 used for timers. * stricter lanes.linda() argument checking. group is imposed if more than one keeper is used. * more tests
Diffstat (limited to 'src')
-rw-r--r--src/keeper.cpp236
-rw-r--r--src/keeper.h53
-rw-r--r--src/lane.cpp2
-rw-r--r--src/lanes.cpp43
-rw-r--r--src/lanes.lua10
-rw-r--r--src/lanesconf.h2
-rw-r--r--src/linda.cpp25
-rw-r--r--src/linda.h3
-rw-r--r--src/universe.cpp210
-rw-r--r--src/universe.h7
10 files changed, 361 insertions, 230 deletions
diff --git a/src/keeper.cpp b/src/keeper.cpp
index ff5fe26..ea924e8 100644
--- a/src/keeper.cpp
+++ b/src/keeper.cpp
@@ -40,6 +40,7 @@
40#include "keeper.h" 40#include "keeper.h"
41 41
42#include "intercopycontext.h" 42#include "intercopycontext.h"
43#include "lane.h"
43#include "linda.h" 44#include "linda.h"
44#include "state.h" 45#include "state.h"
45 46
@@ -548,22 +549,20 @@ int keepercall_count(lua_State* L_)
548 549
549Keeper* Linda::acquireKeeper() const 550Keeper* Linda::acquireKeeper() const
550{ 551{
551 int const _nbKeepers{ U->keepers->nb_keepers }; 552 // can be nullptr if this happens during main state shutdown (lanes is being GC'ed -> no keepers)
552 // can be 0 if this happens during main state shutdown (lanes is being GC'ed -> no keepers) 553 Keeper* const _K{ whichKeeper() };
553 if (_nbKeepers) { 554 if (_K) {
554 Keeper* const _K{ &U->keepers->keeper_array[keeperIndex] };
555 _K->mutex.lock(); 555 _K->mutex.lock();
556 return _K;
557 } 556 }
558 return nullptr; 557 return _K;
559} 558}
560 559
561// ################################################################################################# 560// #################################################################################################
562 561
563void Linda::releaseKeeper(Keeper* K_) const 562void Linda::releaseKeeper(Keeper* const K_) const
564{ 563{
565 if (K_) { // can be nullptr if we tried to acquire during shutdown 564 if (K_) { // can be nullptr if we tried to acquire during shutdown
566 assert(K_ == &U->keepers->keeper_array[keeperIndex]); 565 assert(K_ == whichKeeper());
567 K_->mutex.unlock(); 566 K_->mutex.unlock();
568 } 567 }
569} 568}
@@ -595,16 +594,16 @@ KeeperCallResult keeper_call(KeeperState K_, keeper_api_t func_, lua_State* L_,
595 (InterCopyContext{ linda_->U, DestState{ K_ }, SourceState{ L_ }, {}, {}, {}, LookupMode::ToKeeper, {} }.inter_copy(_args) == InterCopyResult::Success) 594 (InterCopyContext{ linda_->U, DestState{ K_ }, SourceState{ L_ }, {}, {}, {}, LookupMode::ToKeeper, {} }.inter_copy(_args) == InterCopyResult::Success)
596 ) { // L: ... args... K_: func_ linda args... 595 ) { // L: ... args... K_: func_ linda args...
597 lua_call(K_, 1 + _args, LUA_MULTRET); // L: ... args... K_: result... 596 lua_call(K_, 1 + _args, LUA_MULTRET); // L: ... args... K_: result...
598 int const retvals{ lua_gettop(K_) - _top_K }; 597 int const _retvals{ lua_gettop(K_) - _top_K };
599 // note that this can raise a lua error while the keeper state (and its mutex) is acquired 598 // note that this can raise a lua error while the keeper state (and its mutex) is acquired
600 // this may interrupt a lane, causing the destruction of the underlying OS thread 599 // this may interrupt a lane, causing the destruction of the underlying OS thread
601 // after this, another lane making use of this keeper can get an error code from the mutex-locking function 600 // after this, another lane making use of this keeper can get an error code from the mutex-locking function
602 // when attempting to grab the mutex again (WINVER <= 0x400 does this, but locks just fine, I don't know about pthread) 601 // when attempting to grab the mutex again (WINVER <= 0x400 does this, but locks just fine, I don't know about pthread)
603 if ( 602 if (
604 (retvals == 0) || 603 (_retvals == 0) ||
605 (InterCopyContext{ linda_->U, DestState{ L_ }, SourceState{ K_ }, {}, {}, {}, LookupMode::FromKeeper, {} }.inter_move(retvals) == InterCopyResult::Success) 604 (InterCopyContext{ linda_->U, DestState{ L_ }, SourceState{ K_ }, {}, {}, {}, LookupMode::FromKeeper, {} }.inter_move(_retvals) == InterCopyResult::Success)
606 ) { // L: ... args... result... K_: result... 605 ) { // L: ... args... result... K_: result...
607 _result.emplace(retvals); 606 _result.emplace(_retvals);
608 } 607 }
609 } 608 }
610 // whatever happens, restore the stack to where it was at the origin 609 // whatever happens, restore the stack to where it was at the origin
@@ -613,7 +612,7 @@ KeeperCallResult keeper_call(KeeperState K_, keeper_api_t func_, lua_State* L_,
613 // 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 612 // 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
614 if (func_ != KEEPER_API(clear)) [[unlikely]] { 613 if (func_ != KEEPER_API(clear)) [[unlikely]] {
615 // since keeper state GC is stopped, let's run a step once in a while if required 614 // since keeper state GC is stopped, let's run a step once in a while if required
616 int const _gc_threshold{ linda_->U->keepers->gc_threshold }; 615 int const _gc_threshold{ linda_->U->keepers.gc_threshold };
617 if (_gc_threshold == 0) [[unlikely]] { 616 if (_gc_threshold == 0) [[unlikely]] {
618 lua_gc(K_, LUA_GCSTEP, 0); 617 lua_gc(K_, LUA_GCSTEP, 0);
619 } else if (_gc_threshold > 0) [[likely]] { 618 } else if (_gc_threshold > 0) [[likely]] {
@@ -632,8 +631,215 @@ KeeperCallResult keeper_call(KeeperState K_, keeper_api_t func_, lua_State* L_,
632} 631}
633 632
634// ################################################################################################# 633// #################################################################################################
634// #################################################################################################
635// Keeper
636// #################################################################################################
637// #################################################################################################
635 638
636void Keepers::CreateFifosTable(lua_State* L_) 639void* Keeper::operator new[](size_t size_, Universe* U_) noexcept
637{ 640{
638 kFifosRegKey.setValue(L_, [](lua_State* L_) { lua_newtable(L_); }); 641 // size_ is the memory for the element count followed by the elements themselves
642 return U_->internalAllocator.alloc(size_);
643}
644
645// #################################################################################################
646
647// can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception
648void Keeper::operator delete[](void* p_, Universe* U_)
649{
650 U_->internalAllocator.free(p_, *static_cast<size_t*>(p_) * sizeof(Keeper) + sizeof(size_t));
651}
652
653// #################################################################################################
654// #################################################################################################
655// Keepers
656// #################################################################################################
657// #################################################################################################
658
659void Keepers::DeleteKV::operator()(Keeper* k_) const
660{
661 for (Keeper& _k : std::views::counted(k_, count)) {
662 _k.~Keeper();
663 }
664 // operator[] returns the result of the allocation shifted by a size_t (the hidden element count)
665 U->internalAllocator.free(reinterpret_cast<size_t*>(k_) - 1, count * sizeof(Keeper));
666}
667
668// #################################################################################################
669/*
670 * Initialize keeper states
671 *
672 * If there is a problem, returns nullptr and pushes the error message on the stack
673 * else returns the keepers bookkeeping structure.
674 *
675 * Note: Any problems would be design flaws; the created Lua state is left
676 * unclosed, because it does not really matter. In production code, this
677 * function never fails.
678 * settings table is expected at position 1 on the stack
679 */
680
681void Keepers::initialize(Universe& U_, lua_State* L_, int const nbKeepers_, int const gc_threshold_)
682{
683 gc_threshold = gc_threshold_;
684
685 auto _initOneKeeper = [U = &U_, L = L_, gc_threshold = gc_threshold](Keeper& keeper_, int const i_) {
686 STACK_CHECK_START_REL(L, 0);
687 // note that we will leak K if we raise an error later
688 KeeperState const _K{ state::CreateState(U, L) }; // L: settings K:
689 if (_K == nullptr) {
690 raise_luaL_error(L, "out of memory while creating keeper states");
691 }
692
693 keeper_.L = _K;
694
695 // Give a name to the state
696 lua_pushfstring(_K, "Keeper #%d", i_ + 1); // L: settings K: "Keeper #n"
697 if constexpr (HAVE_DECODA_SUPPORT()) {
698 lua_pushvalue(_K, -1); // K: "Keeper #n" Keeper #n"
699 lua_setglobal(_K, "decoda_name"); // L: settings K: "Keeper #n"
700 }
701 kLaneNameRegKey.setValue(_K, [](lua_State* L_) { lua_insert(L_, -2); }); // K:
702
703 STACK_CHECK_START_ABS(_K, 0);
704
705 // copy the universe pointer in the keeper itself
706 Universe::Store(_K, U);
707 STACK_CHECK(_K, 0);
708
709 // make sure 'package' is initialized in keeper states, so that we have require()
710 // this because this is needed when transferring deep userdata object
711 luaL_requiref(_K, LUA_LOADLIBNAME, luaopen_package, 1); // L: settings K: package
712 lua_pop(_K, 1); // L: settings K:
713 STACK_CHECK(_K, 0);
714 tools::SerializeRequire(_K);
715 STACK_CHECK(_K, 0);
716
717 // copy package.path and package.cpath from the source state
718 if (luaG_getmodule(L, LUA_LOADLIBNAME) != LuaType::NIL) { // L: settings package K:
719 // when copying with mode LookupMode::ToKeeper, error message is pushed at the top of the stack, not raised immediately
720 InterCopyContext _c{ U, DestState{ _K }, SourceState{ L }, {}, SourceIndex{ lua_absindex(L, -1) }, {}, LookupMode::ToKeeper, {} };
721 if (_c.inter_copy_package() != InterCopyResult::Success) { // L: settings ... error_msg K:
722 // if something went wrong, the error message is at the top of the stack
723 lua_remove(L, -2); // L: settings error_msg
724 raise_lua_error(L);
725 }
726 }
727 lua_pop(L, 1); // L: settings K:
728 STACK_CHECK(L, 0);
729 STACK_CHECK(_K, 0);
730
731 // attempt to call on_state_create(), if we have one and it is a C function
732 // (only support a C function because we can't transfer executable Lua code in keepers)
733 // will raise an error in L_ in case of problem
734 state::CallOnStateCreate(U, _K, L, LookupMode::ToKeeper);
735
736 // create the fifos table in the keeper state
737 kFifosRegKey.setValue(_K, [](lua_State* L_) { lua_newtable(L_); });
738 STACK_CHECK(_K, 0);
739
740 // configure GC last
741 if (gc_threshold >= 0) {
742 lua_gc(_K, LUA_GCSTOP, 0);
743 }
744 };
745
746 switch (nbKeepers_) {
747 case 0:
748 break;
749
750 case 1:
751 keeper_array.emplace<Keeper>();
752 _initOneKeeper(std::get<Keeper>(keeper_array), 0);
753 break;
754
755 default:
756 KV& _kv = keeper_array.emplace<KV>(
757 std::unique_ptr<Keeper[], DeleteKV>{ new(&U_) Keeper[nbKeepers_], DeleteKV{ &U_, nbKeepers_ } },
758 nbKeepers_
759 );
760 for (int const _i : std::ranges::iota_view{ 0, nbKeepers_ }) {
761 _initOneKeeper(_kv.keepers[_i], _i);
762 }
763 }
764}
765
766// #################################################################################################
767
768void Keepers::close()
769{
770 if (isClosing.test_and_set(std::memory_order_release)) {
771 assert(false); // should never close more than once in practice
772 return;
773 }
774
775 if (std::holds_alternative<std::monostate>(keeper_array)) {
776 return;
777 }
778
779 auto _closeOneKeeper = [](Keeper& keeper_)
780 {
781 lua_State* const _K{ std::exchange(keeper_.L, KeeperState{ nullptr }) };
782 if (_K) {
783 lua_close(_K);
784 }
785 return _K ? true : false;
786 };
787
788 if (std::holds_alternative<Keeper>(keeper_array)) {
789 _closeOneKeeper(std::get<Keeper>(keeper_array));
790 } else {
791 KV& _kv = std::get<KV>(keeper_array);
792
793 // 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
794 // 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
795 // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success
796 // which is early-outed with a keepers->nbKeepers null-check
797 size_t const _nbKeepers{ std::exchange(_kv.nbKeepers, 0) };
798 for (size_t const _i : std::ranges::iota_view{ size_t{ 0 }, _nbKeepers }) {
799 if (!_closeOneKeeper(_kv.keepers[_i])) {
800 // detected partial init: destroy only the mutexes that got initialized properly
801 break;
802 }
803 }
804 }
805
806 keeper_array.emplace<std::monostate>();
807}
808
809// #################################################################################################
810
811[[nodiscard]] Keeper* Keepers::getKeeper(int idx_)
812{
813 if (isClosing.test(std::memory_order_acquire)) {
814 return nullptr;
815 }
816
817 if (std::holds_alternative<std::monostate>(keeper_array)) {
818 return nullptr;
819 }
820
821 if (std::holds_alternative<Keeper>(keeper_array)) {
822 return &std::get<Keeper>(keeper_array);
823 }
824
825 return &std::get<KV>(keeper_array).keepers.get()[idx_];
826}
827
828// #################################################################################################
829
830[[nodiscard]] int Keepers::getNbKeepers() const
831{
832 if (isClosing.test(std::memory_order_acquire)) {
833 return 0;
834 }
835
836 if (std::holds_alternative<std::monostate>(keeper_array)) {
837 return 0;
838 }
839
840 if (std::holds_alternative<Keeper>(keeper_array)) {
841 return 1;
842 }
843
844 return static_cast<int>(std::get<KV>(keeper_array).nbKeepers);
639} 845}
diff --git a/src/keeper.h b/src/keeper.h
index a902caa..682c710 100644
--- a/src/keeper.h
+++ b/src/keeper.h
@@ -11,8 +11,10 @@ extern "C"
11 11
12#include "uniquekey.h" 12#include "uniquekey.h"
13 13
14#include <optional> 14#include <atomic>
15#include <mutex> 15#include <mutex>
16#include <optional>
17#include <variant>
16 18
17// forwards 19// forwards
18class Linda; 20class Linda;
@@ -21,22 +23,63 @@ class Universe;
21 23
22using KeeperState = Unique<lua_State*>; 24using KeeperState = Unique<lua_State*>;
23 25
26// #################################################################################################
27
24struct Keeper 28struct Keeper
25{ 29{
26 std::mutex mutex; 30 std::mutex mutex;
27 KeeperState L{ nullptr }; 31 KeeperState L{ nullptr };
28 // int count; 32
33 [[nodiscard]] static void* operator new[](size_t size_, Universe* U_) noexcept;
34 // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception
35 static void operator delete[](void* p_, Universe* U_);
36
37
38 ~Keeper() = default;
39 Keeper() = default;
40 // non-copyable, non-movable
41 Keeper(Keeper const&) = delete;
42 Keeper(Keeper const&&) = delete;
43 Keeper& operator=(Keeper const&) = delete;
44 Keeper& operator=(Keeper const&&) = delete;
29}; 45};
30 46
47// #################################################################################################
48
31struct Keepers 49struct Keepers
32{ 50{
51 private:
52 struct DeleteKV
53 {
54 Universe* U{};
55 int count{};
56 void operator()(Keeper* k_) const;
57 };
58 // can't use std::vector<Keeper> because Keeper contains a mutex, so we need a raw memory buffer
59 struct KV
60 {
61 std::unique_ptr<Keeper[], DeleteKV> keepers{};
62 size_t nbKeepers{};
63 };
64 std::variant<std::monostate, Keeper, KV> keeper_array;
65 std::atomic_flag isClosing;
66
67 public:
33 int gc_threshold{ 0 }; 68 int gc_threshold{ 0 };
34 int nb_keepers{ 0 };
35 Keeper keeper_array[1];
36 69
37 static void CreateFifosTable(lua_State* L_); 70 public:
71 // can only be instanced as a data member
72 static void* operator new(size_t size_) = delete;
73
74 Keepers() = default;
75 void close();
76 [[nodiscard]] Keeper* getKeeper(int idx_);
77 [[nodiscard]] int getNbKeepers() const;
78 void initialize(Universe& U_, lua_State* L_, int nbKeepers_, int gc_threshold_);
38}; 79};
39 80
81// #################################################################################################
82
40// xxh64 of string "kNilSentinel" generated at https://www.pelock.com/products/hash-calculator 83// xxh64 of string "kNilSentinel" generated at https://www.pelock.com/products/hash-calculator
41static constexpr UniqueKey kNilSentinel{ 0xC457D4EDDB05B5E4ull, "lanes.null" }; 84static constexpr UniqueKey kNilSentinel{ 0xC457D4EDDB05B5E4ull, "lanes.null" };
42 85
diff --git a/src/lane.cpp b/src/lane.cpp
index 22147f1..029efdb 100644
--- a/src/lane.cpp
+++ b/src/lane.cpp
@@ -811,7 +811,7 @@ void Lane::changeDebugName(int const nameIdx_)
811 kLaneNameRegKey.setValue(L, [idx = _nameIdx](lua_State* L_) { lua_pushvalue(L_, idx); }); // L: ... "name" ... 811 kLaneNameRegKey.setValue(L, [idx = _nameIdx](lua_State* L_) { lua_pushvalue(L_, idx); }); // L: ... "name" ...
812 // keep a direct pointer on the string 812 // keep a direct pointer on the string
813 debugName = lua_tostringview(L, _nameIdx); 813 debugName = lua_tostringview(L, _nameIdx);
814 if constexpr (HAVE_DECODA_NAME()) { 814 if constexpr (HAVE_DECODA_SUPPORT()) {
815 // to see VM name in Decoda debugger Virtual Machine window 815 // to see VM name in Decoda debugger Virtual Machine window
816 lua_pushvalue(L, _nameIdx); // L: ... "name" ... "name" 816 lua_pushvalue(L, _nameIdx); // L: ... "name" ... "name"
817 lua_setglobal(L, "decoda_name"); // L: ... "name" ... 817 lua_setglobal(L, "decoda_name"); // L: ... "name" ...
diff --git a/src/lanes.cpp b/src/lanes.cpp
index b74e9ec..4b4f9a8 100644
--- a/src/lanes.cpp
+++ b/src/lanes.cpp
@@ -637,50 +637,11 @@ LUAG_FUNC(configure)
637 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); 637 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U });
638 638
639 if (_U == nullptr) { 639 if (_U == nullptr) {
640 _U = Universe::Create(L_); // L_: settings universe
641 DEBUGSPEW_CODE(DebugSpewIndentScope _scope2{ _U });
642 lua_createtable(L_, 0, 1); // L_: settings universe {mt}
643 std::ignore = luaG_getfield(L_, 1, "shutdown_timeout"); // L_: settings universe {mt} shutdown_timeout
644 std::ignore = luaG_getfield(L_, 1, "shutdown_mode"); // L_: settings universe {mt} shutdown_timeout shutdown_mode
645 lua_pushcclosure(L_, LG_universe_gc, 2); // L_: settings universe {mt} LG_universe_gc
646 lua_setfield(L_, -2, "__gc"); // L_: settings universe {mt}
647 lua_setmetatable(L_, -2); // L_: settings universe
648 lua_pop(L_, 1); // L_: settings
649 std::ignore = luaG_getfield(L_, 1, "verbose_errors"); // L_: settings verbose_errors
650 _U->verboseErrors = lua_toboolean(L_, -1) ? true : false;
651 lua_pop(L_, 1); // L_: settings
652 std::ignore = luaG_getfield(L_, 1, "demote_full_userdata"); // L_: settings demote_full_userdata
653 _U->demoteFullUserdata = lua_toboolean(L_, -1) ? true : false;
654 lua_pop(L_, 1); // L_: settings
655
656 // tracking
657 std::ignore = luaG_getfield(L_, 1, "track_lanes"); // L_: settings track_lanes
658 if (lua_toboolean(L_, -1)) {
659 _U->tracker.activate();
660 }
661 lua_pop(L_, 1); // L_: settings
662
663 // Linked chains handling
664 _U->selfdestructFirst = SELFDESTRUCT_END;
665 _U->initializeAllocatorFunction(L_);
666 state::InitializeOnStateCreate(_U, L_);
667 _U->initializeKeepers(L_);
668 STACK_CHECK(L_, 1);
669
670 // Initialize 'timerLinda'; a common Linda object shared by all states
671 lua_pushcfunction(L_, LG_linda); // L_: settings lanes.linda
672 lua_pushliteral(L_, "lanes-timer"); // L_: settings lanes.linda "lanes-timer"
673 lua_call(L_, 1, 1); // L_: settings linda
674 STACK_CHECK(L_, 2);
675
676 // Proxy userdata contents is only a 'DeepPrelude*' pointer
677 _U->timerLinda = *lua_tofulluserdata<DeepPrelude*>(L_, -1);
678 // increment refcount so that this linda remains alive as long as the universe exists.
679 _U->timerLinda->refcount.fetch_add(1, std::memory_order_relaxed);
680 lua_pop(L_, 1); // L_: settings
681 // store a hidden reference in the registry to make sure the string is kept around even if a lane decides to manually change the "decoda_name" global... 640 // store a hidden reference in the registry to make sure the string is kept around even if a lane decides to manually change the "decoda_name" global...
682 kLaneNameRegKey.setValue(L_, [](lua_State* L_) { std::ignore = lua_pushstringview(L_, "main"); }); 641 kLaneNameRegKey.setValue(L_, [](lua_State* L_) { std::ignore = lua_pushstringview(L_, "main"); });
683 642
643 // create the universe
644 _U = Universe::Create(L_); // L_: settings universe
684 } 645 }
685 STACK_CHECK(L_, 1); 646 STACK_CHECK(L_, 1);
686 647
diff --git a/src/lanes.lua b/src/lanes.lua
index e7763b1..1c36b46 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -90,7 +90,7 @@ local isLuaJIT = (package and package.loaded.jit and jit.version) and true or fa
90 90
91local default_params = 91local default_params =
92{ 92{
93 nb_keepers = 1, 93 nb_user_keepers = 0,
94 keepers_gc_threshold = -1, 94 keepers_gc_threshold = -1,
95 on_state_create = nil, 95 on_state_create = nil,
96 shutdown_timeout = 0.25, 96 shutdown_timeout = 0.25,
@@ -114,9 +114,9 @@ end
114 114
115local param_checkers = 115local param_checkers =
116{ 116{
117 nb_keepers = function(val_) 117 nb_user_keepers = function(val_)
118 -- nb_keepers should be a number in [1,100] (so that nobody tries to run OOM by specifying a huge amount) 118 -- nb_user_keepers should be a number in [0,100] (so that nobody tries to run OOM by specifying a huge amount)
119 return type(val_) == "number" and val_ > 0 and val_ <= 100 119 return type(val_) == "number" and val_ >= 0 and val_ <= 100
120 end, 120 end,
121 keepers_gc_threshold = function(val_) 121 keepers_gc_threshold = function(val_)
122 -- keepers_gc_threshold should be a number 122 -- keepers_gc_threshold should be a number
@@ -176,7 +176,7 @@ local params_checker = function(settings_)
176 param = default_params[key] 176 param = default_params[key]
177 end 177 end
178 if not checker(param) then 178 if not checker(param) then
179 error("Bad " .. key .. ": " .. tostring(param), 2) 179 error("Bad parameter " .. key .. ": " .. tostring(param), 2)
180 end 180 end
181 settings[key] = param 181 settings[key] = param
182 end 182 end
diff --git a/src/lanesconf.h b/src/lanesconf.h
index 2df7a71..938d743 100644
--- a/src/lanesconf.h
+++ b/src/lanesconf.h
@@ -42,4 +42,4 @@
42#endif // (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) 42#endif // (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC)
43 43
44#define USE_DEBUG_SPEW() 0 44#define USE_DEBUG_SPEW() 0
45#define HAVE_DECODA_NAME() 0 45#define HAVE_DECODA_SUPPORT() 0
diff --git a/src/linda.cpp b/src/linda.cpp
index 76cd347..f8d6cdc 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -109,15 +109,10 @@ template <bool OPT>
109// #################################### Linda implementation ####################################### 109// #################################### Linda implementation #######################################
110// ################################################################################################# 110// #################################################################################################
111 111
112// Any hashing will do that maps pointers to [0..Universe::nb_keepers[ consistently.
113// Pointers are often aligned by 8 or so - ignore the low order bits
114// have to cast to unsigned long to avoid compilation warnings about loss of data when converting pointer-to-integer
115static constexpr uintptr_t kPointerMagicShift{ 3 };
116
117Linda::Linda(Universe* U_, LindaGroup group_, std::string_view const& name_) 112Linda::Linda(Universe* U_, LindaGroup group_, std::string_view const& name_)
118: DeepPrelude{ LindaFactory::Instance } 113: DeepPrelude{ LindaFactory::Instance }
119, U{ U_ } 114, U{ U_ }
120, keeperIndex{ (group_ ? group_ : static_cast<int>(std::bit_cast<uintptr_t>(this) >> kPointerMagicShift)) % U_->keepers->nb_keepers } 115, keeperIndex{ group_ % U_->keepers.getNbKeepers() }
121{ 116{
122 setName(name_); 117 setName(name_);
123} 118}
@@ -733,6 +728,7 @@ LUAG_FUNC(linda_tostring)
733 728
734// ################################################################################################# 729// #################################################################################################
735 730
731#if HAVE_DECODA_SUPPORT()
736/* 732/*
737 * table/string = linda:__towatch() 733 * table/string = linda:__towatch()
738 * return a table listing all pending data inside the linda, or the stringified linda if empty 734 * return a table listing all pending data inside the linda, or the stringified linda if empty
@@ -748,6 +744,8 @@ LUAG_FUNC(linda_towatch)
748 return _pushed; 744 return _pushed;
749} 745}
750 746
747#endif // HAVE_DECODA_SUPPORT()
748
751// ################################################################################################# 749// #################################################################################################
752 750
753namespace { 751namespace {
@@ -755,7 +753,9 @@ namespace {
755 static luaL_Reg const sLindaMT[] = { 753 static luaL_Reg const sLindaMT[] = {
756 { "__concat", LG_linda_concat }, 754 { "__concat", LG_linda_concat },
757 { "__tostring", LG_linda_tostring }, 755 { "__tostring", LG_linda_tostring },
756#if HAVE_DECODA_SUPPORT()
758 { "__towatch", LG_linda_towatch }, // Decoda __towatch support 757 { "__towatch", LG_linda_towatch }, // Decoda __towatch support
758#endif // HAVE_DECODA_SUPPORT()
759 { "cancel", LG_linda_cancel }, 759 { "cancel", LG_linda_cancel },
760 { "count", LG_linda_count }, 760 { "count", LG_linda_count },
761 { "deep", LG_linda_deep }, 761 { "deep", LG_linda_deep },
@@ -784,13 +784,24 @@ namespace {
784LUAG_FUNC(linda) 784LUAG_FUNC(linda)
785{ 785{
786 int const _top{ lua_gettop(L_) }; 786 int const _top{ lua_gettop(L_) };
787 int _groupIdx{};
787 luaL_argcheck(L_, _top <= 2, _top, "too many arguments"); 788 luaL_argcheck(L_, _top <= 2, _top, "too many arguments");
788 if (_top == 1) { 789 if (_top == 1) {
789 LuaType const _t{ lua_type_as_enum(L_, 1) }; 790 LuaType const _t{ lua_type_as_enum(L_, 1) };
790 luaL_argcheck(L_, _t == LuaType::STRING || _t == LuaType::NUMBER, 1, "wrong parameter (should be a string or a number)"); 791 int const _nameIdx{ (_t == LuaType::STRING) ? 1 : 0 };
792 _groupIdx = (_t == LuaType::NUMBER) ? 1 : 0;
793 luaL_argcheck(L_, _nameIdx || _groupIdx, 1, "wrong parameter (should be a string or a number)");
791 } else if (_top == 2) { 794 } else if (_top == 2) {
792 luaL_checktype(L_, 1, LUA_TSTRING); 795 luaL_checktype(L_, 1, LUA_TSTRING);
793 luaL_checktype(L_, 2, LUA_TNUMBER); 796 luaL_checktype(L_, 2, LUA_TNUMBER);
797 _groupIdx = 2;
798 }
799 int const _nbKeepers{ Universe::Get(L_)->keepers.getNbKeepers() };
800 if (!_groupIdx) {
801 luaL_argcheck(L_, _nbKeepers < 2, 0, "there are multiple keepers, you must specify a group");
802 } else {
803 int const _group{ static_cast<int>(lua_tointeger(L_, _groupIdx)) };
804 luaL_argcheck(L_, _group >= 0 && _group < _nbKeepers, _groupIdx, "group out of range");
794 } 805 }
795 return LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, 0); 806 return LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, 0);
796} 807}
diff --git a/src/linda.h b/src/linda.h
index 3545544..5753258 100644
--- a/src/linda.h
+++ b/src/linda.h
@@ -38,7 +38,6 @@ class Linda
38 CancelRequest cancelRequest{ CancelRequest::None }; 38 CancelRequest cancelRequest{ CancelRequest::None };
39 39
40 public: 40 public:
41 // a fifo full userdata has one uservalue, the table that holds the actual fifo contents
42 [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internalAllocator.alloc(size_); } 41 [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internalAllocator.alloc(size_); }
43 // always embedded somewhere else or "in-place constructed" as a full userdata 42 // always embedded somewhere else or "in-place constructed" as a full userdata
44 // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception 43 // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception
@@ -64,5 +63,5 @@ class Linda
64 [[nodiscard]] std::string_view getName() const; 63 [[nodiscard]] std::string_view getName() const;
65 void releaseKeeper(Keeper* keeper_) const; 64 void releaseKeeper(Keeper* keeper_) const;
66 [[nodiscard]] static int ProtectedCall(lua_State* L_, lua_CFunction f_); 65 [[nodiscard]] static int ProtectedCall(lua_State* L_, lua_CFunction f_);
67 [[nodiscard]] Keeper* whichKeeper() const { return U->keepers->nb_keepers ? &U->keepers->keeper_array[keeperIndex] : nullptr; } 66 [[nodiscard]] Keeper* whichKeeper() const { return U->keepers.getKeeper(keeperIndex); }
68}; 67};
diff --git a/src/universe.cpp b/src/universe.cpp
index 55c3e69..2e7fd55 100644
--- a/src/universe.cpp
+++ b/src/universe.cpp
@@ -38,6 +38,8 @@ THE SOFTWARE.
38 38
39#include <ranges> 39#include <ranges>
40 40
41extern LUAG_FUNC(linda);
42
41// ################################################################################################# 43// #################################################################################################
42 44
43// xxh64 of string "kUniverseFullRegKey" generated at https://www.pelock.com/products/hash-calculator 45// xxh64 of string "kUniverseFullRegKey" generated at https://www.pelock.com/products/hash-calculator
@@ -77,12 +79,68 @@ Universe::Universe()
77[[nodiscard]] Universe* Universe::Create(lua_State* const L_) 79[[nodiscard]] Universe* Universe::Create(lua_State* const L_)
78{ 80{
79 LUA_ASSERT(L_, Universe::Get(L_) == nullptr); 81 LUA_ASSERT(L_, Universe::Get(L_) == nullptr);
80 STACK_CHECK_START_REL(L_, 0); 82 LUA_ASSERT(L_, lua_gettop(L_) == 1 && lua_istable(L_, 1));
81 Universe* const _U{ new (L_) Universe{} }; // L_: universe 83 STACK_CHECK_START_REL(L_, 0); // L_: settings
84 std::ignore = luaG_getfield(L_, 1, "nb_user_keepers"); // L_: settings nb_user_keepers
85 int const _nbUserKeepers{ static_cast<int>(lua_tointeger(L_, -1)) + 1};
86 lua_pop(L_, 1); // L_: settings
87 if (_nbUserKeepers < 1) {
88 raise_luaL_error(L_, "Bad number of additional keepers (%d)", _nbUserKeepers);
89 }
90 STACK_CHECK(L_, 0);
91 std::ignore = luaG_getfield(L_, 1, "keepers_gc_threshold"); // L_: settings keepers_gc_threshold
92 int const _keepers_gc_threshold{ static_cast<int>(lua_tointeger(L_, -1)) };
93 lua_pop(L_, 1); // L_: settings
94 STACK_CHECK(L_, 0);
95
96 Universe* const _U{ new (L_) Universe{} }; // L_: settings universe
82 STACK_CHECK(L_, 1); 97 STACK_CHECK(L_, 1);
83 kUniverseFullRegKey.setValue(L_, [](lua_State* L_) { lua_pushvalue(L_, -2); }); 98 kUniverseFullRegKey.setValue(L_, [](lua_State* L_) { lua_pushvalue(L_, -2); });
84 kUniverseLightRegKey.setValue(L_, [U = _U](lua_State* L_) { lua_pushlightuserdata(L_, U); }); 99 kUniverseLightRegKey.setValue(L_, [U = _U](lua_State* L_) { lua_pushlightuserdata(L_, U); });
100 STACK_CHECK(L_, 1); // L_: settings
101
102 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U });
103 lua_createtable(L_, 0, 1); // L_: settings universe {mt}
104 std::ignore = luaG_getfield(L_, 1, "shutdown_timeout"); // L_: settings universe {mt} shutdown_timeout
105 std::ignore = luaG_getfield(L_, 1, "shutdown_mode"); // L_: settings universe {mt} shutdown_timeout shutdown_mode
106 lua_pushcclosure(L_, LG_universe_gc, 2); // L_: settings universe {mt} LG_universe_gc
107 lua_setfield(L_, -2, "__gc"); // L_: settings universe {mt}
108 lua_setmetatable(L_, -2); // L_: settings universe
109 lua_pop(L_, 1); // L_: settings
110 std::ignore = luaG_getfield(L_, 1, "verbose_errors"); // L_: settings verbose_errors
111 _U->verboseErrors = lua_toboolean(L_, -1) ? true : false;
112 lua_pop(L_, 1); // L_: settings
113 std::ignore = luaG_getfield(L_, 1, "demote_full_userdata"); // L_: settings demote_full_userdata
114 _U->demoteFullUserdata = lua_toboolean(L_, -1) ? true : false;
115 lua_pop(L_, 1); // L_: settings
116
117 // tracking
118 std::ignore = luaG_getfield(L_, 1, "track_lanes"); // L_: settings track_lanes
119 if (lua_toboolean(L_, -1)) {
120 _U->tracker.activate();
121 }
122 lua_pop(L_, 1); // L_: settings
123
124 // Linked chains handling
125 _U->selfdestructFirst = SELFDESTRUCT_END;
126 _U->initializeAllocatorFunction(L_);
127 state::InitializeOnStateCreate(_U, L_);
128 _U->keepers.initialize(*_U, L_, _nbUserKeepers, _keepers_gc_threshold);
129 STACK_CHECK(L_, 0);
130
131 // Initialize 'timerLinda'; a common Linda object shared by all states
132 lua_pushcfunction(L_, LG_linda); // L_: settings lanes.linda
133 std::ignore = lua_pushstringview(L_, "lanes-timer"); // L_: settings lanes.linda "lanes-timer"
134 lua_pushinteger(L_, 0); // L_: settings lanes.linda "lanes-timer" 0
135 lua_call(L_, 2, 1); // L_: settings linda
85 STACK_CHECK(L_, 1); 136 STACK_CHECK(L_, 1);
137
138 // Proxy userdata contents is only a 'DeepPrelude*' pointer
139 _U->timerLinda = *lua_tofulluserdata<DeepPrelude*>(L_, -1);
140 // increment refcount so that this linda remains alive as long as the universe exists.
141 _U->timerLinda->refcount.fetch_add(1, std::memory_order_relaxed);
142 lua_pop(L_, 1); // L_: settings
143 STACK_CHECK(L_, 0);
86 return _U; 144 return _U;
87} 145}
88 146
@@ -113,45 +171,6 @@ Universe::Universe()
113 171
114// ################################################################################################# 172// #################################################################################################
115 173
116/*
117 * Pool of keeper states
118 *
119 * Access to keeper states is locked (only one OS thread at a time) so the
120 * bigger the pool, the less chances of unnecessary waits. Lindas map to the
121 * keepers randomly, by a hash.
122 */
123
124// called as __gc for the keepers array userdata
125void Universe::closeKeepers()
126{
127 if (keepers != nullptr) {
128 int _nbKeepers{ keepers->nb_keepers };
129 // 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
130 // 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
131 // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success
132 // which is early-outed with a keepers->nbKeepers null-check
133 keepers->nb_keepers = 0;
134 for (int const _i : std::ranges::iota_view{ 0, _nbKeepers }) {
135 lua_State* const _K{ keepers->keeper_array[_i].L };
136 keepers->keeper_array[_i].L = KeeperState{ nullptr };
137 if (_K != nullptr) {
138 lua_close(_K);
139 } else {
140 // detected partial init: destroy only the mutexes that got initialized properly
141 _nbKeepers = _i;
142 }
143 }
144 for (int const _i : std::ranges::iota_view{ 0, _nbKeepers }) {
145 keepers->keeper_array[_i].~Keeper();
146 }
147 // free the keeper bookkeeping structure
148 internalAllocator.free(keepers, sizeof(Keepers) + (_nbKeepers - 1) * sizeof(Keeper));
149 keepers = nullptr;
150 }
151}
152
153// #################################################################################################
154
155// called once at the creation of the universe (therefore L_ is the master Lua state everything originates from) 174// called once at the creation of the universe (therefore L_ is the master Lua state everything originates from)
156// Do I need to disable this when compiling for LuaJIT to prevent issues? 175// Do I need to disable this when compiling for LuaJIT to prevent issues?
157void Universe::initializeAllocatorFunction(lua_State* const L_) 176void Universe::initializeAllocatorFunction(lua_State* const L_)
@@ -224,113 +243,6 @@ int Universe::InitializeFinalizer(lua_State* const L_)
224 243
225// ################################################################################################# 244// #################################################################################################
226 245
227/*
228 * Initialize keeper states
229 *
230 * If there is a problem, returns nullptr and pushes the error message on the stack
231 * else returns the keepers bookkeeping structure.
232 *
233 * Note: Any problems would be design flaws; the created Lua state is left
234 * unclosed, because it does not really matter. In production code, this
235 * function never fails.
236 * settings table is expected at position 1 on the stack
237 */
238void Universe::initializeKeepers(lua_State* const L_)
239{
240 LUA_ASSERT(L_, lua_gettop(L_) == 1 && lua_istable(L_, 1));
241 STACK_CHECK_START_REL(L_, 0); // L_: settings
242 std::ignore = luaG_getfield(L_, 1, "nb_keepers"); // L_: settings nb_keepers
243 int const _nb_keepers{ static_cast<int>(lua_tointeger(L_, -1)) };
244 lua_pop(L_, 1); // L_: settings
245 if (_nb_keepers < 1) {
246 raise_luaL_error(L_, "Bad number of keepers (%d)", _nb_keepers);
247 }
248 STACK_CHECK(L_, 0);
249
250 std::ignore = luaG_getfield(L_, 1, "keepers_gc_threshold"); // L_: settings keepers_gc_threshold
251 int const keepers_gc_threshold{ static_cast<int>(lua_tointeger(L_, -1)) };
252 lua_pop(L_, 1); // L_: settings
253 STACK_CHECK(L_, 0);
254
255 // Keepers contains an array of 1 Keeper, adjust for the actual number of keeper states
256 {
257 size_t const _bytes{ sizeof(Keepers) + (_nb_keepers - 1) * sizeof(Keeper) };
258 keepers = static_cast<Keepers*>(internalAllocator.alloc(_bytes));
259 if (keepers == nullptr) {
260 raise_luaL_error(L_, "out of memory while creating keepers");
261 }
262 keepers->Keepers::Keepers();
263 keepers->gc_threshold = keepers_gc_threshold;
264 keepers->nb_keepers = _nb_keepers;
265
266 // we have to manually call the Keeper constructor on the additional array slots
267 for (int const _i : std::ranges::iota_view{ 1, _nb_keepers }) {
268 new (&keepers->keeper_array[_i]) Keeper{}; // placement new
269 }
270 }
271
272 for (int const _i : std::ranges::iota_view{ 0, _nb_keepers }) {
273 // note that we will leak K if we raise an error later
274 KeeperState const _K{ state::CreateState(this, L_) }; // L_: settings K:
275 if (_K == nullptr) {
276 raise_luaL_error(L_, "out of memory while creating keeper states");
277 }
278
279 keepers->keeper_array[_i].L = _K;
280
281 if (keepers->gc_threshold >= 0) {
282 lua_gc(_K, LUA_GCSTOP, 0);
283 }
284
285 STACK_CHECK_START_ABS(_K, 0);
286
287 // copy the universe pointer in the keeper itself
288 Universe::Store(_K, this);
289 STACK_CHECK(_K, 0);
290
291 // make sure 'package' is initialized in keeper states, so that we have require()
292 // this because this is needed when transferring deep userdata object
293 luaL_requiref(_K, LUA_LOADLIBNAME, luaopen_package, 1); // L_: settings K: package
294 lua_pop(_K, 1); // L_: settings K:
295 STACK_CHECK(_K, 0);
296 tools::SerializeRequire(_K);
297 STACK_CHECK(_K, 0);
298
299 // copy package.path and package.cpath from the source state
300 if (luaG_getmodule(L_, LUA_LOADLIBNAME) != LuaType::NIL) { // L_: settings package K:
301 // when copying with mode LookupMode::ToKeeper, error message is pushed at the top of the stack, not raised immediately
302 InterCopyContext _c{ this, DestState{ _K }, SourceState{ L_ }, {}, SourceIndex{ lua_absindex(L_, -1) }, {}, LookupMode::ToKeeper, {} };
303 if (_c.inter_copy_package() != InterCopyResult::Success) { // L_: settings ... error_msg K:
304 // if something went wrong, the error message is at the top of the stack
305 lua_remove(L_, -2); // L_: settings error_msg
306 raise_lua_error(L_);
307 }
308 }
309 lua_pop(L_, 1); // L_: settings K:
310 STACK_CHECK(L_, 0);
311 STACK_CHECK(_K, 0);
312
313 // attempt to call on_state_create(), if we have one and it is a C function
314 // (only support a C function because we can't transfer executable Lua code in keepers)
315 // will raise an error in L_ in case of problem
316 state::CallOnStateCreate(this, _K, L_, LookupMode::ToKeeper);
317
318 // to see VM name in Decoda debugger
319 lua_pushfstring(_K, "Keeper #%d", _i + 1); // L_: settings K: "Keeper #n"
320 if constexpr (HAVE_DECODA_NAME()) {
321 lua_pushvalue(_K, -1); // K: "Keeper #n" Keeper #n"
322 lua_setglobal(_K, "decoda_name"); // L_: settings K: "Keeper #n"
323 }
324 kLaneNameRegKey.setValue(_K, [](lua_State* L_) { lua_insert(L_, -2); }); // K:
325 // create the fifos table in the keeper state
326 Keepers::CreateFifosTable(_K);
327 STACK_CHECK(_K, 0);
328 }
329 STACK_CHECK(L_, 0);
330}
331
332// #################################################################################################
333
334void Universe::terminateFreeRunningLanes(lua_State* const L_, lua_Duration const shutdownTimeout_, CancelOp const op_) 246void Universe::terminateFreeRunningLanes(lua_State* const L_, lua_Duration const shutdownTimeout_, CancelOp const op_)
335{ 247{
336 if (selfdestructFirst != SELFDESTRUCT_END) { 248 if (selfdestructFirst != SELFDESTRUCT_END) {
@@ -425,7 +337,7 @@ LUAG_FUNC(universe_gc)
425 _U->timerLinda = nullptr; 337 _U->timerLinda = nullptr;
426 } 338 }
427 339
428 _U->closeKeepers(); 340 _U->keepers.close();
429 341
430 // remove the protected allocator, if any 342 // remove the protected allocator, if any
431 _U->protectedAllocator.removeFrom(L_); 343 _U->protectedAllocator.removeFrom(L_);
diff --git a/src/universe.h b/src/universe.h
index 7624fed..7b9f309 100644
--- a/src/universe.h
+++ b/src/universe.h
@@ -9,10 +9,12 @@ extern "C"
9} 9}
10#endif // __cplusplus 10#endif // __cplusplus
11 11
12#include "keeper.h"
12#include "lanesconf.h" 13#include "lanesconf.h"
13#include "tracker.h" 14#include "tracker.h"
14#include "uniquekey.h" 15#include "uniquekey.h"
15 16
17#include <atomic>
16#include <mutex> 18#include <mutex>
17 19
18// ################################################################################################# 20// #################################################################################################
@@ -20,7 +22,6 @@ extern "C"
20// forwards 22// forwards
21enum class CancelOp; 23enum class CancelOp;
22struct DeepPrelude; 24struct DeepPrelude;
23struct Keepers;
24class Lane; 25class Lane;
25 26
26// ################################################################################################# 27// #################################################################################################
@@ -144,7 +145,7 @@ class Universe
144 145
145 AllocatorDefinition internalAllocator; 146 AllocatorDefinition internalAllocator;
146 147
147 Keepers* keepers{ nullptr }; 148 Keepers keepers;
148 149
149 // Initialized by 'init_once_LOCKED()': the deep userdata Linda object 150 // Initialized by 'init_once_LOCKED()': the deep userdata Linda object
150 // used for timers (each lane will get a proxy to this) 151 // used for timers (each lane will get a proxy to this)
@@ -183,12 +184,10 @@ class Universe
183 Universe& operator=(Universe const&) = delete; 184 Universe& operator=(Universe const&) = delete;
184 Universe& operator=(Universe&&) = delete; 185 Universe& operator=(Universe&&) = delete;
185 186
186 void closeKeepers();
187 [[nodiscard]] static Universe* Create(lua_State* L_); 187 [[nodiscard]] static Universe* Create(lua_State* L_);
188 [[nodiscard]] static inline Universe* Get(lua_State* L_); 188 [[nodiscard]] static inline Universe* Get(lua_State* L_);
189 void initializeAllocatorFunction(lua_State* L_); 189 void initializeAllocatorFunction(lua_State* L_);
190 static int InitializeFinalizer(lua_State* L_); 190 static int InitializeFinalizer(lua_State* L_);
191 void initializeKeepers(lua_State* L_);
192 static inline void Store(lua_State* L_, Universe* U_); 191 static inline void Store(lua_State* L_, Universe* U_);
193 void terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_); 192 void terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_);
194}; 193};