diff options
author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-30 17:57:21 +0200 |
---|---|---|
committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-30 17:57:21 +0200 |
commit | 731556711e453a501f1d1d06a6013b8fbd53414e (patch) | |
tree | 4c5c28cd83de320fcf4c9b4c749f2e6e8d5bef48 /src | |
parent | a156aaeb07fada043b308409dcffcae1726eec0b (diff) | |
download | lanes-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.cpp | 236 | ||||
-rw-r--r-- | src/keeper.h | 53 | ||||
-rw-r--r-- | src/lane.cpp | 2 | ||||
-rw-r--r-- | src/lanes.cpp | 43 | ||||
-rw-r--r-- | src/lanes.lua | 10 | ||||
-rw-r--r-- | src/lanesconf.h | 2 | ||||
-rw-r--r-- | src/linda.cpp | 25 | ||||
-rw-r--r-- | src/linda.h | 3 | ||||
-rw-r--r-- | src/universe.cpp | 210 | ||||
-rw-r--r-- | src/universe.h | 7 |
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 | ||
549 | Keeper* Linda::acquireKeeper() const | 550 | Keeper* 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 | ||
563 | void Linda::releaseKeeper(Keeper* K_) const | 562 | void 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 | ||
636 | void Keepers::CreateFifosTable(lua_State* L_) | 639 | void* 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 | ||
648 | void 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 | |||
659 | void 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 | |||
681 | void 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 | |||
768 | void 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 |
18 | class Linda; | 20 | class Linda; |
@@ -21,22 +23,63 @@ class Universe; | |||
21 | 23 | ||
22 | using KeeperState = Unique<lua_State*>; | 24 | using KeeperState = Unique<lua_State*>; |
23 | 25 | ||
26 | // ################################################################################################# | ||
27 | |||
24 | struct Keeper | 28 | struct 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 | |||
31 | struct Keepers | 49 | struct 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 |
41 | static constexpr UniqueKey kNilSentinel{ 0xC457D4EDDB05B5E4ull, "lanes.null" }; | 84 | static 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 | ||
91 | local default_params = | 91 | local 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 | ||
115 | local param_checkers = | 115 | local 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 | ||
115 | static constexpr uintptr_t kPointerMagicShift{ 3 }; | ||
116 | |||
117 | Linda::Linda(Universe* U_, LindaGroup group_, std::string_view const& name_) | 112 | Linda::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 | ||
753 | namespace { | 751 | namespace { |
@@ -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 { | |||
784 | LUAG_FUNC(linda) | 784 | LUAG_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 | ||
41 | extern 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 | ||
125 | void 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? |
157 | void Universe::initializeAllocatorFunction(lua_State* const L_) | 176 | void 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 | */ | ||
238 | void 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 | |||
334 | void Universe::terminateFreeRunningLanes(lua_State* const L_, lua_Duration const shutdownTimeout_, CancelOp const op_) | 246 | void 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 |
21 | enum class CancelOp; | 23 | enum class CancelOp; |
22 | struct DeepPrelude; | 24 | struct DeepPrelude; |
23 | struct Keepers; | ||
24 | class Lane; | 25 | class 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 | }; |