aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-07-29 18:07:16 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2024-07-29 18:07:16 +0200
commitbb11006f635a69dd9244e09e4359ed02eff8fe84 (patch)
tree7fda9eda216b1683ff3dd954b6300c13bfaf2b14 /src
parent34b2b5e712ea1cc59004ca48f79f54af162993a5 (diff)
downloadlanes-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.h25
-rw-r--r--src/lane.cpp425
-rw-r--r--src/lane.h8
-rw-r--r--src/lanes.lua4
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>
237concept RequiresLuaResume51 = requires(LUA_RESUME f_) { { f_(nullptr, 0) } -> std::same_as<int>; }; 237concept RequiresLuaResume51 = requires(LUA_RESUME f_) { { f_(nullptr, 0) } -> std::same_as<int>; };
238 238
239template <RequiresLuaResume51 LUA_RESUME> 239template <RequiresLuaResume51 LUA_RESUME>
240static inline int WrapLuaResume(LUA_RESUME const f_, lua_State* const L_, [[maybe_unused]] lua_State* const from_, int const nargs_, int* const nresults_) 240static 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>
251concept RequiresLuaResume52 = requires(LUA_RESUME f_) { { f_(nullptr, nullptr, 0) } -> std::same_as<int>; }; 253concept RequiresLuaResume52 = requires(LUA_RESUME f_) { { f_(nullptr, nullptr, 0) } -> std::same_as<int>; };
252 254
253template <RequiresLuaResume52 LUA_RESUME> 255template <RequiresLuaResume52 LUA_RESUME>
254static inline int WrapLuaResume(LUA_RESUME const f_, lua_State* const L_, lua_State* const from_, int const nargs_, [[maybe_unused]] int* const nresults_) 256static 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>
265concept RequiresLuaResume54 = requires(LUA_RESUME f_) { { f_(nullptr, nullptr, 0, nullptr) } -> std::same_as<int>; }; 269concept RequiresLuaResume54 = requires(LUA_RESUME f_) { { f_(nullptr, nullptr, 0, nullptr) } -> std::same_as<int>; };
266 270
267template <RequiresLuaResume54 LUA_RESUME> 271template <RequiresLuaResume54 LUA_RESUME>
268static inline int WrapLuaResume(LUA_RESUME const f_, lua_State* const L_, lua_State* const from_, int const nargs_, int* const nresults_) 272static 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)
258static int thread_index_number(lua_State* L_) 255static 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
572static void push_stack_trace(lua_State* L_, Lane::ErrorTraceLevel errorTraceLevel_, LuaError rc_, [[maybe_unused]] int stk_base_) 483static [[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
1065void 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
1190int 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//
diff --git a/src/lane.h b/src/lane.h
index f0fd0ac..a7a348b 100644
--- a/src/lane.h
+++ b/src/lane.h
@@ -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(...)
357end -- process_gen_opt 357end -- 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.