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 | |
| 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
| -rw-r--r-- | Makefile | 3 | ||||
| -rw-r--r-- | docs/index.html | 32 | ||||
| -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 | ||||
| -rw-r--r-- | tests/error.lua | 264 |
7 files changed, 545 insertions, 266 deletions
| @@ -80,6 +80,7 @@ test: | |||
| 80 | $(MAKE) cyclic | 80 | $(MAKE) cyclic |
| 81 | $(MAKE) deadlock | 81 | $(MAKE) deadlock |
| 82 | $(MAKE) errhangtest | 82 | $(MAKE) errhangtest |
| 83 | $(MAKE) error | ||
| 83 | $(MAKE) fibonacci | 84 | $(MAKE) fibonacci |
| 84 | $(MAKE) fifo | 85 | $(MAKE) fifo |
| 85 | $(MAKE) finalizer | 86 | $(MAKE) finalizer |
| @@ -124,7 +125,7 @@ ehynes: tests/ehynes.lua $(_TARGET_SO) | |||
| 124 | errhangtest: tests/errhangtest.lua $(_TARGET_SO) | 125 | errhangtest: tests/errhangtest.lua $(_TARGET_SO) |
| 125 | $(_PREFIX) $(LUA) $< | 126 | $(_PREFIX) $(LUA) $< |
| 126 | 127 | ||
| 127 | error-test: tests/error.lua $(_TARGET_SO) | 128 | error: tests/error.lua $(_TARGET_SO) |
| 128 | $(_PREFIX) $(LUA) $< | 129 | $(_PREFIX) $(LUA) $< |
| 129 | 130 | ||
| 130 | fibonacci: tests/fibonacci.lua $(_TARGET_SO) | 131 | fibonacci: tests/fibonacci.lua $(_TARGET_SO) |
diff --git a/docs/index.html b/docs/index.html index 713955f..f34b672 100644 --- a/docs/index.html +++ b/docs/index.html | |||
| @@ -702,6 +702,17 @@ | |||
| 702 | ATTEMPTING TO TRANSFER A FUNCTION REGISTERED BY A MODULE NOT LISTED HERE WILL RAISE AN ERROR. | 702 | ATTEMPTING TO TRANSFER A FUNCTION REGISTERED BY A MODULE NOT LISTED HERE WILL RAISE AN ERROR. |
| 703 | </td> | 703 | </td> |
| 704 | </tr> | 704 | </tr> |
| 705 | <tr id=".error_trace_level" valign=top> | ||
| 706 | <td> | ||
| 707 | <code>.error_trace_level</code> | ||
| 708 | </td> | ||
| 709 | <td>string</td> | ||
| 710 | <td> | ||
| 711 | Sets the error reporting mode. One of <tt>"minimal"</tt> (the default), <tt>"basic"</tt>, <tt>"extended"</tt>.<br /> | ||
| 712 | <tt>"minimal"</tt> yields only the location of the error.<br /> | ||
| 713 | The other 2 yield a full stack trace, with different amounts of data extracted from the debug infos. See <a href="#results">Results</a>. | ||
| 714 | </td> | ||
| 715 | </tr> | ||
| 705 | <tr id=".name" valign=top> | 716 | <tr id=".name" valign=top> |
| 706 | <td> | 717 | <td> |
| 707 | <code>.name</code> | 718 | <code>.name</code> |
| @@ -819,6 +830,21 @@ | |||
| 819 | 830 | ||
| 820 | <!-- status +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | 831 | <!-- status +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> |
| 821 | <hr/> | 832 | <hr/> |
| 833 | <h2 id="error_trace_level">Error trace level</h2> | ||
| 834 | |||
| 835 | <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"> | ||
| 836 | <tr> | ||
| 837 | <td> | ||
| 838 | <pre> str = lane_h.error_trace_level</pre> | ||
| 839 | </td> | ||
| 840 | </tr> | ||
| 841 | </table> | ||
| 842 | <p> | ||
| 843 | Read back the value set during lane generation, one of <tt>"basic", "minimal", "extended"</tt>. | ||
| 844 | </p> | ||
| 845 | |||
| 846 | <!-- status +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
| 847 | <hr/> | ||
| 822 | <h2 id="status">Status</h2> | 848 | <h2 id="status">Status</h2> |
| 823 | 849 | ||
| 824 | <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"> | 850 | <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"> |
| @@ -926,12 +952,8 @@ | |||
| 926 | <hr/> | 952 | <hr/> |
| 927 | <h2 id="results">Results and errors</h2> | 953 | <h2 id="results">Results and errors</h2> |
| 928 | 954 | ||
| 929 | <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> | ||
| 930 | set_error_reporting("basic"|"extended") | ||
| 931 | </pre></td></tr></table> | ||
| 932 | |||
| 933 | <p> | 955 | <p> |
| 934 | Sets the error reporting mode. "basic" is selected by default. | 956 | Error reporting level is set in the lane generator <a href="#.error_trace_level">settings</a>. |
| 935 | </p> | 957 | </p> |
| 936 | 958 | ||
| 937 | <p> | 959 | <p> |
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 | ||
diff --git a/tests/error.lua b/tests/error.lua index 91ccebc..126161e 100644 --- a/tests/error.lua +++ b/tests/error.lua | |||
| @@ -1,58 +1,242 @@ | |||
| 1 | -- | 1 | -- |
| 2 | -- Error reporting | 2 | -- Error reporting |
| 3 | -- | 3 | -- |
| 4 | -- Note: this code is supposed to end in errors; not included in 'make test' | 4 | local which_tests, remaining_tests = {}, {} |
| 5 | -- | 5 | for k,v in ipairs{...} do |
| 6 | print("got arg:", type(v), tostring(v)) | ||
| 7 | which_tests[v] = true | ||
| 8 | remaining_tests[v] = true | ||
| 9 | end | ||
| 10 | |||
| 11 | --################################################################################################## | ||
| 12 | |||
| 13 | local lanes = require "lanes".configure{with_timers = false} | ||
| 14 | local null = lanes.null | ||
| 15 | |||
| 16 | --################################################################################################## | ||
| 17 | |||
| 18 | -- Lua51 support | ||
| 19 | local table_unpack = unpack or table.unpack | ||
| 6 | 20 | ||
| 7 | local lanes = require "lanes".configure{ with_timers = false} | 21 | --################################################################################################## |
| 8 | 22 | ||
| 9 | local function lane( mode_) | 23 | local WR = function(...) |
| 10 | set_error_reporting( mode_) | 24 | local str="" |
| 11 | local subf= function() -- this so that we can see the call stack | 25 | for i=1,select('#',...) do |
| 12 | error "aa" | 26 | local v = select(i,...) |
| 13 | --error({}) | 27 | if type(v) == "function" then |
| 14 | --error(error) | 28 | local infos = debug.getinfo(v) |
| 29 | --[[for k,v in pairs(infos) do | ||
| 30 | print(k,v) | ||
| 31 | end]] | ||
| 32 | v = infos.source..":"..infos.linedefined | ||
| 33 | end | ||
| 34 | str= str..tostring(v).."\t" | ||
| 15 | end | 35 | end |
| 16 | local subf2= function() | 36 | if io then |
| 17 | subf() | 37 | io.stderr:write(str.."\n") |
| 18 | end | 38 | end |
| 19 | subf2() | ||
| 20 | end | 39 | end |
| 21 | 40 | ||
| 22 | local function cleanup(err) | 41 | --################################################################################################## |
| 42 | |||
| 43 | local lane_body = function(error_value_, finalizer_, finalizer_error_value_) | ||
| 44 | WR( "In Lane body: EV: ", error_value_, " F: ", finalizer_, " FEV: ", finalizer_error_value_) | ||
| 45 | if finalizer_ then | ||
| 46 | local finalizer = function(err_, stack_) | ||
| 47 | finalizer_(err_, stack_) | ||
| 48 | if finalizer_error_value_ then | ||
| 49 | WR ("Finalizer raises ", finalizer_error_value_) | ||
| 50 | error(finalizer_error_value_, 0) -- 0 so that error() doesn't alter the error value | ||
| 51 | end | ||
| 52 | end | ||
| 53 | set_finalizer(finalizer) | ||
| 54 | end | ||
| 55 | |||
| 56 | local subf = function() -- this so that we can see the call stack | ||
| 57 | if error_value_ then | ||
| 58 | error(error_value_, 0) -- 0 so that error() doesn't alter the error value | ||
| 59 | end | ||
| 60 | return "success" | ||
| 61 | end | ||
| 62 | local subf2 = function(b_) | ||
| 63 | return b_ or subf() -- prevent tail call | ||
| 64 | end | ||
| 65 | local subf3 = function(b_) | ||
| 66 | return b_ or subf2() -- prevent tail call | ||
| 67 | end | ||
| 68 | return subf3(false) | ||
| 23 | end | 69 | end |
| 24 | 70 | ||
| 25 | local lgen = lanes.gen("*", { --[[finalizer=cleanup]] }, lane) | 71 | --################################################################################################## |
| 26 | 72 | ||
| 27 | --- | 73 | local lane_finalizer = function(err_, stack_) |
| 28 | io.stderr:write( "\n** Error catching **\n" ) | 74 | WR("In finalizer: ", err_, stack_) |
| 29 | -- | 75 | end |
| 30 | 76 | ||
| 31 | local error_reporting_mode = "extended" | 77 | --################################################################################################## |
| 32 | local h= lgen( error_reporting_mode) | 78 | |
| 33 | local _,err,stack= h:join() -- wait for the lane (no automatic error propagation) | 79 | local start_lane = function(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) |
| 80 | return lanes.gen("*", {error_trace_level = error_reporting_mode_}, lane_body)(error_value_, finalizer_, finalizer_error_value_) | ||
| 81 | end | ||
| 34 | 82 | ||
| 35 | if err then | 83 | --################################################################################################## |
| 36 | assert( type(stack)=="table" ) -- only true if lanes was compiled with ERROR_FULL_STACK == 1 | 84 | --################################################################################################## |
| 37 | io.stderr:write( "Lane error: "..tostring(err).."\n" ) | ||
| 38 | 85 | ||
| 39 | if error_reporting_mode == "basic" then -- each stack line is a string in basic mode | 86 | local make_table_error_mt = function() |
| 40 | io.stderr:write( "\t", table.concat(stack,"\n\t"), "\n" ); | 87 | return { |
| 41 | else -- each stack line is a table in extended mode | 88 | __tostring = function() return "{error as table}" end, |
| 42 | for line, details in pairs( stack) do | 89 | __eq = function(t1_, t2_) |
| 43 | io.stderr:write( "\t", tostring( line), "\n" ); | 90 | -- because tables transfered through linda/error reporting are identical, but not equal |
| 44 | for detail, value in pairs( details) do | 91 | -- however, due to metatable caching by Lanes, their metatables should be the same |
| 45 | io.stderr:write( "\t\t", tostring( detail), ":", tostring( value), "\n") | 92 | return getmetatable(t1_) == getmetatable(t2_) |
| 46 | end | 93 | end |
| 47 | end | 94 | } |
| 48 | end | ||
| 49 | end | 95 | end |
| 50 | 96 | ||
| 51 | --- | 97 | local lane_error_as_string = "'lane error as string'" |
| 52 | io.stderr:write( "\n** Error propagation **\n" ) | 98 | local lane_error_as_table = setmetatable({}, make_table_error_mt()) |
| 53 | -- | 99 | local lane_error_as_linda = lanes.linda("'lane error'") |
| 54 | local h2= lgen( error_reporting_mode) | 100 | |
| 55 | local _= h2[0] | 101 | local finalizer_error_as_string = "'lane error as string'" |
| 56 | assert(false) -- does NOT get here | 102 | local finalizer_error_as_table = setmetatable({}, make_table_error_mt()) |
| 103 | local finalizer_error_as_linda = lanes.linda("'lane error'") | ||
| 104 | |||
| 105 | local test_settings = {} | ||
| 106 | local configure_tests = function() | ||
| 107 | local append_test = function(level, lane_error, finalizer, finalizer_error) | ||
| 108 | -- convert back null to nil | ||
| 109 | lane_error = lane_error ~= null and lane_error or nil | ||
| 110 | finalizer = finalizer ~= null and finalizer or nil | ||
| 111 | finalizer_error = finalizer_error ~= null and finalizer_error or nil | ||
| 112 | -- compose test description string | ||
| 113 | local test_header = table.concat({ | ||
| 114 | level .. " error reporting", | ||
| 115 | (lane_error and tostring(lane_error) or "no error") .. " in lane", | ||
| 116 | (finalizer and "with" or "without").. " finalizer" .. ((finalizer and finalizer_error) and " raising " .. tostring(finalizer_error) or "") | ||
| 117 | }, ", ") | ||
| 118 | print(test_header) | ||
| 119 | test_settings[test_header] = { level, lane_error, finalizer, finalizer_error } | ||
| 120 | end | ||
| 121 | -- can't store nil in tables, use null instead | ||
| 122 | local levels = { | ||
| 123 | "minimal", | ||
| 124 | "basic", | ||
| 125 | "extended", | ||
| 126 | } | ||
| 127 | local errors = { | ||
| 128 | null, | ||
| 129 | lane_error_as_string, | ||
| 130 | lane_error_as_table, | ||
| 131 | lane_error_as_linda, | ||
| 132 | } | ||
| 133 | local finalizers = { | ||
| 134 | null, | ||
| 135 | lane_finalizer, | ||
| 136 | } | ||
| 137 | local finalizer_errors = { | ||
| 138 | null, | ||
| 139 | finalizer_error_as_string, | ||
| 140 | finalizer_error_as_table, | ||
| 141 | finalizer_error_as_linda, | ||
| 142 | } | ||
| 143 | |||
| 144 | for _, level in ipairs(levels) do | ||
| 145 | for _, lane_error in ipairs(errors) do | ||
| 146 | for _, finalizer in ipairs(finalizers) do | ||
| 147 | for _, finalizer_error in ipairs(finalizer_errors) do | ||
| 148 | append_test(level, lane_error, finalizer, finalizer_error) | ||
| 149 | end | ||
| 150 | end | ||
| 151 | end | ||
| 152 | end | ||
| 153 | end | ||
| 154 | |||
| 155 | configure_tests() | ||
| 156 | -- do return end | ||
| 157 | |||
| 158 | --################################################################################################## | ||
| 159 | --################################################################################################## | ||
| 160 | |||
| 161 | local do_error_catching_test = function(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) | ||
| 162 | local h = start_lane(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) | ||
| 163 | local ret,err,stack= h:join() -- wait for the lane (no automatic error propagation) | ||
| 164 | WR("Processing results for {", error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_, "}") | ||
| 165 | if err then | ||
| 166 | assert(ret == nil) | ||
| 167 | assert(error_reporting_mode_ == "minimal" or type(stack)=="table") -- only true if lane was configured with error_trace_level ~= "minimal" | ||
| 168 | if err == error_value_ then | ||
| 169 | WR("Lane regular error: ", err) | ||
| 170 | elseif err == finalizer_error_value_ then | ||
| 171 | WR("Lane finalizer error: ", err) | ||
| 172 | else | ||
| 173 | WR("Unknown error: ", type(err), err) | ||
| 174 | assert(false) | ||
| 175 | end | ||
| 176 | if error_reporting_mode_ == "minimal" then | ||
| 177 | assert(stack == nil, table.concat{"stack is a ", type(stack)}) | ||
| 178 | elseif error_reporting_mode_ == "basic" then -- each stack line is a string in basic mode | ||
| 179 | WR("STACK:\n", table.concat(stack,"\n\t")); | ||
| 180 | else -- each stack line is a table in extended mode | ||
| 181 | WR "STACK:" | ||
| 182 | for line, details in pairs(stack) do | ||
| 183 | WR("\t", line); | ||
| 184 | for detail, value in pairs(details) do | ||
| 185 | WR("\t\t", detail, ": ", value) | ||
| 186 | end | ||
| 187 | end | ||
| 188 | end | ||
| 189 | else -- no error | ||
| 190 | assert(ret == "success") | ||
| 191 | WR("No error in lane: ", ret) | ||
| 192 | end | ||
| 193 | WR "TEST OK" | ||
| 194 | end | ||
| 195 | |||
| 196 | --################################################################################################## | ||
| 197 | |||
| 198 | local do_error_propagation_test = function(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) | ||
| 199 | local wrapper = function() | ||
| 200 | local raises_error = (error_value_ or finalizer_error_value_) and true or false | ||
| 201 | local h = start_lane(error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_) | ||
| 202 | local _ = h[0] | ||
| 203 | -- if the lane is configured to raise an error, we should not get here | ||
| 204 | assert(not raises_error, "should not get here") | ||
| 205 | end | ||
| 206 | local err, msg = pcall(wrapper) | ||
| 207 | WR("Processing results for {", error_reporting_mode_, error_value_, finalizer_, finalizer_error_value_, "}") | ||
| 208 | if err then | ||
| 209 | WR("Lane error: ", msg) | ||
| 210 | end | ||
| 211 | WR "TEST OK" | ||
| 212 | end | ||
| 213 | |||
| 214 | --################################################################################################## | ||
| 215 | |||
| 216 | local perform_test = function(title_, test_) | ||
| 217 | WR "###########################################################################" | ||
| 218 | WR ("** " .. title_ .. " **") | ||
| 219 | for desc, settings in pairs(test_settings) do | ||
| 220 | WR "---------------------------------------------------------------------------" | ||
| 221 | WR(desc) | ||
| 222 | test_(table_unpack(settings)) | ||
| 223 | end | ||
| 224 | end | ||
| 225 | |||
| 226 | if not next(which_tests) or which_tests.catch then | ||
| 227 | remaining_tests.catch = nil | ||
| 228 | perform_test("Error catching", do_error_catching_test) | ||
| 229 | end | ||
| 230 | |||
| 231 | --################################################################################################## | ||
| 232 | if not next(which_tests) or which_tests.propagate then | ||
| 233 | remaining_tests.propagate = nil | ||
| 234 | perform_test("Error propagation", do_error_propagation_test) | ||
| 235 | end | ||
| 236 | |||
| 237 | -- ################################################################################################## | ||
| 238 | |||
| 239 | local unknown_test, val = next(remaining_tests) | ||
| 240 | assert(not unknown_test, tostring(unknown_test) .. " test is unknown") | ||
| 57 | 241 | ||
| 58 | --never ends | 242 | print "\nTHE END" |
