aboutsummaryrefslogtreecommitdiff
path: root/src/universe.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/universe.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/universe.cpp')
-rw-r--r--src/universe.cpp210
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
41extern 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
125void 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?
157void Universe::initializeAllocatorFunction(lua_State* const L_) 176void 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 */
238void 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
334void Universe::terminateFreeRunningLanes(lua_State* const L_, lua_Duration const shutdownTimeout_, CancelOp const op_) 246void 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_);