diff options
Diffstat (limited to 'src/lane.cpp')
-rw-r--r-- | src/lane.cpp | 916 |
1 files changed, 916 insertions, 0 deletions
diff --git a/src/lane.cpp b/src/lane.cpp new file mode 100644 index 0000000..a03412d --- /dev/null +++ b/src/lane.cpp | |||
@@ -0,0 +1,916 @@ | |||
1 | /* | ||
2 | =============================================================================== | ||
3 | |||
4 | Copyright (C) 2024 Benoit Germain <bnt.germain@gmail.com> | ||
5 | |||
6 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
7 | of this software and associated documentation files (the "Software"), to deal | ||
8 | in the Software without restriction, including without limitation the rights | ||
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
10 | copies of the Software, and to permit persons to whom the Software is | ||
11 | furnished to do so, subject to the following conditions: | ||
12 | |||
13 | The above copyright notice and this permission notice shall be included in | ||
14 | all copies or substantial portions of the Software. | ||
15 | |||
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
22 | THE SOFTWARE. | ||
23 | |||
24 | =============================================================================== | ||
25 | */ | ||
26 | |||
27 | #include "lane.h" | ||
28 | |||
29 | #include "intercopycontext.h" | ||
30 | #include "tools.h" | ||
31 | #include "threading.h" | ||
32 | |||
33 | // ################################################################################################# | ||
34 | |||
35 | /* Do you want full call stacks, or just the line where the error happened? | ||
36 | * | ||
37 | * TBD: The full stack feature does not seem to work (try 'make error'). | ||
38 | */ | ||
39 | #define ERROR_FULL_STACK 1 // must be either 0 or 1 as we do some index arithmetics with it! | ||
40 | |||
41 | // ################################################################################################# | ||
42 | // ######################################### Lua API ############################################### | ||
43 | // ################################################################################################# | ||
44 | |||
45 | LUAG_FUNC(get_debug_threadname) | ||
46 | { | ||
47 | Lane* const _lane{ ToLane(L_, 1) }; | ||
48 | luaL_argcheck(L_, lua_gettop(L_) == 1, 2, "too many arguments"); | ||
49 | lua_pushstring(L_, _lane->debugName); | ||
50 | return 1; | ||
51 | } | ||
52 | |||
53 | // ################################################################################################# | ||
54 | |||
55 | // void= finalizer( finalizer_func ) | ||
56 | // | ||
57 | // finalizer_func( [err, stack_tbl] ) | ||
58 | // | ||
59 | // Add a function that will be called when exiting the lane, either via | ||
60 | // normal return or an error. | ||
61 | // | ||
62 | LUAG_FUNC(set_finalizer) | ||
63 | { | ||
64 | luaL_argcheck(L_, lua_isfunction(L_, 1), 1, "finalizer should be a function"); | ||
65 | luaL_argcheck(L_, lua_gettop(L_) == 1, 1, "too many arguments"); | ||
66 | STACK_GROW(L_, 3); | ||
67 | // Get the current finalizer table (if any), create one if it doesn't exist | ||
68 | std::ignore = kFinalizerRegKey.getSubTable(L_, 1, 0); // L_: finalizer {finalisers} | ||
69 | // must cast to int, not lua_Integer, because LuaJIT signature of lua_rawseti is not the same as PUC-Lua. | ||
70 | int const _idx{ static_cast<int>(lua_rawlen(L_, -1) + 1) }; | ||
71 | lua_pushvalue(L_, 1); // L_: finalizer {finalisers} finalizer | ||
72 | lua_rawseti(L_, -2, _idx); // L_: finalizer {finalisers} | ||
73 | // no need to adjust the stack, Lua does this for us | ||
74 | return 0; | ||
75 | } | ||
76 | |||
77 | // ################################################################################################# | ||
78 | |||
79 | #if ERROR_FULL_STACK | ||
80 | LUAG_FUNC(set_error_reporting) | ||
81 | { | ||
82 | luaL_checktype(L_, 1, LUA_TSTRING); | ||
83 | char const* _mode{ lua_tostring(L_, 1) }; | ||
84 | lua_pushliteral(L_, "extended"); | ||
85 | bool const _extended{ strcmp(_mode, "extended") == 0 }; | ||
86 | bool const _basic{ strcmp(_mode, "basic") == 0 }; | ||
87 | if (!_extended && !_basic) { | ||
88 | raise_luaL_error(L_, "unsupported error reporting model %s", _mode); | ||
89 | } | ||
90 | |||
91 | kExtendedStackTraceRegKey.setValue(L_, [extended = _extended](lua_State* L_) { lua_pushboolean(L_, extended ? 1 : 0); }); | ||
92 | return 0; | ||
93 | } | ||
94 | |||
95 | #endif // ERROR_FULL_STACK | ||
96 | |||
97 | // ################################################################################################# | ||
98 | |||
99 | // upvalue #1 is the lane userdata | ||
100 | LUAG_FUNC(set_debug_threadname) | ||
101 | { | ||
102 | // C s_lane structure is a light userdata upvalue | ||
103 | Lane* const _lane{ lua_tolightuserdata<Lane>(L_, lua_upvalueindex(1)) }; | ||
104 | LUA_ASSERT(L_, L_ == _lane->L); // this function is exported in a lane's state, therefore it is callable only from inside the Lane's state | ||
105 | lua_settop(L_, 1); | ||
106 | STACK_CHECK_START_REL(L_, 0); | ||
107 | _lane->changeDebugName(-1); | ||
108 | STACK_CHECK(L_, 0); | ||
109 | return 0; | ||
110 | } | ||
111 | |||
112 | // ################################################################################################# | ||
113 | |||
114 | //--- | ||
115 | // [...] | [nil, err_any, stack_tbl]= thread_join( lane_ud [, wait_secs=-1] ) | ||
116 | // | ||
117 | // timeout: returns nil | ||
118 | // done: returns return values (0..N) | ||
119 | // error: returns nil + error value [+ stack table] | ||
120 | // cancelled: returns nil | ||
121 | // | ||
122 | LUAG_FUNC(thread_join) | ||
123 | { | ||
124 | Lane* const _lane{ ToLane(L_, 1) }; | ||
125 | lua_State* const _L2{ _lane->L }; | ||
126 | |||
127 | std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; | ||
128 | if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion | ||
129 | lua_Duration const duration{ lua_tonumber(L_, 2) }; | ||
130 | if (duration.count() >= 0.0) { | ||
131 | _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(duration); | ||
132 | } else { | ||
133 | raise_luaL_argerror(L_, 2, "duration cannot be < 0"); | ||
134 | } | ||
135 | |||
136 | } else if (!lua_isnoneornil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key | ||
137 | raise_luaL_argerror(L_, 2, "incorrect duration type"); | ||
138 | } | ||
139 | |||
140 | bool const done{ !_lane->thread.joinable() || _lane->waitForCompletion(_until) }; | ||
141 | lua_settop(L_, 1); // L_: lane | ||
142 | if (!done || !_L2) { | ||
143 | lua_pushnil(L_); // L_: lane nil | ||
144 | lua_pushliteral(L_, "timeout"); // L_: lane nil "timeout" | ||
145 | return 2; | ||
146 | } | ||
147 | |||
148 | STACK_CHECK_START_REL(L_, 0); // L_: lane | ||
149 | // Thread is Done/Error/Cancelled; all ours now | ||
150 | |||
151 | int _ret{ 0 }; | ||
152 | // debugName is a pointer to string possibly interned in the lane's state, that no longer exists when the state is closed | ||
153 | // so store it in the userdata uservalue at a key that can't possibly collide | ||
154 | _lane->securizeDebugName(L_); | ||
155 | switch (_lane->status) { | ||
156 | case Lane::Done: | ||
157 | { | ||
158 | int const _n{ lua_gettop(_L2) }; // whole L2 stack | ||
159 | if ( | ||
160 | (_n > 0) && | ||
161 | (InterCopyContext{ _lane->U, DestState{ L_ }, SourceState{ _L2 }, {}, {}, {}, {}, {} }.inter_move(_n) != InterCopyResult::Success) | ||
162 | ) { // L_: lane results L2: | ||
163 | raise_luaL_error(L_, "tried to copy unsupported types"); | ||
164 | } | ||
165 | _ret = _n; | ||
166 | } | ||
167 | break; | ||
168 | |||
169 | case Lane::Error: | ||
170 | { | ||
171 | int const _n{ lua_gettop(_L2) }; // L_: lane L2: "err" [trace] | ||
172 | STACK_GROW(L_, 3); | ||
173 | lua_pushnil(L_); // L_: lane nil | ||
174 | // even when ERROR_FULL_STACK, if the error is not LUA_ERRRUN, the handler wasn't called, and we only have 1 error message on the stack ... | ||
175 | InterCopyContext _c{ _lane->U, DestState{ L_ }, SourceState{ _L2 }, {}, {}, {}, {}, {} }; | ||
176 | if (_c.inter_move(_n) != InterCopyResult::Success) { // L_: lane nil "err" [trace] L2: | ||
177 | raise_luaL_error(L_, "tried to copy unsupported types: %s", lua_tostring(L_, -_n)); | ||
178 | } | ||
179 | _ret = 1 + _n; | ||
180 | } | ||
181 | break; | ||
182 | |||
183 | case Lane::Cancelled: | ||
184 | _ret = 0; | ||
185 | break; | ||
186 | |||
187 | default: | ||
188 | DEBUGSPEW_CODE(fprintf(stderr, "Status: %d\n", _lane->status)); | ||
189 | LUA_ASSERT(L_, false); | ||
190 | _ret = 0; | ||
191 | } | ||
192 | lua_close(_L2); | ||
193 | _lane->L = nullptr; | ||
194 | STACK_CHECK(L_, _ret); | ||
195 | return _ret; | ||
196 | } | ||
197 | |||
198 | // ################################################################################################# | ||
199 | |||
200 | // lane:__index(key,usr) -> value | ||
201 | // | ||
202 | // If key is found in the environment, return it | ||
203 | // If key is numeric, wait until the thread returns and populate the environment with the return values | ||
204 | // If the return values signal an error, propagate it | ||
205 | // If key is "status" return the thread status | ||
206 | // Else raise an error | ||
207 | LUAG_FUNC(thread_index) | ||
208 | { | ||
209 | static constexpr int kSelf{ 1 }; | ||
210 | static constexpr int kKey{ 2 }; | ||
211 | Lane* const _lane{ ToLane(L_, kSelf) }; | ||
212 | LUA_ASSERT(L_, lua_gettop(L_) == 2); | ||
213 | |||
214 | STACK_GROW(L_, 8); // up to 8 positions are needed in case of error propagation | ||
215 | |||
216 | // If key is numeric, wait until the thread returns and populate the environment with the return values | ||
217 | if (lua_type(L_, kKey) == LUA_TNUMBER) { | ||
218 | static constexpr int kUsr{ 3 }; | ||
219 | // first, check that we don't already have an environment that holds the requested value | ||
220 | { | ||
221 | // If key is found in the uservalue, return it | ||
222 | lua_getiuservalue(L_, kSelf, 1); | ||
223 | lua_pushvalue(L_, kKey); | ||
224 | lua_rawget(L_, kUsr); | ||
225 | if (!lua_isnil(L_, -1)) { | ||
226 | return 1; | ||
227 | } | ||
228 | lua_pop(L_, 1); | ||
229 | } | ||
230 | { | ||
231 | // check if we already fetched the values from the thread or not | ||
232 | lua_pushinteger(L_, 0); | ||
233 | lua_rawget(L_, kUsr); | ||
234 | bool const _fetched{ !lua_isnil(L_, -1) }; | ||
235 | lua_pop(L_, 1); // back to our 2 args + uservalue on the stack | ||
236 | if (!_fetched) { | ||
237 | lua_pushinteger(L_, 0); | ||
238 | lua_pushboolean(L_, 1); | ||
239 | lua_rawset(L_, kUsr); | ||
240 | // wait until thread has completed | ||
241 | lua_pushcfunction(L_, LG_thread_join); | ||
242 | lua_pushvalue(L_, kSelf); | ||
243 | lua_call(L_, 1, LUA_MULTRET); // all return values are on the stack, at slots 4+ | ||
244 | switch (_lane->status) { | ||
245 | default: | ||
246 | // this is an internal error, we probably never get here | ||
247 | lua_settop(L_, 0); | ||
248 | lua_pushliteral(L_, "Unexpected status: "); | ||
249 | _lane->pushThreadStatus(L_); | ||
250 | lua_concat(L_, 2); | ||
251 | raise_lua_error(L_); | ||
252 | [[fallthrough]]; // fall through if we are killed, as we got nil, "killed" on the stack | ||
253 | |||
254 | case Lane::Done: // got regular return values | ||
255 | { | ||
256 | int const _nvalues{ lua_gettop(L_) - 3 }; | ||
257 | for (int _i = _nvalues; _i > 0; --_i) { | ||
258 | // pop the last element of the stack, to store it in the uservalue at its proper index | ||
259 | lua_rawseti(L_, kUsr, _i); | ||
260 | } | ||
261 | } | ||
262 | break; | ||
263 | |||
264 | case Lane::Error: // got 3 values: nil, errstring, callstack table | ||
265 | // me[-2] could carry the stack table, but even | ||
266 | // me[-1] is rather unnecessary (and undocumented); | ||
267 | // use ':join()' instead. --AKa 22-Jan-2009 | ||
268 | LUA_ASSERT(L_, lua_isnil(L_, 4) && !lua_isnil(L_, 5) && lua_istable(L_, 6)); | ||
269 | // store errstring at key -1 | ||
270 | lua_pushnumber(L_, -1); | ||
271 | lua_pushvalue(L_, 5); | ||
272 | lua_rawset(L_, kUsr); | ||
273 | break; | ||
274 | |||
275 | case Lane::Cancelled: | ||
276 | // do nothing | ||
277 | break; | ||
278 | } | ||
279 | } | ||
280 | lua_settop(L_, 3); // L_: self KEY ENV | ||
281 | int const _key{ static_cast<int>(lua_tointeger(L_, kKey)) }; | ||
282 | if (_key != -1) { | ||
283 | lua_pushnumber(L_, -1); // L_: self KEY ENV -1 | ||
284 | lua_rawget(L_, kUsr); // L_: self KEY ENV "error"|nil | ||
285 | if (!lua_isnil(L_, -1)) { // L_: an error was stored | ||
286 | // Note: Lua 5.1 interpreter is not prepared to show | ||
287 | // non-string errors, so we use 'tostring()' here | ||
288 | // to get meaningful output. --AKa 22-Jan-2009 | ||
289 | // | ||
290 | // Also, the stack dump we get is no good; it only | ||
291 | // lists our internal Lanes functions. There seems | ||
292 | // to be no way to switch it off, though. | ||
293 | // | ||
294 | // Level 3 should show the line where 'h[x]' was read | ||
295 | // but this only seems to work for string messages | ||
296 | // (Lua 5.1.4). No idea, why. --AKa 22-Jan-2009 | ||
297 | lua_getmetatable(L_, kSelf); // L_: self KEY ENV "error" mt | ||
298 | lua_getfield(L_, -1, "cached_error"); // L_: self KEY ENV "error" mt error() | ||
299 | lua_getfield(L_, -2, "cached_tostring"); // L_: self KEY ENV "error" mt error() tostring() | ||
300 | lua_pushvalue(L_, 4); // L_: self KEY ENV "error" mt error() tostring() "error" | ||
301 | lua_call(L_, 1, 1); // tostring(errstring) -- just in case // L_: self KEY ENV "error" mt error() "error" | ||
302 | lua_pushinteger(L_, 3); // L_: self KEY ENV "error" mt error() "error" 3 | ||
303 | lua_call(L_, 2, 0); // error(tostring(errstring), 3) -> doesn't return // L_: self KEY ENV "error" mt | ||
304 | } else { | ||
305 | lua_pop(L_, 1); // L_: self KEY ENV | ||
306 | } | ||
307 | } | ||
308 | lua_rawgeti(L_, kUsr, _key); | ||
309 | } | ||
310 | return 1; | ||
311 | } | ||
312 | if (lua_type(L_, kKey) == LUA_TSTRING) { | ||
313 | char const* const _keystr{ lua_tostring(L_, kKey) }; | ||
314 | lua_settop(L_, 2); // keep only our original arguments on the stack | ||
315 | if (strcmp(_keystr, "status") == 0) { | ||
316 | _lane->pushThreadStatus(L_); // push the string representing the status | ||
317 | return 1; | ||
318 | } | ||
319 | // return self.metatable[key] | ||
320 | lua_getmetatable(L_, kSelf); // L_: self KEY mt | ||
321 | lua_replace(L_, -3); // L_: mt KEY | ||
322 | lua_rawget(L_, -2); // L_: mt value | ||
323 | // only "cancel" and "join" are registered as functions, any other string will raise an error | ||
324 | if (!lua_iscfunction(L_, -1)) { | ||
325 | raise_luaL_error(L_, "can't index a lane with '%s'", _keystr); | ||
326 | } | ||
327 | return 1; | ||
328 | } | ||
329 | // unknown key | ||
330 | lua_getmetatable(L_, kSelf); // L_: mt | ||
331 | lua_getfield(L_, -1, "cached_error"); // L_: mt error | ||
332 | lua_pushliteral(L_, "Unknown key: "); // L_: mt error "Unknown key: " | ||
333 | lua_pushvalue(L_, kKey); // L_: mt error "Unknown key: " k | ||
334 | lua_concat(L_, 2); // L_: mt error "Unknown key: <k>" | ||
335 | lua_call(L_, 1, 0); // error( "Unknown key: " .. key) -> doesn't return // L_: mt | ||
336 | return 0; | ||
337 | } | ||
338 | |||
339 | // ################################################################################################# | ||
340 | // ######################################## Utilities ############################################## | ||
341 | // ################################################################################################# | ||
342 | |||
343 | #if USE_DEBUG_SPEW() | ||
344 | // can't use direct LUA_x errcode indexing because the sequence is not the same between Lua 5.1 and 5.2 :-( | ||
345 | // LUA_ERRERR doesn't have the same value | ||
346 | struct errcode_name | ||
347 | { | ||
348 | int code; | ||
349 | char const* name; | ||
350 | }; | ||
351 | |||
352 | static struct errcode_name s_errcodes[] = { | ||
353 | { LUA_OK, "LUA_OK" }, | ||
354 | { LUA_YIELD, "LUA_YIELD" }, | ||
355 | { LUA_ERRRUN, "LUA_ERRRUN" }, | ||
356 | { LUA_ERRSYNTAX, "LUA_ERRSYNTAX" }, | ||
357 | { LUA_ERRMEM, "LUA_ERRMEM" }, | ||
358 | { LUA_ERRGCMM, "LUA_ERRGCMM" }, | ||
359 | { LUA_ERRERR, "LUA_ERRERR" }, | ||
360 | }; | ||
361 | static char const* get_errcode_name(int _code) | ||
362 | { | ||
363 | for (errcode_name const& _entry : s_errcodes) { | ||
364 | if (_entry.code == _code) { | ||
365 | return _entry.name; | ||
366 | } | ||
367 | } | ||
368 | return "<nullptr>"; | ||
369 | } | ||
370 | #endif // USE_DEBUG_SPEW() | ||
371 | |||
372 | // ################################################################################################# | ||
373 | |||
374 | /* | ||
375 | * str= lane_error( error_val|str ) | ||
376 | * | ||
377 | * Called if there's an error in some lane; add call stack to error message | ||
378 | * just like 'lua.c' normally does. | ||
379 | * | ||
380 | * ".. will be called with the error message and its return value will be the | ||
381 | * message returned on the stack by lua_pcall." | ||
382 | * | ||
383 | * Note: Rather than modifying the error message itself, it would be better | ||
384 | * to provide the call stack (as string) completely separated. This would | ||
385 | * work great with non-string error values as well (current system does not). | ||
386 | * (This is NOT possible with the Lua 5.1 'lua_pcall()'; we could of course | ||
387 | * implement a Lanes-specific 'pcall' of our own that does this). TBD!!! :) | ||
388 | * --AKa 22-Jan-2009 | ||
389 | */ | ||
390 | #if ERROR_FULL_STACK | ||
391 | |||
392 | // xxh64 of string "kStackTraceRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
393 | static constexpr RegistryUniqueKey kStackTraceRegKey{ 0x3F327747CACAA904ull }; | ||
394 | |||
395 | [[nodiscard]] static int lane_error(lua_State* L_) | ||
396 | { | ||
397 | // error message (any type) | ||
398 | STACK_CHECK_START_ABS(L_, 1); // L_: some_error | ||
399 | |||
400 | // Don't do stack survey for cancelled lanes. | ||
401 | // | ||
402 | if (kCancelError.equals(L_, 1)) { | ||
403 | return 1; // just pass on | ||
404 | } | ||
405 | |||
406 | STACK_GROW(L_, 3); | ||
407 | bool const _extended{ kExtendedStackTraceRegKey.readBoolValue(L_) }; | ||
408 | STACK_CHECK(L_, 1); | ||
409 | |||
410 | // Place stack trace at 'registry[kStackTraceRegKey]' for the 'lua_pcall()' | ||
411 | // caller to fetch. This bypasses the Lua 5.1 limitation of only one | ||
412 | // return value from error handler to 'lua_pcall()' caller. | ||
413 | |||
414 | // It's adequate to push stack trace as a table. This gives the receiver | ||
415 | // of the stack best means to format it to their liking. Also, it allows | ||
416 | // us to add more stack info later, if needed. | ||
417 | // | ||
418 | // table of { "sourcefile.lua:<line>", ... } | ||
419 | // | ||
420 | lua_newtable(L_); // L_: some_error {} | ||
421 | |||
422 | // Best to start from level 1, but in some cases it might be a C function | ||
423 | // and we don't get '.currentline' for that. It's okay - just keep level | ||
424 | // and table index growing separate. --AKa 22-Jan-2009 | ||
425 | // | ||
426 | lua_Debug _ar; | ||
427 | for (int _n = 1; lua_getstack(L_, _n, &_ar); ++_n) { | ||
428 | lua_getinfo(L_, _extended ? "Sln" : "Sl", &_ar); | ||
429 | if (_extended) { | ||
430 | lua_newtable(L_); // L_: some_error {} {} | ||
431 | |||
432 | lua_pushstring(L_, _ar.source); // L_: some_error {} {} source | ||
433 | lua_setfield(L_, -2, "source"); // L_: some_error {} {} | ||
434 | |||
435 | lua_pushinteger(L_, _ar.currentline); // L_: some_error {} {} currentline | ||
436 | lua_setfield(L_, -2, "currentline"); // L_: some_error {} {} | ||
437 | |||
438 | lua_pushstring(L_, _ar.name); // L_: some_error {} {} name | ||
439 | lua_setfield(L_, -2, "name"); // L_: some_error {} {} | ||
440 | |||
441 | lua_pushstring(L_, _ar.namewhat); // L_: some_error {} {} namewhat | ||
442 | lua_setfield(L_, -2, "namewhat"); // L_: some_error {} {} | ||
443 | |||
444 | lua_pushstring(L_, _ar.what); // L_: some_error {} {} what | ||
445 | lua_setfield(L_, -2, "what"); // L_: some_error {} {} | ||
446 | } else if (_ar.currentline > 0) { | ||
447 | lua_pushfstring(L_, "%s:%d", _ar.short_src, _ar.currentline); // L_: some_error {} "blah:blah" | ||
448 | } else { | ||
449 | lua_pushfstring(L_, "%s:?", _ar.short_src); // L_: some_error {} "blah" | ||
450 | } | ||
451 | lua_rawseti(L_, -2, static_cast<lua_Integer>(_n)); // L_: some_error {} | ||
452 | } | ||
453 | |||
454 | // store the stack trace table in the registry | ||
455 | kStackTraceRegKey.setValue(L_, [](lua_State* L_) { lua_insert(L_, -2); }); // L_: some_error | ||
456 | |||
457 | STACK_CHECK(L_, 1); | ||
458 | return 1; // the untouched error value | ||
459 | } | ||
460 | #endif // ERROR_FULL_STACK | ||
461 | |||
462 | // ################################################################################################# | ||
463 | // ########################################## Finalizer ############################################ | ||
464 | // ################################################################################################# | ||
465 | |||
466 | static void push_stack_trace(lua_State* L_, int rc_, int stk_base_) | ||
467 | { | ||
468 | // Lua 5.1 error handler is limited to one return value; it stored the stack trace in the registry | ||
469 | switch (rc_) { | ||
470 | case LUA_OK: // no error, body return values are on the stack | ||
471 | break; | ||
472 | |||
473 | case LUA_ERRRUN: // cancellation or a runtime error | ||
474 | #if ERROR_FULL_STACK // when ERROR_FULL_STACK, we installed a handler | ||
475 | { | ||
476 | STACK_CHECK_START_REL(L_, 0); | ||
477 | // fetch the call stack table from the registry where the handler stored it | ||
478 | STACK_GROW(L_, 1); | ||
479 | // yields nil if no stack was generated (in case of cancellation for example) | ||
480 | kStackTraceRegKey.pushValue(L_); // L_: err trace|nil | ||
481 | STACK_CHECK(L_, 1); | ||
482 | |||
483 | // For cancellation the error message is kCancelError, and a stack trace isn't placed | ||
484 | // For other errors, the message can be whatever was thrown, and we should have a stack trace table | ||
485 | LUA_ASSERT(L_, lua_type(L_, 1 + stk_base_) == (kCancelError.equals(L_, stk_base_) ? LUA_TNIL : LUA_TTABLE)); | ||
486 | // Just leaving the stack trace table on the stack is enough to get it through to the master. | ||
487 | break; | ||
488 | } | ||
489 | #else // !ERROR_FULL_STACK | ||
490 | [[fallthrough]]; // fall through if not ERROR_FULL_STACK | ||
491 | #endif // !ERROR_FULL_STACK | ||
492 | |||
493 | case LUA_ERRMEM: // memory allocation error (handler not called) | ||
494 | case LUA_ERRERR: // error while running the error handler (if any, for example an out-of-memory condition) | ||
495 | default: | ||
496 | // we should have a single value which is either a string (the error message) or kCancelError | ||
497 | LUA_ASSERT(L_, (lua_gettop(L_) == stk_base_) && ((lua_type(L_, stk_base_) == LUA_TSTRING) || kCancelError.equals(L_, stk_base_))); | ||
498 | break; | ||
499 | } | ||
500 | } | ||
501 | |||
502 | // ################################################################################################# | ||
503 | //--- | ||
504 | // Run finalizers - if any - with the given parameters | ||
505 | // | ||
506 | // If 'rc' is nonzero, error message and stack index (the latter only when ERROR_FULL_STACK == 1) are available as: | ||
507 | // [-1]: stack trace (table) | ||
508 | // [-2]: error message (any type) | ||
509 | // | ||
510 | // Returns: | ||
511 | // 0 if finalizers were run without error (or there were none) | ||
512 | // LUA_ERRxxx return code if any of the finalizers failed | ||
513 | // | ||
514 | // TBD: should we add stack trace on failing finalizer, wouldn't be hard.. | ||
515 | // | ||
516 | |||
517 | [[nodiscard]] static int run_finalizers(lua_State* L_, int lua_rc_) | ||
518 | { | ||
519 | kFinalizerRegKey.pushValue(L_); // L_: ... finalizers? | ||
520 | if (lua_isnil(L_, -1)) { | ||
521 | lua_pop(L_, 1); | ||
522 | return 0; // no finalizers | ||
523 | } | ||
524 | |||
525 | STACK_GROW(L_, 5); | ||
526 | |||
527 | int const _finalizers_index{ lua_gettop(L_) }; | ||
528 | int const _err_handler_index{ ERROR_FULL_STACK ? (lua_pushcfunction(L_, lane_error), lua_gettop(L_)) : 0 }; | ||
529 | |||
530 | int rc{ LUA_OK }; | ||
531 | for (int n = static_cast<int>(lua_rawlen(L_, _finalizers_index)); n > 0; --n) { | ||
532 | int args = 0; | ||
533 | lua_pushinteger(L_, n); // L_: ... finalizers lane_error n | ||
534 | lua_rawget(L_, _finalizers_index); // L_: ... finalizers lane_error finalizer | ||
535 | LUA_ASSERT(L_, lua_isfunction(L_, -1)); | ||
536 | if (lua_rc_ != LUA_OK) { // we have an error message and an optional stack trace at the bottom of the stack | ||
537 | LUA_ASSERT(L_, _finalizers_index == 2 || _finalizers_index == 3); | ||
538 | // char const* err_msg = lua_tostring(L_, 1); | ||
539 | lua_pushvalue(L_, 1); // L_: ... finalizers lane_error finalizer err_msg | ||
540 | // 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 | ||
541 | if (_finalizers_index == 3) { | ||
542 | lua_pushvalue(L_, 2); // L_: ... finalizers lane_error finalizer err_msg stack_trace | ||
543 | } | ||
544 | args = _finalizers_index - 1; | ||
545 | } | ||
546 | |||
547 | // if no error from the main body, finalizer doesn't receive any argument, else it gets the error message and optional stack trace | ||
548 | rc = lua_pcall(L_, args, 0, _err_handler_index); // L_: ... finalizers lane_error err_msg2? | ||
549 | if (rc != LUA_OK) { | ||
550 | push_stack_trace(L_, rc, lua_gettop(L_)); // L_: ... finalizers lane_error err_msg2? trace | ||
551 | // If one finalizer fails, don't run the others. Return this | ||
552 | // as the 'real' error, replacing what we could have had (or not) | ||
553 | // from the actual code. | ||
554 | break; | ||
555 | } | ||
556 | // no error, proceed to next finalizer // L_: ... finalizers lane_error | ||
557 | } | ||
558 | |||
559 | if (rc != LUA_OK) { | ||
560 | // ERROR_FULL_STACK accounts for the presence of lane_error on the stack | ||
561 | int const nb_err_slots{ lua_gettop(L_) - _finalizers_index - ERROR_FULL_STACK }; | ||
562 | // a finalizer generated an error, this is what we leave of the stack | ||
563 | for (int n = nb_err_slots; n > 0; --n) { | ||
564 | lua_replace(L_, n); | ||
565 | } | ||
566 | // leave on the stack only the error and optional stack trace produced by the error in the finalizer | ||
567 | lua_settop(L_, nb_err_slots); // L_: ... lane_error trace | ||
568 | } else { // no error from the finalizers, make sure only the original return values from the lane body remain on the stack | ||
569 | lua_settop(L_, _finalizers_index - 1); | ||
570 | } | ||
571 | |||
572 | return rc; | ||
573 | } | ||
574 | |||
575 | // ################################################################################################# | ||
576 | |||
577 | /* | ||
578 | * Add the lane to selfdestruct chain; the ones still running at the end of the | ||
579 | * whole process will be cancelled. | ||
580 | */ | ||
581 | static void selfdestruct_add(Lane* lane_) | ||
582 | { | ||
583 | std::lock_guard<std::mutex> _guard{ lane_->U->selfdestructMutex }; | ||
584 | assert(lane_->selfdestruct_next == nullptr); | ||
585 | |||
586 | lane_->selfdestruct_next = lane_->U->selfdestructFirst; | ||
587 | lane_->U->selfdestructFirst = lane_; | ||
588 | } | ||
589 | |||
590 | // ################################################################################################# | ||
591 | |||
592 | // A free-running lane has ended; remove it from selfdestruct chain | ||
593 | [[nodiscard]] static bool selfdestruct_remove(Lane* lane_) | ||
594 | { | ||
595 | bool _found{ false }; | ||
596 | std::lock_guard<std::mutex> _guard{ lane_->U->selfdestructMutex }; | ||
597 | // Make sure (within the MUTEX) that we actually are in the chain | ||
598 | // still (at process exit they will remove us from chain and then | ||
599 | // cancel/kill). | ||
600 | // | ||
601 | if (lane_->selfdestruct_next != nullptr) { | ||
602 | Lane* volatile* _ref = static_cast<Lane* volatile*>(&lane_->U->selfdestructFirst); | ||
603 | |||
604 | while (*_ref != SELFDESTRUCT_END) { | ||
605 | if (*_ref == lane_) { | ||
606 | *_ref = lane_->selfdestruct_next; | ||
607 | lane_->selfdestruct_next = nullptr; | ||
608 | // the terminal shutdown should wait until the lane is done with its lua_close() | ||
609 | lane_->U->selfdestructingCount.fetch_add(1, std::memory_order_release); | ||
610 | _found = true; | ||
611 | break; | ||
612 | } | ||
613 | _ref = static_cast<Lane* volatile*>(&((*_ref)->selfdestruct_next)); | ||
614 | } | ||
615 | assert(_found); | ||
616 | } | ||
617 | return _found; | ||
618 | } | ||
619 | |||
620 | // ################################################################################################# | ||
621 | // ########################################## Main ################################################# | ||
622 | // ################################################################################################# | ||
623 | |||
624 | static void lane_main(Lane* lane_) | ||
625 | { | ||
626 | lua_State* const _L{ lane_->L }; | ||
627 | // wait until the launching thread has finished preparing L | ||
628 | lane_->ready.wait(); | ||
629 | int _rc{ LUA_ERRRUN }; | ||
630 | if (lane_->status == Lane::Pending) { // nothing wrong happened during preparation, we can work | ||
631 | // At this point, the lane function and arguments are on the stack | ||
632 | int const nargs{ lua_gettop(_L) - 1 }; | ||
633 | DEBUGSPEW_CODE(Universe* U = universe_get(_L)); | ||
634 | lane_->status = Lane::Running; // Pending -> Running | ||
635 | |||
636 | // Tie "set_finalizer()" to the state | ||
637 | lua_pushcfunction(_L, LG_set_finalizer); | ||
638 | populate_func_lookup_table(_L, -1, "set_finalizer"); | ||
639 | lua_setglobal(_L, "set_finalizer"); | ||
640 | |||
641 | // Tie "set_debug_threadname()" to the state | ||
642 | // But don't register it in the lookup database because of the Lane pointer upvalue | ||
643 | lua_pushlightuserdata(_L, lane_); | ||
644 | lua_pushcclosure(_L, LG_set_debug_threadname, 1); | ||
645 | lua_setglobal(_L, "set_debug_threadname"); | ||
646 | |||
647 | // Tie "cancel_test()" to the state | ||
648 | lua_pushcfunction(_L, LG_cancel_test); | ||
649 | populate_func_lookup_table(_L, -1, "cancel_test"); | ||
650 | lua_setglobal(_L, "cancel_test"); | ||
651 | |||
652 | // this could be done in lane_new before the lane body function is pushed on the stack to avoid unnecessary stack slot shifting around | ||
653 | #if ERROR_FULL_STACK | ||
654 | // Tie "set_error_reporting()" to the state | ||
655 | lua_pushcfunction(_L, LG_set_error_reporting); | ||
656 | populate_func_lookup_table(_L, -1, "set_error_reporting"); | ||
657 | lua_setglobal(_L, "set_error_reporting"); | ||
658 | |||
659 | STACK_GROW(_L, 1); | ||
660 | lua_pushcfunction(_L, lane_error); // L: func args handler | ||
661 | lua_insert(_L, 1); // L: handler func args | ||
662 | #endif // L: ERROR_FULL_STACK | ||
663 | |||
664 | _rc = lua_pcall(_L, nargs, LUA_MULTRET, ERROR_FULL_STACK); // L: retvals|err | ||
665 | |||
666 | #if ERROR_FULL_STACK | ||
667 | lua_remove(_L, 1); // L: retvals|error | ||
668 | #endif // ERROR_FULL_STACK | ||
669 | |||
670 | // in case of error and if it exists, fetch stack trace from registry and push it | ||
671 | push_stack_trace(_L, _rc, 1); // L: retvals|error [trace] | ||
672 | |||
673 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "Lane %p body: %s (%s)\n" INDENT_END(U), _L, get_errcode_name(_rc), kCancelError.equals(_L, 1) ? "cancelled" : lua_typename(_L, lua_type(_L, 1)))); | ||
674 | // Call finalizers, if the script has set them up. | ||
675 | // | ||
676 | int _rc2{ run_finalizers(_L, _rc) }; | ||
677 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "Lane %p finalizer: %s\n" INDENT_END(U), _L, get_errcode_name(_rc2))); | ||
678 | if (_rc2 != LUA_OK) { // Error within a finalizer! | ||
679 | // the finalizer generated an error, and left its own error message [and stack trace] on the stack | ||
680 | _rc = _rc2; // we're overruling the earlier script error or normal return | ||
681 | } | ||
682 | lane_->waiting_on = nullptr; // just in case | ||
683 | if (selfdestruct_remove(lane_)) { // check and remove (under lock!) | ||
684 | // We're a free-running thread and no-one's there to clean us up. | ||
685 | lua_close(lane_->L); | ||
686 | lane_->L = nullptr; // just in case | ||
687 | lane_->U->selfdestructMutex.lock(); | ||
688 | // done with lua_close(), terminal shutdown sequence may proceed | ||
689 | lane_->U->selfdestructingCount.fetch_sub(1, std::memory_order_release); | ||
690 | lane_->U->selfdestructMutex.unlock(); | ||
691 | |||
692 | // we destroy our jthread member from inside the thread body, so we have to detach so that we don't try to join, as this doesn't seem a good idea | ||
693 | lane_->thread.detach(); | ||
694 | delete lane_; | ||
695 | lane_ = nullptr; | ||
696 | } | ||
697 | } | ||
698 | if (lane_) { | ||
699 | // leave results (1..top) or error message + stack trace (1..2) on the stack - master will copy them | ||
700 | |||
701 | Lane::Status const _st = (_rc == LUA_OK) ? Lane::Done : kCancelError.equals(_L, 1) ? Lane::Cancelled : Lane::Error; | ||
702 | |||
703 | { | ||
704 | // 'doneMutex' protects the -> Done|Error|Cancelled state change | ||
705 | std::lock_guard lock{ lane_->doneMutex }; | ||
706 | lane_->status = _st; | ||
707 | lane_->doneCondVar.notify_one(); // wake up master (while 'lane_->doneMutex' is on) | ||
708 | } | ||
709 | } | ||
710 | } | ||
711 | |||
712 | // ################################################################################################# | ||
713 | |||
714 | // = thread_gc( lane_ud ) | ||
715 | // | ||
716 | // Cleanup for a thread userdata. If the thread is still executing, leave it | ||
717 | // alive as a free-running thread (will clean up itself). | ||
718 | // | ||
719 | // * Why NOT cancel/kill a loose thread: | ||
720 | // | ||
721 | // At least timer system uses a free-running thread, they should be handy | ||
722 | // and the issue of canceling/killing threads at gc is not very nice, either | ||
723 | // (would easily cause waits at gc cycle, which we don't want). | ||
724 | // | ||
725 | [[nodiscard]] static int lane_gc(lua_State* L_) | ||
726 | { | ||
727 | bool _have_gc_cb{ false }; | ||
728 | Lane* const _lane{ ToLane(L_, 1) }; // L_: ud | ||
729 | |||
730 | // if there a gc callback? | ||
731 | lua_getiuservalue(L_, 1, 1); // L_: ud uservalue | ||
732 | kLaneGC.pushKey(L_); // L_: ud uservalue __gc | ||
733 | lua_rawget(L_, -2); // L_: ud uservalue gc_cb|nil | ||
734 | if (!lua_isnil(L_, -1)) { | ||
735 | lua_remove(L_, -2); // L_: ud gc_cb|nil | ||
736 | lua_pushstring(L_, _lane->debugName); // L_: ud gc_cb name | ||
737 | _have_gc_cb = true; | ||
738 | } else { | ||
739 | lua_pop(L_, 2); // L_: ud | ||
740 | } | ||
741 | |||
742 | // We can read 'lane->status' without locks, but not wait for it | ||
743 | if (_lane->status < Lane::Done) { | ||
744 | // still running: will have to be cleaned up later | ||
745 | selfdestruct_add(_lane); | ||
746 | assert(_lane->selfdestruct_next); | ||
747 | if (_have_gc_cb) { | ||
748 | lua_pushliteral(L_, "selfdestruct"); // L_: ud gc_cb name status | ||
749 | lua_call(L_, 2, 0); // L_: ud | ||
750 | } | ||
751 | return 0; | ||
752 | } else if (_lane->L) { | ||
753 | // no longer accessing the Lua VM: we can close right now | ||
754 | lua_close(_lane->L); | ||
755 | _lane->L = nullptr; | ||
756 | // just in case, but s will be freed soon so... | ||
757 | _lane->debugName = "<gc>"; | ||
758 | } | ||
759 | |||
760 | // Clean up after a (finished) thread | ||
761 | delete _lane; | ||
762 | |||
763 | // do this after lane cleanup in case the callback triggers an error | ||
764 | if (_have_gc_cb) { | ||
765 | lua_pushliteral(L_, "closed"); // L_: ud gc_cb name status | ||
766 | lua_call(L_, 2, 0); // L_: ud | ||
767 | } | ||
768 | return 0; | ||
769 | } | ||
770 | |||
771 | // ################################################################################################# | ||
772 | // #################################### Lane implementation ######################################## | ||
773 | // ################################################################################################# | ||
774 | |||
775 | Lane::Lane(Universe* U_, lua_State* L_) | ||
776 | : U{ U_ } | ||
777 | , L{ L_ } | ||
778 | { | ||
779 | #if HAVE_LANE_TRACKING() | ||
780 | U->tracker.tracking_add(this); | ||
781 | #endif // HAVE_LANE_TRACKING() | ||
782 | } | ||
783 | |||
784 | // ################################################################################################# | ||
785 | |||
786 | Lane::~Lane() | ||
787 | { | ||
788 | // Clean up after a (finished) thread | ||
789 | // | ||
790 | #if HAVE_LANE_TRACKING() | ||
791 | std::ignore = U->tracker.tracking_remove(this); | ||
792 | #endif // HAVE_LANE_TRACKING() | ||
793 | } | ||
794 | |||
795 | // ################################################################################################# | ||
796 | |||
797 | void Lane::changeDebugName(int nameIdx_) | ||
798 | { | ||
799 | // xxh64 of string "debugName" generated at https://www.pelock.com/products/hash-calculator | ||
800 | static constexpr RegistryUniqueKey kRegKey{ 0xA194E2645C57F6DDull }; | ||
801 | nameIdx_ = lua_absindex(L, nameIdx_); | ||
802 | luaL_checktype(L, nameIdx_, LUA_TSTRING); // L: ... "name" ... | ||
803 | STACK_CHECK_START_REL(L, 0); | ||
804 | // store a hidden reference in the registry to make sure the string is kept around even if a lane decides to manually change the "decoda_name" global... | ||
805 | kRegKey.setValue(L, [nameIdx = nameIdx_](lua_State* L_) { lua_pushvalue(L_, nameIdx); }); // L: ... "name" ... | ||
806 | // keep a direct pointer on the string | ||
807 | debugName = lua_tostring(L, nameIdx_); | ||
808 | // to see VM name in Decoda debugger Virtual Machine window | ||
809 | lua_pushvalue(L, nameIdx_); // L: ... "name" ... "name" | ||
810 | lua_setglobal(L, "decoda_name"); // L: ... "name" ... | ||
811 | // and finally set the OS thread name | ||
812 | THREAD_SETNAME(debugName); | ||
813 | STACK_CHECK(L, 0); | ||
814 | } | ||
815 | |||
816 | // ################################################################################################# | ||
817 | |||
818 | // contains keys: { __gc, __index, cached_error, cached_tostring, cancel, join, get_debug_threadname } | ||
819 | void Lane::PushMetatable(lua_State* L_) | ||
820 | { | ||
821 | if (luaL_newmetatable(L_, kLaneMetatableName)) { // L_: mt | ||
822 | lua_pushcfunction(L_, lane_gc); // L_: mt lane_gc | ||
823 | lua_setfield(L_, -2, "__gc"); // L_: mt | ||
824 | lua_pushcfunction(L_, LG_thread_index); // L_: mt LG_thread_index | ||
825 | lua_setfield(L_, -2, "__index"); // L_: mt | ||
826 | lua_getglobal(L_, "error"); // L_: mt error | ||
827 | LUA_ASSERT(L_, lua_isfunction(L_, -1)); | ||
828 | lua_setfield(L_, -2, "cached_error"); // L_: mt | ||
829 | lua_getglobal(L_, "tostring"); // L_: mt tostring | ||
830 | LUA_ASSERT(L_, lua_isfunction(L_, -1)); | ||
831 | lua_setfield(L_, -2, "cached_tostring"); // L_: mt | ||
832 | lua_pushcfunction(L_, LG_thread_join); // L_: mt LG_thread_join | ||
833 | lua_setfield(L_, -2, "join"); // L_: mt | ||
834 | lua_pushcfunction(L_, LG_get_debug_threadname); // L_: mt LG_get_debug_threadname | ||
835 | lua_setfield(L_, -2, "get_debug_threadname"); // L_: mt | ||
836 | lua_pushcfunction(L_, LG_thread_cancel); // L_: mt LG_thread_cancel | ||
837 | lua_setfield(L_, -2, "cancel"); // L_: mt | ||
838 | lua_pushliteral(L_, kLaneMetatableName); // L_: mt "Lane" | ||
839 | lua_setfield(L_, -2, "__metatable"); // L_: mt | ||
840 | } | ||
841 | } | ||
842 | // ################################################################################################# | ||
843 | |||
844 | void Lane::pushThreadStatus(lua_State* L_) const | ||
845 | { | ||
846 | char const* const _str{ threadStatusString() }; | ||
847 | LUA_ASSERT(L_, _str); | ||
848 | |||
849 | lua_pushstring(L_, _str); | ||
850 | } | ||
851 | |||
852 | // ################################################################################################# | ||
853 | |||
854 | // intern the debug name in the caller lua state so that the pointer remains valid after the lane's state is closed | ||
855 | void Lane::securizeDebugName(lua_State* L_) | ||
856 | { | ||
857 | STACK_CHECK_START_REL(L_, 0); | ||
858 | STACK_GROW(L_, 3); | ||
859 | // a Lane's uservalue should be a table | ||
860 | lua_getiuservalue(L_, 1, 1); // L_: lane ... {uv} | ||
861 | LUA_ASSERT(L_, lua_istable(L_, -1)); | ||
862 | // we don't care about the actual key, so long as it's unique and can't collide with anything. | ||
863 | lua_newtable(L_); // L_: lane ... {uv} {} | ||
864 | // Lua 5.1 can't do 'lane_->debugName = lua_pushstring(L_, lane_->debugName);' | ||
865 | lua_pushstring(L_, debugName); // L_: lane ... {uv} {} name | ||
866 | debugName = lua_tostring(L_, -1); | ||
867 | lua_rawset(L_, -3); // L_: lane ... {uv} | ||
868 | lua_pop(L_, 1); // L_: lane | ||
869 | STACK_CHECK(L_, 0); | ||
870 | } | ||
871 | |||
872 | // ################################################################################################# | ||
873 | |||
874 | void Lane::startThread(int priority_) | ||
875 | { | ||
876 | thread = std::jthread([this]() { lane_main(this); }); | ||
877 | if (priority_ != kThreadPrioDefault) { | ||
878 | JTHREAD_SET_PRIORITY(thread, priority_, U->sudo); | ||
879 | } | ||
880 | } | ||
881 | |||
882 | // ################################################################################################# | ||
883 | |||
884 | //--- | ||
885 | // str= thread_status( lane ) | ||
886 | // | ||
887 | // Returns: "pending" not started yet | ||
888 | // -> "running" started, doing its work.. | ||
889 | // <-> "waiting" blocked in a receive() | ||
890 | // -> "done" finished, results are there | ||
891 | // / "error" finished at an error, error value is there | ||
892 | // / "cancelled" execution cancelled by M (state gone) | ||
893 | // | ||
894 | [[nodiscard]] char const* Lane::threadStatusString() const | ||
895 | { | ||
896 | char const* const _str{ | ||
897 | (status == Lane::Pending) ? "pending" : | ||
898 | (status == Lane::Running) ? "running" : // like in 'co.status()' | ||
899 | (status == Lane::Waiting) ? "waiting" : | ||
900 | (status == Lane::Done) ? "done" : | ||
901 | (status == Lane::Error) ? "error" : | ||
902 | (status == Lane::Cancelled) ? "cancelled" : | ||
903 | nullptr | ||
904 | }; | ||
905 | return _str; | ||
906 | } | ||
907 | |||
908 | // ################################################################################################# | ||
909 | |||
910 | bool Lane::waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_) | ||
911 | { | ||
912 | std::unique_lock _guard{ doneMutex }; | ||
913 | // std::stop_token token{ thread.get_stop_token() }; | ||
914 | // return doneCondVar.wait_until(lock, token, secs_, [this](){ return status >= Lane::Done; }); | ||
915 | return doneCondVar.wait_until(_guard, until_, [this]() { return status >= Lane::Done; }); | ||
916 | } | ||