aboutsummaryrefslogtreecommitdiff
path: root/src/universe.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/universe.cpp')
-rw-r--r--src/universe.cpp222
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
87extern "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
118void 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?
150void 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 */
212void 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
82void Universe::terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_) 302void 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_);