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/keeper.cpp | |
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/keeper.cpp')
-rw-r--r-- | src/keeper.cpp | 236 |
1 files changed, 221 insertions, 15 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 | } |