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 | |
| parent | 34b2b5e712ea1cc59004ca48f79f54af162993a5 (diff) | |
| download | lanes-bb11006f635a69dd9244e09e4359ed02eff8fe84.tar.gz lanes-bb11006f635a69dd9244e09e4359ed02eff8fe84.tar.bz2 lanes-bb11006f635a69dd9244e09e4359ed02eff8fe84.zip | |
Internal refactorization to properly handle lua_resume idiosyncrasies
| -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 | ||||
| -rw-r--r-- | tests/cancel.lua | 2 | ||||
| -rw-r--r-- | tests/error.lua | 9 | ||||
| -rw-r--r-- | tests/finalizer.lua | 4 | ||||
| -rw-r--r-- | tests/tobeclosed.lua | 2 |
8 files changed, 292 insertions, 187 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. |
diff --git a/tests/cancel.lua b/tests/cancel.lua index 8dfccd2..1984c85 100644 --- a/tests/cancel.lua +++ b/tests/cancel.lua | |||
| @@ -72,7 +72,7 @@ local waitCancellation = function( h, expected_status) | |||
| 72 | until h.status ~= "running" | 72 | until h.status ~= "running" |
| 73 | end | 73 | end |
| 74 | print( "lane status:", h.status) | 74 | print( "lane status:", h.status) |
| 75 | assert( h.status == expected_status, h.status .. " ~= " .. expected_status) | 75 | assert( h.status == expected_status, "lane status " .. h.status .. " (actual) ~= " .. expected_status .. " (expected)") |
| 76 | print "test OK" | 76 | print "test OK" |
| 77 | end | 77 | end |
| 78 | 78 | ||
diff --git a/tests/error.lua b/tests/error.lua index 126161e..a1ba1ce 100644 --- a/tests/error.lua +++ b/tests/error.lua | |||
| @@ -95,12 +95,12 @@ local make_table_error_mt = function() | |||
| 95 | end | 95 | end |
| 96 | 96 | ||
| 97 | local lane_error_as_string = "'lane error as string'" | 97 | local lane_error_as_string = "'lane error as string'" |
| 98 | local lane_error_as_table = setmetatable({}, make_table_error_mt()) | 98 | local lane_error_as_table = setmetatable({"lane error as table"}, make_table_error_mt()) |
| 99 | local lane_error_as_linda = lanes.linda("'lane error'") | 99 | local lane_error_as_linda = lanes.linda("'lane error'") |
| 100 | 100 | ||
| 101 | local finalizer_error_as_string = "'lane error as string'" | 101 | local finalizer_error_as_string = "'finalizer error as string'" |
| 102 | local finalizer_error_as_table = setmetatable({}, make_table_error_mt()) | 102 | local finalizer_error_as_table = setmetatable({"finalizer error as table"}, make_table_error_mt()) |
| 103 | local finalizer_error_as_linda = lanes.linda("'lane error'") | 103 | local finalizer_error_as_linda = lanes.linda("'finalizer error'") |
| 104 | 104 | ||
| 105 | local test_settings = {} | 105 | local test_settings = {} |
| 106 | local configure_tests = function() | 106 | local configure_tests = function() |
| @@ -150,6 +150,7 @@ local configure_tests = function() | |||
| 150 | end | 150 | end |
| 151 | end | 151 | end |
| 152 | end | 152 | end |
| 153 | WR "Tests configured" | ||
| 153 | end | 154 | end |
| 154 | 155 | ||
| 155 | configure_tests() | 156 | configure_tests() |
diff --git a/tests/finalizer.lua b/tests/finalizer.lua index 1412a67..8bf89b4 100644 --- a/tests/finalizer.lua +++ b/tests/finalizer.lua | |||
| @@ -39,6 +39,7 @@ local function lane(error_) | |||
| 39 | error(error_, 0) -- exception here; the value needs NOT be a string | 39 | error(error_, 0) -- exception here; the value needs NOT be a string |
| 40 | end | 40 | end |
| 41 | -- no exception | 41 | -- no exception |
| 42 | io.stderr:write("Lane completed\n") | ||
| 42 | return true | 43 | return true |
| 43 | end | 44 | end |
| 44 | 45 | ||
| @@ -81,10 +82,13 @@ local do_test = function(error_) | |||
| 81 | assert(stack, "no stack trace on error, check 'error_trace_level'") | 82 | assert(stack, "no stack trace on error, check 'error_trace_level'") |
| 82 | io.stderr:write( "Lane error: "..tostring(err).."\n" ) | 83 | io.stderr:write( "Lane error: "..tostring(err).."\n" ) |
| 83 | io.stderr:write( "\t", table.concat(stack,"\t\n"), "\n" ) | 84 | io.stderr:write( "\t", table.concat(stack,"\t\n"), "\n" ) |
| 85 | else | ||
| 86 | io.stderr:write( "Done\n" ) | ||
| 84 | end | 87 | end |
| 85 | end | 88 | end |
| 86 | 89 | ||
| 87 | do_test(nil) | 90 | do_test(nil) |
| 91 | -- do return end | ||
| 88 | do_test("An error") | 92 | do_test("An error") |
| 89 | 93 | ||
| 90 | local on_exit = function() | 94 | local on_exit = function() |
diff --git a/tests/tobeclosed.lua b/tests/tobeclosed.lua index 5ac8ab7..7a5e3ae 100644 --- a/tests/tobeclosed.lua +++ b/tests/tobeclosed.lua | |||
| @@ -83,6 +83,7 @@ do | |||
| 83 | local lane_body = function() | 83 | local lane_body = function() |
| 84 | WR "In lane body" | 84 | WR "In lane body" |
| 85 | lanes.sleep(1) | 85 | lanes.sleep(1) |
| 86 | return "success" | ||
| 86 | end | 87 | end |
| 87 | 88 | ||
| 88 | local h = lanes.gen("*", lane_body)() | 89 | local h = lanes.gen("*", lane_body)() |
| @@ -90,6 +91,7 @@ do | |||
| 90 | local tobeclosed <close> = h | 91 | local tobeclosed <close> = h |
| 91 | end | 92 | end |
| 92 | assert(h.status == "done") | 93 | assert(h.status == "done") |
| 94 | return "success" | ||
| 93 | end | 95 | end |
| 94 | 96 | ||
| 95 | -- ################################################################################################# | 97 | -- ################################################################################################# |
