diff options
Diffstat (limited to 'src/universe.cpp')
-rw-r--r-- | src/universe.cpp | 222 |
1 files changed, 221 insertions, 1 deletions
diff --git a/src/universe.cpp b/src/universe.cpp index dd293f2..9ab1290 100644 --- a/src/universe.cpp +++ b/src/universe.cpp | |||
@@ -31,8 +31,10 @@ THE SOFTWARE. | |||
31 | #include "universe.h" | 31 | #include "universe.h" |
32 | 32 | ||
33 | #include "deep.h" | 33 | #include "deep.h" |
34 | #include "intercopycontext.h" | ||
34 | #include "keeper.h" | 35 | #include "keeper.h" |
35 | #include "lane.h" | 36 | #include "lane.h" |
37 | #include "state.h" | ||
36 | 38 | ||
37 | // ################################################################################################# | 39 | // ################################################################################################# |
38 | 40 | ||
@@ -78,6 +80,224 @@ Universe::Universe() | |||
78 | } | 80 | } |
79 | 81 | ||
80 | // ################################################################################################# | 82 | // ################################################################################################# |
83 | // ################################### custom allocator support #################################### | ||
84 | // ################################################################################################# | ||
85 | |||
86 | // same as PUC-Lua l_alloc | ||
87 | extern "C" [[nodiscard]] static void* libc_lua_Alloc([[maybe_unused]] void* ud_, [[maybe_unused]] void* ptr_, [[maybe_unused]] size_t osize_, size_t nsize_) | ||
88 | { | ||
89 | if (nsize_ == 0) { | ||
90 | free(ptr_); | ||
91 | return nullptr; | ||
92 | } else { | ||
93 | return realloc(ptr_, nsize_); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | // ################################################################################################# | ||
98 | |||
99 | [[nodiscard]] static int luaG_provide_protected_allocator(lua_State* L_) | ||
100 | { | ||
101 | Universe* const _U{ universe_get(L_) }; | ||
102 | // push a new full userdata on the stack, giving access to the universe's protected allocator | ||
103 | [[maybe_unused]] AllocatorDefinition* const def{ new (L_) AllocatorDefinition{ _U->protectedAllocator.makeDefinition() } }; | ||
104 | return 1; | ||
105 | } | ||
106 | |||
107 | // ################################################################################################# | ||
108 | |||
109 | /* | ||
110 | * Pool of keeper states | ||
111 | * | ||
112 | * Access to keeper states is locked (only one OS thread at a time) so the | ||
113 | * bigger the pool, the less chances of unnecessary waits. Lindas map to the | ||
114 | * keepers randomly, by a hash. | ||
115 | */ | ||
116 | |||
117 | // called as __gc for the keepers array userdata | ||
118 | void Universe::closeKeepers() | ||
119 | { | ||
120 | if (keepers != nullptr) { | ||
121 | int _nbKeepers{ keepers->nb_keepers }; | ||
122 | // 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 | ||
123 | // 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 | ||
124 | // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success | ||
125 | // which is early-outed with a keepers->nbKeepers null-check | ||
126 | keepers->nb_keepers = 0; | ||
127 | for (int _i = 0; _i < _nbKeepers; ++_i) { | ||
128 | lua_State* const _K{ keepers->keeper_array[_i].L }; | ||
129 | keepers->keeper_array[_i].L = KeeperState{ nullptr }; | ||
130 | if (_K != nullptr) { | ||
131 | lua_close(_K); | ||
132 | } else { | ||
133 | // detected partial init: destroy only the mutexes that got initialized properly | ||
134 | _nbKeepers = _i; | ||
135 | } | ||
136 | } | ||
137 | for (int _i = 0; _i < _nbKeepers; ++_i) { | ||
138 | keepers->keeper_array[_i].~Keeper(); | ||
139 | } | ||
140 | // free the keeper bookkeeping structure | ||
141 | internalAllocator.free(keepers, sizeof(Keepers) + (_nbKeepers - 1) * sizeof(Keeper)); | ||
142 | keepers = nullptr; | ||
143 | } | ||
144 | } | ||
145 | |||
146 | // ################################################################################################# | ||
147 | |||
148 | // called once at the creation of the universe (therefore L_ is the master Lua state everything originates from) | ||
149 | // Do I need to disable this when compiling for LuaJIT to prevent issues? | ||
150 | void Universe::initializeAllocatorFunction(lua_State* L_) | ||
151 | { | ||
152 | STACK_CHECK_START_REL(L_, 1); // L_: settings | ||
153 | lua_getfield(L_, -1, "allocator"); // L_: settings allocator|nil|"protected" | ||
154 | if (!lua_isnil(L_, -1)) { | ||
155 | // store C function pointer in an internal variable | ||
156 | provideAllocator = lua_tocfunction(L_, -1); // L_: settings allocator | ||
157 | if (provideAllocator != nullptr) { | ||
158 | // make sure the function doesn't have upvalues | ||
159 | char const* upname = lua_getupvalue(L_, -1, 1); // L_: settings allocator upval? | ||
160 | if (upname != nullptr) { // should be "" for C functions with upvalues if any | ||
161 | raise_luaL_error(L_, "config.allocator() shouldn't have upvalues"); | ||
162 | } | ||
163 | // remove this C function from the config table so that it doesn't cause problems | ||
164 | // when we transfer the config table in newly created Lua states | ||
165 | lua_pushnil(L_); // L_: settings allocator nil | ||
166 | lua_setfield(L_, -3, "allocator"); // L_: settings allocator | ||
167 | } else if (lua_type(L_, -1) == LUA_TSTRING) { // should be "protected" | ||
168 | LUA_ASSERT(L_, strcmp(lua_tostring(L_, -1), "protected") == 0); | ||
169 | // set the original allocator to call from inside protection by the mutex | ||
170 | protectedAllocator.initFrom(L_); | ||
171 | protectedAllocator.installIn(L_); | ||
172 | // before a state is created, this function will be called to obtain the allocator | ||
173 | provideAllocator = luaG_provide_protected_allocator; | ||
174 | } | ||
175 | } else { | ||
176 | // just grab whatever allocator was provided to lua_newstate | ||
177 | protectedAllocator.initFrom(L_); | ||
178 | } | ||
179 | lua_pop(L_, 1); // L_: settings | ||
180 | STACK_CHECK(L_, 1); | ||
181 | |||
182 | lua_getfield(L_, -1, "internal_allocator"); // L_: settings "libc"|"allocator" | ||
183 | { | ||
184 | char const* const _allocator{ lua_tostring(L_, -1) }; | ||
185 | if (strcmp(_allocator, "libc") == 0) { | ||
186 | internalAllocator = AllocatorDefinition{ libc_lua_Alloc, nullptr }; | ||
187 | } else if (provideAllocator == luaG_provide_protected_allocator) { | ||
188 | // user wants mutex protection on the state's allocator. Use protection for our own allocations too, just in case. | ||
189 | internalAllocator = protectedAllocator.makeDefinition(); | ||
190 | } else { | ||
191 | // no protection required, just use whatever we have as-is. | ||
192 | internalAllocator = protectedAllocator; | ||
193 | } | ||
194 | } | ||
195 | lua_pop(L_, 1); // L_: settings | ||
196 | STACK_CHECK(L_, 1); | ||
197 | } | ||
198 | |||
199 | // ################################################################################################# | ||
200 | |||
201 | /* | ||
202 | * Initialize keeper states | ||
203 | * | ||
204 | * If there is a problem, returns nullptr and pushes the error message on the stack | ||
205 | * else returns the keepers bookkeeping structure. | ||
206 | * | ||
207 | * Note: Any problems would be design flaws; the created Lua state is left | ||
208 | * unclosed, because it does not really matter. In production code, this | ||
209 | * function never fails. | ||
210 | * settings table is expected at position 1 on the stack | ||
211 | */ | ||
212 | void Universe::initializeKeepers(lua_State* L_) | ||
213 | { | ||
214 | LUA_ASSERT(L_, lua_gettop(L_) == 1 && lua_istable(L_, 1)); | ||
215 | STACK_CHECK_START_REL(L_, 0); // L_: settings | ||
216 | lua_getfield(L_, 1, "nb_keepers"); // L_: settings nb_keepers | ||
217 | int const _nb_keepers{ static_cast<int>(lua_tointeger(L_, -1)) }; | ||
218 | lua_pop(L_, 1); // L_: settings | ||
219 | if (_nb_keepers < 1) { | ||
220 | raise_luaL_error(L_, "Bad number of keepers (%d)", _nb_keepers); | ||
221 | } | ||
222 | STACK_CHECK(L_, 0); | ||
223 | |||
224 | lua_getfield(L_, 1, "keepers_gc_threshold"); // L_: settings keepers_gc_threshold | ||
225 | int const keepers_gc_threshold{ static_cast<int>(lua_tointeger(L_, -1)) }; | ||
226 | lua_pop(L_, 1); // L_: settings | ||
227 | STACK_CHECK(L_, 0); | ||
228 | |||
229 | // Keepers contains an array of 1 Keeper, adjust for the actual number of keeper states | ||
230 | { | ||
231 | size_t const bytes = sizeof(Keepers) + (_nb_keepers - 1) * sizeof(Keeper); | ||
232 | keepers = static_cast<Keepers*>(internalAllocator.alloc(bytes)); | ||
233 | if (keepers == nullptr) { | ||
234 | raise_luaL_error(L_, "out of memory while creating keepers"); | ||
235 | } | ||
236 | keepers->Keepers::Keepers(); | ||
237 | keepers->gc_threshold = keepers_gc_threshold; | ||
238 | keepers->nb_keepers = _nb_keepers; | ||
239 | |||
240 | for (int _i = 0; _i < _nb_keepers; ++_i) { | ||
241 | keepers->keeper_array[_i].Keeper::Keeper(); | ||
242 | } | ||
243 | } | ||
244 | for (int _i = 0; _i < _nb_keepers; ++_i) { | ||
245 | // note that we will leak K if we raise an error later | ||
246 | KeeperState const _K{ create_state(this, L_) }; // L_: settings K: | ||
247 | if (_K == nullptr) { | ||
248 | raise_luaL_error(L_, "out of memory while creating keeper states"); | ||
249 | } | ||
250 | |||
251 | keepers->keeper_array[_i].L = _K; | ||
252 | |||
253 | if (keepers->gc_threshold >= 0) { | ||
254 | lua_gc(_K, LUA_GCSTOP, 0); | ||
255 | } | ||
256 | |||
257 | STACK_CHECK_START_ABS(_K, 0); | ||
258 | |||
259 | // copy the universe pointer in the keeper itself | ||
260 | universe_store(_K, this); | ||
261 | STACK_CHECK(_K, 0); | ||
262 | |||
263 | // make sure 'package' is initialized in keeper states, so that we have require() | ||
264 | // this because this is needed when transferring deep userdata object | ||
265 | luaL_requiref(_K, LUA_LOADLIBNAME, luaopen_package, 1); // L_: settings K: package | ||
266 | lua_pop(_K, 1); // L_: settings K: | ||
267 | STACK_CHECK(_K, 0); | ||
268 | serialize_require(DEBUGSPEW_PARAM_COMMA(this) _K); | ||
269 | STACK_CHECK(_K, 0); | ||
270 | |||
271 | // copy package.path and package.cpath from the source state | ||
272 | if (luaG_getmodule(L_, LUA_LOADLIBNAME) != LuaType::NIL) { // L_: settings package K: | ||
273 | // when copying with mode LookupMode::ToKeeper, error message is pushed at the top of the stack, not raised immediately | ||
274 | InterCopyContext _c{ this, DestState{ _K }, SourceState{ L_ }, {}, SourceIndex{ lua_absindex(L_, -1) }, {}, LookupMode::ToKeeper, {} }; | ||
275 | if (_c.inter_copy_package() != InterCopyResult::Success) { // L_: settings ... error_msg K: | ||
276 | // if something went wrong, the error message is at the top of the stack | ||
277 | lua_remove(L_, -2); // L_: settings error_msg | ||
278 | raise_lua_error(L_); | ||
279 | } | ||
280 | } | ||
281 | lua_pop(L_, 1); // L_: settings K: | ||
282 | STACK_CHECK(L_, 0); | ||
283 | STACK_CHECK(_K, 0); | ||
284 | |||
285 | // attempt to call on_state_create(), if we have one and it is a C function | ||
286 | // (only support a C function because we can't transfer executable Lua code in keepers) | ||
287 | // will raise an error in L_ in case of problem | ||
288 | CallOnStateCreate(this, _K, L_, LookupMode::ToKeeper); | ||
289 | |||
290 | // to see VM name in Decoda debugger | ||
291 | lua_pushfstring(_K, "Keeper #%d", _i + 1); // L_: settings K: "Keeper #n" | ||
292 | lua_setglobal(_K, "decoda_name"); // L_: settings K: | ||
293 | // create the fifos table in the keeper state | ||
294 | Keepers::CreateFifosTable(_K); | ||
295 | STACK_CHECK(_K, 0); | ||
296 | } | ||
297 | STACK_CHECK(L_, 0); | ||
298 | } | ||
299 | |||
300 | // ################################################################################################# | ||
81 | 301 | ||
82 | void Universe::terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_) | 302 | void Universe::terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_) |
83 | { | 303 | { |
@@ -160,7 +380,7 @@ int universe_gc(lua_State* L_) | |||
160 | _U->timerLinda = nullptr; | 380 | _U->timerLinda = nullptr; |
161 | } | 381 | } |
162 | 382 | ||
163 | close_keepers(_U); | 383 | _U->closeKeepers(); |
164 | 384 | ||
165 | // remove the protected allocator, if any | 385 | // remove the protected allocator, if any |
166 | _U->protectedAllocator.removeFrom(L_); | 386 | _U->protectedAllocator.removeFrom(L_); |