diff options
author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-17 15:34:44 +0200 |
---|---|---|
committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-17 15:34:44 +0200 |
commit | 823d3d570236e32bdbe270306df5602ff586c4a7 (patch) | |
tree | 88e60e92f4c5bf618588ec0b64f295540390ae8f /src | |
parent | d359e8cf7d71c2f80a1706fef38e603478ac003f (diff) | |
download | lanes-823d3d570236e32bdbe270306df5602ff586c4a7.tar.gz lanes-823d3d570236e32bdbe270306df5602ff586c4a7.tar.bz2 lanes-823d3d570236e32bdbe270306df5602ff586c4a7.zip |
Error reporting revamp
* set_error_reporting() is gone
* new lane generator setting error_reporting_level
* basic/extended stack trace is selectable at runtime instead of compile-time
Diffstat (limited to 'src')
-rw-r--r-- | src/lane.cpp | 428 | ||||
-rw-r--r-- | src/lane.h | 21 | ||||
-rw-r--r-- | src/lanes.cpp | 42 | ||||
-rw-r--r-- | src/lanes.lua | 21 |
4 files changed, 292 insertions, 220 deletions
diff --git a/src/lane.cpp b/src/lane.cpp index f320071..e321a52 100644 --- a/src/lane.cpp +++ b/src/lane.cpp | |||
@@ -28,16 +28,15 @@ THE SOFTWARE. | |||
28 | 28 | ||
29 | #include "debugspew.h" | 29 | #include "debugspew.h" |
30 | #include "intercopycontext.h" | 30 | #include "intercopycontext.h" |
31 | #include "tools.h" | ||
32 | #include "threading.h" | 31 | #include "threading.h" |
32 | #include "tools.h" | ||
33 | 33 | ||
34 | // ################################################################################################# | 34 | // ################################################################################################# |
35 | 35 | ||
36 | /* Do you want full call stacks, or just the line where the error happened? | 36 | // xxh64 of string "error" generated at https://www.pelock.com/products/hash-calculator |
37 | * | 37 | static constexpr UniqueKey kCachedError{ 0xD6F35DD608D0A203ull }; |
38 | * TBD: The full stack feature does not seem to work (try 'make error'). | 38 | // xxh64 of string "tostring" generated at https://www.pelock.com/products/hash-calculator |
39 | */ | 39 | static constexpr UniqueKey kCachedTostring{ 0xAB5EA23BCEA0C35Cull }; |
40 | #define ERROR_FULL_STACK 1 // must be either 0 or 1 as we do some index arithmetics with it! | ||
41 | 40 | ||
42 | // ################################################################################################# | 41 | // ################################################################################################# |
43 | // ######################################### Lua API ############################################### | 42 | // ######################################### Lua API ############################################### |
@@ -77,26 +76,6 @@ static LUAG_FUNC(set_finalizer) | |||
77 | 76 | ||
78 | // ################################################################################################# | 77 | // ################################################################################################# |
79 | 78 | ||
80 | #if ERROR_FULL_STACK | ||
81 | LUAG_FUNC(set_error_reporting) | ||
82 | { | ||
83 | luaL_checktype(L_, 1, LUA_TSTRING); | ||
84 | char const* _mode{ lua_tostring(L_, 1) }; | ||
85 | lua_pushliteral(L_, "extended"); | ||
86 | bool const _extended{ strcmp(_mode, "extended") == 0 }; | ||
87 | bool const _basic{ strcmp(_mode, "basic") == 0 }; | ||
88 | if (!_extended && !_basic) { | ||
89 | raise_luaL_error(L_, "unsupported error reporting model %s", _mode); | ||
90 | } | ||
91 | |||
92 | kExtendedStackTraceRegKey.setValue(L_, [extended = _extended](lua_State* L_) { lua_pushboolean(L_, extended ? 1 : 0); }); | ||
93 | return 0; | ||
94 | } | ||
95 | |||
96 | #endif // ERROR_FULL_STACK | ||
97 | |||
98 | // ################################################################################################# | ||
99 | |||
100 | // upvalue #1 is the lane userdata | 79 | // upvalue #1 is the lane userdata |
101 | static LUAG_FUNC(set_debug_threadname) | 80 | static LUAG_FUNC(set_debug_threadname) |
102 | { | 81 | { |
@@ -172,7 +151,7 @@ static LUAG_FUNC(thread_join) | |||
172 | int const _n{ lua_gettop(_L2) }; // L_: lane L2: "err" [trace] | 151 | int const _n{ lua_gettop(_L2) }; // L_: lane L2: "err" [trace] |
173 | STACK_GROW(L_, 3); | 152 | STACK_GROW(L_, 3); |
174 | lua_pushnil(L_); // L_: lane nil | 153 | lua_pushnil(L_); // L_: lane nil |
175 | // 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 ... | 154 | // 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 ... |
176 | InterCopyContext _c{ _lane->U, DestState{ L_ }, SourceState{ _L2 }, {}, {}, {}, {}, {} }; | 155 | InterCopyContext _c{ _lane->U, DestState{ L_ }, SourceState{ _L2 }, {}, {}, {}, {}, {} }; |
177 | if (_c.inter_move(_n) != InterCopyResult::Success) { // L_: lane nil "err" [trace] L2: | 156 | if (_c.inter_move(_n) != InterCopyResult::Success) { // L_: lane nil "err" [trace] L2: |
178 | raise_luaL_error(L_, "tried to copy unsupported types: %s", lua_tostring(L_, -_n)); | 157 | raise_luaL_error(L_, "tried to copy unsupported types: %s", lua_tostring(L_, -_n)); |
@@ -197,143 +176,177 @@ static LUAG_FUNC(thread_join) | |||
197 | 176 | ||
198 | // ################################################################################################# | 177 | // ################################################################################################# |
199 | 178 | ||
200 | // lane:__index(key,usr) -> value | 179 | // key is numeric, wait until the thread returns and populate the environment with the return values |
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 | 180 | // If the return values signal an error, propagate it |
205 | // If key is "status" return the thread status | 181 | // Else If key is found in the environment, return it |
206 | // Else raise an error | 182 | static int thread_index_number(lua_State* L_) |
207 | LUAG_FUNC(thread_index) | ||
208 | { | 183 | { |
209 | static constexpr int kSelf{ 1 }; | 184 | static constexpr int kSelf{ 1 }; |
210 | static constexpr int kKey{ 2 }; | 185 | static constexpr int kKey{ 2 }; |
186 | static constexpr int kUsr{ 3 }; | ||
187 | |||
211 | Lane* const _lane{ ToLane(L_, kSelf) }; | 188 | Lane* const _lane{ ToLane(L_, kSelf) }; |
212 | LUA_ASSERT(L_, lua_gettop(L_) == 2); | 189 | LUA_ASSERT(L_, lua_gettop(L_) == 2); // L_: lane n |
213 | 190 | ||
214 | STACK_GROW(L_, 8); // up to 8 positions are needed in case of error propagation | 191 | // first, check that we don't already have an environment that holds the requested value |
192 | // If key is found in the uservalue, return it | ||
193 | lua_getiuservalue(L_, kSelf, 1); // L_: lane n {uv} | ||
194 | lua_pushvalue(L_, kKey); // L_: lane n {uv} n | ||
195 | lua_rawget(L_, kUsr); // L_: lane n {uv} v|nil | ||
196 | if (!lua_isnil(L_, -1)) { | ||
197 | return 1; | ||
198 | } | ||
199 | lua_pop(L_, 1); // L_: lane n {uv} | ||
200 | |||
201 | // check if we already fetched the values from the thread or not | ||
202 | lua_pushinteger(L_, 0); // L_: lane n {uv} 0 | ||
203 | lua_rawget(L_, kUsr); // L_: lane n {uv} uv[0]|nil | ||
204 | bool const _fetched{ !lua_isnil(L_, -1) }; | ||
205 | lua_pop(L_, 1); // L_: lane n {uv} | ||
206 | if (!_fetched) { | ||
207 | lua_pushinteger(L_, 0); // L_: lane n {uv} 0 | ||
208 | lua_pushboolean(L_, 1); // L_: lane n {uv} 0 true | ||
209 | lua_rawset(L_, kUsr); // L_: lane n {uv} | ||
210 | // wait until thread has completed, transfer everything from the lane's stack to our side | ||
211 | lua_pushcfunction(L_, LG_thread_join); // L_: lane n {uv} join | ||
212 | lua_pushvalue(L_, kSelf); // L_: lane n {uv} join lane | ||
213 | lua_call(L_, 1, LUA_MULTRET); // lane:join() // L_: lane n {uv} ... | ||
214 | switch (_lane->status) { | ||
215 | default: | ||
216 | // this is an internal error, we probably never get here | ||
217 | lua_settop(L_, 0); // L_: | ||
218 | lua_pushliteral(L_, "Unexpected status: "); // L_: "Unexpected status: " | ||
219 | _lane->pushThreadStatus(L_); // L_: "Unexpected status: " "<status>" | ||
220 | lua_concat(L_, 2); // L_: "Unexpected status: <status>" | ||
221 | raise_lua_error(L_); | ||
222 | |||
223 | case Lane::Done: // got regular return values | ||
224 | { | ||
225 | int const _nvalues{ lua_gettop(L_) - 3 }; // L_: lane n {uv} ... | ||
226 | for (int _i = _nvalues; _i > 0; --_i) { | ||
227 | // pop the last element of the stack, to store it in the uservalue at its proper index | ||
228 | lua_rawseti(L_, kUsr, _i); // L_: lane n {uv} | ||
229 | } | ||
230 | } | ||
231 | break; | ||
215 | 232 | ||
216 | // If key is numeric, wait until the thread returns and populate the environment with the return values | 233 | case Lane::Error: // got 2 or 3 values: nil, errstring, and possibly a callstack table |
217 | if (lua_type(L_, kKey) == LUA_TNUMBER) { | 234 | if (_lane->errorTraceLevel == Lane::Minimal) { |
218 | static constexpr int kUsr{ 3 }; | 235 | LUA_ASSERT(L_, lua_gettop(L_) == 5 && lua_isnil(L_, 4) && !lua_isnil(L_, 5)); // L_: lane n {uv} nil "<msg>" |
219 | // first, check that we don't already have an environment that holds the requested value | 236 | } else { |
220 | { | 237 | LUA_ASSERT(L_, lua_gettop(L_) == 6 && lua_isnil(L_, 4) && !lua_isnil(L_, 5) && lua_istable(L_, 6)); |
221 | // If key is found in the uservalue, return it | 238 | lua_insert(L_, -2); // L_: lane n {uv} nil {trace} "<msg>" |
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 | } | 239 | } |
228 | lua_pop(L_, 1); | 240 | // uv[-1] = "<msg>" |
241 | lua_rawseti(L_, kUsr, -1); // L_: lane n {uv} nil {trace}? | ||
242 | break; | ||
243 | |||
244 | case Lane::Cancelled: | ||
245 | // do nothing | ||
246 | break; | ||
229 | } | 247 | } |
230 | { | 248 | } |
231 | // check if we already fetched the values from the thread or not | 249 | STACK_GROW(L_, 6); // up to 6 positions are needed in case of error propagation |
232 | lua_pushinteger(L_, 0); | 250 | lua_settop(L_, 3); // L_: lane n {uv} |
233 | lua_rawget(L_, kUsr); | 251 | int const _key{ static_cast<int>(lua_tointeger(L_, kKey)) }; |
234 | bool const _fetched{ !lua_isnil(L_, -1) }; | 252 | if (_key != -1) { |
235 | lua_pop(L_, 1); // back to our 2 args + uservalue on the stack | 253 | lua_rawgeti(L_, kUsr, -1); // L_: lane n {uv} <error>|nil |
236 | if (!_fetched) { | 254 | if (!lua_isnil(L_, -1)) { // an error was stored // L_: lane n {uv} <error> |
237 | lua_pushinteger(L_, 0); | 255 | lua_getmetatable(L_, 1); // L_: lane n {uv} <error> {mt} |
238 | lua_pushboolean(L_, 1); | 256 | lua_replace(L_, -3); // L_: lane n {mt} <error> |
239 | lua_rawset(L_, kUsr); | 257 | #if LUA_VERSION_NUM == 501 |
240 | // wait until thread has completed | 258 | // Note: Lua 5.1 interpreter is not prepared to show |
241 | lua_pushcfunction(L_, LG_thread_join); | 259 | // non-string errors, so we use 'tostring()' here |
242 | lua_pushvalue(L_, kSelf); | 260 | // to get meaningful output. --AKa 22-Jan-2009 |
243 | lua_call(L_, 1, LUA_MULTRET); // all return values are on the stack, at slots 4+ | 261 | // |
244 | switch (_lane->status) { | 262 | // Also, the stack dump we get is no good; it only |
245 | default: | 263 | // lists our internal Lanes functions. There seems |
246 | // this is an internal error, we probably never get here | 264 | // to be no way to switch it off, though. |
247 | lua_settop(L_, 0); | 265 | // |
248 | lua_pushliteral(L_, "Unexpected status: "); | 266 | // Level 3 should show the line where 'h[x]' was read |
249 | _lane->pushThreadStatus(L_); | 267 | // but this only seems to work for string messages |
250 | lua_concat(L_, 2); | 268 | // (Lua 5.1.4). No idea, why. --AKa 22-Jan-2009 |
251 | raise_lua_error(L_); | 269 | if (!lua_isstring(L_, -1)) { |
252 | [[fallthrough]]; // fall through if we are killed, as we got nil, "killed" on the stack | 270 | kCachedTostring.pushKey(L_); // L_: lane n {mt} <error> kCachedTostring |
253 | 271 | lua_rawget(L_, -3); // L_: lane n {mt} <error> tostring() | |
254 | case Lane::Done: // got regular return values | 272 | lua_insert(L_, -2); // L_: lane n {mt} tostring() <error> |
255 | { | 273 | lua_call(L_, 1, 1); // tostring(errstring) // L_: lane n {mt} "error" |
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 | } | 274 | } |
308 | lua_rawgeti(L_, kUsr, _key); | 275 | #endif // LUA_VERSION_NUM == 501 |
276 | kCachedError.pushKey(L_); // L_: lane n {mt} "error" kCachedError | ||
277 | lua_rawget(L_, -3); // L_: lane n {mt} "error" error() | ||
278 | lua_replace(L_, -3); // L_: lane n error() "error" | ||
279 | lua_pushinteger(L_, 3); // L_: lane n error() "error" 3 | ||
280 | lua_call(L_, 2, 0); // error(tostring(errstring), 3) -> doesn't return // L_: lane n | ||
281 | raise_luaL_error(L_, "%s: should not get here!", _lane->debugName); | ||
282 | } else { | ||
283 | lua_pop(L_, 1); // L_: lane n {uv} | ||
309 | } | 284 | } |
285 | } | ||
286 | lua_rawgeti(L_, kUsr, _key); // L_: lane n {uv} uv[n] | ||
287 | return 1; | ||
288 | } | ||
289 | |||
290 | // ################################################################################################# | ||
291 | |||
292 | // If key is "status" return the thread status | ||
293 | // If key is found in the environment, return it | ||
294 | // Else raise an error | ||
295 | static int thread_index_string(lua_State* L_) | ||
296 | { | ||
297 | static constexpr int kSelf{ 1 }; | ||
298 | static constexpr int kKey{ 2 }; | ||
299 | |||
300 | Lane* const _lane{ ToLane(L_, kSelf) }; | ||
301 | LUA_ASSERT(L_, lua_gettop(L_) == 2); // L_: lane "key" | ||
302 | |||
303 | char const* const _keystr{ lua_tostring(L_, kKey) }; | ||
304 | lua_settop(L_, 2); // keep only our original arguments on the stack | ||
305 | if (strcmp(_keystr, "status") == 0) { | ||
306 | _lane->pushThreadStatus(L_); // L_: lane "key" "<status>" | ||
310 | return 1; | 307 | return 1; |
311 | } | 308 | } |
312 | if (lua_type(L_, kKey) == LUA_TSTRING) { | 309 | if (strcmp(_keystr, "error_trace_level") == 0) { |
313 | char const* const _keystr{ lua_tostring(L_, kKey) }; | 310 | _lane->pushErrorTraceLevel(L_); // L_: lane "key" "<level>" |
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; | 311 | return 1; |
328 | } | 312 | } |
329 | // unknown key | 313 | // return self.metatable[key] |
330 | lua_getmetatable(L_, kSelf); // L_: mt | 314 | lua_getmetatable(L_, kSelf); // L_: lane "key" mt |
331 | lua_getfield(L_, -1, "cached_error"); // L_: mt error | 315 | lua_replace(L_, -3); // L_: mt "key" |
332 | lua_pushliteral(L_, "Unknown key: "); // L_: mt error "Unknown key: " | 316 | lua_rawget(L_, -2); // L_: mt value |
333 | lua_pushvalue(L_, kKey); // L_: mt error "Unknown key: " k | 317 | // only "cancel" and "join" are registered as functions, any other string will raise an error |
334 | lua_concat(L_, 2); // L_: mt error "Unknown key: <k>" | 318 | if (!lua_iscfunction(L_, -1)) { |
335 | lua_call(L_, 1, 0); // error( "Unknown key: " .. key) -> doesn't return // L_: mt | 319 | raise_luaL_error(L_, "can't index a lane with '%s'", _keystr); |
336 | return 0; | 320 | } |
321 | return 1; | ||
322 | } | ||
323 | |||
324 | // ################################################################################################# | ||
325 | |||
326 | // lane:__index(key,usr) -> value | ||
327 | static LUAG_FUNC(thread_index) | ||
328 | { | ||
329 | static constexpr int kSelf{ 1 }; | ||
330 | static constexpr int kKey{ 2 }; | ||
331 | Lane* const _lane{ ToLane(L_, kSelf) }; | ||
332 | LUA_ASSERT(L_, lua_gettop(L_) == 2); | ||
333 | |||
334 | switch (lua_type(L_, kKey)) { | ||
335 | case LUA_TNUMBER: | ||
336 | return thread_index_number(L_); // stack modification is undefined, returned value is at the top | ||
337 | |||
338 | case LUA_TSTRING: | ||
339 | return thread_index_string(L_); // stack modification is undefined, returned value is at the top | ||
340 | |||
341 | default: // unknown key | ||
342 | lua_getmetatable(L_, kSelf); // L_: mt | ||
343 | lua_getfield(L_, -1, "cached_error"); // L_: mt error | ||
344 | lua_pushliteral(L_, "Unknown key: "); // L_: mt error "Unknown key: " | ||
345 | lua_pushvalue(L_, kKey); // L_: mt error "Unknown key: " k | ||
346 | lua_concat(L_, 2); // L_: mt error "Unknown key: <k>" | ||
347 | lua_call(L_, 1, 0); // error( "Unknown key: " .. key) -> doesn't return // L_: mt | ||
348 | raise_luaL_error(L_, "%s[%s]: should not get here!", _lane->debugName, lua_typename(L_, lua_type(L_, kKey))); | ||
349 | } | ||
337 | } | 350 | } |
338 | 351 | ||
339 | // ################################################################################################# | 352 | // ################################################################################################# |
@@ -387,7 +400,6 @@ static char const* get_errcode_name(LuaError _code) | |||
387 | * implement a Lanes-specific 'pcall' of our own that does this). TBD!!! :) | 400 | * implement a Lanes-specific 'pcall' of our own that does this). TBD!!! :) |
388 | * --AKa 22-Jan-2009 | 401 | * --AKa 22-Jan-2009 |
389 | */ | 402 | */ |
390 | #if ERROR_FULL_STACK | ||
391 | 403 | ||
392 | // xxh64 of string "kStackTraceRegKey" generated at https://www.pelock.com/products/hash-calculator | 404 | // xxh64 of string "kStackTraceRegKey" generated at https://www.pelock.com/products/hash-calculator |
393 | static constexpr RegistryUniqueKey kStackTraceRegKey{ 0x3F327747CACAA904ull }; | 405 | static constexpr RegistryUniqueKey kStackTraceRegKey{ 0x3F327747CACAA904ull }; |
@@ -457,13 +469,12 @@ static constexpr RegistryUniqueKey kStackTraceRegKey{ 0x3F327747CACAA904ull }; | |||
457 | STACK_CHECK(L_, 1); | 469 | STACK_CHECK(L_, 1); |
458 | return 1; // the untouched error value | 470 | return 1; // the untouched error value |
459 | } | 471 | } |
460 | #endif // ERROR_FULL_STACK | ||
461 | 472 | ||
462 | // ################################################################################################# | 473 | // ################################################################################################# |
463 | // ########################################## Finalizer ############################################ | 474 | // ########################################## Finalizer ############################################ |
464 | // ################################################################################################# | 475 | // ################################################################################################# |
465 | 476 | ||
466 | static void push_stack_trace(lua_State* L_, LuaError rc_, int stk_base_) | 477 | static void push_stack_trace(lua_State* L_, Lane::ErrorTraceLevel errorTraceLevel_, LuaError rc_, int stk_base_) |
467 | { | 478 | { |
468 | // Lua 5.1 error handler is limited to one return value; it stored the stack trace in the registry | 479 | // Lua 5.1 error handler is limited to one return value; it stored the stack trace in the registry |
469 | switch (rc_) { | 480 | switch (rc_) { |
@@ -471,8 +482,7 @@ static void push_stack_trace(lua_State* L_, LuaError rc_, int stk_base_) | |||
471 | break; | 482 | break; |
472 | 483 | ||
473 | case LuaError::ERRRUN: // cancellation or a runtime error | 484 | case LuaError::ERRRUN: // cancellation or a runtime error |
474 | #if ERROR_FULL_STACK // when ERROR_FULL_STACK, we installed a handler | 485 | if (errorTraceLevel_ != Lane::Minimal) { // when not Minimal, we installed a handler |
475 | { | ||
476 | STACK_CHECK_START_REL(L_, 0); | 486 | STACK_CHECK_START_REL(L_, 0); |
477 | // fetch the call stack table from the registry where the handler stored it | 487 | // fetch the call stack table from the registry where the handler stored it |
478 | STACK_GROW(L_, 1); | 488 | STACK_GROW(L_, 1); |
@@ -484,11 +494,10 @@ static void push_stack_trace(lua_State* L_, LuaError rc_, int stk_base_) | |||
484 | // For other errors, the message can be whatever was thrown, and we should have a stack trace table | 494 | // 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)); | 495 | 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. | 496 | // Just leaving the stack trace table on the stack is enough to get it through to the master. |
497 | } else { | ||
498 | // any kind of error can be thrown with error(), or through a lane/linda cancellation | ||
499 | LUA_ASSERT(L_, lua_gettop(L_) == stk_base_); | ||
487 | } | 500 | } |
488 | #else // !ERROR_FULL_STACK | ||
489 | // any kind of error can be thrown with error(), or through a lane/linda cancellation | ||
490 | LUA_ASSERT(L_, lua_gettop(L_) == stk_base_); | ||
491 | #endif // !ERROR_FULL_STACK | ||
492 | break; | 501 | break; |
493 | 502 | ||
494 | case LuaError::ERRMEM: // memory allocation error (handler not called) | 503 | case LuaError::ERRMEM: // memory allocation error (handler not called) |
@@ -504,7 +513,7 @@ static void push_stack_trace(lua_State* L_, LuaError rc_, int stk_base_) | |||
504 | //--- | 513 | //--- |
505 | // Run finalizers - if any - with the given parameters | 514 | // Run finalizers - if any - with the given parameters |
506 | // | 515 | // |
507 | // If 'rc' is nonzero, error message and stack index (the latter only when ERROR_FULL_STACK == 1) are available as: | 516 | // If 'rc' is nonzero, error message and stack index (the latter only when errorTraceLevel_ == 1) are available as: |
508 | // [-1]: stack trace (table) | 517 | // [-1]: stack trace (table) |
509 | // [-2]: error message (any type) | 518 | // [-2]: error message (any type) |
510 | // | 519 | // |
@@ -515,7 +524,7 @@ static void push_stack_trace(lua_State* L_, LuaError rc_, int stk_base_) | |||
515 | // TBD: should we add stack trace on failing finalizer, wouldn't be hard.. | 524 | // TBD: should we add stack trace on failing finalizer, wouldn't be hard.. |
516 | // | 525 | // |
517 | 526 | ||
518 | [[nodiscard]] static LuaError run_finalizers(lua_State* L_, LuaError lua_rc_) | 527 | [[nodiscard]] static LuaError run_finalizers(lua_State* L_, Lane::ErrorTraceLevel errorTraceLevel_, LuaError lua_rc_) |
519 | { | 528 | { |
520 | kFinalizerRegKey.pushValue(L_); // L_: ... finalizers? | 529 | kFinalizerRegKey.pushValue(L_); // L_: ... finalizers? |
521 | if (lua_isnil(L_, -1)) { | 530 | if (lua_isnil(L_, -1)) { |
@@ -526,7 +535,7 @@ static void push_stack_trace(lua_State* L_, LuaError rc_, int stk_base_) | |||
526 | STACK_GROW(L_, 5); | 535 | STACK_GROW(L_, 5); |
527 | 536 | ||
528 | int const _finalizers_index{ lua_gettop(L_) }; | 537 | int const _finalizers_index{ lua_gettop(L_) }; |
529 | int const _err_handler_index{ ERROR_FULL_STACK ? (lua_pushcfunction(L_, lane_error), lua_gettop(L_)) : 0 }; | 538 | int const _err_handler_index{ (errorTraceLevel_ != Lane::Minimal) ? (lua_pushcfunction(L_, lane_error), lua_gettop(L_)) : 0 }; |
530 | 539 | ||
531 | LuaError _rc{ LuaError::OK }; | 540 | LuaError _rc{ LuaError::OK }; |
532 | for (int _n = static_cast<int>(lua_rawlen(L_, _finalizers_index)); _n > 0; --_n) { | 541 | for (int _n = static_cast<int>(lua_rawlen(L_, _finalizers_index)); _n > 0; --_n) { |
@@ -548,7 +557,7 @@ static void push_stack_trace(lua_State* L_, LuaError rc_, int stk_base_) | |||
548 | // if no error from the main body, finalizer doesn't receive any argument, else it gets the error message and optional stack trace | 557 | // if no error from the main body, finalizer doesn't receive any argument, else it gets the error message and optional stack trace |
549 | _rc = ToLuaError(lua_pcall(L_, _args, 0, _err_handler_index)); // L_: ... finalizers lane_error err_msg2? | 558 | _rc = ToLuaError(lua_pcall(L_, _args, 0, _err_handler_index)); // L_: ... finalizers lane_error err_msg2? |
550 | if (_rc != LuaError::OK) { | 559 | if (_rc != LuaError::OK) { |
551 | push_stack_trace(L_, _rc, lua_gettop(L_)); // L_: ... finalizers lane_error err_msg2? trace | 560 | push_stack_trace(L_, errorTraceLevel_, _rc, lua_gettop(L_)); // L_: ... finalizers lane_error err_msg2? trace |
552 | // If one finalizer fails, don't run the others. Return this | 561 | // If one finalizer fails, don't run the others. Return this |
553 | // as the 'real' error, replacing what we could have had (or not) | 562 | // as the 'real' error, replacing what we could have had (or not) |
554 | // from the actual code. | 563 | // from the actual code. |
@@ -558,8 +567,8 @@ static void push_stack_trace(lua_State* L_, LuaError rc_, int stk_base_) | |||
558 | } | 567 | } |
559 | 568 | ||
560 | if (_rc != LuaError::OK) { | 569 | if (_rc != LuaError::OK) { |
561 | // ERROR_FULL_STACK accounts for the presence of lane_error on the stack | 570 | // errorTraceLevel_ accounts for the presence of lane_error on the stack |
562 | int const _nb_err_slots{ lua_gettop(L_) - _finalizers_index - ERROR_FULL_STACK }; | 571 | int const _nb_err_slots{ lua_gettop(L_) - _finalizers_index - ((errorTraceLevel_ != Lane::Minimal) ? 1 : 0) }; |
563 | // a finalizer generated an error, this is what we leave of the stack | 572 | // a finalizer generated an error, this is what we leave of the stack |
564 | for (int _n = _nb_err_slots; _n > 0; --_n) { | 573 | for (int _n = _nb_err_slots; _n > 0; --_n) { |
565 | lua_replace(L_, _n); | 574 | lua_replace(L_, _n); |
@@ -651,38 +660,27 @@ static void lane_main(Lane* lane_) | |||
651 | lane_->ready.wait(); | 660 | lane_->ready.wait(); |
652 | LuaError _rc{ LuaError::ERRRUN }; | 661 | LuaError _rc{ LuaError::ERRRUN }; |
653 | if (lane_->status == Lane::Pending) { // nothing wrong happened during preparation, we can work | 662 | if (lane_->status == Lane::Pending) { // nothing wrong happened during preparation, we can work |
654 | // At this point, the lane function and arguments are on the stack | 663 | // At this point, the lane function and arguments are on the stack, possibly preceded by the error handler |
655 | int const _nargs{ lua_gettop(_L) - 1 }; | 664 | int const _errorHandlerCount{ lane_->errorTraceLevel == Lane::Minimal ? 0 : 1}; |
665 | int const _nargs{ lua_gettop(_L) - 1 - _errorHandlerCount }; | ||
656 | DEBUGSPEW_CODE(Universe* _U = universe_get(_L)); | 666 | DEBUGSPEW_CODE(Universe* _U = universe_get(_L)); |
657 | lane_->status = Lane::Running; // Pending -> Running | 667 | lane_->status = Lane::Running; // Pending -> Running |
658 | 668 | ||
659 | PrepareLaneHelpers(lane_); | 669 | PrepareLaneHelpers(lane_); |
660 | 670 | ||
661 | // this could be done in lane_new before the lane body function is pushed on the stack to avoid unnecessary stack slot shifting around | 671 | _rc = ToLuaError(lua_pcall(_L, _nargs, LUA_MULTRET, _errorHandlerCount)); // L: eh? retvals|err |
662 | #if ERROR_FULL_STACK | ||
663 | // Tie "set_error_reporting()" to the state | ||
664 | lua_pushcfunction(_L, LG_set_error_reporting); | ||
665 | populate_func_lookup_table(_L, -1, "set_error_reporting"); | ||
666 | lua_setglobal(_L, "set_error_reporting"); | ||
667 | 672 | ||
668 | STACK_GROW(_L, 1); | 673 | if (_errorHandlerCount) { |
669 | lua_pushcfunction(_L, lane_error); // L: func args handler | 674 | lua_remove(_L, 1); // L: retvals|error |
670 | lua_insert(_L, 1); // L: handler func args | 675 | } |
671 | #endif // L: ERROR_FULL_STACK | ||
672 | |||
673 | _rc = ToLuaError(lua_pcall(_L, _nargs, LUA_MULTRET, ERROR_FULL_STACK)); // L: retvals|err | ||
674 | |||
675 | #if ERROR_FULL_STACK | ||
676 | lua_remove(_L, 1); // L: retvals|error | ||
677 | #endif // ERROR_FULL_STACK | ||
678 | 676 | ||
679 | // in case of error and if it exists, fetch stack trace from registry and push it | 677 | // in case of error and if it exists, fetch stack trace from registry and push it |
680 | push_stack_trace(_L, _rc, 1); // L: retvals|error [trace] | 678 | push_stack_trace(_L, lane_->errorTraceLevel, _rc, 1); // L: retvals|error [trace] |
681 | 679 | ||
682 | 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)))); | 680 | 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)))); |
683 | // Call finalizers, if the script has set them up. | 681 | // Call finalizers, if the script has set them up. |
684 | // | 682 | // |
685 | LuaError const _rc2{ run_finalizers(_L, _rc) }; | 683 | LuaError const _rc2{ run_finalizers(_L, lane_->errorTraceLevel, _rc) }; |
686 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "Lane %p finalizer: %s\n" INDENT_END(_U), _L, get_errcode_name(_rc2))); | 684 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "Lane %p finalizer: %s\n" INDENT_END(_U), _L, get_errcode_name(_rc2))); |
687 | if (_rc2 != LuaError::OK) { // Error within a finalizer! | 685 | if (_rc2 != LuaError::OK) { // Error within a finalizer! |
688 | // the finalizer generated an error, and left its own error message [and stack trace] on the stack | 686 | // the finalizer generated an error, and left its own error message [and stack trace] on the stack |
@@ -779,10 +777,13 @@ static void lane_main(Lane* lane_) | |||
779 | // #################################### Lane implementation ######################################## | 777 | // #################################### Lane implementation ######################################## |
780 | // ################################################################################################# | 778 | // ################################################################################################# |
781 | 779 | ||
782 | Lane::Lane(Universe* U_, lua_State* L_) | 780 | Lane::Lane(Universe* U_, lua_State* L_, ErrorTraceLevel errorTraceLevel_) |
783 | : U{ U_ } | 781 | : U{ U_ } |
784 | , L{ L_ } | 782 | , L{ L_ } |
783 | , errorTraceLevel{ errorTraceLevel_ } | ||
785 | { | 784 | { |
785 | assert(errorTraceLevel == ErrorTraceLevel::Minimal || errorTraceLevel == ErrorTraceLevel::Basic || errorTraceLevel == ErrorTraceLevel::Extended); | ||
786 | kExtendedStackTraceRegKey.setValue(L_, [yes = errorTraceLevel == ErrorTraceLevel::Extended ? 1 : 0](lua_State* L_) { lua_pushboolean(L_, yes); }); | ||
786 | #if HAVE_LANE_TRACKING() | 787 | #if HAVE_LANE_TRACKING() |
787 | U->tracker.tracking_add(this); | 788 | U->tracker.tracking_add(this); |
788 | #endif // HAVE_LANE_TRACKING() | 789 | #endif // HAVE_LANE_TRACKING() |
@@ -822,6 +823,29 @@ void Lane::changeDebugName(int nameIdx_) | |||
822 | 823 | ||
823 | // ################################################################################################# | 824 | // ################################################################################################# |
824 | 825 | ||
826 | //--- | ||
827 | // str= thread_status( lane ) | ||
828 | // | ||
829 | // Returns: "pending" not started yet | ||
830 | // -> "running" started, doing its work.. | ||
831 | // <-> "waiting" blocked in a receive() | ||
832 | // -> "done" finished, results are there | ||
833 | // / "error" finished at an error, error value is there | ||
834 | // / "cancelled" execution cancelled by M (state gone) | ||
835 | // | ||
836 | [[nodiscard]] char const* Lane::errorTraceLevelString() const | ||
837 | { | ||
838 | char const* const _str{ | ||
839 | (errorTraceLevel == ErrorTraceLevel::Minimal) ? "minimal" : | ||
840 | (errorTraceLevel == ErrorTraceLevel::Basic) ? "basic" : | ||
841 | (errorTraceLevel == ErrorTraceLevel::Extended) ? "extended" : | ||
842 | nullptr | ||
843 | }; | ||
844 | return _str; | ||
845 | } | ||
846 | |||
847 | // ################################################################################################# | ||
848 | |||
825 | namespace global { | 849 | namespace global { |
826 | static struct luaL_Reg const sLaneFunctions[] = { | 850 | static struct luaL_Reg const sLaneFunctions[] = { |
827 | { "__gc", lane_gc }, | 851 | { "__gc", lane_gc }, |
@@ -840,18 +864,30 @@ void Lane::PushMetatable(lua_State* L_) | |||
840 | if (luaL_newmetatable(L_, kLaneMetatableName)) { // L_: mt | 864 | if (luaL_newmetatable(L_, kLaneMetatableName)) { // L_: mt |
841 | luaG_registerlibfuncs(L_, global::sLaneFunctions); | 865 | luaG_registerlibfuncs(L_, global::sLaneFunctions); |
842 | // cache error() and tostring() | 866 | // cache error() and tostring() |
843 | lua_getglobal(L_, "error"); // L_: mt error | 867 | kCachedError.pushKey(L_); // L_: mt kCachedError |
844 | LUA_ASSERT(L_, lua_isfunction(L_, -1)); | 868 | lua_getglobal(L_, "error"); // L_: mt kCachedError error() |
845 | lua_setfield(L_, -2, "cached_error"); // L_: mt | 869 | lua_rawset(L_, -3); // L_: mt |
846 | lua_getglobal(L_, "tostring"); // L_: mt tostring | 870 | kCachedTostring.pushKey(L_); // L_: mt kCachedTostring |
847 | LUA_ASSERT(L_, lua_isfunction(L_, -1)); | 871 | lua_getglobal(L_, "tostring"); // L_: mt kCachedTostring tostring() |
848 | lua_setfield(L_, -2, "cached_tostring"); // L_: mt | 872 | lua_rawset(L_, -3); // L_: mt |
849 | // hide the actual metatable from getmetatable() | 873 | // hide the actual metatable from getmetatable() |
850 | lua_pushliteral(L_, kLaneMetatableName); // L_: mt "Lane" | 874 | lua_pushliteral(L_, kLaneMetatableName); // L_: mt "Lane" |
851 | lua_setfield(L_, -2, "__metatable"); // L_: mt | 875 | lua_setfield(L_, -2, "__metatable"); // L_: mt |
852 | } | 876 | } |
853 | STACK_CHECK(L_, 1); | 877 | STACK_CHECK(L_, 1); |
854 | } | 878 | } |
879 | |||
880 | // ################################################################################################# | ||
881 | |||
882 | [[nodiscard]] int Lane::pushErrorHandler() const | ||
883 | { | ||
884 | if (errorTraceLevel != ErrorTraceLevel::Minimal) { | ||
885 | lua_pushcfunction(L, lane_error); | ||
886 | return 1; | ||
887 | } | ||
888 | return 0; | ||
889 | } | ||
890 | |||
855 | // ################################################################################################# | 891 | // ################################################################################################# |
856 | 892 | ||
857 | void Lane::pushThreadStatus(lua_State* L_) const | 893 | void Lane::pushThreadStatus(lua_State* L_) const |
@@ -864,6 +900,16 @@ void Lane::pushThreadStatus(lua_State* L_) const | |||
864 | 900 | ||
865 | // ################################################################################################# | 901 | // ################################################################################################# |
866 | 902 | ||
903 | void Lane::pushErrorTraceLevel(lua_State* L_) const | ||
904 | { | ||
905 | char const* const _str{ errorTraceLevelString() }; | ||
906 | LUA_ASSERT(L_, _str); | ||
907 | |||
908 | lua_pushstring(L_, _str); | ||
909 | } | ||
910 | |||
911 | // ################################################################################################# | ||
912 | |||
867 | // intern the debug name in the caller lua state so that the pointer remains valid after the lane's state is closed | 913 | // intern the debug name in the caller lua state so that the pointer remains valid after the lane's state is closed |
868 | void Lane::securizeDebugName(lua_State* L_) | 914 | void Lane::securizeDebugName(lua_State* L_) |
869 | { | 915 | { |
@@ -64,6 +64,14 @@ class Lane | |||
64 | }; | 64 | }; |
65 | using enum Status; | 65 | using enum Status; |
66 | 66 | ||
67 | enum class ErrorTraceLevel | ||
68 | { | ||
69 | Minimal, // no error handler function when running the lane body | ||
70 | Basic, // lane body errors caught by a error handler | ||
71 | Extended // same as above, but with more data extracted from the debug infos | ||
72 | }; | ||
73 | using enum ErrorTraceLevel; | ||
74 | |||
67 | // the thread | 75 | // the thread |
68 | std::jthread thread; | 76 | std::jthread thread; |
69 | // a latch to wait for the lua_State to be ready | 77 | // a latch to wait for the lua_State to be ready |
@@ -104,10 +112,11 @@ class Lane | |||
104 | // S: cleans up after itself if non-nullptr at lane exit | 112 | // S: cleans up after itself if non-nullptr at lane exit |
105 | 113 | ||
106 | #if HAVE_LANE_TRACKING() | 114 | #if HAVE_LANE_TRACKING() |
115 | // For tracking only | ||
107 | Lane* volatile tracking_next{ nullptr }; | 116 | Lane* volatile tracking_next{ nullptr }; |
108 | #endif // HAVE_LANE_TRACKING() | 117 | #endif // HAVE_LANE_TRACKING() |
109 | // | 118 | |
110 | // For tracking only | 119 | ErrorTraceLevel const errorTraceLevel{ Basic }; |
111 | 120 | ||
112 | [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internalAllocator.alloc(size_); } | 121 | [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internalAllocator.alloc(size_); } |
113 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception | 122 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception |
@@ -115,18 +124,20 @@ class Lane | |||
115 | // this one is for us, to make sure memory is freed by the correct allocator | 124 | // this one is for us, to make sure memory is freed by the correct allocator |
116 | static void operator delete(void* p_) { static_cast<Lane*>(p_)->U->internalAllocator.free(p_, sizeof(Lane)); } | 125 | static void operator delete(void* p_) { static_cast<Lane*>(p_)->U->internalAllocator.free(p_, sizeof(Lane)); } |
117 | 126 | ||
118 | Lane(Universe* U_, lua_State* L_); | 127 | Lane(Universe* U_, lua_State* L_, ErrorTraceLevel errorTraceLevel_); |
119 | ~Lane(); | 128 | ~Lane(); |
120 | 129 | ||
121 | void changeDebugName(int nameIdx_); | 130 | void changeDebugName(int nameIdx_); |
122 | void close() { lua_State* _L{ L }; L = nullptr; lua_close(_L); } | 131 | void close() { lua_State* _L{ L }; L = nullptr; lua_close(_L); } |
132 | [[nodiscard]] char const* errorTraceLevelString() const; | ||
133 | [[nodiscard]] int pushErrorHandler() const; | ||
134 | void pushErrorTraceLevel(lua_State* L_) const; | ||
135 | static void PushMetatable(lua_State* L_); | ||
123 | void pushThreadStatus(lua_State* L_) const; | 136 | void pushThreadStatus(lua_State* L_) const; |
124 | void securizeDebugName(lua_State* L_); | 137 | void securizeDebugName(lua_State* L_); |
125 | void startThread(int priority_); | 138 | void startThread(int priority_); |
126 | [[nodiscard]] char const* threadStatusString() const; | 139 | [[nodiscard]] char const* threadStatusString() const; |
127 | [[nodiscard]] bool waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_); | 140 | [[nodiscard]] bool waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_); |
128 | |||
129 | static void PushMetatable(lua_State* L_); | ||
130 | }; | 141 | }; |
131 | 142 | ||
132 | // ################################################################################################# | 143 | // ################################################################################################# |
diff --git a/src/lanes.cpp b/src/lanes.cpp index b3f7be7..7b730cd 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp | |||
@@ -205,32 +205,33 @@ LUAG_FUNC(register) | |||
205 | 205 | ||
206 | // ################################################################################################# | 206 | // ################################################################################################# |
207 | 207 | ||
208 | //--- | 208 | //--- [] means can be nil |
209 | // lane_ud = lane_new( function | 209 | // lane_ud = lane_new( function |
210 | // , [libs_str] | 210 | // , [libs_str] |
211 | // , [priority_int=0] | 211 | // , [priority_int] |
212 | // , [globals_tbl] | 212 | // , [globals_tbl] |
213 | // , [package_tbl] | 213 | // , [package_tbl] |
214 | // , [required_tbl] | 214 | // , [required_tbl] |
215 | // , [gc_cb_func] | 215 | // , [gc_cb_func] |
216 | // , [name] | 216 | // , [name] |
217 | // , error_trace_level | ||
217 | // [, ... args ...]) | 218 | // [, ... args ...]) |
218 | // | 219 | // |
219 | // Upvalues: metatable to use for 'lane_ud' | 220 | // Upvalues: metatable to use for 'lane_ud' |
220 | // | 221 | // |
221 | LUAG_FUNC(lane_new) | 222 | LUAG_FUNC(lane_new) |
222 | { | 223 | { |
223 | // first 8 args: func libs priority globals package required gc_cb name | 224 | // first 9 args: func libs priority globals package required gc_cb name error_trace_level |
224 | char const* const _libs_str{ lua_tostring(L_, 2) }; | 225 | char const* const _libs_str{ lua_tostring(L_, 2) }; |
225 | bool const _have_priority{ !lua_isnoneornil(L_, 3) }; | 226 | bool const _have_priority{ !lua_isnoneornil(L_, 3) }; |
226 | int const _priority{ _have_priority ? static_cast<int>(lua_tointeger(L_, 3)) : kThreadPrioDefault }; | ||
227 | int const _globals_idx{ lua_isnoneornil(L_, 4) ? 0 : 4 }; | 227 | int const _globals_idx{ lua_isnoneornil(L_, 4) ? 0 : 4 }; |
228 | int const _package_idx{ lua_isnoneornil(L_, 5) ? 0 : 5 }; | 228 | int const _package_idx{ lua_isnoneornil(L_, 5) ? 0 : 5 }; |
229 | int const _required_idx{ lua_isnoneornil(L_, 6) ? 0 : 6 }; | 229 | int const _required_idx{ lua_isnoneornil(L_, 6) ? 0 : 6 }; |
230 | int const _gc_cb_idx{ lua_isnoneornil(L_, 7) ? 0 : 7 }; | 230 | int const _gc_cb_idx{ lua_isnoneornil(L_, 7) ? 0 : 7 }; |
231 | int const _name_idx{ lua_isnoneornil(L_, 8) ? 0 : 8 }; | 231 | int const _name_idx{ lua_isnoneornil(L_, 8) ? 0 : 8 }; |
232 | int const _error_trace_level_idx{ 9 }; | ||
232 | 233 | ||
233 | static constexpr int kFixedArgsIdx{ 8 }; | 234 | static constexpr int kFixedArgsIdx{ 9 }; |
234 | int const _nargs{ lua_gettop(L_) - kFixedArgsIdx }; | 235 | int const _nargs{ lua_gettop(L_) - kFixedArgsIdx }; |
235 | Universe* const _U{ universe_get(L_) }; | 236 | Universe* const _U{ universe_get(L_) }; |
236 | LUA_ASSERT(L_, _nargs >= 0); | 237 | LUA_ASSERT(L_, _nargs >= 0); |
@@ -238,6 +239,7 @@ LUAG_FUNC(lane_new) | |||
238 | // public Lanes API accepts a generic range -3/+3 | 239 | // public Lanes API accepts a generic range -3/+3 |
239 | // that will be remapped into the platform-specific scheduler priority scheme | 240 | // that will be remapped into the platform-specific scheduler priority scheme |
240 | // On some platforms, -3 is equivalent to -2 and +3 to +2 | 241 | // On some platforms, -3 is equivalent to -2 and +3 to +2 |
242 | int const _priority{ _have_priority ? static_cast<int>(lua_tointeger(L_, 3)) : kThreadPrioDefault }; | ||
241 | if (_have_priority && (_priority < kThreadPrioMin || _priority > kThreadPrioMax)) { | 243 | if (_have_priority && (_priority < kThreadPrioMin || _priority > kThreadPrioMax)) { |
242 | raise_luaL_error(L_, "Priority out of range: %d..+%d (%d)", kThreadPrioMin, kThreadPrioMax, _priority); | 244 | raise_luaL_error(L_, "Priority out of range: %d..+%d (%d)", kThreadPrioMin, kThreadPrioMax, _priority); |
243 | } | 245 | } |
@@ -246,11 +248,11 @@ LUAG_FUNC(lane_new) | |||
246 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: setup\n" INDENT_END(_U))); | 248 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: setup\n" INDENT_END(_U))); |
247 | 249 | ||
248 | // populate with selected libraries at the same time. | 250 | // populate with selected libraries at the same time. |
249 | lua_State* const _L2{ luaG_newstate(_U, SourceState{ L_ }, _libs_str) }; // L_: [8 args] ... L2: | 251 | lua_State* const _L2{ luaG_newstate(_U, SourceState{ L_ }, _libs_str) }; // L_: [9 args] ... L2: |
250 | STACK_CHECK_START_REL(_L2, 0); | 252 | STACK_CHECK_START_REL(_L2, 0); |
251 | 253 | ||
252 | // 'lane' is allocated from heap, not Lua, since its life span may surpass the handle's (if free running thread) | 254 | // 'lane' is allocated from heap, not Lua, since its life span may surpass the handle's (if free running thread) |
253 | Lane* const _lane{ new (_U) Lane{ _U, _L2 } }; | 255 | Lane* const _lane{ new (_U) Lane{ _U, _L2, static_cast<Lane::ErrorTraceLevel>(lua_tointeger(L_, _error_trace_level_idx)) } }; |
254 | if (_lane == nullptr) { | 256 | if (_lane == nullptr) { |
255 | raise_luaL_error(L_, "could not create lane: out of memory"); | 257 | raise_luaL_error(L_, "could not create lane: out of memory"); |
256 | } | 258 | } |
@@ -410,7 +412,7 @@ LUAG_FUNC(lane_new) | |||
410 | lua_pop(_L2, 1); // L_: [8 args] args... n "modname" L2: | 412 | lua_pop(_L2, 1); // L_: [8 args] args... n "modname" L2: |
411 | } | 413 | } |
412 | } | 414 | } |
413 | lua_pop(L_, 1); // L_: func libs priority globals package required gc_cb [... args ...] n | 415 | lua_pop(L_, 1); // L_: func libs priority globals package required gc_cb [... args ...] n |
414 | ++_nbRequired; | 416 | ++_nbRequired; |
415 | } // L_: [8 args] args... | 417 | } // L_: [8 args] args... |
416 | } | 418 | } |
@@ -443,35 +445,36 @@ LUAG_FUNC(lane_new) | |||
443 | STACK_CHECK(_L2, 0); | 445 | STACK_CHECK(_L2, 0); |
444 | 446 | ||
445 | // Lane main function | 447 | // Lane main function |
448 | int const errorHandlerCount{ _lane->pushErrorHandler() }; // L2: eh? | ||
446 | LuaType const _func_type{ lua_type_as_enum(L_, 1) }; | 449 | LuaType const _func_type{ lua_type_as_enum(L_, 1) }; |
447 | if (_func_type == LuaType::FUNCTION) { | 450 | if (_func_type == LuaType::FUNCTION) { |
448 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: transfer lane body\n" INDENT_END(_U))); | 451 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: transfer lane body\n" INDENT_END(_U))); |
449 | DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); | 452 | DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); |
450 | lua_pushvalue(L_, 1); // L_: [8 args] args... func L2: | 453 | lua_pushvalue(L_, 1); // L_: [8 args] args... func L2: eh? |
451 | InterCopyContext _c{ _U, DestState{ _L2 }, SourceState{ L_ }, {}, {}, {}, {}, {} }; | 454 | InterCopyContext _c{ _U, DestState{ _L2 }, SourceState{ L_ }, {}, {}, {}, {}, {} }; |
452 | InterCopyResult const _res{ _c.inter_move(1) }; // L_: [8 args] args... L2: func | 455 | InterCopyResult const _res{ _c.inter_move(1) }; // L_: [8 args] args... L2: eh? func |
453 | if (_res != InterCopyResult::Success) { | 456 | if (_res != InterCopyResult::Success) { |
454 | raise_luaL_error(L_, "tried to copy unsupported types"); | 457 | raise_luaL_error(L_, "tried to copy unsupported types"); |
455 | } | 458 | } |
456 | } else if (_func_type == LuaType::STRING) { | 459 | } else if (_func_type == LuaType::STRING) { |
457 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: compile lane body\n" INDENT_END(_U))); | 460 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: compile lane body\n" INDENT_END(_U))); |
458 | // compile the string | 461 | // compile the string |
459 | if (luaL_loadstring(_L2, lua_tostring(L_, 1)) != 0) { // L_: [8 args] args... L2: func | 462 | if (luaL_loadstring(_L2, lua_tostring(L_, 1)) != 0) { // L_: [8 args] args... L2: eh? func |
460 | raise_luaL_error(L_, "error when parsing lane function code"); | 463 | raise_luaL_error(L_, "error when parsing lane function code"); |
461 | } | 464 | } |
462 | } else { | 465 | } else { |
463 | raise_luaL_error(L_, "Expected function, got %s", lua_typename(L_, _func_type)); | 466 | raise_luaL_error(L_, "Expected function, got %s", lua_typename(L_, _func_type)); |
464 | } | 467 | } |
465 | STACK_CHECK(L_, 0); | 468 | STACK_CHECK(L_, 0); |
466 | STACK_CHECK(_L2, 1); | 469 | STACK_CHECK(_L2, errorHandlerCount + 1); |
467 | LUA_ASSERT(L_, lua_isfunction(_L2, 1)); | 470 | LUA_ASSERT(L_, lua_isfunction(_L2, errorHandlerCount + 1)); |
468 | 471 | ||
469 | // revive arguments | 472 | // revive arguments |
470 | if (_nargs > 0) { | 473 | if (_nargs > 0) { |
471 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: transfer lane arguments\n" INDENT_END(_U))); | 474 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "lane_new: transfer lane arguments\n" INDENT_END(_U))); |
472 | DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); | 475 | DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); |
473 | InterCopyContext _c{ _U, DestState{ _L2 }, SourceState{ L_ }, {}, {}, {}, {}, {} }; | 476 | InterCopyContext _c{ _U, DestState{ _L2 }, SourceState{ L_ }, {}, {}, {}, {}, {} }; |
474 | InterCopyResult const res{ _c.inter_move(_nargs) }; // L_: [8 args] L2: func args... | 477 | InterCopyResult const res{ _c.inter_move(_nargs) }; // L_: [8 args] L2: eh? func args... |
475 | if (res != InterCopyResult::Success) { | 478 | if (res != InterCopyResult::Success) { |
476 | raise_luaL_error(L_, "tried to copy unsupported types"); | 479 | raise_luaL_error(L_, "tried to copy unsupported types"); |
477 | } | 480 | } |
@@ -480,8 +483,8 @@ LUAG_FUNC(lane_new) | |||
480 | LUA_ASSERT(L_, lua_gettop(L_) == kFixedArgsIdx); | 483 | LUA_ASSERT(L_, lua_gettop(L_) == kFixedArgsIdx); |
481 | 484 | ||
482 | // Store 'lane' in the lane's registry, for 'cancel_test()' (we do cancel tests at pending send/receive). | 485 | // Store 'lane' in the lane's registry, for 'cancel_test()' (we do cancel tests at pending send/receive). |
483 | kLanePointerRegKey.setValue(_L2, [lane = _lane](lua_State* L_) { lua_pushlightuserdata(L_, lane); });// L_: [8 args] L2: func args... | 486 | kLanePointerRegKey.setValue(_L2, [lane = _lane](lua_State* L_) { lua_pushlightuserdata(L_, lane); });// L_: [8 args] L2: eh? func args... |
484 | STACK_CHECK(_L2, 1 + _nargs); | 487 | STACK_CHECK(_L2, errorHandlerCount + 1 + _nargs); |
485 | 488 | ||
486 | STACK_CHECK_RESET_REL(L_, 0); | 489 | STACK_CHECK_RESET_REL(L_, 0); |
487 | // all went well, the lane's thread can start working | 490 | // all went well, the lane's thread can start working |
@@ -579,7 +582,7 @@ LUAG_FUNC(wakeup_conv) | |||
579 | // ######################################## Module linkage ######################################### | 582 | // ######################################## Module linkage ######################################### |
580 | // ################################################################################################# | 583 | // ################################################################################################# |
581 | 584 | ||
582 | extern int LG_linda(lua_State* L_); | 585 | extern LUAG_FUNC(linda); |
583 | 586 | ||
584 | namespace global { | 587 | namespace global { |
585 | static struct luaL_Reg const sLanesFunctions[] = { | 588 | static struct luaL_Reg const sLanesFunctions[] = { |
@@ -700,9 +703,8 @@ LUAG_FUNC(configure) | |||
700 | STACK_CHECK(L_, 2); | 703 | STACK_CHECK(L_, 2); |
701 | 704 | ||
702 | // prepare the metatable for threads | 705 | // prepare the metatable for threads |
703 | // contains keys: { __gc, __index, cached_error, cached_tostring, cancel, join, get_debug_threadname } | 706 | // contains keys: { __gc, __index, cancel, join, get_debug_threadname } |
704 | Lane::PushMetatable(L_); | 707 | Lane::PushMetatable(L_); // L_: settings M {lane_mt} |
705 | |||
706 | lua_pushcclosure(L_, LG_lane_new, 1); // L_: settings M lane_new | 708 | lua_pushcclosure(L_, LG_lane_new, 1); // L_: settings M lane_new |
707 | lua_setfield(L_, -2, "lane_new"); // L_: settings M | 709 | lua_setfield(L_, -2, "lane_new"); // L_: settings M |
708 | 710 | ||
diff --git a/src/lanes.lua b/src/lanes.lua index 85d8e58..ff5fdf3 100644 --- a/src/lanes.lua +++ b/src/lanes.lua | |||
@@ -65,6 +65,8 @@ local type = assert(type) | |||
65 | 65 | ||
66 | -- ################################################################################################# | 66 | -- ################################################################################################# |
67 | 67 | ||
68 | local isLuaJIT = (package and package.loaded.jit and jit.version) and true or false | ||
69 | |||
68 | local default_params = | 70 | local default_params = |
69 | { | 71 | { |
70 | nb_keepers = 1, | 72 | nb_keepers = 1, |
@@ -77,9 +79,9 @@ local default_params = | |||
77 | demote_full_userdata = nil, | 79 | demote_full_userdata = nil, |
78 | verbose_errors = false, | 80 | verbose_errors = false, |
79 | -- LuaJIT provides a thread-unsafe allocator by default, so we need to protect it when used in parallel lanes | 81 | -- LuaJIT provides a thread-unsafe allocator by default, so we need to protect it when used in parallel lanes |
80 | allocator = (package and package.loaded.jit and jit.version) and "protected" or nil, | 82 | allocator = isLuaJIT and "protected" or nil, |
81 | -- it looks also like LuaJIT allocator may not appreciate direct use of its allocator for other purposes than the VM operation | 83 | -- it looks also like LuaJIT allocator may not appreciate direct use of its allocator for other purposes than the VM operation |
82 | internal_allocator = (package and package.loaded.jit and jit.version) and "libc" or "allocator" | 84 | internal_allocator = isLuaJIT and "libc" or "allocator" |
83 | } | 85 | } |
84 | 86 | ||
85 | -- ################################################################################################# | 87 | -- ################################################################################################# |
@@ -207,12 +209,23 @@ end | |||
207 | 209 | ||
208 | -- ################################################################################################# | 210 | -- ################################################################################################# |
209 | 211 | ||
212 | -- must match Lane::ErrorTraceLevel values | ||
213 | local error_trace_levels = { | ||
214 | minimal = 0, | ||
215 | basic = 1, | ||
216 | extended = 2 | ||
217 | } | ||
218 | |||
210 | local opt_validators = | 219 | local opt_validators = |
211 | { | 220 | { |
212 | gc_cb = function(v_) | 221 | gc_cb = function(v_) |
213 | local tv = type(v_) | 222 | local tv = type(v_) |
214 | return (tv == "function") and v_ or raise_option_error("gc_cb", tv, v_) | 223 | return (tv == "function") and v_ or raise_option_error("gc_cb", tv, v_) |
215 | end, | 224 | end, |
225 | error_trace_level = function(v_) | ||
226 | local tv = type(v_) | ||
227 | return (error_trace_levels[v_] ~= nil) and v_ or raise_option_error("error_trace_level", tv, v_) | ||
228 | end, | ||
216 | globals = function(v_) | 229 | globals = function(v_) |
217 | local tv = type(v_) | 230 | local tv = type(v_) |
218 | return (tv == "table") and v_ or raise_option_error("globals", tv, v_) | 231 | return (tv == "table") and v_ or raise_option_error("globals", tv, v_) |
@@ -343,10 +356,10 @@ local gen = function(...) | |||
343 | end | 356 | end |
344 | 357 | ||
345 | local core_lane_new = assert(core.lane_new) | 358 | local core_lane_new = assert(core.lane_new) |
346 | local priority, globals, package, required, gc_cb, name = opt.priority, opt.globals, opt.package or package, opt.required, opt.gc_cb, opt.name | 359 | local priority, globals, package, required, gc_cb, name, error_trace_level = opt.priority, opt.globals, opt.package or package, opt.required, opt.gc_cb, opt.name, error_trace_levels[opt.error_trace_level] |
347 | return function(...) | 360 | return function(...) |
348 | -- must pass functions args last else they will be truncated to the first one | 361 | -- must pass functions args last else they will be truncated to the first one |
349 | return core_lane_new(func, libs, priority, globals, package, required, gc_cb, name, ...) | 362 | return core_lane_new(func, libs, priority, globals, package, required, gc_cb, name, error_trace_level, ...) |
350 | end | 363 | end |
351 | end -- gen() | 364 | end -- gen() |
352 | 365 | ||