diff options
author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-07-29 18:07:16 +0200 |
---|---|---|
committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-07-29 18:07:16 +0200 |
commit | bb11006f635a69dd9244e09e4359ed02eff8fe84 (patch) | |
tree | 7fda9eda216b1683ff3dd954b6300c13bfaf2b14 /src | |
parent | 34b2b5e712ea1cc59004ca48f79f54af162993a5 (diff) | |
download | lanes-bb11006f635a69dd9244e09e4359ed02eff8fe84.tar.gz lanes-bb11006f635a69dd9244e09e4359ed02eff8fe84.tar.bz2 lanes-bb11006f635a69dd9244e09e4359ed02eff8fe84.zip |
Internal refactorization to properly handle lua_resume idiosyncrasies
Diffstat (limited to 'src')
-rw-r--r-- | src/compat.h | 25 | ||||
-rw-r--r-- | src/lane.cpp | 425 | ||||
-rw-r--r-- | src/lane.h | 8 | ||||
-rw-r--r-- | src/lanes.lua | 4 |
4 files changed, 280 insertions, 182 deletions
diff --git a/src/compat.h b/src/compat.h index 9e15230..2690a41 100644 --- a/src/compat.h +++ b/src/compat.h | |||
@@ -237,11 +237,13 @@ template <typename LUA_RESUME> | |||
237 | concept RequiresLuaResume51 = requires(LUA_RESUME f_) { { f_(nullptr, 0) } -> std::same_as<int>; }; | 237 | concept RequiresLuaResume51 = requires(LUA_RESUME f_) { { f_(nullptr, 0) } -> std::same_as<int>; }; |
238 | 238 | ||
239 | template <RequiresLuaResume51 LUA_RESUME> | 239 | template <RequiresLuaResume51 LUA_RESUME> |
240 | static inline int WrapLuaResume(LUA_RESUME const f_, lua_State* const L_, [[maybe_unused]] lua_State* const from_, int const nargs_, int* const nresults_) | 240 | static inline int WrapLuaResume(LUA_RESUME const lua_resume_, lua_State* const L_, [[maybe_unused]] lua_State* const from_, int const nargs_, int* const nresults_) |
241 | { | 241 | { |
242 | int const _resultsStart{ lua_gettop(L_) - nargs_ - 1 }; | 242 | // lua_resume is supposed to be called from a "clean" stack: |
243 | int const _rc{ f_(L_, nargs_) }; | 243 | // it should only contain the function and its initial arguments on first call, or the resume arguments on subsequent invocations |
244 | *nresults_ = lua_gettop(L_) - _resultsStart; | 244 | int const _rc{ lua_resume_(L_, nargs_) }; |
245 | // after resuming, the stack should only contain the yielded values | ||
246 | *nresults_ = lua_gettop(L_); | ||
245 | return _rc; | 247 | return _rc; |
246 | } | 248 | } |
247 | 249 | ||
@@ -251,11 +253,13 @@ template <typename LUA_RESUME> | |||
251 | concept RequiresLuaResume52 = requires(LUA_RESUME f_) { { f_(nullptr, nullptr, 0) } -> std::same_as<int>; }; | 253 | concept RequiresLuaResume52 = requires(LUA_RESUME f_) { { f_(nullptr, nullptr, 0) } -> std::same_as<int>; }; |
252 | 254 | ||
253 | template <RequiresLuaResume52 LUA_RESUME> | 255 | template <RequiresLuaResume52 LUA_RESUME> |
254 | static inline int WrapLuaResume(LUA_RESUME const f_, lua_State* const L_, lua_State* const from_, int const nargs_, [[maybe_unused]] int* const nresults_) | 256 | static inline int WrapLuaResume(LUA_RESUME const lua_resume_, lua_State* const L_, lua_State* const from_, int const nargs_, [[maybe_unused]] int* const nresults_) |
255 | { | 257 | { |
256 | int const _resultsStart{ lua_gettop(L_) - nargs_ - 1 }; | 258 | // lua_resume is supposed to be called from a "clean" stack: |
257 | int const _rc{ f_(L_, from_, nargs_) }; | 259 | // it should only contain the function and its initial arguments on first call, or the resume arguments on subsequent invocations |
258 | *nresults_ = lua_gettop(L_) - _resultsStart; | 260 | int const _rc{ lua_resume_(L_, from_, nargs_) }; |
261 | // after resuming, the stack should only contain the yielded values | ||
262 | *nresults_ = lua_gettop(L_); | ||
259 | return _rc; | 263 | return _rc; |
260 | } | 264 | } |
261 | 265 | ||
@@ -265,9 +269,10 @@ template <typename LUA_RESUME> | |||
265 | concept RequiresLuaResume54 = requires(LUA_RESUME f_) { { f_(nullptr, nullptr, 0, nullptr) } -> std::same_as<int>; }; | 269 | concept RequiresLuaResume54 = requires(LUA_RESUME f_) { { f_(nullptr, nullptr, 0, nullptr) } -> std::same_as<int>; }; |
266 | 270 | ||
267 | template <RequiresLuaResume54 LUA_RESUME> | 271 | template <RequiresLuaResume54 LUA_RESUME> |
268 | static inline int WrapLuaResume(LUA_RESUME const f_, lua_State* const L_, lua_State* const from_, int const nargs_, int* const nresults_) | 272 | static inline int WrapLuaResume(LUA_RESUME const lua_resume_, lua_State* const L_, lua_State* const from_, int const nargs_, int* const nresults_) |
269 | { | 273 | { |
270 | return f_(L_, from_, nargs_, nresults_); | 274 | // starting with Lua 5.4, the stack can contain stuff below the actual yielded values, but lua_resume tells us the correct nresult |
275 | return lua_resume_(L_, from_, nargs_, nresults_); | ||
271 | } | 276 | } |
272 | 277 | ||
273 | // ------------------------------------------------------------------------------------------------- | 278 | // ------------------------------------------------------------------------------------------------- |
diff --git a/src/lane.cpp b/src/lane.cpp index 7182ad6..e38c4bb 100644 --- a/src/lane.cpp +++ b/src/lane.cpp | |||
@@ -128,10 +128,10 @@ static LUAG_FUNC(thread_join) | |||
128 | raise_luaL_argerror(L_, 2, "incorrect duration type"); | 128 | raise_luaL_argerror(L_, 2, "incorrect duration type"); |
129 | } | 129 | } |
130 | 130 | ||
131 | bool const _done{ !_lane->thread.joinable() || _lane->waitForCompletion(_until) }; | ||
132 | lua_settop(L_, 1); // L_: lane | 131 | lua_settop(L_, 1); // L_: lane |
133 | lua_State* const _L2{ _lane->L }; | 132 | bool const _done{ !_lane->thread.joinable() || _lane->waitForCompletion(_until) }; |
134 | if (!_done || !_L2) { | 133 | |
134 | if (!_done) { | ||
135 | lua_pushnil(L_); // L_: lane nil | 135 | lua_pushnil(L_); // L_: lane nil |
136 | luaG_pushstring(L_, "timeout"); // L_: lane nil "timeout" | 136 | luaG_pushstring(L_, "timeout"); // L_: lane nil "timeout" |
137 | return 2; | 137 | return 2; |
@@ -141,47 +141,48 @@ static LUAG_FUNC(thread_join) | |||
141 | // Thread is Suspended or Done/Error/Cancelled; the Lane thread isn't working with it, therefore we can. | 141 | // Thread is Suspended or Done/Error/Cancelled; the Lane thread isn't working with it, therefore we can. |
142 | 142 | ||
143 | int _ret{ 0 }; | 143 | int _ret{ 0 }; |
144 | // debugName is a pointer to string possibly interned in the lane's state, that no longer exists when the state is closed | 144 | int const _stored{ _lane->storeResults(L_) }; |
145 | // so store it in the userdata uservalue at a key that can't possibly collide | 145 | STACK_GROW(L_, std::max(3, _stored + 1)); |
146 | _lane->securizeDebugName(L_); | ||
147 | switch (_lane->status) { | 146 | switch (_lane->status) { |
148 | case Lane::Suspended: // got yielded values | 147 | case Lane::Suspended: // got yielded values |
149 | case Lane::Done: // got regular return values | 148 | case Lane::Done: // got regular return values |
150 | { | 149 | { |
151 | bool const _calledFromLua{ lua_toboolean(L_, lua_upvalueindex(1)) ? false : true }; // this upvalue doesn't exist when called from Lua | 150 | if (_stored == 0) { |
152 | int const _n{ lua_gettop(_L2) }; // whole L2 stack | 151 | raise_luaL_error(L_, _lane->L ? "First return value must be non-nil when using join()" : "Can't join() more than once or after indexing"); |
153 | if (_calledFromLua && (_n == 0 || lua_isnil(_L2, 1))) { | ||
154 | raise_luaL_error(L_, "First return value must be non-nil when using join()"); | ||
155 | } | 152 | } |
156 | if ( | 153 | lua_getiuservalue(L_, 1, 1); // L_: lane {uv} |
157 | (_n > 0) && | 154 | for (int _i = 2; _i <= _stored; ++_i) { |
158 | (InterCopyContext{ _lane->U, DestState{ L_ }, SourceState{ _L2 }, {}, {}, {}, {}, {} }.interMove(_n) != InterCopyResult::Success) | 155 | lua_rawgeti(L_, 2, _i); // L_: lane {uv} results2...N |
159 | ) { // L_: lane results L2: | ||
160 | raise_luaL_error(L_, "tried to copy unsupported types"); | ||
161 | } | 156 | } |
162 | _ret = _n; | 157 | lua_rawgeti(L_, 2, 1); // L_: lane {uv} results2...N result1 |
158 | lua_replace(L_, 2); // L_: lane results | ||
159 | _ret = _stored; | ||
163 | } | 160 | } |
164 | break; | 161 | break; |
165 | 162 | ||
166 | case Lane::Error: | 163 | case Lane::Error: |
167 | { | 164 | { |
168 | int const _n{ lua_gettop(_L2) }; // L_: lane L2: "err" [trace] | 165 | LUA_ASSERT(L_, _stored == 2 || _stored == 3); |
169 | STACK_GROW(L_, 3); | 166 | lua_getiuservalue(L_, 1, 1); // L_: lane {uv} |
170 | lua_pushnil(L_); // L_: lane nil | 167 | lua_rawgeti(L_, 2, 2); // L_: lane {uv} <error> |
171 | // even when _lane->errorTraceLevel != Minimal, if the error is not LUA_ERRRUN, the handler wasn't called, and we only have 1 error message on the stack ... | 168 | lua_rawgeti(L_, 2, 3); // L_: lane {uv} <error> <trace>|nil |
172 | InterCopyContext _c{ _lane->U, DestState{ L_ }, SourceState{ _L2 }, {}, {}, {}, {}, {} }; | 169 | if (lua_isnil(L_, -1)) { |
173 | if (_c.interMove(_n) != InterCopyResult::Success) { // L_: lane nil "err" [trace] L2: | 170 | lua_replace(L_, 2); // L_: lane nil <error> |
174 | raise_luaL_error(L_, "tried to copy unsupported types: %s", lua_tostring(L_, -_n)); | 171 | } else { |
172 | lua_rawgeti(L_, 2, 1); // L_: lane {uv} <error> <trace> nil | ||
173 | lua_replace(L_, 2); // L_: lane nil <error> <trace> | ||
175 | } | 174 | } |
176 | _ret = 1 + _n; | 175 | _ret = lua_gettop(L_) - 1; // 2 or 3 |
177 | } | 176 | } |
178 | break; | 177 | break; |
179 | 178 | ||
180 | case Lane::Cancelled: | 179 | case Lane::Cancelled: |
181 | // we should have a single value, kCancelError, in the stack of _L2 | 180 | LUA_ASSERT(L_, _stored == 2); |
182 | LUA_ASSERT(L_, lua_gettop(_L2) == 1 && kCancelError.equals(_L2, 1)); | 181 | lua_getiuservalue(L_, 1, 1); // L_: lane {uv} |
183 | lua_pushnil(L_); // L_: lane nil | 182 | lua_rawgeti(L_, 2, 2); // L_: lane {uv} cancel_error |
184 | kCancelError.pushKey(L_); // L_: lane nil cancel_error | 183 | lua_rawgeti(L_, 2, 1); // L_: lane {uv} cancel_error nil |
184 | lua_replace(L_, -3); // L_: lane nil cancel_error | ||
185 | LUA_ASSERT(L_, lua_isnil(L_, -2) && kCancelError.equals(L_, -1)); | ||
185 | _ret = 2; | 186 | _ret = 2; |
186 | break; | 187 | break; |
187 | 188 | ||
@@ -190,10 +191,6 @@ static LUAG_FUNC(thread_join) | |||
190 | LUA_ASSERT(L_, false); | 191 | LUA_ASSERT(L_, false); |
191 | _ret = 0; | 192 | _ret = 0; |
192 | } | 193 | } |
193 | // if we are suspended, all we want to do is gather the current yielded values | ||
194 | if (_lane->status != Lane::Suspended) { | ||
195 | _lane->closeState(); | ||
196 | } | ||
197 | STACK_CHECK(L_, _ret); | 194 | STACK_CHECK(L_, _ret); |
198 | return _ret; | 195 | return _ret; |
199 | } | 196 | } |
@@ -258,111 +255,25 @@ LUAG_FUNC(thread_resume) | |||
258 | static int thread_index_number(lua_State* L_) | 255 | static int thread_index_number(lua_State* L_) |
259 | { | 256 | { |
260 | static constexpr int kSelf{ 1 }; | 257 | static constexpr int kSelf{ 1 }; |
261 | static constexpr int kKey{ 2 }; | ||
262 | static constexpr int kUsr{ 3 }; | ||
263 | 258 | ||
264 | Lane* const _lane{ ToLane(L_, kSelf) }; | 259 | Lane* const _lane{ ToLane(L_, kSelf) }; |
265 | LUA_ASSERT(L_, lua_gettop(L_) == 2); // L_: lane n | 260 | LUA_ASSERT(L_, lua_gettop(L_) == 2); // L_: lane n |
261 | int const _key{ static_cast<int>(lua_tointeger(L_, 2)) }; | ||
262 | lua_pop(L_, 1); // L_: lane | ||
266 | 263 | ||
267 | // first, check that we don't already have an environment that holds the requested value | 264 | std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; |
268 | // If key is found in the uservalue, return it | 265 | if (!_lane->waitForCompletion(_until)) { |
269 | lua_getiuservalue(L_, kSelf, 1); // L_: lane n {uv} | 266 | raise_luaL_error(L_, "INTERNAL ERROR: Failed to join"); |
270 | lua_pushvalue(L_, kKey); // L_: lane n {uv} n | ||
271 | lua_rawget(L_, kUsr); // L_: lane n {uv} v|nil | ||
272 | if (!lua_isnil(L_, -1)) { | ||
273 | return 1; | ||
274 | } | 267 | } |
275 | lua_pop(L_, 1); // L_: lane n {uv} | ||
276 | |||
277 | // check if we already fetched the values from the thread or not | ||
278 | lua_pushinteger(L_, 0); // L_: lane n {uv} 0 | ||
279 | lua_rawget(L_, kUsr); // L_: lane n {uv} uv[0]|nil | ||
280 | bool const _fetched{ !lua_isnil(L_, -1) }; | ||
281 | lua_pop(L_, 1); // L_: lane n {uv} | ||
282 | if (!_fetched) { | ||
283 | lua_pushinteger(L_, 0); // L_: lane n {uv} 0 | ||
284 | lua_pushboolean(L_, 1); // L_: lane n {uv} 0 true | ||
285 | lua_rawset(L_, kUsr); // L_: lane n {uv} | ||
286 | // tell join() that we are called from __index, to avoid raising an error if the first returned value is not nil | ||
287 | luaG_pushstring(L_, "[]"); // L_: lane n {uv} "[]" | ||
288 | // wait until thread has completed, transfer everything from the lane's stack to our side | ||
289 | lua_pushcclosure(L_, LG_thread_join, 1); // L_: lane n {uv} join | ||
290 | lua_pushvalue(L_, kSelf); // L_: lane n {uv} join lane | ||
291 | lua_call(L_, 1, LUA_MULTRET); // lane:join() // L_: lane n {uv} ... | ||
292 | switch (_lane->status) { | ||
293 | default: | ||
294 | // this is an internal error, we probably never get here | ||
295 | lua_settop(L_, 0); // L_: | ||
296 | luaG_pushstring(L_, "Unexpected status: "); // L_: "Unexpected status: " | ||
297 | _lane->pushStatusString(L_); // L_: "Unexpected status: " "<status>" | ||
298 | lua_concat(L_, 2); // L_: "Unexpected status: <status>" | ||
299 | raise_lua_error(L_); | ||
300 | |||
301 | case Lane::Suspended: // got yielded values | ||
302 | case Lane::Done: // got regular return values | ||
303 | { | ||
304 | int const _nvalues{ lua_gettop(L_) - 3 }; // L_: lane n {uv} ... | ||
305 | for (int _i = _nvalues; _i > 0; --_i) { | ||
306 | // pop the last element of the stack, to store it in the uservalue at its proper index | ||
307 | lua_rawseti(L_, kUsr, _i); // L_: lane n {uv} | ||
308 | } | ||
309 | } | ||
310 | break; | ||
311 | 268 | ||
312 | case Lane::Error: // got 2 or 3 values: nil, errstring, and possibly a callstack table | 269 | // make sure results are stored |
313 | if (_lane->errorTraceLevel == Lane::Minimal) { | 270 | int const _stored{ _lane->storeResults(L_) }; |
314 | LUA_ASSERT(L_, lua_gettop(L_) == 5 && lua_isnil(L_, 4) && !lua_isnil(L_, 5)); // L_: lane n {uv} nil "<msg>" | 271 | if (_key > _stored) { |
315 | } else { | 272 | // get nil if indexing beyond the actual returned value count |
316 | LUA_ASSERT(L_, lua_gettop(L_) == 6 && lua_isnil(L_, 4) && !lua_isnil(L_, 5) && lua_istable(L_, 6)); | 273 | lua_pushnil(L_); // L_: lane nil |
317 | lua_insert(L_, -2); // L_: lane n {uv} nil {trace} "<msg>" | 274 | } else { |
318 | } | 275 | _lane->pushIndexedResult(L_, _key); // L_: lane result |
319 | // uv[-1] = "<msg>" | ||
320 | lua_rawseti(L_, kUsr, -1); // L_: lane n {uv} nil {trace}? | ||
321 | break; | ||
322 | |||
323 | case Lane::Cancelled: | ||
324 | // do nothing | ||
325 | break; | ||
326 | } | ||
327 | } | ||
328 | STACK_GROW(L_, 6); // up to 6 positions are needed in case of error propagation | ||
329 | lua_settop(L_, 3); // L_: lane n {uv} | ||
330 | int const _key{ static_cast<int>(lua_tointeger(L_, kKey)) }; | ||
331 | if (_key != -1) { | ||
332 | lua_rawgeti(L_, kUsr, -1); // L_: lane n {uv} <error>|nil | ||
333 | if (!lua_isnil(L_, -1)) { // an error was stored // L_: lane n {uv} <error> | ||
334 | lua_getmetatable(L_, 1); // L_: lane n {uv} <error> {mt} | ||
335 | lua_replace(L_, -3); // L_: lane n {mt} <error> | ||
336 | // Note: Lua 5.1 interpreter is not prepared to show | ||
337 | // non-string errors, so we use 'tostring()' here | ||
338 | // to get meaningful output. --AKa 22-Jan-2009 | ||
339 | // | ||
340 | // Also, the stack dump we get is no good; it only | ||
341 | // lists our internal Lanes functions. There seems | ||
342 | // to be no way to switch it off, though. | ||
343 | // | ||
344 | // Level 3 should show the line where 'h[x]' was read | ||
345 | // but this only seems to work for string messages | ||
346 | // (Lua 5.1.4). No idea, why. --AKa 22-Jan-2009 | ||
347 | if constexpr (LUA_VERSION_NUM == 501) { | ||
348 | if (!lua_isstring(L_, -1)) { | ||
349 | kCachedTostring.pushKey(L_); // L_: lane n {mt} <error> kCachedTostring | ||
350 | lua_rawget(L_, -3); // L_: lane n {mt} <error> tostring() | ||
351 | lua_insert(L_, -2); // L_: lane n {mt} tostring() <error> | ||
352 | lua_call(L_, 1, 1); // tostring(errstring) // L_: lane n {mt} "error" | ||
353 | } | ||
354 | } | ||
355 | kCachedError.pushKey(L_); // L_: lane n {mt} "error" kCachedError | ||
356 | lua_rawget(L_, -3); // L_: lane n {mt} "error" error() | ||
357 | lua_replace(L_, -3); // L_: lane n error() "error" | ||
358 | lua_pushinteger(L_, 3); // L_: lane n error() "error" 3 | ||
359 | lua_call(L_, 2, 0); // error(tostring(errstring), 3) -> doesn't return // L_: lane n | ||
360 | raise_luaL_error(L_, "%s: should not get here!", _lane->getDebugName().data()); | ||
361 | } else { | ||
362 | lua_pop(L_, 1); // L_: lane n {uv} | ||
363 | } | ||
364 | } | 276 | } |
365 | lua_rawgeti(L_, kUsr, _key); // L_: lane n {uv} uv[n] | ||
366 | return 1; | 277 | return 1; |
367 | } | 278 | } |
368 | 279 | ||
@@ -569,9 +480,10 @@ int Lane::LuaErrorHandler(lua_State* L_) | |||
569 | // ########################################## Finalizer ############################################ | 480 | // ########################################## Finalizer ############################################ |
570 | // ################################################################################################# | 481 | // ################################################################################################# |
571 | 482 | ||
572 | static void push_stack_trace(lua_State* L_, Lane::ErrorTraceLevel errorTraceLevel_, LuaError rc_, [[maybe_unused]] int stk_base_) | 483 | static [[nodiscard]] int push_stack_trace(lua_State* const L_, Lane::ErrorTraceLevel const errorTraceLevel_, LuaError const rc_, [[maybe_unused]] int const stk_base_) |
573 | { | 484 | { |
574 | // Lua 5.1 error handler is limited to one return value; it stored the stack trace in the registry | 485 | // Lua 5.1 error handler is limited to one return value; it stored the stack trace in the registry |
486 | int const _top{ lua_gettop(L_) }; | ||
575 | switch (rc_) { | 487 | switch (rc_) { |
576 | case LuaError::OK: // no error, body return values are on the stack | 488 | case LuaError::OK: // no error, body return values are on the stack |
577 | break; | 489 | break; |
@@ -602,6 +514,7 @@ static void push_stack_trace(lua_State* L_, Lane::ErrorTraceLevel errorTraceLeve | |||
602 | LUA_ASSERT(L_, (lua_gettop(L_) == stk_base_) && (luaG_type(L_, stk_base_) == LuaType::STRING)); | 514 | LUA_ASSERT(L_, (lua_gettop(L_) == stk_base_) && (luaG_type(L_, stk_base_) == LuaType::STRING)); |
603 | break; | 515 | break; |
604 | } | 516 | } |
517 | return lua_gettop(L_) - _top; // either 0 or 1 | ||
605 | } | 518 | } |
606 | 519 | ||
607 | // ################################################################################################# | 520 | // ################################################################################################# |
@@ -619,59 +532,85 @@ static void push_stack_trace(lua_State* L_, Lane::ErrorTraceLevel errorTraceLeve | |||
619 | // TBD: should we add stack trace on failing finalizer, wouldn't be hard.. | 532 | // TBD: should we add stack trace on failing finalizer, wouldn't be hard.. |
620 | // | 533 | // |
621 | 534 | ||
622 | [[nodiscard]] static LuaError run_finalizers(lua_State* L_, Lane::ErrorTraceLevel errorTraceLevel_, LuaError lua_rc_) | 535 | [[nodiscard]] static LuaError run_finalizers(Lane* const lane_, Lane::ErrorTraceLevel errorTraceLevel_, LuaError lua_rc_) |
623 | { | 536 | { |
624 | kFinalizerRegKey.pushValue(L_); // L_: ... finalizers? | 537 | // if we are a coroutine, we can't run the finalizers in the coroutine state! |
625 | if (lua_isnil(L_, -1)) { | 538 | lua_State* const _L{ lane_->S }; |
626 | lua_pop(L_, 1); | 539 | kFinalizerRegKey.pushValue(_L); // _L: ... finalizers|nil |
540 | if (lua_isnil(_L, -1)) { | ||
541 | lua_pop(_L, 1); | ||
627 | return LuaError::OK; // no finalizers | 542 | return LuaError::OK; // no finalizers |
628 | } | 543 | } |
629 | 544 | ||
630 | STACK_GROW(L_, 5); | 545 | STACK_GROW(_L, 5); |
631 | 546 | ||
632 | int const _finalizers_index{ lua_gettop(L_) }; | 547 | int const _finalizers{ lua_gettop(_L) }; |
633 | int const _err_handler_index{ (errorTraceLevel_ != Lane::Minimal) ? (lua_pushcfunction(L_, Lane::LuaErrorHandler), lua_gettop(L_)) : 0 }; | 548 | // always push something as error handler, to have the same stack structure |
549 | int const _error_handler{ (errorTraceLevel_ != Lane::Minimal) | ||
550 | ? (lua_pushcfunction(_L, Lane::LuaErrorHandler), lua_gettop(_L)) | ||
551 | : (lua_pushnil(_L), 0) | ||
552 | }; // _L: ... finalizers error_handler() | ||
634 | 553 | ||
635 | LuaError _rc{ LuaError::OK }; | 554 | LuaError _rc{ LuaError::OK }; |
636 | for (int _n = static_cast<int>(lua_rawlen(L_, _finalizers_index)); _n > 0; --_n) { | 555 | int _finalizer_pushed{}; |
556 | for (int _n = static_cast<int>(lua_rawlen(_L, _finalizers)); _n > 0; --_n) { | ||
637 | int _args{ 0 }; | 557 | int _args{ 0 }; |
638 | lua_pushinteger(L_, _n); // L_: ... finalizers lane_error n | 558 | lua_rawgeti(_L, _finalizers, _n); // _L: ... finalizers error_handler() finalizer() |
639 | lua_rawget(L_, _finalizers_index); // L_: ... finalizers lane_error finalizer | 559 | LUA_ASSERT(_L, lua_isfunction(_L, -1)); |
640 | LUA_ASSERT(L_, lua_isfunction(L_, -1)); | 560 | if (lua_rc_ != LuaError::OK) { // we have <error>, [trace] on the thread stack |
641 | if (lua_rc_ != LuaError::OK) { // we have an error message and an optional stack trace at the bottom of the stack | 561 | LUA_ASSERT(_L, lane_->nresults == 1 || lane_->nresults == 2); |
642 | LUA_ASSERT(L_, _finalizers_index == 2 || _finalizers_index == 3); | 562 | //std::string_view const _err_msg{ luaG_tostring(_L, 1) }; |
643 | //std::string_view const _err_msg{ luaG_tostring(L_, 1) }; | 563 | if (lane_->isCoroutine()) { |
644 | lua_pushvalue(L_, 1); // L_: ... finalizers lane_error finalizer err_msg | 564 | // transfer them on the main state |
645 | // note we don't always have a stack trace for example when kCancelError, or when we got an error that doesn't call our handler, such as LUA_ERRMEM | 565 | lua_pushvalue(lane_->L, 1); |
646 | if (_finalizers_index == 3) { | 566 | // note we don't always have a stack trace for example when kCancelError, or when we got an error that doesn't call our handler, such as LUA_ERRMEM |
647 | lua_pushvalue(L_, 2); // L_: ... finalizers lane_error finalizer err_msg stack_trace | 567 | if (lane_->nresults == 2) { |
568 | lua_pushvalue(lane_->L, 2); | ||
569 | } | ||
570 | lua_xmove(lane_->L, _L, lane_->nresults); // _L: ... finalizers error_handler() finalizer <error> [trace] | ||
571 | } else { | ||
572 | lua_pushvalue(_L, 1); // _L: ... finalizers error_handler() finalizer <error> | ||
573 | // note we don't always have a stack trace for example when kCancelError, or when we got an error that doesn't call our handler, such as LUA_ERRMEM | ||
574 | if (lane_->nresults == 2) { | ||
575 | lua_pushvalue(_L, 2); // _L: ... finalizers error_handler() finalizer <error> trace | ||
576 | } | ||
648 | } | 577 | } |
649 | _args = _finalizers_index - 1; | 578 | _args = lane_->nresults; |
650 | } | 579 | } |
651 | 580 | ||
652 | // if no error from the main body, finalizer doesn't receive any argument, else it gets the error message and optional stack trace | 581 | // if no error from the main body, finalizer doesn't receive any argument, else it gets the error message and optional stack trace |
653 | _rc = ToLuaError(lua_pcall(L_, _args, 0, _err_handler_index)); // L_: ... finalizers lane_error err_msg2? | 582 | _rc = ToLuaError(lua_pcall(_L, _args, 0, _error_handler)); // _L: ... finalizers error_handler() err_msg2? |
654 | if (_rc != LuaError::OK) { | 583 | if (_rc != LuaError::OK) { |
655 | push_stack_trace(L_, errorTraceLevel_, _rc, lua_gettop(L_)); // L_: ... finalizers lane_error err_msg2? trace | 584 | _finalizer_pushed = 1 + push_stack_trace(_L, errorTraceLevel_, _rc, lua_gettop(_L)); // _L: ... finalizers error_handler() err_msg2? trace |
656 | // If one finalizer fails, don't run the others. Return this | 585 | // If one finalizer fails, don't run the others. Return this |
657 | // as the 'real' error, replacing what we could have had (or not) | 586 | // as the 'real' error, replacing what we could have had (or not) |
658 | // from the actual code. | 587 | // from the actual code. |
659 | break; | 588 | break; |
660 | } | 589 | } |
661 | // no error, proceed to next finalizer // L_: ... finalizers lane_error | 590 | // no error, proceed to next finalizer // _L: ... finalizers error_handler() |
662 | } | 591 | } |
663 | 592 | ||
664 | if (_rc != LuaError::OK) { | 593 | if (_rc != LuaError::OK) { |
665 | // errorTraceLevel_ accounts for the presence of lane_error on the stack | 594 | lane_->nresults = _finalizer_pushed; |
666 | int const _nb_err_slots{ lua_gettop(L_) - _finalizers_index - ((errorTraceLevel_ != Lane::Minimal) ? 1 : 0) }; | 595 | if (lane_->isCoroutine()) { // _L: ... finalizers error_handler() <error2> trace2 |
667 | // a finalizer generated an error, this is what we leave of the stack | 596 | // put them back in the thread state stack, as if the finalizer was run from there |
668 | for (int _n = _nb_err_slots; _n > 0; --_n) { | 597 | lua_settop(lane_->L, 0); |
669 | lua_replace(L_, _n); | 598 | lua_xmove(_L, lane_->L, _finalizer_pushed); // _L: ... finalizers error_handler() L: <error2> [trace2] |
599 | lua_pop(_L, 2); // _L: ... L: <error2> [trace2] | ||
600 | } else { // _L: ... finalizers error_handler() <error2> [trace2] | ||
601 | // adjust the stack to keep only the error data from the finalizer | ||
602 | for (int const _n : std::ranges::reverse_view{ std::ranges::iota_view{ 1, _finalizer_pushed + 1 } }) { | ||
603 | lua_replace(_L, _n); | ||
604 | } | ||
605 | lua_settop(_L, _finalizer_pushed); // _L: <error2> [trace2] | ||
670 | } | 606 | } |
671 | // leave on the stack only the error and optional stack trace produced by the error in the finalizer | ||
672 | lua_settop(L_, _nb_err_slots); // L_: ... lane_error trace | ||
673 | } else { // no error from the finalizers, make sure only the original return values from the lane body remain on the stack | 607 | } else { // no error from the finalizers, make sure only the original return values from the lane body remain on the stack |
674 | lua_settop(L_, _finalizers_index - 1); | 608 | lua_settop(_L, _finalizers - 1); |
609 | } | ||
610 | |||
611 | if (lane_->isCoroutine()) { | ||
612 | // only the coroutine thread should remain on the master state when we are done | ||
613 | LUA_ASSERT(_L, lua_gettop(_L) == 1 && luaG_type(_L, 1) == LuaType::THREAD); | ||
675 | } | 614 | } |
676 | 615 | ||
677 | return _rc; | 616 | return _rc; |
@@ -773,11 +712,13 @@ static void lane_main(Lane* const lane_) | |||
773 | PrepareLaneHelpers(lane_); | 712 | PrepareLaneHelpers(lane_); |
774 | if (lane_->S == lane_->L) { // L: eh? f args... | 713 | if (lane_->S == lane_->L) { // L: eh? f args... |
775 | _rc = ToLuaError(lua_pcall(_L, _nargs, LUA_MULTRET, _errorHandlerCount)); // L: eh? retvals|err | 714 | _rc = ToLuaError(lua_pcall(_L, _nargs, LUA_MULTRET, _errorHandlerCount)); // L: eh? retvals|err |
715 | lane_->nresults = lua_gettop(_L) - _errorHandlerCount; | ||
776 | } else { | 716 | } else { |
777 | // S and L are different: we run as a coroutine in Lua thread L created in state S | 717 | // S and L are different: we run as a coroutine in Lua thread L created in state S |
778 | do { | 718 | do { |
779 | int _nresults{}; | 719 | // starting with Lua 5.4, lua_resume can leave more stuff on the stack below the actual yielded values. |
780 | _rc = luaG_resume(_L, nullptr, _nargs, &_nresults); // L: eh? retvals|err... | 720 | // that's why we have lane_->nresults |
721 | _rc = luaG_resume(_L, nullptr, _nargs, &lane_->nresults); // L: eh? ... retvals|err... | ||
781 | if (_rc == LuaError::YIELD) { | 722 | if (_rc == LuaError::YIELD) { |
782 | // change our status to suspended, and wait until someone wants us to resume | 723 | // change our status to suspended, and wait until someone wants us to resume |
783 | std::unique_lock _guard{ lane_->doneMutex }; | 724 | std::unique_lock _guard{ lane_->doneMutex }; |
@@ -798,6 +739,7 @@ static void lane_main(Lane* const lane_) | |||
798 | // for some reason, in my tests with Lua 5.4, when the coroutine raises an error, I have 3 copies of it on the stack | 739 | // for some reason, in my tests with Lua 5.4, when the coroutine raises an error, I have 3 copies of it on the stack |
799 | // or false + the error message when running Lua 5.1 | 740 | // or false + the error message when running Lua 5.1 |
800 | // since the rest of our code wants only the error message, let us keep only the latter. | 741 | // since the rest of our code wants only the error message, let us keep only the latter. |
742 | lane_->nresults = 1; | ||
801 | lua_replace(_L, 1); // L: err... | 743 | lua_replace(_L, 1); // L: err... |
802 | lua_settop(_L, 1); // L: err | 744 | lua_settop(_L, 1); // L: err |
803 | // now we build the stack trace table if the error trace level requests it | 745 | // now we build the stack trace table if the error trace level requests it |
@@ -810,13 +752,13 @@ static void lane_main(Lane* const lane_) | |||
810 | } | 752 | } |
811 | 753 | ||
812 | // in case of error and if it exists, fetch stack trace from registry and push it | 754 | // in case of error and if it exists, fetch stack trace from registry and push it |
813 | push_stack_trace(_L, lane_->errorTraceLevel, _rc, 1); // L: retvals|error [trace] | 755 | lane_->nresults += push_stack_trace(_L, lane_->errorTraceLevel, _rc, 1); // L: retvals|error [trace] |
814 | 756 | ||
815 | DEBUGSPEW_CODE(DebugSpew(lane_->U) << "Lane " << _L << " body: " << GetErrcodeName(_rc) << " (" << (kCancelError.equals(_L, 1) ? "cancelled" : luaG_typename(_L, 1)) << ")" << std::endl); | 757 | DEBUGSPEW_CODE(DebugSpew(lane_->U) << "Lane " << _L << " body: " << GetErrcodeName(_rc) << " (" << (kCancelError.equals(_L, 1) ? "cancelled" : luaG_typename(_L, 1)) << ")" << std::endl); |
816 | // Call finalizers, if the script has set them up. | 758 | // Call finalizers, if the script has set them up. |
817 | // If the lane is not a coroutine, there is only a regular state, so everything is the same whether we use S or L. | 759 | // If the lane is not a coroutine, there is only a regular state, so everything is the same whether we use S or L. |
818 | // If the lane is a coroutine, this has to be done from the master state (S), not the thread (L), because we can't lua_pcall in a thread state | 760 | // If the lane is a coroutine, this has to be done from the master state (S), not the thread (L), because we can't lua_pcall in a thread state |
819 | LuaError const _rc2{ run_finalizers(lane_->S, lane_->errorTraceLevel, _rc) }; | 761 | LuaError const _rc2{ run_finalizers(lane_, lane_->errorTraceLevel, _rc) }; |
820 | DEBUGSPEW_CODE(DebugSpew(lane_->U) << "Lane " << _L << " finalizer: " << GetErrcodeName(_rc2) << std::endl); | 762 | DEBUGSPEW_CODE(DebugSpew(lane_->U) << "Lane " << _L << " finalizer: " << GetErrcodeName(_rc2) << std::endl); |
821 | if (_rc2 != LuaError::OK) { // Error within a finalizer! | 763 | if (_rc2 != LuaError::OK) { // Error within a finalizer! |
822 | // the finalizer generated an error, and left its own error message [and stack trace] on the stack | 764 | // the finalizer generated an error, and left its own error message [and stack trace] on the stack |
@@ -907,6 +849,7 @@ static LUAG_FUNC(lane_gc) | |||
907 | return 0; | 849 | return 0; |
908 | } else if (_lane->L) { | 850 | } else if (_lane->L) { |
909 | // no longer accessing the Lua VM: we can close right now | 851 | // no longer accessing the Lua VM: we can close right now |
852 | _lane->securizeDebugName(L_); | ||
910 | _lane->closeState(); | 853 | _lane->closeState(); |
911 | } | 854 | } |
912 | 855 | ||
@@ -1119,6 +1062,59 @@ void Lane::pushStatusString(lua_State* L_) const | |||
1119 | 1062 | ||
1120 | // ################################################################################################# | 1063 | // ################################################################################################# |
1121 | 1064 | ||
1065 | void Lane::pushIndexedResult(lua_State* const L_, int const key_) const | ||
1066 | { | ||
1067 | static constexpr int kSelf{ 1 }; | ||
1068 | LUA_ASSERT(L_, ToLane(L_, kSelf) == this); // L_: lane ... | ||
1069 | STACK_GROW(L_, 3); | ||
1070 | |||
1071 | lua_getiuservalue(L_, kSelf, 1); // L_: lane ... {uv} | ||
1072 | if (status != Lane::Error) { | ||
1073 | lua_rawgeti(L_, -1, key_); // L_: lane ... {uv} uv[i] | ||
1074 | lua_remove(L_, -2); // L_: lane ... uv[i] | ||
1075 | return; | ||
1076 | } | ||
1077 | |||
1078 | // [1] = nil [2] = <error> [3] = [stack_trace] // L_: lane ... {uv} | ||
1079 | lua_rawgeti(L_, -1, 2); // L_: lane ... {uv} <error> | ||
1080 | |||
1081 | // any negative index gives just the error message without propagation | ||
1082 | if (key_ < 0) { | ||
1083 | lua_remove(L_, -2); // L_: lane ... <error> | ||
1084 | return; | ||
1085 | } | ||
1086 | lua_getmetatable(L_, kSelf); // L_: lane ... {uv} <error> {mt} | ||
1087 | lua_replace(L_, -3); // L_: lane ... {mt} <error> | ||
1088 | // Note: Lua 5.1 interpreter is not prepared to show | ||
1089 | // non-string errors, so we use 'tostring()' here | ||
1090 | // to get meaningful output. --AKa 22-Jan-2009 | ||
1091 | // | ||
1092 | // Also, the stack dump we get is no good; it only | ||
1093 | // lists our internal Lanes functions. There seems | ||
1094 | // to be no way to switch it off, though. | ||
1095 | // | ||
1096 | // Level 2 should show the line where 'h[x]' was read | ||
1097 | // but this only seems to work for string messages | ||
1098 | // (Lua 5.1.4). No idea, why. --AKa 22-Jan-2009 | ||
1099 | if constexpr (LUA_VERSION_NUM == 501) { | ||
1100 | if (!lua_isstring(L_, -1)) { | ||
1101 | // tostring() and error() are hidden in the lane metatable | ||
1102 | kCachedTostring.pushKey(L_); // L_: lane ... {mt} <error> kCachedTostring | ||
1103 | lua_rawget(L_, -3); // L_: lane ... {mt} <error> tostring() | ||
1104 | lua_insert(L_, -2); // L_: lane ... {mt} tostring() <error> | ||
1105 | lua_call(L_, 1, 1); // tostring(errstring) // L_: lane ... {mt} "error" | ||
1106 | } | ||
1107 | } | ||
1108 | kCachedError.pushKey(L_); // L_: lane ... {mt} "error" kCachedError | ||
1109 | lua_rawget(L_, -3); // L_: lane ... {mt} "error" error() | ||
1110 | lua_replace(L_, -3); // L_: lane ... error() "error" | ||
1111 | lua_pushinteger(L_, 2); // L_: lane ... error() "error" 2 | ||
1112 | lua_call(L_, 2, 0); // error(tostring(errstring), 3) -> doesn't return // L_: lane ... | ||
1113 | raise_luaL_error(L_, "%s: should not get here!", getDebugName().data()); | ||
1114 | } | ||
1115 | |||
1116 | // ################################################################################################# | ||
1117 | |||
1122 | [[nodiscard]] std::string_view Lane::pushErrorTraceLevel(lua_State* L_) const | 1118 | [[nodiscard]] std::string_view Lane::pushErrorTraceLevel(lua_State* L_) const |
1123 | { | 1119 | { |
1124 | std::string_view const _str{ errorTraceLevelString() }; | 1120 | std::string_view const _str{ errorTraceLevelString() }; |
@@ -1169,7 +1165,7 @@ void Lane::securizeDebugName(lua_State* L_) | |||
1169 | lua_newtable(L_); // L_: lane ... {uv} {} | 1165 | lua_newtable(L_); // L_: lane ... {uv} {} |
1170 | { | 1166 | { |
1171 | std::lock_guard<std::mutex> _guard{ debugNameMutex }; | 1167 | std::lock_guard<std::mutex> _guard{ debugNameMutex }; |
1172 | debugName = luaG_pushstring(L_, debugName); // L_: lane ... {uv} {} name | 1168 | debugName = luaG_pushstring(L_, debugName); // L_: lane ... {uv} {} name |
1173 | } | 1169 | } |
1174 | lua_rawset(L_, -3); // L_: lane ... {uv} | 1170 | lua_rawset(L_, -3); // L_: lane ... {uv} |
1175 | lua_pop(L_, 1); // L_: lane | 1171 | lua_pop(L_, 1); // L_: lane |
@@ -1188,6 +1184,103 @@ void Lane::startThread(int priority_) | |||
1188 | 1184 | ||
1189 | // ################################################################################################# | 1185 | // ################################################################################################# |
1190 | 1186 | ||
1187 | // take the results on the lane state stack and store them in our uservalue table, at numeric indices: | ||
1188 | // t[0] = nresults | ||
1189 | // t[i] = result #i | ||
1190 | int Lane::storeResults(lua_State* const L_) | ||
1191 | { | ||
1192 | static constexpr int kSelf{ 1 }; | ||
1193 | LUA_ASSERT(L_, ToLane(L_, kSelf) == this); | ||
1194 | |||
1195 | STACK_CHECK_START_REL(L_, 0); | ||
1196 | lua_getiuservalue(L_, kSelf, 1); // L_: lane ... {uv} | ||
1197 | int const _tidx{ lua_gettop(L_) }; | ||
1198 | |||
1199 | int _stored{}; | ||
1200 | if (nresults == 0) { | ||
1201 | lua_rawgeti(L_, -1, 0); // L_: lane ... {uv} nresults | ||
1202 | _stored = static_cast<int>(lua_tointeger(L_, -1)); | ||
1203 | lua_pop(L_, 2); | ||
1204 | STACK_CHECK(L_, 0); | ||
1205 | return _stored; | ||
1206 | } | ||
1207 | |||
1208 | switch (status) { | ||
1209 | default: | ||
1210 | // this is an internal error, we probably never get here | ||
1211 | lua_settop(L_, 0); // L_: | ||
1212 | luaG_pushstring(L_, "Unexpected status: "); // L_: "Unexpected status: " | ||
1213 | pushStatusString(L_); // L_: "Unexpected status: " "<status>" | ||
1214 | lua_concat(L_, 2); // L_: "Unexpected status: <status>" | ||
1215 | raise_lua_error(L_); | ||
1216 | |||
1217 | case Lane::Error: // got 1 or 2 error values: <error> <trace>? | ||
1218 | // error can be anything (even nil!) | ||
1219 | if (errorTraceLevel == Lane::Minimal) { | ||
1220 | LUA_ASSERT(L_, (lua_gettop(L) == nresults) && (nresults == 1)); | ||
1221 | } else { | ||
1222 | LUA_ASSERT(L_, (lua_gettop(L) == nresults) && (nresults == 2)); | ||
1223 | LUA_ASSERT(L_, lua_istable(L, 2)); | ||
1224 | } | ||
1225 | // convert this to nil,<error>,<trace> | ||
1226 | lua_pushnil(L); // L: <error> <trace> nil | ||
1227 | lua_insert(L, 1); // L: nil <error> <trace> | ||
1228 | ++nresults; | ||
1229 | [[fallthrough]]; | ||
1230 | |||
1231 | case Lane::Suspended: // got yielded values | ||
1232 | case Lane::Done: // got regular return values | ||
1233 | { | ||
1234 | InterCopyResult const _r{ InterCopyContext{ U, DestState{ L_ }, SourceState{ L }, {}, {}, {}, {}, {} }.interMove(nresults) }; | ||
1235 | lua_settop(L, 0); // L_: lane ... {uv} results L: | ||
1236 | _stored = nresults; | ||
1237 | nresults = 0; | ||
1238 | if (_r != InterCopyResult::Success) { | ||
1239 | raise_luaL_error(L_, "Tried to copy unsupported types"); | ||
1240 | } | ||
1241 | for (int _i = _stored; _i > 0; --_i) { | ||
1242 | // pop the last element of the stack, to store it in the uservalue at its proper index | ||
1243 | lua_rawseti(L_, _tidx, _i); // L_: lane ... {uv} results... | ||
1244 | } // L_: lane ... {uv} | ||
1245 | // results[0] = nresults | ||
1246 | lua_pushinteger(L_, _stored); // L_: lane ... {uv} nresults | ||
1247 | lua_rawseti(L_, _tidx, 0); // L_: lane ... {uv} | ||
1248 | lua_pop(L_, 1); // L_: lane ... | ||
1249 | } | ||
1250 | break; | ||
1251 | |||
1252 | case Lane::Cancelled: | ||
1253 | _stored = 2; | ||
1254 | // we should have a single value, kCancelError, in the stack of L | ||
1255 | LUA_ASSERT(L_, nresults == 1 && lua_gettop(L) == 1 && kCancelError.equals(L, 1)); | ||
1256 | // store nil, cancel_error in the results | ||
1257 | lua_pushnil(L_); // L_: lane ... {uv} nil | ||
1258 | lua_rawseti(L_, _tidx, 1); // L_: lane ... {uv} | ||
1259 | kCancelError.pushKey(L_); // L_: lane ... {uv} cancel_error | ||
1260 | lua_rawseti(L_, _tidx, 2); // L_: lane ... {uv} | ||
1261 | // results[0] = nresults | ||
1262 | lua_pushinteger(L_, _stored); // L_: lane ... {uv} nresults | ||
1263 | lua_rawseti(L_, _tidx, 0); // L_: lane ... {uv} | ||
1264 | lua_pop(L_, 1); // L_: lane ... | ||
1265 | lua_settop(L, 0); // L: | ||
1266 | nresults = 0; | ||
1267 | break; | ||
1268 | } | ||
1269 | STACK_CHECK(L_, 0); | ||
1270 | LUA_ASSERT(L_, lua_gettop(L) == 0 && nresults == 0); | ||
1271 | // if we are suspended, all we want to do is gather the current yielded values | ||
1272 | if (status != Lane::Suspended) { | ||
1273 | // debugName is a pointer to string possibly interned in the lane's state, that no longer exists when the state is closed | ||
1274 | // so store it in the userdata uservalue at a key that can't possibly collide | ||
1275 | securizeDebugName(L_); | ||
1276 | closeState(); | ||
1277 | } | ||
1278 | |||
1279 | return _stored; | ||
1280 | } | ||
1281 | |||
1282 | // ################################################################################################# | ||
1283 | |||
1191 | //--- | 1284 | //--- |
1192 | // str= thread_status( lane ) | 1285 | // str= thread_status( lane ) |
1193 | // | 1286 | // |
@@ -99,6 +99,7 @@ class Lane | |||
99 | Universe* const U{}; | 99 | Universe* const U{}; |
100 | lua_State* S{}; // the master state of the lane | 100 | lua_State* S{}; // the master state of the lane |
101 | lua_State* L{}; // the state we run things in (either S or a lua_newthread() state if we run in coroutine mode) | 101 | lua_State* L{}; // the state we run things in (either S or a lua_newthread() state if we run in coroutine mode) |
102 | int nresults{}; // number of results to extract from the stack (can differ from the actual number of values when in coroutine mode) | ||
102 | // | 103 | // |
103 | // M: prepares the state, and reads results | 104 | // M: prepares the state, and reads results |
104 | // S: while S is running, M must keep out of modifying the state | 105 | // S: while S is running, M must keep out of modifying the state |
@@ -148,13 +149,10 @@ class Lane | |||
148 | void changeDebugName(int const nameIdx_); | 149 | void changeDebugName(int const nameIdx_); |
149 | void closeState() | 150 | void closeState() |
150 | { | 151 | { |
151 | { | ||
152 | std::lock_guard<std::mutex> _guard{ debugNameMutex }; | ||
153 | debugName = std::string_view{ "<gc>" }; | ||
154 | } | ||
155 | lua_State* _L{ S }; | 152 | lua_State* _L{ S }; |
156 | S = nullptr; | 153 | S = nullptr; |
157 | L = nullptr; | 154 | L = nullptr; |
155 | nresults = 0; | ||
158 | lua_close(_L); // this collects our coroutine thread at the same time | 156 | lua_close(_L); // this collects our coroutine thread at the same time |
159 | } | 157 | } |
160 | [[nodiscard]] std::string_view errorTraceLevelString() const; | 158 | [[nodiscard]] std::string_view errorTraceLevelString() const; |
@@ -174,9 +172,11 @@ class Lane | |||
174 | [[nodiscard]] std::string_view pushErrorTraceLevel(lua_State* L_) const; | 172 | [[nodiscard]] std::string_view pushErrorTraceLevel(lua_State* L_) const; |
175 | static void PushMetatable(lua_State* L_); | 173 | static void PushMetatable(lua_State* L_); |
176 | void pushStatusString(lua_State* L_) const; | 174 | void pushStatusString(lua_State* L_) const; |
175 | void pushIndexedResult(lua_State* L_, int key_) const; | ||
177 | void resetResultsStorage(lua_State* const L_, int gc_cb_idx_); | 176 | void resetResultsStorage(lua_State* const L_, int gc_cb_idx_); |
178 | void securizeDebugName(lua_State* L_); | 177 | void securizeDebugName(lua_State* L_); |
179 | void startThread(int priority_); | 178 | void startThread(int priority_); |
179 | [[nodiscard]] int storeResults(lua_State* L_); | ||
180 | [[nodiscard]] std::string_view threadStatusString() const; | 180 | [[nodiscard]] std::string_view threadStatusString() const; |
181 | // wait until the lane stops working with its state (either Suspended or Done+) | 181 | // wait until the lane stops working with its state (either Suspended or Done+) |
182 | [[nodiscard]] bool waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_); | 182 | [[nodiscard]] bool waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_); |
diff --git a/src/lanes.lua b/src/lanes.lua index 57183e9..dfa959d 100644 --- a/src/lanes.lua +++ b/src/lanes.lua | |||
@@ -357,8 +357,8 @@ local process_gen_opt = function(...) | |||
357 | end -- process_gen_opt | 357 | end -- process_gen_opt |
358 | 358 | ||
359 | -- lane_h[1..n]: lane results, same as via 'lane_h:join()' | 359 | -- lane_h[1..n]: lane results, same as via 'lane_h:join()' |
360 | -- lane_h[0]: can be read to make sure a thread has finished (always gives 'true') | 360 | -- lane_h[0]: can be read to make sure a thread has finished (gives the number of available results) |
361 | -- lane_h[-1]: error message, without propagating the error | 361 | -- lane_h[negative]: error message, without propagating the error |
362 | -- | 362 | -- |
363 | -- Reading a Lane result (or [0]) propagates a possible error in the lane | 363 | -- Reading a Lane result (or [0]) propagates a possible error in the lane |
364 | -- (and execution does not return). Cancelled lanes give 'nil' values. | 364 | -- (and execution does not return). Cancelled lanes give 'nil' values. |