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/universe.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/universe.cpp')
-rw-r--r-- | src/universe.cpp | 210 |
1 files changed, 61 insertions, 149 deletions
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_); |