aboutsummaryrefslogtreecommitdiff
path: root/src/keeper.cpp
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/keeper.cpp
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/keeper.cpp')
-rw-r--r--src/keeper.cpp236
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
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}