aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2025-04-07 08:50:10 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2025-04-07 08:50:10 +0200
commit98351dc6e48dabc02a941872b0aad7f795e665bd (patch)
treeaaa37853fca9437759dd2312debfed585700a608
parent5e29202b78d4932db079b21f0324ea57d8902bda (diff)
downloadlanes-98351dc6e48dabc02a941872b0aad7f795e665bd.tar.gz
lanes-98351dc6e48dabc02a941872b0aad7f795e665bd.tar.bz2
lanes-98351dc6e48dabc02a941872b0aad7f795e665bd.zip
Fix crash with multi keepers (invalid memory access on close)
Diffstat (limited to '')
-rw-r--r--Makefile8
-rw-r--r--src/keeper.cpp36
-rw-r--r--src/keeper.hpp13
-rw-r--r--unit_tests/init_and_shutdown.cpp7
4 files changed, 29 insertions, 35 deletions
diff --git a/Makefile b/Makefile
index 7200939..5b5d27a 100644
--- a/Makefile
+++ b/Makefile
@@ -79,7 +79,13 @@ build_DUE:
79# also run a test that shows whether lanes is successfully loaded or not 79# also run a test that shows whether lanes is successfully loaded or not
80run_unit_tests: build_lanes build_unit_tests build_DUE 80run_unit_tests: build_lanes build_unit_tests build_DUE
81 @echo ========================================================================================= 81 @echo =========================================================================================
82 $(_PREFIX) $(_UNITTEST_TARGET) -s 82 $(_PREFIX) $(_UNITTEST_TARGET) --list-tests
83 $(_PREFIX) $(_UNITTEST_TARGET) --rng-seed 0 -s
84
85debug_unit_tests: build_lanes build_unit_tests build_DUE
86 @echo =========================================================================================
87 $(_PREFIX) $(_UNITTEST_TARGET) --list-tests
88 $(_PREFIX) gdb --args $(_UNITTEST_TARGET) --rng-seed 0 -s "lanes.configure.nb_user_keepers"
83 89
84clean: 90clean:
85 cd src && $(MAKE) -f Lanes.makefile clean 91 cd src && $(MAKE) -f Lanes.makefile clean
diff --git a/src/keeper.cpp b/src/keeper.cpp
index cad9207..c8c470f 100644
--- a/src/keeper.cpp
+++ b/src/keeper.cpp
@@ -811,22 +811,6 @@ KeeperCallResult keeper_call(KeeperState const K_, keeper_api_t const func_, lua
811// ################################################################################################# 811// #################################################################################################
812// ################################################################################################# 812// #################################################################################################
813 813
814void* Keeper::operator new[](size_t size_, Universe* U_) noexcept
815{
816 // size_ is the memory for the element count followed by the elements themselves
817 return U_->internalAllocator.alloc(size_);
818}
819
820// #################################################################################################
821
822// can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception
823void Keeper::operator delete[](void* p_, Universe* U_)
824{
825 U_->internalAllocator.free(p_, *static_cast<size_t*>(p_) * sizeof(Keeper) + sizeof(size_t));
826}
827
828// #################################################################################################
829
830// only used by linda:dump() and linda:__towatch() for debugging purposes 814// only used by linda:dump() and linda:__towatch() for debugging purposes
831// table is populated as follows: 815// table is populated as follows:
832// { 816// {
@@ -927,7 +911,7 @@ void Keepers::DeleteKV::operator()(Keeper* const k_) const
927 for (auto& _k : std::span<Keeper>(k_, count)) { 911 for (auto& _k : std::span<Keeper>(k_, count)) {
928 _k.~Keeper(); 912 _k.~Keeper();
929 } 913 }
930 U->internalAllocator.free(k_, count * sizeof(Keeper)); 914 U.internalAllocator.free(k_, count * sizeof(Keeper));
931} 915}
932 916
933// ################################################################################################# 917// #################################################################################################
@@ -959,8 +943,8 @@ void Keepers::collectGarbage()
959 // 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 943 // 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
960 // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success 944 // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success
961 // which is early-outed with a keepers->nbKeepers null-check 945 // which is early-outed with a keepers->nbKeepers null-check
962 for (size_t const _i : std::ranges::iota_view{ size_t{ 0 }, _kv.nbKeepers }) { 946 for (Keeper& _k : std::span<Keeper>{ _kv.keepers.get(), _kv.nbKeepers }) {
963 _gcOneKeeper(_kv.keepers[_i]); 947 _gcOneKeeper(_k);
964 } 948 }
965 } 949 }
966} 950}
@@ -996,9 +980,8 @@ void Keepers::close()
996 // 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 980 // 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
997 // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success 981 // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success
998 // which is early-outed with a keepers->nbKeepers null-check 982 // which is early-outed with a keepers->nbKeepers null-check
999 size_t const _nbKeepers{ std::exchange(_kv.nbKeepers, size_t{ 0 }) }; 983 for (Keeper& _k : std::span<Keeper>{ _kv.keepers.get(), std::exchange(_kv.nbKeepers, size_t{ 0 }) }) {
1000 for (size_t const _i : std::ranges::iota_view{ size_t{ 0 }, _nbKeepers }) { 984 if (!_closeOneKeeper(_k)) {
1001 if (!_closeOneKeeper(_kv.keepers[_i])) {
1002 // detected partial init: destroy only the mutexes that got initialized properly 985 // detected partial init: destroy only the mutexes that got initialized properly
1003 break; 986 break;
1004 } 987 }
@@ -1137,11 +1120,14 @@ void Keepers::initialize(Universe& U_, lua_State* L_, size_t const nbKeepers_, i
1137 1120
1138 default: 1121 default:
1139 KV& _kv = keeper_array.emplace<KV>( 1122 KV& _kv = keeper_array.emplace<KV>(
1140 std::unique_ptr<Keeper[], DeleteKV>{ new(&U_) Keeper[nbKeepers_], DeleteKV{ &U_, nbKeepers_ } }, 1123 std::unique_ptr<Keeper, DeleteKV>{ static_cast<Keeper*>(U_.internalAllocator.alloc(sizeof(Keeper) * nbKeepers_)), DeleteKV{ U_, nbKeepers_ } },
1141 nbKeepers_ 1124 nbKeepers_
1142 ); 1125 );
1143 for (size_t const _i : std::ranges::iota_view{ size_t{ 0 }, nbKeepers_ }) { 1126 // fak. std::ranges::views::enumerate is c++23 (would help having item and index iterated over simultaneously)
1144 _initOneKeeper(_kv.keepers[_i], static_cast<int>(_i)); 1127 int _i{};
1128 for (Keeper& _k : std::span<Keeper>{ _kv.keepers.get(), nbKeepers_ }) {
1129 new (&_k) Keeper{};
1130 _initOneKeeper(_k, _i++);
1145 } 1131 }
1146 } 1132 }
1147} 1133}
diff --git a/src/keeper.hpp b/src/keeper.hpp
index 5cebe07..f1083b3 100644
--- a/src/keeper.hpp
+++ b/src/keeper.hpp
@@ -26,12 +26,6 @@ struct Keeper
26 std::mutex mutex; 26 std::mutex mutex;
27 KeeperState K{ static_cast<lua_State*>(nullptr) }; 27 KeeperState K{ static_cast<lua_State*>(nullptr) };
28 28
29 [[nodiscard]]
30 static void* operator new[](size_t size_, Universe* U_) noexcept;
31 // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception
32 static void operator delete[](void* p_, Universe* U_);
33
34
35 ~Keeper() = default; 29 ~Keeper() = default;
36 Keeper() = default; 30 Keeper() = default;
37 // non-copyable, non-movable 31 // non-copyable, non-movable
@@ -51,14 +45,15 @@ struct Keepers
51 private: 45 private:
52 struct DeleteKV 46 struct DeleteKV
53 { 47 {
54 Universe* U{}; 48 Universe& U;
55 size_t count{}; 49 size_t count{};
56 void operator()(Keeper* k_) const; 50 void operator()(Keeper* k_) const;
57 }; 51 };
58 // can't use std::vector<Keeper> because Keeper contains a mutex, so we need a raw memory buffer 52 // can't use std::unique_ptr<Keeper[]> because of interactions with placement new and custom deleters
53 // and I'm not using std::vector<Keeper> because I don't have an allocator to plug on the Universe (yet)
59 struct KV 54 struct KV
60 { 55 {
61 std::unique_ptr<Keeper[], DeleteKV> keepers; 56 std::unique_ptr<Keeper, DeleteKV> keepers;
62 size_t nbKeepers{}; 57 size_t nbKeepers{};
63 }; 58 };
64 std::variant<std::monostate, Keeper, KV> keeper_array; 59 std::variant<std::monostate, Keeper, KV> keeper_array;
diff --git a/unit_tests/init_and_shutdown.cpp b/unit_tests/init_and_shutdown.cpp
index 53f4a2a..384af43 100644
--- a/unit_tests/init_and_shutdown.cpp
+++ b/unit_tests/init_and_shutdown.cpp
@@ -323,6 +323,13 @@ TEST_CASE("lanes.configure.nb_user_keepers")
323 323
324 // ----------------------------------------------------------------------------------------- 324 // -----------------------------------------------------------------------------------------
325 325
326 SECTION("nb_user_keepers = 1")
327 {
328 L.requireSuccess("require 'lanes'.configure{nb_user_keepers = 1}");
329 }
330
331 // -----------------------------------------------------------------------------------------
332
326 SECTION("nb_user_keepers = 100") 333 SECTION("nb_user_keepers = 100")
327 { 334 {
328 L.requireSuccess("require 'lanes'.configure{nb_user_keepers = 100}"); 335 L.requireSuccess("require 'lanes'.configure{nb_user_keepers = 100}");