* [Tarantool-patches] [PATCH v2 0/5] Extending error functionality @ 2020-04-10 8:10 Leonid Vasiliev 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error Leonid Vasiliev ` (5 more replies) 0 siblings, 6 replies; 37+ messages in thread From: Leonid Vasiliev @ 2020-04-10 8:10 UTC (permalink / raw) To: v.shpilevoy; +Cc: tarantool-patches https://github.com/tarantool/tarantool/issues/4398 https://github.com/tarantool/tarantool/tree/lvasiliev/gh-4398-expose-error-module-4 According to https://github.com/tarantool/tarantool/issues/4398 (and after some discussion) we would like box.error to have: * Ability to create new error types * Transparent marshalling through net.box * Lua backtrace @Changelog Added: Lua traceback to error. Posibility to create errors of a custom user type Transparent marshalling error through net.box(gh-4398) Leonid Vasiliev (5): error: Add a Lua backtrace to error error: Add the custom error type error: Increase the number of fields transmitted through IPROTO iproto: Add session settings for IPROTO iproto: Update error MsgPack encoding src/box/CMakeLists.txt | 1 + src/box/errcode.h | 1 + src/box/error.cc | 87 ++++++- src/box/error.h | 49 +++- src/box/iproto.cc | 97 ++++++++ src/box/iproto_constants.h | 2 + src/box/lua/call.c | 29 ++- src/box/lua/error.cc | 189 ++++++++++++--- src/box/lua/execute.c | 2 +- src/box/lua/init.c | 56 +++++ src/box/lua/mp_error.cc | 454 +++++++++++++++++++++++++++++++++++ src/box/lua/mp_error.h | 49 ++++ src/box/lua/net_box.lua | 33 ++- src/box/lua/tuple.c | 28 +-- src/box/session.cc | 3 + src/box/session.h | 3 + src/box/session_settings.h | 1 + src/box/sql/func.c | 4 +- src/box/xrow.c | 19 +- src/lib/core/diag.c | 32 +++ src/lib/core/diag.h | 11 + src/lib/core/exception.cc | 1 + src/lib/core/mp_extension_types.h | 5 +- src/lua/error.c | 12 +- src/lua/error.h | 3 +- src/lua/error.lua | 23 +- src/lua/msgpack.c | 28 ++- src/lua/msgpack.h | 8 +- src/lua/utils.c | 16 +- src/lua/utils.h | 26 +- test/app/fiber.result | 4 +- test/box-tap/extended_error.test.lua | 164 +++++++++++++ test/box/error.result | 9 +- test/engine/func_index.result | 18 +- 34 files changed, 1344 insertions(+), 123 deletions(-) create mode 100644 src/box/lua/mp_error.cc create mode 100644 src/box/lua/mp_error.h create mode 100755 test/box-tap/extended_error.test.lua -- 2.7.4 ^ permalink raw reply [flat|nested] 37+ messages in thread
* [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error 2020-04-10 8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev @ 2020-04-10 8:10 ` Leonid Vasiliev 2020-04-14 1:11 ` Vladislav Shpilevoy 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type Leonid Vasiliev ` (4 subsequent siblings) 5 siblings, 1 reply; 37+ messages in thread From: Leonid Vasiliev @ 2020-04-10 8:10 UTC (permalink / raw) To: v.shpilevoy; +Cc: tarantool-patches In accordance with https://github.com/tarantool/tarantool/issues/4398 Lua traceback has been added for box.error. Has been added a per server flag for turn on/off traceback adding and ability to force it at creation time. @TarantoolBot document Title: error.traceback Was added: Per server flag for turn on/off adding a traceback to the errors. box.error.cfg({traceback_supplementation = true/false}) Adding a traceback can be forced on creation. box.error.new({type = "CustomType", reason = "reason", traceback = true/false}) Needed for #4398 --- src/box/lua/error.cc | 33 ++++++++++++++++++++++++++++++++- src/lib/core/diag.c | 32 ++++++++++++++++++++++++++++++++ src/lib/core/diag.h | 11 +++++++++++ src/lib/core/exception.cc | 1 + src/lua/error.c | 10 ++++++++++ src/lua/error.lua | 12 +++++++++++- test/app/fiber.result | 5 +++-- test/box/error.result | 5 +++-- test/engine/func_index.result | 10 ++++++---- 9 files changed, 109 insertions(+), 10 deletions(-) diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc index b2625bf..4432bc8 100644 --- a/src/box/lua/error.cc +++ b/src/box/lua/error.cc @@ -54,6 +54,8 @@ luaT_error_create(lua_State *L, int top_base) { uint32_t code = 0; const char *reason = NULL; + bool tb_parsed = false; + bool tb_mode = false; const char *file = ""; unsigned line = 0; lua_Debug info; @@ -87,6 +89,12 @@ luaT_error_create(lua_State *L, int top_base) if (reason == NULL) reason = ""; lua_pop(L, 1); + lua_getfield(L, top_base, "traceback"); + if (lua_isboolean(L, -1)) { + tb_parsed = true; + tb_mode = lua_toboolean(L, -1); + } + lua_pop(L, -1); } else { return NULL; } @@ -102,7 +110,12 @@ raise: } line = info.currentline; } - return box_error_new(file, line, code, "%s", reason); + + struct error *err = box_error_new(file, line, code, "%s", reason); + if (tb_parsed) + err->traceback_mode = tb_mode; + + return err; } static int @@ -180,6 +193,20 @@ luaT_error_set(struct lua_State *L) } static int +luaT_error_cfg(struct lua_State *L) +{ + if (lua_gettop(L) < 1 || !lua_istable(L, 1)) + return luaL_error(L, "Usage: box.error.cfg({}})"); + + lua_getfield(L, 1, "traceback_supplementation"); + if (lua_isnil(L, -1) == 0) + error_set_traceback_supplementation(lua_toboolean(L, -1)); + lua_pop(L, 1); + + return 0; +} + +static int lbox_errinj_set(struct lua_State *L) { char *name = (char*)luaL_checkstring(L, 1); @@ -297,6 +324,10 @@ box_lua_error_init(struct lua_State *L) { lua_pushcfunction(L, luaT_error_set); lua_setfield(L, -2, "set"); } + { + lua_pushcfunction(L, luaT_error_cfg); + lua_setfield(L, -2, "cfg"); + } lua_setfield(L, -2, "__index"); } lua_setmetatable(L, -2); diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c index e143db1..1caa75e 100644 --- a/src/lib/core/diag.c +++ b/src/lib/core/diag.c @@ -31,6 +31,11 @@ #include "diag.h" #include "fiber.h" +/** + * Global flag to add or not backtrace to errors. + */ +static bool global_traceback_mode = false; + int error_set_prev(struct error *e, struct error *prev) { @@ -97,6 +102,8 @@ error_create(struct error *e, e->errmsg[0] = '\0'; e->cause = NULL; e->effect = NULL; + e->lua_traceback = NULL; + e->traceback_mode = global_traceback_mode; } struct diag * @@ -120,3 +127,28 @@ error_vformat_msg(struct error *e, const char *format, va_list ap) vsnprintf(e->errmsg, sizeof(e->errmsg), format, ap); } +void +error_set_lua_traceback(struct error *e, const char *lua_traceback) +{ + if (e == NULL) + return; + + if (lua_traceback == NULL) { + free(e->lua_traceback); + e->lua_traceback = NULL; + return; + } + + size_t tb_len = strlen(lua_traceback); + e->lua_traceback = realloc(e->lua_traceback, tb_len + 1); + if (e->lua_traceback == NULL) + return; + strcpy(e->lua_traceback, lua_traceback); + return; +} + +void +error_set_traceback_supplementation(bool traceback_mode) +{ + global_traceback_mode = traceback_mode; +} diff --git a/src/lib/core/diag.h b/src/lib/core/diag.h index 7a5454d..f38009c 100644 --- a/src/lib/core/diag.h +++ b/src/lib/core/diag.h @@ -110,6 +110,8 @@ struct error { */ struct error *cause; struct error *effect; + char *lua_traceback; + bool traceback_mode; }; static inline void @@ -197,6 +199,15 @@ error_format_msg(struct error *e, const char *format, ...); void error_vformat_msg(struct error *e, const char *format, va_list ap); +void +error_set_lua_traceback(struct error *e, const char *lua_traceback); + +/** +* Sets the global flag to add or not backtrace to errors. +*/ +void +error_set_traceback_supplementation(bool traceback_mode); + /** * Diagnostics Area - a container for errors */ diff --git a/src/lib/core/exception.cc b/src/lib/core/exception.cc index 180cb0e..0e4b6ca 100644 --- a/src/lib/core/exception.cc +++ b/src/lib/core/exception.cc @@ -42,6 +42,7 @@ extern "C" { static void exception_destroy(struct error *e) { + free(e->lua_traceback); delete (Exception *) e; } diff --git a/src/lua/error.c b/src/lua/error.c index 18a990a..cd6ab54 100644 --- a/src/lua/error.c +++ b/src/lua/error.c @@ -85,6 +85,16 @@ luaT_pusherror(struct lua_State *L, struct error *e) * then set the finalizer. */ error_ref(e); + + if (e->lua_traceback == NULL && e->traceback_mode) { + int top = lua_gettop(L); + luaL_traceback(L, L, NULL, 0); + if (lua_isstring(L, -1)) { + error_set_lua_traceback(e, lua_tostring(L, -1)); + } + lua_settop(L, top); + } + assert(CTID_CONST_STRUCT_ERROR_REF != 0); struct error **ptr = (struct error **) luaL_pushcdata(L, CTID_CONST_STRUCT_ERROR_REF); diff --git a/src/lua/error.lua b/src/lua/error.lua index bdc9c71..46d2866 100644 --- a/src/lua/error.lua +++ b/src/lua/error.lua @@ -26,6 +26,8 @@ struct error { char _errmsg[DIAG_ERRMSG_MAX]; struct error *_cause; struct error *_effect; + char *lua_traceback; + bool traceback_mode; }; char * @@ -92,6 +94,14 @@ local function error_trace(err) } end +local function error_traceback(err) + local result = "Traceback is absent" + if err.lua_traceback ~= ffi.nullptr then + result = ffi.string(err.lua_traceback) + end + return result +end + local function error_errno(err) local e = err._saved_errno if e == 0 then @@ -122,7 +132,6 @@ local function error_set_prev(err, prev) if ok ~= 0 then error("Cycles are not allowed") end - end local error_fields = { @@ -131,6 +140,7 @@ local error_fields = { ["trace"] = error_trace; ["errno"] = error_errno; ["prev"] = error_prev; + ["traceback"] = error_traceback; } local function error_unpack(err) diff --git a/test/app/fiber.result b/test/app/fiber.result index debfc67..fc6b3c9 100644 --- a/test/app/fiber.result +++ b/test/app/fiber.result @@ -1038,12 +1038,13 @@ st; ... e:unpack(); --- -- type: ClientError +- traceback: Traceback is absent code: 1 - message: Illegal parameters, oh my trace: - file: '[string "function err() box.error(box.error.ILLEGAL_PA..."]' line: 1 + type: ClientError + message: Illegal parameters, oh my ... flag = false; --- diff --git a/test/box/error.result b/test/box/error.result index 234c263..22788a1 100644 --- a/test/box/error.result +++ b/test/box/error.result @@ -34,12 +34,13 @@ e | ... e:unpack() | --- - | - type: ClientError + | - traceback: Traceback is absent | code: 1 - | message: Illegal parameters, bla bla | trace: | - file: '[C]' | line: 4294967295 + | type: ClientError + | message: Illegal parameters, bla bla | ... e.type | --- diff --git a/test/engine/func_index.result b/test/engine/func_index.result index a827c92..28befca 100644 --- a/test/engine/func_index.result +++ b/test/engine/func_index.result @@ -276,7 +276,8 @@ e = box.error.last() | ... e:unpack() | --- - | - code: 198 + | - traceback: Traceback is absent + | code: 198 | trace: | - file: <filename> | line: <line> @@ -291,12 +292,13 @@ e = e.prev | ... e:unpack() | --- - | - type: LuajitError - | message: '[string "return function(tuple) local ..."]:1: attempt - | to call global ''require'' (a nil value)' + | - traceback: Traceback is absent | trace: | - file: <filename> | line: <line> + | type: LuajitError + | message: '[string "return function(tuple) local ..."]:1: attempt + | to call global ''require'' (a nil value)' | ... e = e.prev | --- -- 2.7.4 ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error Leonid Vasiliev @ 2020-04-14 1:11 ` Vladislav Shpilevoy 2020-04-15 9:25 ` lvasiliev 0 siblings, 1 reply; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-14 1:11 UTC (permalink / raw) To: Leonid Vasiliev; +Cc: tarantool-patches Thanks for the patch! Sorry, some of my comments may contain typos, because it is very late and I can't continue polishing it today. First of all there are some major issues with the patch, which IMO should be clarified before it is pushed. 1) Why do we need the traceback in scope of 4398? It has nothing to do with marshaling through IProto, nor with custom error types. 2) What to do with stacked errors? Currently only the first error in the stack gets a traceback, because luaT_pusherror() is called only on the top error in the stack. Consider this test: box.cfg{} lua_code = [[function(tuple) local json = require('json') return json.encode(tuple) end]] box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true}) s = box.schema.space.create('withdata') pk = s:create_index('pk') idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}}) function test_func() return pcall(s.insert, s, {1}) end box.error.cfg{traceback_enable = true} ok, err = test_func() tarantool> err:unpack() --- - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382: in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725: in function <builtin/box/console.lua:713>" ... <snipped> tarantool> err.prev:unpack() --- - type: LuajitError message: '[string "return function(tuple)..."]:2: attempt to call global ''require'' (a nil value)' ... <snipped> The second error does not have a traceback at all. See 10 comments below. Also I started a new branch with review fixes to speed up the process. The branch is called lvasiliev/gh-4398-expose-error-module-4-review You can find review fixes to this commit in the end of the email and on this branch in a separate commit. Note, my branch is rebased on master already. So some changes are related to cleaning the mess after it. On 10/04/2020 10:10, Leonid Vasiliev wrote: > In accordance with https://github.com/tarantool/tarantool/issues/4398 1. We don't put links when issue is in the same repository. We always use '#<number>' reference. This concerns this and all next commits. > Lua traceback has been added for box.error. > Has been added a per server flag for turn on/off traceback adding > and ability to force it at creation time. > > @TarantoolBot document > Title: error.traceback > Was added: > Per server flag for turn on/off adding a traceback to the errors. > box.error.cfg({traceback_supplementation = true/false}) 2. The option name is utterly overcomplicated. Just use traceback_enable > Adding a traceback can be forced on creation. > box.error.new({type = "CustomType", reason = "reason", traceback = true/false}) 3. CustomType errors are not defined here, you can't use it in the request description. > Needed for #4398 4. Please, don't put issue references inside docbot requests. This concerns this and all next commits. > --- > src/box/lua/error.cc | 33 ++++++++++++++++++++++++++++++++- > src/lib/core/diag.c | 32 ++++++++++++++++++++++++++++++++ > src/lib/core/diag.h | 11 +++++++++++ > src/lib/core/exception.cc | 1 + > src/lua/error.c | 10 ++++++++++ > src/lua/error.lua | 12 +++++++++++- > test/app/fiber.result | 5 +++-- > test/box/error.result | 5 +++-- > test/engine/func_index.result | 10 ++++++---- > 9 files changed, 109 insertions(+), 10 deletions(-) > > diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc > index b2625bf..4432bc8 100644 > --- a/src/box/lua/error.cc > +++ b/src/box/lua/error.cc> @@ -180,6 +193,20 @@ luaT_error_set(struct lua_State *L) > } > > static int > +luaT_error_cfg(struct lua_State *L) > +{ > + if (lua_gettop(L) < 1 || !lua_istable(L, 1)) > + return luaL_error(L, "Usage: box.error.cfg({}})"); > + > + lua_getfield(L, 1, "traceback_supplementation"); > + if (lua_isnil(L, -1) == 0) 5. If something is not nil, it does not mean it is a boolean. Better check lua_isboolean(). > + error_set_traceback_supplementation(lua_toboolean(L, -1)); > + lua_pop(L, 1); > + > + return 0; > +} > diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c > index e143db1..1caa75e 100644 > --- a/src/lib/core/diag.c > +++ b/src/lib/core/diag.c > @@ -120,3 +127,28 @@ error_vformat_msg(struct error *e, const char *format, va_list ap) > vsnprintf(e->errmsg, sizeof(e->errmsg), format, ap); > } > > +void > +error_set_lua_traceback(struct error *e, const char *lua_traceback) > +{ > + if (e == NULL) > + return; 6. It makes no sense to call this function on a NULL 'e'. This should be an assertion. As well as it is not possible to 'reset' the trace, so e->lua_traceback should be always NULL here. 7. Also I don't think it is a good idea to call traceback attribute 'lua_traceback'. Because we won't have separated 'lua_traceback' and 'c_traceback'. Anyway there will be always only one. Probably with C and Lua mixed (it is possible via space triggers to mix Lua and C in layers). > + > + if (lua_traceback == NULL) { > + free(e->lua_traceback); > + e->lua_traceback = NULL; > + return; > + } > + > + size_t tb_len = strlen(lua_traceback); > + e->lua_traceback = realloc(e->lua_traceback, tb_len + 1); > + if (e->lua_traceback == NULL) > + return; > + strcpy(e->lua_traceback, lua_traceback); > + return; > +} > + > +void > +error_set_traceback_supplementation(bool traceback_mode) > +{ > + global_traceback_mode = traceback_mode; > +} > diff --git a/src/lua/error.lua b/src/lua/error.lua > index bdc9c71..46d2866 100644 > --- a/src/lua/error.lua > +++ b/src/lua/error.lua > @@ -92,6 +94,14 @@ local function error_trace(err) > } > end > > +local function error_traceback(err) > + local result = "Traceback is absent" 8. I don't think it is a good idea to add this to every error object when there is no a trace. Just leave it nil. > + if err.lua_traceback ~= ffi.nullptr then > + result = ffi.string(err.lua_traceback) > + end > + return result > +end > + > local function error_errno(err) > local e = err._saved_errno > if e == 0 then > @@ -122,7 +132,6 @@ local function error_set_prev(err, prev) > if ok ~= 0 then > error("Cycles are not allowed") > end > - 9. Please, avoid unnecessary diff. > end > > local error_fields = { > diff --git a/test/engine/func_index.result b/test/engine/func_index.result > index a827c92..28befca 100644 > --- a/test/engine/func_index.result > +++ b/test/engine/func_index.result > @@ -291,12 +292,13 @@ e = e.prev > | ... > e:unpack() > | --- > - | - type: LuajitError > - | message: '[string "return function(tuple) local ..."]:1: attempt > - | to call global ''require'' (a nil value)' > + | - traceback: Traceback is absent > | trace: > | - file: <filename> > | line: <line> > + | type: LuajitError > + | message: '[string "return function(tuple) local ..."]:1: attempt > + | to call global ''require'' (a nil value)' > | ... > e = e.prev > | --- 10. Usually every commit should add tests on the feature it introduces. You didn't add any single test. Why? Consider my review fixes below and on a new branch in a separate commit. It solves all the code-related problems, but I still don't know what to do with the major issues described in the beginning. Branch is: lvasiliev/gh-4398-expose-error-module-4-review ==================== Review fixes New commit message proposal: error: add a Lua traceback to error Lua backtrace can be enabled for all errors using a server-wide option, or on a per-error basis using box.error() and box.error.new() arguments. Needed for #4398 @TarantoolBot document Title: Lua error.traceback Lua error objects now feature 'traceback' optional attribute. It contains Lua traceback. C part is not present here. Traceback collection is a relatively expensive operation, so it is disabled by default. In case you need to enable it, there are 2 options: * Global option `traceback_enable` for `box.error.cfg` call: ``` box.error.cfg({traceback_enable = true}) ``` * Per object option, in case you want traceback only for certain cases: ``` box.error.new({ code = 1000, reason = 'Reason', traceback = true/false }) ``` diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc index 4432bc89b..a616e87a4 100644 --- a/src/box/lua/error.cc +++ b/src/box/lua/error.cc @@ -54,8 +54,8 @@ luaT_error_create(lua_State *L, int top_base) { uint32_t code = 0; const char *reason = NULL; - bool tb_parsed = false; - bool tb_mode = false; + bool is_traceback_enabled = false; + bool is_traceback_specified = false; const char *file = ""; unsigned line = 0; lua_Debug info; @@ -91,10 +91,10 @@ luaT_error_create(lua_State *L, int top_base) lua_pop(L, 1); lua_getfield(L, top_base, "traceback"); if (lua_isboolean(L, -1)) { - tb_parsed = true; - tb_mode = lua_toboolean(L, -1); + is_traceback_enabled = lua_toboolean(L, -1); + is_traceback_specified = true; } - lua_pop(L, -1); + lua_pop(L, 1); } else { return NULL; } @@ -112,9 +112,11 @@ raise: } struct error *err = box_error_new(file, line, code, "%s", reason); - if (tb_parsed) - err->traceback_mode = tb_mode; - + /* + * Explicit traceback option overrides the global setting. + */ + if (err != NULL && is_traceback_specified) + err->is_traceback_enabled = is_traceback_enabled; return err; } @@ -198,11 +200,9 @@ luaT_error_cfg(struct lua_State *L) if (lua_gettop(L) < 1 || !lua_istable(L, 1)) return luaL_error(L, "Usage: box.error.cfg({}})"); - lua_getfield(L, 1, "traceback_supplementation"); - if (lua_isnil(L, -1) == 0) - error_set_traceback_supplementation(lua_toboolean(L, -1)); - lua_pop(L, 1); - + lua_getfield(L, 1, "traceback_enable"); + if (lua_isboolean(L, -1)) + error_is_traceback_enabled = lua_toboolean(L, -1); return 0; } diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c index 1caa75ee9..833454bac 100644 --- a/src/lib/core/diag.c +++ b/src/lib/core/diag.c @@ -31,10 +31,7 @@ #include "diag.h" #include "fiber.h" -/** - * Global flag to add or not backtrace to errors. - */ -static bool global_traceback_mode = false; +bool error_is_traceback_enabled = false; int error_set_prev(struct error *e, struct error *prev) @@ -102,8 +99,8 @@ error_create(struct error *e, e->errmsg[0] = '\0'; e->cause = NULL; e->effect = NULL; - e->lua_traceback = NULL; - e->traceback_mode = global_traceback_mode; + e->traceback = NULL; + e->is_traceback_enabled = error_is_traceback_enabled; } struct diag * @@ -128,27 +125,14 @@ error_vformat_msg(struct error *e, const char *format, va_list ap) } void -error_set_lua_traceback(struct error *e, const char *lua_traceback) -{ - if (e == NULL) - return; - - if (lua_traceback == NULL) { - free(e->lua_traceback); - e->lua_traceback = NULL; - return; - } - - size_t tb_len = strlen(lua_traceback); - e->lua_traceback = realloc(e->lua_traceback, tb_len + 1); - if (e->lua_traceback == NULL) - return; - strcpy(e->lua_traceback, lua_traceback); - return; -} - -void -error_set_traceback_supplementation(bool traceback_mode) +error_set_traceback(struct error *e, const char *traceback) { - global_traceback_mode = traceback_mode; + assert(e->traceback == NULL); + e->traceback = strdup(traceback); + /* + * Don't try to set it again. Traceback can be NULL in case of OOM, so + * it is not a reliable source of information whether need to collect a + * traceback. + */ + e->is_traceback_enabled = false; } diff --git a/src/lib/core/diag.h b/src/lib/core/diag.h index f38009c54..e918d3089 100644 --- a/src/lib/core/diag.h +++ b/src/lib/core/diag.h @@ -42,6 +42,13 @@ extern "C" { #endif /* defined(__cplusplus) */ +/** + * Flag to turn on/off traceback automatic collection. Currently + * it works only for Lua. Stack is collected at the moment of + * error object push onto the stack. + */ +extern bool error_is_traceback_enabled; + enum { DIAG_ERRMSG_MAX = 512, DIAG_FILENAME_MAX = 256 @@ -110,8 +117,19 @@ struct error { */ struct error *cause; struct error *effect; - char *lua_traceback; - bool traceback_mode; + /** + * Optional traceback. At the moment it can contain only + * Lua traceback, and only when it is requested + * explicitly. + */ + char *traceback; + /** + * Flag whether a traceback should be collected when the + * error object is exposed to a user next time. When the + * tracing is disabled, or it is enabled but already + * collected for this error object, it becomes false. + */ + bool is_traceback_enabled; }; static inline void @@ -174,6 +192,14 @@ error_unlink_effect(struct error *e) int error_set_prev(struct error *e, struct error *prev); +/** + * Set traceback of @a e error object. It can be done only once. + * In case of OOM the traceback is kept NULL, and can't be + * collected again. + */ +void +error_set_traceback(struct error *e, const char *traceback); + NORETURN static inline void error_raise(struct error *e) { @@ -199,15 +225,6 @@ error_format_msg(struct error *e, const char *format, ...); void error_vformat_msg(struct error *e, const char *format, va_list ap); -void -error_set_lua_traceback(struct error *e, const char *lua_traceback); - -/** -* Sets the global flag to add or not backtrace to errors. -*/ -void -error_set_traceback_supplementation(bool traceback_mode); - /** * Diagnostics Area - a container for errors */ diff --git a/src/lib/core/exception.cc b/src/lib/core/exception.cc index 0e4b6ca8b..1717b76fb 100644 --- a/src/lib/core/exception.cc +++ b/src/lib/core/exception.cc @@ -42,7 +42,7 @@ extern "C" { static void exception_destroy(struct error *e) { - free(e->lua_traceback); + free(e->traceback); delete (Exception *) e; } diff --git a/src/lua/error.c b/src/lua/error.c index cd6ab54b1..a21548ce6 100644 --- a/src/lua/error.c +++ b/src/lua/error.c @@ -86,12 +86,11 @@ luaT_pusherror(struct lua_State *L, struct error *e) */ error_ref(e); - if (e->lua_traceback == NULL && e->traceback_mode) { + if (e->traceback == NULL && e->is_traceback_enabled) { int top = lua_gettop(L); luaL_traceback(L, L, NULL, 0); - if (lua_isstring(L, -1)) { - error_set_lua_traceback(e, lua_tostring(L, -1)); - } + if (lua_isstring(L, -1)) + error_set_traceback(e, lua_tostring(L, -1)); lua_settop(L, top); } diff --git a/src/lua/error.lua b/src/lua/error.lua index 46d28667f..535110588 100644 --- a/src/lua/error.lua +++ b/src/lua/error.lua @@ -26,8 +26,8 @@ struct error { char _errmsg[DIAG_ERRMSG_MAX]; struct error *_cause; struct error *_effect; - char *lua_traceback; - bool traceback_mode; + char *traceback; + bool is_traceback_enabled; }; char * @@ -95,11 +95,7 @@ local function error_trace(err) end local function error_traceback(err) - local result = "Traceback is absent" - if err.lua_traceback ~= ffi.nullptr then - result = ffi.string(err.lua_traceback) - end - return result + return err.traceback ~= ffi.nullptr and ffi.string(err.traceback) or nil end local function error_errno(err) @@ -132,6 +128,7 @@ local function error_set_prev(err, prev) if ok ~= 0 then error("Cycles are not allowed") end + end local error_fields = { diff --git a/test/app/fiber.result b/test/app/fiber.result index fc6b3c92d..debfc6718 100644 --- a/test/app/fiber.result +++ b/test/app/fiber.result @@ -1038,13 +1038,12 @@ st; ... e:unpack(); --- -- traceback: Traceback is absent +- type: ClientError code: 1 + message: Illegal parameters, oh my trace: - file: '[string "function err() box.error(box.error.ILLEGAL_PA..."]' line: 1 - type: ClientError - message: Illegal parameters, oh my ... flag = false; --- diff --git a/test/box/error.result b/test/box/error.result index 574bc481f..2502d88c4 100644 --- a/test/box/error.result +++ b/test/box/error.result @@ -34,13 +34,12 @@ e | ... e:unpack() | --- - | - traceback: Traceback is absent + | - type: ClientError | code: 1 + | message: Illegal parameters, bla bla | trace: | - file: '[C]' | line: 4294967295 - | type: ClientError - | message: Illegal parameters, bla bla | ... e.type | --- @@ -832,3 +831,62 @@ assert(box.error.last() == e1) | --- | - true | ... + +-- +-- gh-4398: Lua traceback for errors. +-- +function t1(traceback, throw) \ + local opts = {code = 0, reason = 'Reason', traceback = traceback} \ + if throw then \ + box.error(opts) \ + else \ + return box.error.new(opts) \ + end \ +end + | --- + | ... +function t2(...) return t1(...), nil end + | --- + | ... +function t3(...) return t2(...), nil end + | --- + | ... + +function check_trace(trace) \ + local t1loc = trace:find('t1') \ + local t2loc = trace:find('t2') \ + local t3loc = trace:find('t3') \ + return t1loc < t2loc and t2loc < t3loc or {t1loc, t2loc, t3loc, trace} \ +end + | --- + | ... + +check_trace(t3(true, false):unpack().traceback) + | --- + | - true + | ... + +box.error.cfg{traceback_enable = true} + | --- + | ... +-- Explicit 'traceback = false' overrides the global setting. +t3(false, false):unpack().traceback + | --- + | - null + | ... +-- When explicit option is not specified, global setting works. +check_trace(t3(nil, false):unpack().traceback) + | --- + | - true + | ... + +box.error.cfg{traceback_enable = false} + | --- + | ... +_, e = pcall(t3, true, true) + | --- + | ... +check_trace(e:unpack().traceback) + | --- + | - true + | ... diff --git a/test/box/error.test.lua b/test/box/error.test.lua index 41baed52d..6f1271630 100644 --- a/test/box/error.test.lua +++ b/test/box/error.test.lua @@ -229,3 +229,36 @@ box.error({code = 111, reason = "err"}) box.error.last() box.error(e1) assert(box.error.last() == e1) + +-- +-- gh-4398: Lua traceback for errors. +-- +function t1(traceback, throw) \ + local opts = {code = 0, reason = 'Reason', traceback = traceback} \ + if throw then \ + box.error(opts) \ + else \ + return box.error.new(opts) \ + end \ +end +function t2(...) return t1(...), nil end +function t3(...) return t2(...), nil end + +function check_trace(trace) \ + local t1loc = trace:find('t1') \ + local t2loc = trace:find('t2') \ + local t3loc = trace:find('t3') \ + return t1loc < t2loc and t2loc < t3loc or {t1loc, t2loc, t3loc, trace} \ +end + +check_trace(t3(true, false):unpack().traceback) + +box.error.cfg{traceback_enable = true} +-- Explicit 'traceback = false' overrides the global setting. +t3(false, false):unpack().traceback +-- When explicit option is not specified, global setting works. +check_trace(t3(nil, false):unpack().traceback) + +box.error.cfg{traceback_enable = false} +_, e = pcall(t3, true, true) +check_trace(e:unpack().traceback) diff --git a/test/engine/func_index.result b/test/engine/func_index.result index 28befca39..a827c929f 100644 --- a/test/engine/func_index.result +++ b/test/engine/func_index.result @@ -276,8 +276,7 @@ e = box.error.last() | ... e:unpack() | --- - | - traceback: Traceback is absent - | code: 198 + | - code: 198 | trace: | - file: <filename> | line: <line> @@ -292,13 +291,12 @@ e = e.prev | ... e:unpack() | --- - | - traceback: Traceback is absent + | - type: LuajitError + | message: '[string "return function(tuple) local ..."]:1: attempt + | to call global ''require'' (a nil value)' | trace: | - file: <filename> | line: <line> - | type: LuajitError - | message: '[string "return function(tuple) local ..."]:1: attempt - | to call global ''require'' (a nil value)' | ... e = e.prev | --- ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error 2020-04-14 1:11 ` Vladislav Shpilevoy @ 2020-04-15 9:25 ` lvasiliev 2020-04-16 0:00 ` Vladislav Shpilevoy 0 siblings, 1 reply; 37+ messages in thread From: lvasiliev @ 2020-04-15 9:25 UTC (permalink / raw) To: Vladislav Shpilevoy, Alexander Turenko; +Cc: tarantool-patches Hi! Thanks for the review. On 14.04.2020 4:11, Vladislav Shpilevoy wrote: > Thanks for the patch! > > Sorry, some of my comments may contain typos, because it is > very late and I can't continue polishing it today. > > First of all there are some major issues with the > patch, which IMO should be clarified before it is pushed. > > 1) Why do we need the traceback in scope of 4398? It has > nothing to do with marshaling through IProto, nor with > custom error types. This is part of the errors.lua functional, which was attached as an example. After that it was also one of the functionality requested by Nazarov. > > 2) What to do with stacked errors? Currently only the first > error in the stack gets a traceback, because luaT_pusherror() is > called only on the top error in the stack. Consider this test: > > box.cfg{} > lua_code = [[function(tuple) > local json = require('json') > return json.encode(tuple) > end]] > box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true}) > s = box.schema.space.create('withdata') > pk = s:create_index('pk') > idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}}) > > function test_func() return pcall(s.insert, s, {1}) end > box.error.cfg{traceback_enable = true} > ok, err = test_func() > > > tarantool> err:unpack() > --- > - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string > \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382: > in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725: > in function <builtin/box/console.lua:713>" > ... <snipped> > > tarantool> err.prev:unpack() > --- > - type: LuajitError > message: '[string "return function(tuple)..."]:2: attempt to call global ''require'' > (a nil value)' > ... <snipped> > > The second error does not have a traceback at all. (I added Turenko to To) I have two variants: - Leave as is and to document such behavior - Add the same traceback to all errors in the stack Alexander what do you think? > > > See 10 comments below. Also I started a new branch with review > fixes to speed up the process. The branch is called > > lvasiliev/gh-4398-expose-error-module-4-review > > You can find review fixes to this commit in the end of the > email and on this branch in a separate commit. Note, my branch > is rebased on master already. So some changes are related to > cleaning the mess after it. > > On 10/04/2020 10:10, Leonid Vasiliev wrote: >> In accordance with https://github.com/tarantool/tarantool/issues/4398 > > 1. We don't put links when issue is in the same repository. > We always use '#<number>' reference. This concerns this and all > next commits. > >> Lua traceback has been added for box.error. >> Has been added a per server flag for turn on/off traceback adding >> and ability to force it at creation time. >> >> @TarantoolBot document >> Title: error.traceback >> Was added: >> Per server flag for turn on/off adding a traceback to the errors. >> box.error.cfg({traceback_supplementation = true/false}) > > 2. The option name is utterly overcomplicated. Just use > > traceback_enable > >> Adding a traceback can be forced on creation. >> box.error.new({type = "CustomType", reason = "reason", traceback = true/false}) > > 3. CustomType errors are not defined here, you can't use it in > the request description. > >> Needed for #4398 > > 4. Please, don't put issue references inside docbot requests. This > concerns this and all next commits. > >> --- >> src/box/lua/error.cc | 33 ++++++++++++++++++++++++++++++++- >> src/lib/core/diag.c | 32 ++++++++++++++++++++++++++++++++ >> src/lib/core/diag.h | 11 +++++++++++ >> src/lib/core/exception.cc | 1 + >> src/lua/error.c | 10 ++++++++++ >> src/lua/error.lua | 12 +++++++++++- >> test/app/fiber.result | 5 +++-- >> test/box/error.result | 5 +++-- >> test/engine/func_index.result | 10 ++++++---- >> 9 files changed, 109 insertions(+), 10 deletions(-) >> >> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc >> index b2625bf..4432bc8 100644 >> --- a/src/box/lua/error.cc >> +++ b/src/box/lua/error.cc> @@ -180,6 +193,20 @@ luaT_error_set(struct lua_State *L) >> } >> >> static int >> +luaT_error_cfg(struct lua_State *L) >> +{ >> + if (lua_gettop(L) < 1 || !lua_istable(L, 1)) >> + return luaL_error(L, "Usage: box.error.cfg({}})"); >> + >> + lua_getfield(L, 1, "traceback_supplementation"); >> + if (lua_isnil(L, -1) == 0) > > 5. If something is not nil, it does not mean it is a boolean. > Better check lua_isboolean(). > >> + error_set_traceback_supplementation(lua_toboolean(L, -1)); >> + lua_pop(L, 1); >> + >> + return 0; >> +} >> diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c >> index e143db1..1caa75e 100644 >> --- a/src/lib/core/diag.c >> +++ b/src/lib/core/diag.c >> @@ -120,3 +127,28 @@ error_vformat_msg(struct error *e, const char *format, va_list ap) >> vsnprintf(e->errmsg, sizeof(e->errmsg), format, ap); >> } >> >> +void >> +error_set_lua_traceback(struct error *e, const char *lua_traceback) >> +{ >> + if (e == NULL) >> + return; > > 6. It makes no sense to call this function on a NULL 'e'. This > should be an assertion. As well as it is not possible to 'reset' > the trace, so e->lua_traceback should be always NULL here. > > 7. Also I don't think it is a good idea to call traceback attribute > 'lua_traceback'. Because we won't have separated 'lua_traceback' > and 'c_traceback'. Anyway there will be always only one. Probably > with C and Lua mixed (it is possible via space triggers to mix Lua > and C in layers). > >> + >> + if (lua_traceback == NULL) { >> + free(e->lua_traceback); >> + e->lua_traceback = NULL; >> + return; >> + } >> + >> + size_t tb_len = strlen(lua_traceback); >> + e->lua_traceback = realloc(e->lua_traceback, tb_len + 1); >> + if (e->lua_traceback == NULL) >> + return; >> + strcpy(e->lua_traceback, lua_traceback); >> + return; >> +} >> + >> +void >> +error_set_traceback_supplementation(bool traceback_mode) >> +{ >> + global_traceback_mode = traceback_mode; >> +} >> diff --git a/src/lua/error.lua b/src/lua/error.lua >> index bdc9c71..46d2866 100644 >> --- a/src/lua/error.lua >> +++ b/src/lua/error.lua >> @@ -92,6 +94,14 @@ local function error_trace(err) >> } >> end >> >> +local function error_traceback(err) >> + local result = "Traceback is absent" > > 8. I don't think it is a good idea to add this to > every error object when there is no a trace. Just > leave it nil. > >> + if err.lua_traceback ~= ffi.nullptr then >> + result = ffi.string(err.lua_traceback) >> + end >> + return result >> +end >> + >> local function error_errno(err) >> local e = err._saved_errno >> if e == 0 then >> @@ -122,7 +132,6 @@ local function error_set_prev(err, prev) >> if ok ~= 0 then >> error("Cycles are not allowed") >> end >> - > > 9. Please, avoid unnecessary diff. > >> end >> >> local error_fields = { >> diff --git a/test/engine/func_index.result b/test/engine/func_index.result >> index a827c92..28befca 100644 >> --- a/test/engine/func_index.result >> +++ b/test/engine/func_index.result >> @@ -291,12 +292,13 @@ e = e.prev >> | ... >> e:unpack() >> | --- >> - | - type: LuajitError >> - | message: '[string "return function(tuple) local ..."]:1: attempt >> - | to call global ''require'' (a nil value)' >> + | - traceback: Traceback is absent >> | trace: >> | - file: <filename> >> | line: <line> >> + | type: LuajitError >> + | message: '[string "return function(tuple) local ..."]:1: attempt >> + | to call global ''require'' (a nil value)' >> | ... >> e = e.prev >> | --- > > 10. Usually every commit should add tests on the feature it > introduces. You didn't add any single test. Why? That's my mistake. After our discussion for testing I used the test added in the last commit and deleted the ones added before in misc. > > Consider my review fixes below and on a new branch in a > separate commit. It solves all the code-related problems, > but I still don't know what to do with the major issues > described in the beginning. I applied your patch and added you to Co-authored. But I have a couple of questions. See 4 comments below. > > Branch is: > > lvasiliev/gh-4398-expose-error-module-4-review > > ==================== > Review fixes > > New commit message proposal: > > error: add a Lua traceback to error > > Lua backtrace can be enabled for all errors using a server-wide > option, or on a per-error basis using box.error() and > box.error.new() arguments. > > Needed for #4398 > > @TarantoolBot document > Title: Lua error.traceback > > Lua error objects now feature 'traceback' optional attribute. > It contains Lua traceback. C part is not present here. > > Traceback collection is a relatively expensive operation, so it > is disabled by default. In case you need to enable it, there are > 2 options: > > * Global option `traceback_enable` for `box.error.cfg` call: > ``` > box.error.cfg({traceback_enable = true}) > ``` > > * Per object option, in case you want traceback only for certain > cases: > ``` > box.error.new({ > code = 1000, > reason = 'Reason', > traceback = true/false > }) > ``` > > diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc > index 4432bc89b..a616e87a4 100644 > --- a/src/box/lua/error.cc > +++ b/src/box/lua/error.cc > @@ -54,8 +54,8 @@ luaT_error_create(lua_State *L, int top_base) > { > uint32_t code = 0; > const char *reason = NULL; > - bool tb_parsed = false; > - bool tb_mode = false; > + bool is_traceback_enabled = false; > + bool is_traceback_specified = false; > const char *file = ""; > unsigned line = 0; > lua_Debug info; > @@ -91,10 +91,10 @@ luaT_error_create(lua_State *L, int top_base) > lua_pop(L, 1); > lua_getfield(L, top_base, "traceback"); > if (lua_isboolean(L, -1)) { > - tb_parsed = true; > - tb_mode = lua_toboolean(L, -1); > + is_traceback_enabled = lua_toboolean(L, -1); > + is_traceback_specified = true; > } > - lua_pop(L, -1); > + lua_pop(L, 1); > } else { > return NULL; > } > @@ -112,9 +112,11 @@ raise: > } > > struct error *err = box_error_new(file, line, code, "%s", reason); > - if (tb_parsed) > - err->traceback_mode = tb_mode; > - > + /* > + * Explicit traceback option overrides the global setting. > + */ > + if (err != NULL && is_traceback_specified) 1) box_error_new don't return NULL > + err->is_traceback_enabled = is_traceback_enabled; > return err; > } > > @@ -198,11 +200,9 @@ luaT_error_cfg(struct lua_State *L) > if (lua_gettop(L) < 1 || !lua_istable(L, 1)) > return luaL_error(L, "Usage: box.error.cfg({}})"); > > - lua_getfield(L, 1, "traceback_supplementation"); > - if (lua_isnil(L, -1) == 0) > - error_set_traceback_supplementation(lua_toboolean(L, -1)); > - lua_pop(L, 1); > - > + lua_getfield(L, 1, "traceback_enable"); > + if (lua_isboolean(L, -1)) > + error_is_traceback_enabled = lua_toboolean(L, -1); > return 0; > } > > diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c > index 1caa75ee9..833454bac 100644 > --- a/src/lib/core/diag.c > +++ b/src/lib/core/diag.c > @@ -31,10 +31,7 @@ > #include "diag.h" > #include "fiber.h" > > -/** > - * Global flag to add or not backtrace to errors. > - */ > -static bool global_traceback_mode = false; > +bool error_is_traceback_enabled = false; > > int > error_set_prev(struct error *e, struct error *prev) > @@ -102,8 +99,8 @@ error_create(struct error *e, > e->errmsg[0] = '\0'; > e->cause = NULL; > e->effect = NULL; > - e->lua_traceback = NULL; > - e->traceback_mode = global_traceback_mode; > + e->traceback = NULL; > + e->is_traceback_enabled = error_is_traceback_enabled; > } > > struct diag * > @@ -128,27 +125,14 @@ error_vformat_msg(struct error *e, const char *format, va_list ap) > } > > void > -error_set_lua_traceback(struct error *e, const char *lua_traceback) > -{ > - if (e == NULL) > - return; > - > - if (lua_traceback == NULL) { > - free(e->lua_traceback); > - e->lua_traceback = NULL; > - return; > - } > - > - size_t tb_len = strlen(lua_traceback); > - e->lua_traceback = realloc(e->lua_traceback, tb_len + 1); > - if (e->lua_traceback == NULL) > - return; > - strcpy(e->lua_traceback, lua_traceback); > - return; > -} > - > -void > -error_set_traceback_supplementation(bool traceback_mode) > +error_set_traceback(struct error *e, const char *traceback) > { > - global_traceback_mode = traceback_mode; > + assert(e->traceback == NULL); 2) Do I understand correctly that asserts only work on debug? Will this approach not be dangerous by memory leaks on release? > + e->traceback = strdup(traceback); > + /* > + * Don't try to set it again. Traceback can be NULL in case of OOM, so > + * it is not a reliable source of information whether need to collect a > + * traceback. > + */ > + e->is_traceback_enabled = false; > } > diff --git a/src/lib/core/diag.h b/src/lib/core/diag.h > index f38009c54..e918d3089 100644 > --- a/src/lib/core/diag.h > +++ b/src/lib/core/diag.h > @@ -42,6 +42,13 @@ > extern "C" { > #endif /* defined(__cplusplus) */ > > +/** > + * Flag to turn on/off traceback automatic collection. Currently > + * it works only for Lua. Stack is collected at the moment of > + * error object push onto the stack. > + */ > +extern bool error_is_traceback_enabled; 3) Why a global variable is more preferable than static? > + > enum { > DIAG_ERRMSG_MAX = 512, > DIAG_FILENAME_MAX = 256 > @@ -110,8 +117,19 @@ struct error { > */ > struct error *cause; > struct error *effect; > - char *lua_traceback; > - bool traceback_mode; > + /** > + * Optional traceback. At the moment it can contain only > + * Lua traceback, and only when it is requested > + * explicitly. > + */ > + char *traceback; > + /** > + * Flag whether a traceback should be collected when the > + * error object is exposed to a user next time. When the > + * tracing is disabled, or it is enabled but already > + * collected for this error object, it becomes false. > + */ > + bool is_traceback_enabled; > }; > > static inline void > @@ -174,6 +192,14 @@ error_unlink_effect(struct error *e) > int > error_set_prev(struct error *e, struct error *prev); > > +/** > + * Set traceback of @a e error object. It can be done only once. > + * In case of OOM the traceback is kept NULL, and can't be > + * collected again. > + */ > +void > +error_set_traceback(struct error *e, const char *traceback); > + > NORETURN static inline void > error_raise(struct error *e) > { > @@ -199,15 +225,6 @@ error_format_msg(struct error *e, const char *format, ...); > void > error_vformat_msg(struct error *e, const char *format, va_list ap); > > -void > -error_set_lua_traceback(struct error *e, const char *lua_traceback); > - > -/** > -* Sets the global flag to add or not backtrace to errors. > -*/ > -void > -error_set_traceback_supplementation(bool traceback_mode); > - > /** > * Diagnostics Area - a container for errors > */ > diff --git a/src/lib/core/exception.cc b/src/lib/core/exception.cc > index 0e4b6ca8b..1717b76fb 100644 > --- a/src/lib/core/exception.cc > +++ b/src/lib/core/exception.cc > @@ -42,7 +42,7 @@ extern "C" { > static void > exception_destroy(struct error *e) > { > - free(e->lua_traceback); > + free(e->traceback); > delete (Exception *) e; > } > > diff --git a/src/lua/error.c b/src/lua/error.c > index cd6ab54b1..a21548ce6 100644 > --- a/src/lua/error.c > +++ b/src/lua/error.c > @@ -86,12 +86,11 @@ luaT_pusherror(struct lua_State *L, struct error *e) > */ > error_ref(e); > > - if (e->lua_traceback == NULL && e->traceback_mode) { > + if (e->traceback == NULL && e->is_traceback_enabled) { > int top = lua_gettop(L); > luaL_traceback(L, L, NULL, 0); > - if (lua_isstring(L, -1)) { > - error_set_lua_traceback(e, lua_tostring(L, -1)); > - } > + if (lua_isstring(L, -1)) > + error_set_traceback(e, lua_tostring(L, -1)); > lua_settop(L, top); > } > > diff --git a/src/lua/error.lua b/src/lua/error.lua > index 46d28667f..535110588 100644 > --- a/src/lua/error.lua > +++ b/src/lua/error.lua > @@ -26,8 +26,8 @@ struct error { > char _errmsg[DIAG_ERRMSG_MAX]; > struct error *_cause; > struct error *_effect; > - char *lua_traceback; > - bool traceback_mode; > + char *traceback; 4) Replaced to err_traceback. It overlaps the traceback getter. > + bool is_traceback_enabled; > }; > > char * > @@ -95,11 +95,7 @@ local function error_trace(err) > end > > local function error_traceback(err) > - local result = "Traceback is absent" > - if err.lua_traceback ~= ffi.nullptr then > - result = ffi.string(err.lua_traceback) > - end > - return result > + return err.traceback ~= ffi.nullptr and ffi.string(err.traceback) or nil > end > > local function error_errno(err) > @@ -132,6 +128,7 @@ local function error_set_prev(err, prev) > if ok ~= 0 then > error("Cycles are not allowed") > end > + > end > > local error_fields = { > diff --git a/test/app/fiber.result b/test/app/fiber.result > index fc6b3c92d..debfc6718 100644 > --- a/test/app/fiber.result > +++ b/test/app/fiber.result > @@ -1038,13 +1038,12 @@ st; > ... > e:unpack(); > --- > -- traceback: Traceback is absent > +- type: ClientError > code: 1 > + message: Illegal parameters, oh my > trace: > - file: '[string "function err() box.error(box.error.ILLEGAL_PA..."]' > line: 1 > - type: ClientError > - message: Illegal parameters, oh my > ... > flag = false; > --- > diff --git a/test/box/error.result b/test/box/error.result > index 574bc481f..2502d88c4 100644 > --- a/test/box/error.result > +++ b/test/box/error.result > @@ -34,13 +34,12 @@ e > | ... > e:unpack() > | --- > - | - traceback: Traceback is absent > + | - type: ClientError > | code: 1 > + | message: Illegal parameters, bla bla > | trace: > | - file: '[C]' > | line: 4294967295 > - | type: ClientError > - | message: Illegal parameters, bla bla > | ... > e.type > | --- > @@ -832,3 +831,62 @@ assert(box.error.last() == e1) > | --- > | - true > | ... > + > +-- > +-- gh-4398: Lua traceback for errors. > +-- > +function t1(traceback, throw) \ > + local opts = {code = 0, reason = 'Reason', traceback = traceback} \ > + if throw then \ > + box.error(opts) \ > + else \ > + return box.error.new(opts) \ > + end \ > +end > + | --- > + | ... > +function t2(...) return t1(...), nil end > + | --- > + | ... > +function t3(...) return t2(...), nil end > + | --- > + | ... > + > +function check_trace(trace) \ > + local t1loc = trace:find('t1') \ > + local t2loc = trace:find('t2') \ > + local t3loc = trace:find('t3') \ > + return t1loc < t2loc and t2loc < t3loc or {t1loc, t2loc, t3loc, trace} \ > +end > + | --- > + | ... > + > +check_trace(t3(true, false):unpack().traceback) > + | --- > + | - true > + | ... > + > +box.error.cfg{traceback_enable = true} > + | --- > + | ... > +-- Explicit 'traceback = false' overrides the global setting. > +t3(false, false):unpack().traceback > + | --- > + | - null > + | ... > +-- When explicit option is not specified, global setting works. > +check_trace(t3(nil, false):unpack().traceback) > + | --- > + | - true > + | ... > + > +box.error.cfg{traceback_enable = false} > + | --- > + | ... > +_, e = pcall(t3, true, true) > + | --- > + | ... > +check_trace(e:unpack().traceback) > + | --- > + | - true > + | ... > diff --git a/test/box/error.test.lua b/test/box/error.test.lua > index 41baed52d..6f1271630 100644 > --- a/test/box/error.test.lua > +++ b/test/box/error.test.lua > @@ -229,3 +229,36 @@ box.error({code = 111, reason = "err"}) > box.error.last() > box.error(e1) > assert(box.error.last() == e1) > + > +-- > +-- gh-4398: Lua traceback for errors. > +-- > +function t1(traceback, throw) \ > + local opts = {code = 0, reason = 'Reason', traceback = traceback} \ > + if throw then \ > + box.error(opts) \ > + else \ > + return box.error.new(opts) \ > + end \ > +end > +function t2(...) return t1(...), nil end > +function t3(...) return t2(...), nil end > + > +function check_trace(trace) \ > + local t1loc = trace:find('t1') \ > + local t2loc = trace:find('t2') \ > + local t3loc = trace:find('t3') \ > + return t1loc < t2loc and t2loc < t3loc or {t1loc, t2loc, t3loc, trace} \ > +end > + > +check_trace(t3(true, false):unpack().traceback) > + > +box.error.cfg{traceback_enable = true} > +-- Explicit 'traceback = false' overrides the global setting. > +t3(false, false):unpack().traceback > +-- When explicit option is not specified, global setting works. > +check_trace(t3(nil, false):unpack().traceback) > + > +box.error.cfg{traceback_enable = false} > +_, e = pcall(t3, true, true) > +check_trace(e:unpack().traceback) > diff --git a/test/engine/func_index.result b/test/engine/func_index.result > index 28befca39..a827c929f 100644 > --- a/test/engine/func_index.result > +++ b/test/engine/func_index.result > @@ -276,8 +276,7 @@ e = box.error.last() > | ... > e:unpack() > | --- > - | - traceback: Traceback is absent > - | code: 198 > + | - code: 198 > | trace: > | - file: <filename> > | line: <line> > @@ -292,13 +291,12 @@ e = e.prev > | ... > e:unpack() > | --- > - | - traceback: Traceback is absent > + | - type: LuajitError > + | message: '[string "return function(tuple) local ..."]:1: attempt > + | to call global ''require'' (a nil value)' > | trace: > | - file: <filename> > | line: <line> > - | type: LuajitError > - | message: '[string "return function(tuple) local ..."]:1: attempt > - | to call global ''require'' (a nil value)' > | ... > e = e.prev > | --- > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error 2020-04-15 9:25 ` lvasiliev @ 2020-04-16 0:00 ` Vladislav Shpilevoy 2020-04-16 1:11 ` Alexander Turenko 2020-04-16 8:40 ` lvasiliev 0 siblings, 2 replies; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-16 0:00 UTC (permalink / raw) To: lvasiliev, Alexander Turenko; +Cc: tarantool-patches Hi! Thanks for the fixes! > On 14.04.2020 4:11, Vladislav Shpilevoy wrote: >> Thanks for the patch! >> >> Sorry, some of my comments may contain typos, because it is >> very late and I can't continue polishing it today. >> >> First of all there are some major issues with the >> patch, which IMO should be clarified before it is pushed. >> >> 1) Why do we need the traceback in scope of 4398? It has >> nothing to do with marshaling through IProto, nor with >> custom error types. > This is part of the errors.lua functional, which was attached as an example. After that it was also one of the functionality requested by Nazarov. The original ticket does not contain any mentioning of a traceback: https://github.com/tarantool/tarantool/issues/4398#issue-475632900 I agree that it was requested later, in comments, but still it looks pretty separate from this, don't you agree? >> 2) What to do with stacked errors? Currently only the first >> error in the stack gets a traceback, because luaT_pusherror() is >> called only on the top error in the stack. Consider this test: >> >> box.cfg{} >> lua_code = [[function(tuple) >> local json = require('json') >> return json.encode(tuple) >> end]] >> box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true}) >> s = box.schema.space.create('withdata') >> pk = s:create_index('pk') >> idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}}) >> >> function test_func() return pcall(s.insert, s, {1}) end >> box.error.cfg{traceback_enable = true} >> ok, err = test_func() >> >> >> tarantool> err:unpack() >> --- >> - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string >> \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382: >> in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725: >> in function <builtin/box/console.lua:713>" >> ... <snipped> >> >> tarantool> err.prev:unpack() >> --- >> - type: LuajitError >> message: '[string "return function(tuple)..."]:2: attempt to call global ''require'' >> (a nil value)' >> ... <snipped> >> >> The second error does not have a traceback at all. > (I added Turenko to To) > I have two variants: > - Leave as is and to document such behavior > - Add the same traceback to all errors in the stack > Alexander what do you think? There is a third option - leave traceback out of this patchset for a next release. Because they are clearly underdesigned. But on the other hand it is not something critical. After all, we just can say, that a traceback can be present, or can be not, and its content is just a string, which can't be assumed to have any special format. That would allow us to add/remove them and change their format anytime. >> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc >> index 4432bc89b..a616e87a4 100644 >> --- a/src/box/lua/error.cc >> +++ b/src/box/lua/error.cc >> @@ -112,9 +112,11 @@ raise: >> } >> struct error *err = box_error_new(file, line, code, "%s", reason); >> - if (tb_parsed) >> - err->traceback_mode = tb_mode; >> - >> + /* >> + * Explicit traceback option overrides the global setting. >> + */ >> + if (err != NULL && is_traceback_specified) > 1) box_error_new don't return NULL True, then 'err != NULL' check can be dropped. >> diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c >> index 1caa75ee9..833454bac 100644 >> --- a/src/lib/core/diag.c >> +++ b/src/lib/core/diag.c >> @@ -128,27 +125,14 @@ error_vformat_msg(struct error *e, const char *format, va_list ap) >> -void >> -error_set_traceback_supplementation(bool traceback_mode) >> +error_set_traceback(struct error *e, const char *traceback) >> { >> - global_traceback_mode = traceback_mode; >> + assert(e->traceback == NULL); > 2) Do I understand correctly that asserts only work on debug? Will this approach not be dangerous by memory leaks on release? It should not be. Because the method is not public, and in private methods it is never called with non-zero e->traceback. >> + e->traceback = strdup(traceback); >> + /* >> + * Don't try to set it again. Traceback can be NULL in case of OOM, so >> + * it is not a reliable source of information whether need to collect a >> + * traceback. >> + */ >> + e->is_traceback_enabled = false; >> } >> diff --git a/src/lib/core/diag.h b/src/lib/core/diag.h >> index f38009c54..e918d3089 100644 >> --- a/src/lib/core/diag.h >> +++ b/src/lib/core/diag.h >> @@ -42,6 +42,13 @@ >> extern "C" { >> #endif /* defined(__cplusplus) */ >> +/** >> + * Flag to turn on/off traceback automatic collection. Currently >> + * it works only for Lua. Stack is collected at the moment of >> + * error object push onto the stack. >> + */ >> +extern bool error_is_traceback_enabled; > 3) Why a global variable is more preferable than static? Static variables are also global. I just removed the setter, since it is trivial. >> diff --git a/src/lua/error.lua b/src/lua/error.lua >> index 46d28667f..535110588 100644 >> --- a/src/lua/error.lua >> +++ b/src/lua/error.lua >> @@ -26,8 +26,8 @@ struct error { >> char _errmsg[DIAG_ERRMSG_MAX]; >> struct error *_cause; >> struct error *_effect; >> - char *lua_traceback; >> - bool traceback_mode; >> + char *traceback; > 4) Replaced to err_traceback. It overlaps the traceback getter. >> + bool is_traceback_enabled; Better replace it with _traceback. Similar to other members. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error 2020-04-16 0:00 ` Vladislav Shpilevoy @ 2020-04-16 1:11 ` Alexander Turenko 2020-04-16 8:58 ` lvasiliev 2020-04-16 8:40 ` lvasiliev 1 sibling, 1 reply; 37+ messages in thread From: Alexander Turenko @ 2020-04-16 1:11 UTC (permalink / raw) To: Vladislav Shpilevoy; +Cc: tarantool-patches > >> 2) What to do with stacked errors? Currently only the first > >> error in the stack gets a traceback, because luaT_pusherror() is > >> called only on the top error in the stack. Consider this test: > >> > >> box.cfg{} > >> lua_code = [[function(tuple) > >> local json = require('json') > >> return json.encode(tuple) > >> end]] > >> box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true}) > >> s = box.schema.space.create('withdata') > >> pk = s:create_index('pk') > >> idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}}) > >> > >> function test_func() return pcall(s.insert, s, {1}) end > >> box.error.cfg{traceback_enable = true} > >> ok, err = test_func() > >> > >> > >> tarantool> err:unpack() > >> --- > >> - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string > >> \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382: > >> in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725: > >> in function <builtin/box/console.lua:713>" > >> ... <snipped> BTW, can we call :split('\n') for .traceback field in at least __serialize? The cited output is hard to read. Alternative: place two newlines in row somewhere to force yaml serializer to use multiline string format. > >> > >> tarantool> err.prev:unpack() > >> --- > >> - type: LuajitError > >> message: '[string "return function(tuple)..."]:2: attempt to call global ''require'' > >> (a nil value)' > >> ... <snipped> > >> > >> The second error does not have a traceback at all. > > (I added Turenko to To) > > I have two variants: > > - Leave as is and to document such behavior > > - Add the same traceback to all errors in the stack > > Alexander what do you think? The first approach look inconsistent. A user may want to get a cause of a topmost error and pass it somewhere. The function, where the error will be processed (say, serialized), don't know whether a traceback should be grabbed from some other error object (and how to find it). > > There is a third option - leave traceback out of this patchset for > a next release. Because they are clearly underdesigned. But on the > other hand it is not something critical. After all, we just can say, > that a traceback can be present, or can be not, and its content is > just a string, which can't be assumed to have any special format. > > That would allow us to add/remove them and change their format anytime. Are there other problems with traceback? BTW, how traceback is linked with .trace? Generally I'm for decomposing problems. Leonid, you may just keep the feature at top patches of your series. If it'll look ready, it will land. Otherwise, only first part will land. I think that it is okay to implement marshalling over net.box first. WBR, Alexander Turenko. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error 2020-04-16 1:11 ` Alexander Turenko @ 2020-04-16 8:58 ` lvasiliev 2020-04-16 9:30 ` Alexander Turenko 0 siblings, 1 reply; 37+ messages in thread From: lvasiliev @ 2020-04-16 8:58 UTC (permalink / raw) To: Alexander Turenko, Vladislav Shpilevoy; +Cc: tarantool-patches Hi! Thanks for the feedback. On 16.04.2020 4:11, Alexander Turenko wrote: >>>> 2) What to do with stacked errors? Currently only the first >>>> error in the stack gets a traceback, because luaT_pusherror() is >>>> called only on the top error in the stack. Consider this test: >>>> >>>> box.cfg{} >>>> lua_code = [[function(tuple) >>>> local json = require('json') >>>> return json.encode(tuple) >>>> end]] >>>> box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true}) >>>> s = box.schema.space.create('withdata') >>>> pk = s:create_index('pk') >>>> idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}}) >>>> >>>> function test_func() return pcall(s.insert, s, {1}) end >>>> box.error.cfg{traceback_enable = true} >>>> ok, err = test_func() >>>> >>>> >>>> tarantool> err:unpack() >>>> --- >>>> - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string >>>> \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382: >>>> in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725: >>>> in function <builtin/box/console.lua:713>" >>>> ... <snipped> > > BTW, can we call :split('\n') for .traceback field in at least > __serialize? The cited output is hard to read. Alternative: place two > newlines in row somewhere to force yaml serializer to use multiline > string format. Traceback is absent in __serialize, because it will change the error view for old client. If the client matches result with some pattern it, will be broken. >>>> >>>> tarantool> err.prev:unpack() >>>> --- >>>> - type: LuajitError >>>> message: '[string "return function(tuple)..."]:2: attempt to call global ''require'' >>>> (a nil value)' >>>> ... <snipped> >>>> >>>> The second error does not have a traceback at all. >>> (I added Turenko to To) >>> I have two variants: >>> - Leave as is and to document such behavior >>> - Add the same traceback to all errors in the stack >>> Alexander what do you think? > > The first approach look inconsistent. A user may want to get a cause of > a topmost error and pass it somewhere. The function, where the error > will be processed (say, serialized), don't know whether a traceback > should be grabbed from some other error object (and how to find it). > Not quite, you either have a traceback or not. Don't try to get it from another error. >> >> There is a third option - leave traceback out of this patchset for >> a next release. Because they are clearly underdesigned. But on the >> other hand it is not something critical. After all, we just can say, >> that a traceback can be present, or can be not, and its content is >> just a string, which can't be assumed to have any special format. >> >> That would allow us to add/remove them and change their format anytime. > > Are there other problems with traceback? > > BTW, how traceback is linked with .trace? > > Generally I'm for decomposing problems. Leonid, you may just keep the > feature at top patches of your series. If it'll look ready, it will > land. Otherwise, only first part will land. > > I think that it is okay to implement marshalling over net.box first. > > WBR, Alexander Turenko. > As I said here 3 separate parts (Custom type, traceback and marshalling) but I don’t want to shuffle the patchset (patches have some dependencies from previous). I suggest just making a decision whether we will add a traceback or not. If not, I will simply remove this patch. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error 2020-04-16 8:58 ` lvasiliev @ 2020-04-16 9:30 ` Alexander Turenko 2020-04-16 12:27 ` lvasiliev 0 siblings, 1 reply; 37+ messages in thread From: Alexander Turenko @ 2020-04-16 9:30 UTC (permalink / raw) To: lvasiliev; +Cc: tarantool-patches, Vladislav Shpilevoy On Thu, Apr 16, 2020 at 11:58:03AM +0300, lvasiliev wrote: > Hi! Thanks for the feedback. > > On 16.04.2020 4:11, Alexander Turenko wrote: > > > > > 2) What to do with stacked errors? Currently only the first > > > > > error in the stack gets a traceback, because luaT_pusherror() is > > > > > called only on the top error in the stack. Consider this test: > > > > > > > > > > box.cfg{} > > > > > lua_code = [[function(tuple) > > > > > local json = require('json') > > > > > return json.encode(tuple) > > > > > end]] > > > > > box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true}) > > > > > s = box.schema.space.create('withdata') > > > > > pk = s:create_index('pk') > > > > > idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}}) > > > > > > > > > > function test_func() return pcall(s.insert, s, {1}) end > > > > > box.error.cfg{traceback_enable = true} > > > > > ok, err = test_func() > > > > > > > > > > > > > > > tarantool> err:unpack() > > > > > --- > > > > > - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string > > > > > \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382: > > > > > in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725: > > > > > in function <builtin/box/console.lua:713>" > > > > > ... <snipped> > > > > BTW, can we call :split('\n') for .traceback field in at least > > __serialize? The cited output is hard to read. Alternative: place two > > newlines in row somewhere to force yaml serializer to use multiline > > string format. > > Traceback is absent in __serialize, because it will change the error view > for old client. If the client matches result with some pattern it, will be > broken. It is better to keep __serialize on track with newly added fields. I would not bother with possibility that someone may call __serialize manually or capture console output to compare against a sample. Extending a map should be okay from backward-compatibility point of view. > > > > > > > > > > > tarantool> err.prev:unpack() > > > > > --- > > > > > - type: LuajitError > > > > > message: '[string "return function(tuple)..."]:2: attempt to call global ''require'' > > > > > (a nil value)' > > > > > ... <snipped> > > > > > > > > > > The second error does not have a traceback at all. > > > > (I added Turenko to To) > > > > I have two variants: > > > > - Leave as is and to document such behavior > > > > - Add the same traceback to all errors in the stack > > > > Alexander what do you think? > > > > The first approach look inconsistent. A user may want to get a cause of > > a topmost error and pass it somewhere. The function, where the error > > will be processed (say, serialized), don't know whether a traceback > > should be grabbed from some other error object (and how to find it). > > > Not quite, you either have a traceback or not. Don't try to get it from > another error. You propose to introduce some kind of 'full' and 'partial' errors. It is hard to document, because there is no rationale for this. When something is introduced, it should be for the sake of something. A kind of 'the API is complex, but, I see, it is highly flexible' or 'here I should take care of this peculiar, but OTOH some cases may be processed much faster due to this'. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error 2020-04-16 9:30 ` Alexander Turenko @ 2020-04-16 12:27 ` lvasiliev 2020-04-16 12:45 ` Alexander Turenko 0 siblings, 1 reply; 37+ messages in thread From: lvasiliev @ 2020-04-16 12:27 UTC (permalink / raw) To: Alexander Turenko; +Cc: tarantool-patches, Vladislav Shpilevoy On 16.04.2020 12:30, Alexander Turenko wrote: > On Thu, Apr 16, 2020 at 11:58:03AM +0300, lvasiliev wrote: >> Hi! Thanks for the feedback. >> >> On 16.04.2020 4:11, Alexander Turenko wrote: >>>>>> 2) What to do with stacked errors? Currently only the first >>>>>> error in the stack gets a traceback, because luaT_pusherror() is >>>>>> called only on the top error in the stack. Consider this test: >>>>>> >>>>>> box.cfg{} >>>>>> lua_code = [[function(tuple) >>>>>> local json = require('json') >>>>>> return json.encode(tuple) >>>>>> end]] >>>>>> box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true}) >>>>>> s = box.schema.space.create('withdata') >>>>>> pk = s:create_index('pk') >>>>>> idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}}) >>>>>> >>>>>> function test_func() return pcall(s.insert, s, {1}) end >>>>>> box.error.cfg{traceback_enable = true} >>>>>> ok, err = test_func() >>>>>> >>>>>> >>>>>> tarantool> err:unpack() >>>>>> --- >>>>>> - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string >>>>>> \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382: >>>>>> in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725: >>>>>> in function <builtin/box/console.lua:713>" >>>>>> ... <snipped> >>> >>> BTW, can we call :split('\n') for .traceback field in at least >>> __serialize? The cited output is hard to read. Alternative: place two >>> newlines in row somewhere to force yaml serializer to use multiline >>> string format. >> >> Traceback is absent in __serialize, because it will change the error view >> for old client. If the client matches result with some pattern it, will be >> broken. > > It is better to keep __serialize on track with newly added fields. I > would not bother with possibility that someone may call __serialize > manually or capture console output to compare against a sample. > Extending a map should be okay from backward-compatibility point of > view. > Now __serialize is used for transfer old style error too. >> >>>>>> >>>>>> tarantool> err.prev:unpack() >>>>>> --- >>>>>> - type: LuajitError >>>>>> message: '[string "return function(tuple)..."]:2: attempt to call global ''require'' >>>>>> (a nil value)' >>>>>> ... <snipped> >>>>>> >>>>>> The second error does not have a traceback at all. >>>>> (I added Turenko to To) >>>>> I have two variants: >>>>> - Leave as is and to document such behavior >>>>> - Add the same traceback to all errors in the stack >>>>> Alexander what do you think? >>> >>> The first approach look inconsistent. A user may want to get a cause of >>> a topmost error and pass it somewhere. The function, where the error >>> will be processed (say, serialized), don't know whether a traceback >>> should be grabbed from some other error object (and how to find it). >>> >> Not quite, you either have a traceback or not. Don't try to get it from >> another error. > > You propose to introduce some kind of 'full' and 'partial' errors. It is > hard to document, because there is no rationale for this. When something > is introduced, it should be for the sake of something. No, the error without traceback is not 'partial'. If global error_is_traceback_enabled is false - all errors haven't a traceback. If error creates with traceback=false, it hasn't a traceback. > > A kind of 'the API is complex, but, I see, it is highly flexible' or > 'here I should take care of this peculiar, but OTOH some cases may be > processed much faster due to this'. > You are hyperbolizing. It can be regarded as "Technical debt". If you insist, I can remove this patch. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error 2020-04-16 12:27 ` lvasiliev @ 2020-04-16 12:45 ` Alexander Turenko 2020-04-16 17:48 ` lvasiliev 0 siblings, 1 reply; 37+ messages in thread From: Alexander Turenko @ 2020-04-16 12:45 UTC (permalink / raw) To: lvasiliev; +Cc: tarantool-patches, Vladislav Shpilevoy On Thu, Apr 16, 2020 at 03:27:35PM +0300, lvasiliev wrote: > > > On 16.04.2020 12:30, Alexander Turenko wrote: > > On Thu, Apr 16, 2020 at 11:58:03AM +0300, lvasiliev wrote: > > > Hi! Thanks for the feedback. > > > > > > On 16.04.2020 4:11, Alexander Turenko wrote: > > > > > > > 2) What to do with stacked errors? Currently only the first > > > > > > > error in the stack gets a traceback, because luaT_pusherror() is > > > > > > > called only on the top error in the stack. Consider this test: > > > > > > > > > > > > > > box.cfg{} > > > > > > > lua_code = [[function(tuple) > > > > > > > local json = require('json') > > > > > > > return json.encode(tuple) > > > > > > > end]] > > > > > > > box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true}) > > > > > > > s = box.schema.space.create('withdata') > > > > > > > pk = s:create_index('pk') > > > > > > > idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}}) > > > > > > > > > > > > > > function test_func() return pcall(s.insert, s, {1}) end > > > > > > > box.error.cfg{traceback_enable = true} > > > > > > > ok, err = test_func() > > > > > > > > > > > > > > > > > > > > > tarantool> err:unpack() > > > > > > > --- > > > > > > > - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string > > > > > > > \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382: > > > > > > > in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725: > > > > > > > in function <builtin/box/console.lua:713>" > > > > > > > ... <snipped> > > > > > > > > BTW, can we call :split('\n') for .traceback field in at least > > > > __serialize? The cited output is hard to read. Alternative: place two > > > > newlines in row somewhere to force yaml serializer to use multiline > > > > string format. > > > > > > Traceback is absent in __serialize, because it will change the error view > > > for old client. If the client matches result with some pattern it, will be > > > broken. > > > > It is better to keep __serialize on track with newly added fields. I > > would not bother with possibility that someone may call __serialize > > manually or capture console output to compare against a sample. > > Extending a map should be okay from backward-compatibility point of > > view. > > > Now __serialize is used for transfer old style error too. As we discussed, a serializer should not see at __serialize of a type it knows natively. Let's keep __serialize for REPL only. > > > > > > > > > > > > > > > > > tarantool> err.prev:unpack() > > > > > > > --- > > > > > > > - type: LuajitError > > > > > > > message: '[string "return function(tuple)..."]:2: attempt to call global ''require'' > > > > > > > (a nil value)' > > > > > > > ... <snipped> > > > > > > > > > > > > > > The second error does not have a traceback at all. > > > > > > (I added Turenko to To) > > > > > > I have two variants: > > > > > > - Leave as is and to document such behavior > > > > > > - Add the same traceback to all errors in the stack > > > > > > Alexander what do you think? > > > > > > > > The first approach look inconsistent. A user may want to get a cause of > > > > a topmost error and pass it somewhere. The function, where the error > > > > will be processed (say, serialized), don't know whether a traceback > > > > should be grabbed from some other error object (and how to find it). > > > > > > > Not quite, you either have a traceback or not. Don't try to get it from > > > another error. > > > > You propose to introduce some kind of 'full' and 'partial' errors. It is > > hard to document, because there is no rationale for this. When something > > is introduced, it should be for the sake of something. > No, the error without traceback is not 'partial'. If > global error_is_traceback_enabled is false - all errors haven't a > traceback. If error creates with traceback=false, it hasn't a traceback. So we anyway need to docuemnt a rule when an error will contain the traceback and the way you implement it does not add much complexity. I can buy this point. > > > > A kind of 'the API is complex, but, I see, it is highly flexible' or > > 'here I should take care of this peculiar, but OTOH some cases may be > > processed much faster due to this'. > > > You are hyperbolizing. It can be regarded as "Technical debt". > If you insist, I can remove this patch. I didn't review the patchset, just looked at the question, so I cannot insist here. Personally I have questions around traceback (see [1]), so I would prefer safe way: concentrate on marshaling, postpone traceback. However I don't feel myself in power to decide. [1]: https://lists.tarantool.org/pipermail/tarantool-patches/2020-April/016043.html ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error 2020-04-16 12:45 ` Alexander Turenko @ 2020-04-16 17:48 ` lvasiliev 0 siblings, 0 replies; 37+ messages in thread From: lvasiliev @ 2020-04-16 17:48 UTC (permalink / raw) To: Alexander Turenko; +Cc: tarantool-patches, Vladislav Shpilevoy Hi! Thanks for the feedback. On 16.04.2020 15:45, Alexander Turenko wrote: > On Thu, Apr 16, 2020 at 03:27:35PM +0300, lvasiliev wrote: >> >> >> On 16.04.2020 12:30, Alexander Turenko wrote: >>> On Thu, Apr 16, 2020 at 11:58:03AM +0300, lvasiliev wrote: >>>> Hi! Thanks for the feedback. >>>> >>>> On 16.04.2020 4:11, Alexander Turenko wrote: >>>>>>>> 2) What to do with stacked errors? Currently only the first >>>>>>>> error in the stack gets a traceback, because luaT_pusherror() is >>>>>>>> called only on the top error in the stack. Consider this test: >>>>>>>> >>>>>>>> box.cfg{} >>>>>>>> lua_code = [[function(tuple) >>>>>>>> local json = require('json') >>>>>>>> return json.encode(tuple) >>>>>>>> end]] >>>>>>>> box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true}) >>>>>>>> s = box.schema.space.create('withdata') >>>>>>>> pk = s:create_index('pk') >>>>>>>> idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}}) >>>>>>>> >>>>>>>> function test_func() return pcall(s.insert, s, {1}) end >>>>>>>> box.error.cfg{traceback_enable = true} >>>>>>>> ok, err = test_func() >>>>>>>> >>>>>>>> >>>>>>>> tarantool> err:unpack() >>>>>>>> --- >>>>>>>> - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string >>>>>>>> \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382: >>>>>>>> in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725: >>>>>>>> in function <builtin/box/console.lua:713>" >>>>>>>> ... <snipped> >>>>> >>>>> BTW, can we call :split('\n') for .traceback field in at least >>>>> __serialize? The cited output is hard to read. Alternative: place two >>>>> newlines in row somewhere to force yaml serializer to use multiline >>>>> string format. >>>> >>>> Traceback is absent in __serialize, because it will change the error view >>>> for old client. If the client matches result with some pattern it, will be >>>> broken. >>> >>> It is better to keep __serialize on track with newly added fields. I >>> would not bother with possibility that someone may call __serialize >>> manually or capture console output to compare against a sample. >>> Extending a map should be okay from backward-compatibility point of >>> view. >>> >> Now __serialize is used for transfer old style error too. > > As we discussed, a serializer should not see at __serialize of a type it > knows natively. Let's keep __serialize for REPL only. > Discussed with Alexander. Will be moved to a separate task. Now we continue to use __serialize to transfer the error through net.box in the old style. >>>> >>>>>>>> >>>>>>>> tarantool> err.prev:unpack() >>>>>>>> --- >>>>>>>> - type: LuajitError >>>>>>>> message: '[string "return function(tuple)..."]:2: attempt to call global ''require'' >>>>>>>> (a nil value)' >>>>>>>> ... <snipped> >>>>>>>> >>>>>>>> The second error does not have a traceback at all. >>>>>>> (I added Turenko to To) >>>>>>> I have two variants: >>>>>>> - Leave as is and to document such behavior >>>>>>> - Add the same traceback to all errors in the stack >>>>>>> Alexander what do you think? >>>>> >>>>> The first approach look inconsistent. A user may want to get a cause of >>>>> a topmost error and pass it somewhere. The function, where the error >>>>> will be processed (say, serialized), don't know whether a traceback >>>>> should be grabbed from some other error object (and how to find it). >>>>> >>>> Not quite, you either have a traceback or not. Don't try to get it from >>>> another error. >>> >>> You propose to introduce some kind of 'full' and 'partial' errors. It is >>> hard to document, because there is no rationale for this. When something >>> is introduced, it should be for the sake of something. >> No, the error without traceback is not 'partial'. If >> global error_is_traceback_enabled is false - all errors haven't a >> traceback. If error creates with traceback=false, it hasn't a traceback. > > So we anyway need to docuemnt a rule when an error will contain the > traceback and the way you implement it does not add much complexity. > > I can buy this point. > >>> >>> A kind of 'the API is complex, but, I see, it is highly flexible' or >>> 'here I should take care of this peculiar, but OTOH some cases may be >>> processed much faster due to this'. >>> >> You are hyperbolizing. It can be regarded as "Technical debt". >> If you insist, I can remove this patch. > > I didn't review the patchset, just looked at the question, so I cannot > insist here. > > Personally I have questions around traceback (see [1]), so I would > prefer safe way: concentrate on marshaling, postpone traceback. > > However I don't feel myself in power to decide. > > [1]: https://lists.tarantool.org/pipermail/tarantool-patches/2020-April/016043.html > We omit traceback and implement it later (after discussion with Alexander Turenko). ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error 2020-04-16 0:00 ` Vladislav Shpilevoy 2020-04-16 1:11 ` Alexander Turenko @ 2020-04-16 8:40 ` lvasiliev 2020-04-16 9:04 ` lvasiliev 1 sibling, 1 reply; 37+ messages in thread From: lvasiliev @ 2020-04-16 8:40 UTC (permalink / raw) To: Vladislav Shpilevoy, Alexander Turenko; +Cc: tarantool-patches Hi! Thanks you for the feedback. On 16.04.2020 3:00, Vladislav Shpilevoy wrote: > Hi! Thanks for the fixes! > >> On 14.04.2020 4:11, Vladislav Shpilevoy wrote: >>> Thanks for the patch! >>> >>> Sorry, some of my comments may contain typos, because it is >>> very late and I can't continue polishing it today. >>> >>> First of all there are some major issues with the >>> patch, which IMO should be clarified before it is pushed. >>> >>> 1) Why do we need the traceback in scope of 4398? It has >>> nothing to do with marshaling through IProto, nor with >>> custom error types. >> This is part of the errors.lua functional, which was attached as an example. After that it was also one of the functionality requested by Nazarov. > > The original ticket does not contain any mentioning of a traceback: > https://github.com/tarantool/tarantool/issues/4398#issue-475632900 > > I agree that it was requested later, in comments, but still it > looks pretty separate from this, don't you agree? At the beginning of my work on the task I said that it has three separate tasks: 1) Add Custom type. 2) Add traceback. 3) Marshalling through net.box. But when I tried to send it for review separated it has not of any interest to reviewers, because the task didn't seem to be completed (as I understand). Or I could not correctly explain my point of view. So, I agree that this looks like one of the separate parts of the task. > >>> 2) What to do with stacked errors? Currently only the first >>> error in the stack gets a traceback, because luaT_pusherror() is >>> called only on the top error in the stack. Consider this test: >>> >>> box.cfg{} >>> lua_code = [[function(tuple) >>> local json = require('json') >>> return json.encode(tuple) >>> end]] >>> box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true}) >>> s = box.schema.space.create('withdata') >>> pk = s:create_index('pk') >>> idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}}) >>> >>> function test_func() return pcall(s.insert, s, {1}) end >>> box.error.cfg{traceback_enable = true} >>> ok, err = test_func() >>> >>> >>> tarantool> err:unpack() >>> --- >>> - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string >>> \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382: >>> in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725: >>> in function <builtin/box/console.lua:713>" >>> ... <snipped> >>> >>> tarantool> err.prev:unpack() >>> --- >>> - type: LuajitError >>> message: '[string "return function(tuple)..."]:2: attempt to call global ''require'' >>> (a nil value)' >>> ... <snipped> >>> >>> The second error does not have a traceback at all. >> (I added Turenko to To) >> I have two variants: >> - Leave as is and to document such behavior >> - Add the same traceback to all errors in the stack >> Alexander what do you think? > > There is a third option - leave traceback out of this patchset for > a next release. Because they are clearly underdesigned. But on the > other hand it is not something critical. After all, we just can say, > that a traceback can be present, or can be not, and its content is > just a string, which can't be assumed to have any special format. > > That would allow us to add/remove them and change their format anytime. If I understand the problem correctly, now we will not have an error traceback for some errors (in the case of the stack diagnostic), if we delete the traceback patch, it will not be at all. Perhaps, it is more user-friendly to leave a traceback for at least part of the errors. If string is the normal format for a traceback (and I think it is), then it seems to me good to leave it as is and finalize in the future with some users feedbacks. But, if string is a bad data format for traceback then patch must be deleted from the patchset. > >>> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc >>> index 4432bc89b..a616e87a4 100644 >>> --- a/src/box/lua/error.cc >>> +++ b/src/box/lua/error.cc >>> @@ -112,9 +112,11 @@ raise: >>> } >>> struct error *err = box_error_new(file, line, code, "%s", reason); >>> - if (tb_parsed) >>> - err->traceback_mode = tb_mode; >>> - >>> + /* >>> + * Explicit traceback option overrides the global setting. >>> + */ >>> + if (err != NULL && is_traceback_specified) >> 1) box_error_new don't return NULL > > True, then 'err != NULL' check can be dropped. > >>> diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c >>> index 1caa75ee9..833454bac 100644 >>> --- a/src/lib/core/diag.c >>> +++ b/src/lib/core/diag.c >>> @@ -128,27 +125,14 @@ error_vformat_msg(struct error *e, const char *format, va_list ap) >>> -void >>> -error_set_traceback_supplementation(bool traceback_mode) >>> +error_set_traceback(struct error *e, const char *traceback) >>> { >>> - global_traceback_mode = traceback_mode; >>> + assert(e->traceback == NULL); >> 2) Do I understand correctly that asserts only work on debug? Will this approach not be dangerous by memory leaks on release? > > It should not be. Because the method is not public, and > in private methods it is never called with non-zero e->traceback. Ok, I understand you point of view. > >>> + e->traceback = strdup(traceback); >>> + /* >>> + * Don't try to set it again. Traceback can be NULL in case of OOM, so >>> + * it is not a reliable source of information whether need to collect a >>> + * traceback. >>> + */ >>> + e->is_traceback_enabled = false; >>> } >>> diff --git a/src/lib/core/diag.h b/src/lib/core/diag.h >>> index f38009c54..e918d3089 100644 >>> --- a/src/lib/core/diag.h >>> +++ b/src/lib/core/diag.h >>> @@ -42,6 +42,13 @@ >>> extern "C" { >>> #endif /* defined(__cplusplus) */ >>> +/** >>> + * Flag to turn on/off traceback automatic collection. Currently >>> + * it works only for Lua. Stack is collected at the moment of >>> + * error object push onto the stack. >>> + */ >>> +extern bool error_is_traceback_enabled; >> 3) Why a global variable is more preferable than static? > > Static variables are also global. I just removed the setter, since > it is trivial. Hmm, ok. > >>> diff --git a/src/lua/error.lua b/src/lua/error.lua >>> index 46d28667f..535110588 100644 >>> --- a/src/lua/error.lua >>> +++ b/src/lua/error.lua >>> @@ -26,8 +26,8 @@ struct error { >>> char _errmsg[DIAG_ERRMSG_MAX]; >>> struct error *_cause; >>> struct error *_effect; >>> - char *lua_traceback; >>> - bool traceback_mode; >>> + char *traceback; >> 4) Replaced to err_traceback. It overlaps the traceback getter. >>> + bool is_traceback_enabled; > > Better replace it with _traceback. Similar to other members. Ok, I will replace it in the new version of the patchset if the traceback patch will be saved. But I can replace only traceback and traceback_mode(is_traceback_enabled), because other is "Unneccessary diff". > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error 2020-04-16 8:40 ` lvasiliev @ 2020-04-16 9:04 ` lvasiliev 0 siblings, 0 replies; 37+ messages in thread From: lvasiliev @ 2020-04-16 9:04 UTC (permalink / raw) To: Vladislav Shpilevoy, Alexander Turenko; +Cc: tarantool-patches Hi! On 16.04.2020 11:40, lvasiliev wrote: >> Better replace it with _traceback. Similar to other members. > Ok, I will replace it in the new version of the patchset if the > traceback patch will be saved. But I can replace only traceback and > traceback_mode(is_traceback_enabled), because other is "Unneccessary diff". Sorry, I was wrong. I will replace it in the new version of the patchset. ^ permalink raw reply [flat|nested] 37+ messages in thread
* [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type 2020-04-10 8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error Leonid Vasiliev @ 2020-04-10 8:10 ` Leonid Vasiliev 2020-04-14 1:11 ` Vladislav Shpilevoy 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO Leonid Vasiliev ` (3 subsequent siblings) 5 siblings, 1 reply; 37+ messages in thread From: Leonid Vasiliev @ 2020-04-10 8:10 UTC (permalink / raw) To: v.shpilevoy; +Cc: tarantool-patches A possibility to create an error with a custom subtype was added. In accordance with https://github.com/tarantool/tarantool/issues/4398 a custom error type has been added to the box.error. Now, it's possible to create an error with a custom subtype (string value) for use it in applications. @TarantoolBot document Title: error.custom_type A custom error type has been added to the box.error. Now, it's possible to create an error with a custom subtype (string value) for use it in applications. To API has been added: - For create CustomError: box.error.new(type(str), format, ...) box.error.new({type = str, reason = str}) - error.type - CustomType error return custom type other errors return "base" error tybe - base_type - return "base" errror type Example: err_custom = box.error.new("My Custom Type", "Reason") Now: err_custom.type == "My Custom Type" err_custom.base_type == "CustomType" err_custom.message == "Reason" Needed for #4398 --- src/box/errcode.h | 1 + src/box/error.cc | 59 +++++++++++++++++ src/box/error.h | 38 ++++++++++- src/box/lua/error.cc | 147 ++++++++++++++++++++++++++++++------------ src/lua/error.lua | 11 ++++ test/app/fiber.result | 5 +- test/box/error.result | 10 +-- test/engine/func_index.result | 18 +++--- 8 files changed, 234 insertions(+), 55 deletions(-) diff --git a/src/box/errcode.h b/src/box/errcode.h index 4441717..9f5f993 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -265,6 +265,7 @@ struct errcode_record { /*210 */_(ER_SQL_PREPARE, "Failed to prepare SQL statement: %s") \ /*211 */_(ER_WRONG_QUERY_ID, "Prepared statement with id %u does not exist") \ /*212 */_(ER_SEQUENCE_NOT_STARTED, "Sequence '%s' is not started") \ + /*213 */_(ER_CUSTOM_ERROR, "%s") \ /* * !IMPORTANT! Please follow instructions at start of the file diff --git a/src/box/error.cc b/src/box/error.cc index 233b312..8179e52 100644 --- a/src/box/error.cc +++ b/src/box/error.cc @@ -125,6 +125,31 @@ box_error_add(const char *file, unsigned line, uint32_t code, /* }}} */ +const char * +box_custom_error_type(const box_error_t *e) +{ + CustomError *custom_error = type_cast(CustomError, e); + if (custom_error) + return custom_error->custom_type(); + + return NULL; +} + +struct error * +box_custom_error_new(const char *file, unsigned line, + const char *custom, const char *fmt, ...) +{ + struct error *e = BuildCustomError(file, line, custom); + CustomError *custom_error = type_cast(CustomError, e); + if (custom_error != NULL) { + va_list ap; + va_start(ap, fmt); + error_vformat_msg(e, fmt, ap); + va_end(ap); + } + return e; +} + struct rmean *rmean_error = NULL; const char *rmean_error_strings[RMEAN_ERROR_LAST] = { @@ -290,3 +315,37 @@ BuildAccessDeniedError(const char *file, unsigned int line, return e; } } + +static struct method_info customerror_methods[] = { + make_method(&type_CustomError, "custom_type", &CustomError::custom_type), + METHODS_SENTINEL +}; + +const struct type_info type_CustomError = + make_type("CustomError", &type_ClientError, + customerror_methods); + +CustomError::CustomError(const char *file, unsigned int line, + const char *custom_type) + :ClientError(&type_CustomError, file, line, ER_CUSTOM_ERROR) +{ + error_format_msg(this, tnt_errcode_desc(m_errcode), + custom_type ?: ""); + + if (custom_type) { + strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1); + m_custom_type[sizeof(m_custom_type) - 1] = '\0'; + } else { + m_custom_type[0] = '\0'; + } +} + +struct error * +BuildCustomError(const char *file, unsigned int line, const char *custom_type) +{ + try { + return new CustomError(file, line, custom_type); + } catch (OutOfMemory *e) { + return e; + } +} diff --git a/src/box/error.h b/src/box/error.h index ca5d5b2..5013488 100644 --- a/src/box/error.h +++ b/src/box/error.h @@ -53,6 +53,9 @@ struct error * BuildXlogGapError(const char *file, unsigned line, const struct vclock *from, const struct vclock *to); +struct error * +BuildCustomError(const char *file, unsigned int line, const char *custom_type); + /** \cond public */ struct error; @@ -138,6 +141,14 @@ box_error_set(const char *file, unsigned line, uint32_t code, /** \endcond public */ /** + * Return the error custom type, + * \param error + * \return pointer to custom error type. On error, return NULL + */ +const char * +box_custom_error_type(const box_error_t *e); + +/** * Add error to the diagnostic area. In contrast to box_error_set() * it does not replace previous error being set, but rather link * them into list. @@ -155,15 +166,24 @@ box_error_add(const char *file, unsigned line, uint32_t code, /** * Construct error object without setting it in the diagnostics - * area. On the memory allocation fail returns NULL. + * area. On the memory allocation fail returns OutOfMemory error. */ struct error * box_error_new(const char *file, unsigned line, uint32_t code, const char *fmt, ...); +/** + * Construct error object without setting it in the diagnostics + * area. On the memory allocation fail returns OutOfMemory error. + */ +struct error * +box_custom_error_new(const char *file, unsigned line, + const char *custom, const char *fmt, ...); + extern const struct type_info type_ClientError; extern const struct type_info type_XlogError; extern const struct type_info type_AccessDeniedError; +extern const struct type_info type_CustomError; #if defined(__cplusplus) } /* extern "C" */ @@ -290,6 +310,22 @@ struct XlogGapError: public XlogError virtual void raise() { throw this; } }; +class CustomError: public ClientError +{ +public: + CustomError(const char *file, unsigned int line, + const char *custom_type); + + const char* + custom_type() + { + return m_custom_type; + } +private: + /** Custom type name */ + char m_custom_type[64]; +}; + #endif /* defined(__cplusplus) */ #endif /* TARANTOOL_BOX_ERROR_H_INCLUDED */ diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc index 4432bc8..1bcce1f 100644 --- a/src/box/lua/error.cc +++ b/src/box/lua/error.cc @@ -42,64 +42,101 @@ extern "C" { #include "lua/utils.h" #include "box/error.h" -/** - * Parse Lua arguments (they can come as single table - * f({code : number, reason : string}) or as separate members - * f(code, reason)) and construct struct error with given values. - * In case one of arguments is missing its corresponding field - * in struct error is filled with default value. - */ -static struct error * -luaT_error_create(lua_State *L, int top_base) +struct error_args { + uint32_t code; + const char *reason; + const char *custom; + bool tb_mode; + bool tb_parsed; +}; + +static int +luaT_error_parse_args(struct lua_State *L, int top_base, + struct error_args *args) { - uint32_t code = 0; - const char *reason = NULL; - bool tb_parsed = false; - bool tb_mode = false; - const char *file = ""; - unsigned line = 0; - lua_Debug info; int top = lua_gettop(L); - if (top >= top_base && lua_type(L, top_base) == LUA_TNUMBER) { - code = lua_tonumber(L, top_base); - reason = tnt_errcode_desc(code); + int top_type = lua_type(L, top_base); + if (top >= top_base && + (top_type == LUA_TNUMBER || top_type == LUA_TSTRING)) { + if (top_type == LUA_TNUMBER) { + args->code = lua_tonumber(L, top_base); + } else if (top_type == LUA_TSTRING) { + args->code = ER_CUSTOM_ERROR; + args->custom = lua_tostring(L, top_base); + } else { + return -1; + } + args->reason = tnt_errcode_desc(args->code); if (top > top_base) { /* Call string.format(reason, ...) to format message */ lua_getglobal(L, "string"); if (lua_isnil(L, -1)) - goto raise; + return 0; lua_getfield(L, -1, "format"); if (lua_isnil(L, -1)) - goto raise; - lua_pushstring(L, reason); + return 0; + lua_pushstring(L, args->reason); for (int i = top_base + 1; i <= top; i++) lua_pushvalue(L, i); lua_call(L, top - top_base + 1, 1); - reason = lua_tostring(L, -1); - } else if (strchr(reason, '%') != NULL) { + args->reason = lua_tostring(L, -1); + } else if (strchr(args->reason, '%') != NULL) { /* Missing arguments to format string */ - return NULL; + return -1; } - } else if (top == top_base && lua_istable(L, top_base)) { + } else if (top == top_base && top_type == LUA_TTABLE) { lua_getfield(L, top_base, "code"); - code = lua_tonumber(L, -1); + if (lua_isnil(L, -1) == 0) + args->code = lua_tonumber(L, -1); lua_pop(L, 1); lua_getfield(L, top_base, "reason"); - reason = lua_tostring(L, -1); - if (reason == NULL) - reason = ""; + if (lua_isnil(L, -1) == 0) + args->reason = lua_tostring(L, -1); + lua_pop(L, 1); + lua_getfield(L, top_base, "type"); + if (lua_isnil(L, -1) == 0) + args->custom = lua_tostring(L, -1); lua_pop(L, 1); lua_getfield(L, top_base, "traceback"); if (lua_isboolean(L, -1)) { - tb_parsed = true; - tb_mode = lua_toboolean(L, -1); + args->tb_parsed = true; + args->tb_mode = lua_toboolean(L, -1); } lua_pop(L, -1); } else { + return -1; + } + + return 0; +} + +/** + * Parse Lua arguments (they can come as single table + * f({code : number, reason : string}) or as separate members + * f(code, reason)) and construct struct error with given values. + * In case one of arguments is missing its corresponding field + * in struct error is filled with default value. + */ +static struct error * +luaT_error_create(lua_State *L, int top_base) +{ + struct error_args args; + args.code = UINT32_MAX; + args.reason = NULL; + args.custom = NULL; + args.tb_mode = false; + args.tb_parsed = false; + + if (luaT_error_parse_args(L, top_base, &args) != 0) { return NULL; } -raise: + if (args.reason == NULL) + args.reason = ""; + + const char *file = ""; + unsigned line = 0; + lua_Debug info; if (lua_getstack(L, 1, &info) && lua_getinfo(L, "Sl", &info)) { if (*info.short_src) { file = info.short_src; @@ -111,9 +148,16 @@ raise: line = info.currentline; } - struct error *err = box_error_new(file, line, code, "%s", reason); - if (tb_parsed) - err->traceback_mode = tb_mode; + struct error *err = NULL; + if (args.custom) { + err = box_custom_error_new(file, line, args.custom, + "%s", args.reason); + } else { + err = box_error_new(file, line, args.code, "%s", args.reason); + } + + if (args.tb_parsed) + err->traceback_mode = args.tb_mode; return err; } @@ -162,17 +206,36 @@ luaT_error_last(lua_State *L) static int luaT_error_new(lua_State *L) { - if (lua_gettop(L) == 0) - return luaL_error(L, "Usage: box.error.new(code, args)"); + if (lua_gettop(L) == 0) { + return luaL_error(L, "Usage: box.error.new(code, args) or "\ + "box.error.new(type, args)"); + } struct error *e = luaT_error_create(L, 1); - if (e == NULL) - return luaL_error(L, "Usage: box.error.new(code, args)"); + if (e == NULL) { + return luaL_error(L, "Usage: box.error.new(code, args) or "\ + "box.error.new(type, args)"); + } lua_settop(L, 0); luaT_pusherror(L, e); return 1; } static int +luaT_error_custom_type(lua_State *L) +{ + struct error *e = luaL_checkerror(L, -1); + + const char *custom_type = box_custom_error_type(e); + if (custom_type == NULL) { + lua_pushfstring(L, "The error has't custom type"); + return 1; + } + + lua_pushstring(L, custom_type); + return 1; +} + +static int luaT_error_clear(lua_State *L) { if (lua_gettop(L) >= 1) @@ -328,6 +391,10 @@ box_lua_error_init(struct lua_State *L) { lua_pushcfunction(L, luaT_error_cfg); lua_setfield(L, -2, "cfg"); } + { + lua_pushcfunction(L, luaT_error_custom_type); + lua_setfield(L, -2, "custom_type"); + } lua_setfield(L, -2, "__index"); } lua_setmetatable(L, -2); diff --git a/src/lua/error.lua b/src/lua/error.lua index 46d2866..5b32607 100644 --- a/src/lua/error.lua +++ b/src/lua/error.lua @@ -78,6 +78,16 @@ local function reflection_get(err, method) end local function error_type(err) + local res + if ffi.string(err._type.name) == 'CustomError' then + res = box.error.custom_type(err) + else + res = ffi.string(err._type.name) + end + return res +end + +local function error_base_type(err) return ffi.string(err._type.name) end @@ -141,6 +151,7 @@ local error_fields = { ["errno"] = error_errno; ["prev"] = error_prev; ["traceback"] = error_traceback; + ["base_type"] = error_base_type } local function error_unpack(err) diff --git a/test/app/fiber.result b/test/app/fiber.result index fc6b3c9..00d915c 100644 --- a/test/app/fiber.result +++ b/test/app/fiber.result @@ -1040,11 +1040,12 @@ e:unpack(); --- - traceback: Traceback is absent code: 1 + base_type: ClientError + type: ClientError + message: Illegal parameters, oh my trace: - file: '[string "function err() box.error(box.error.ILLEGAL_PA..."]' line: 1 - type: ClientError - message: Illegal parameters, oh my ... flag = false; --- diff --git a/test/box/error.result b/test/box/error.result index 22788a1..611044b 100644 --- a/test/box/error.result +++ b/test/box/error.result @@ -36,11 +36,12 @@ e:unpack() | --- | - traceback: Traceback is absent | code: 1 + | base_type: ClientError + | type: ClientError + | message: Illegal parameters, bla bla | trace: | - file: '[C]' | line: 4294967295 - | type: ClientError - | message: Illegal parameters, bla bla | ... e.type | --- @@ -106,7 +107,7 @@ e | ... box.error.new() | --- - | - error: 'Usage: box.error.new(code, args)' + | - error: 'Usage: box.error.new(code, args) or box.error.new(type, args)' | ... -- @@ -431,6 +432,7 @@ t; | 210: box.error.SQL_PREPARE | 211: box.error.WRONG_QUERY_ID | 212: box.error.SEQUENCE_NOT_STARTED + | 213: box.error.CUSTOM_ERROR | ... test_run:cmd("setopt delimiter ''"); @@ -489,7 +491,7 @@ assert(box.error.last() == nil) -- box.error.new(err) | --- - | - error: 'Usage: box.error.new(code, args)' + | - error: 'Usage: box.error.new(code, args) or box.error.new(type, args)' | ... -- box.error() is supposed to re-throw last diagnostic error. diff --git a/test/engine/func_index.result b/test/engine/func_index.result index 28befca..aee3ca4 100644 --- a/test/engine/func_index.result +++ b/test/engine/func_index.result @@ -278,14 +278,15 @@ e:unpack() | --- | - traceback: Traceback is absent | code: 198 - | trace: - | - file: <filename> - | line: <line> + | base_type: ClientError | type: ClientError - | message: 'Failed to build a key for functional index ''idx'' of space ''withdata'': - | can''t evaluate function' | prev: '[string "return function(tuple) local ..."]:1: attempt to | call global ''require'' (a nil value)' + | message: 'Failed to build a key for functional index ''idx'' of space ''withdata'': + | can''t evaluate function' + | trace: + | - file: <filename> + | line: <line> | ... e = e.prev | --- @@ -293,12 +294,13 @@ e = e.prev e:unpack() | --- | - traceback: Traceback is absent - | trace: - | - file: <filename> - | line: <line> + | base_type: LuajitError | type: LuajitError | message: '[string "return function(tuple) local ..."]:1: attempt | to call global ''require'' (a nil value)' + | trace: + | - file: <filename> + | line: <line> | ... e = e.prev | --- -- 2.7.4 ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type Leonid Vasiliev @ 2020-04-14 1:11 ` Vladislav Shpilevoy 2020-04-15 9:25 ` lvasiliev 0 siblings, 1 reply; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-14 1:11 UTC (permalink / raw) To: Leonid Vasiliev; +Cc: tarantool-patches Thanks for the patch! Sorry, some of my comments may contain typos, because it is very late and I can't continue polishing it today. See 21 comments below, review fixes in the end of the email, and on a new branch in a separate commit. On 10/04/2020 10:10, Leonid Vasiliev wrote: > A possibility to create an error with a custom subtype was added. > > In accordance with https://github.com/tarantool/tarantool/issues/4398 > a custom error type has been added to the box.error. > Now, it's possible to create an error with a custom subtype (string value) > for use it in applications. > > @TarantoolBot document > Title: error.custom_type 1. custom_type field does not exist in any user visible API. Even after this patch. > A custom error type has been added to the box.error. > Now, it's possible to create an error with a custom subtype (string value) > for use it in applications. 2. When docbot duplicates commit message, you can omit the latter. > > To API has been added: > - For create CustomError: box.error.new(type(str), format, ...) > box.error.new({type = str, > reason = str}) > > - error.type - CustomType error return custom type > other errors return "base" error tybe > - base_type - return "base" errror type > Example: > > err_custom = box.error.new("My Custom Type", "Reason") > Now: > err_custom.type == "My Custom Type" > err_custom.base_type == "CustomType" > err_custom.message == "Reason" 3. There is no a single word about name limit in 63 bytes. > > Needed for #4398 > --- > src/box/errcode.h | 1 + > src/box/error.cc | 59 +++++++++++++++++ > src/box/error.h | 38 ++++++++++- > src/box/lua/error.cc | 147 ++++++++++++++++++++++++++++++------------ > src/lua/error.lua | 11 ++++ > test/app/fiber.result | 5 +- > test/box/error.result | 10 +-- > test/engine/func_index.result | 18 +++--- > 8 files changed, 234 insertions(+), 55 deletions(-) > > diff --git a/src/box/error.cc b/src/box/error.cc > index 233b312..8179e52 100644 > --- a/src/box/error.cc > +++ b/src/box/error.cc > @@ -125,6 +125,31 @@ box_error_add(const char *file, unsigned line, uint32_t code, > > /* }}} */ > > +const char * > +box_custom_error_type(const box_error_t *e) 4. box_error_t is a type for the public API. This API is not public and therefore should use 'struct error' type name. 5. Naming policy for functions is that there should be a prefix with the object type name in all functions. Or at least the prefix should be the same on all methods of one type. Here the prefix should be box_error_*, so this method is box_error_custom_type(), and the method below is box_error_custom_new(). > +{ > + CustomError *custom_error = type_cast(CustomError, e); > + if (custom_error) > + return custom_error->custom_type(); > + > + return NULL; > +} > + > +struct error * > +box_custom_error_new(const char *file, unsigned line, > + const char *custom, const char *fmt, ...) > +{ > + struct error *e = BuildCustomError(file, line, custom); > + CustomError *custom_error = type_cast(CustomError, e); > + if (custom_error != NULL) { > + va_list ap; > + va_start(ap, fmt); > + error_vformat_msg(e, fmt, ap); > + va_end(ap); > + } > + return e; > +} > + > struct rmean *rmean_error = NULL; > > const char *rmean_error_strings[RMEAN_ERROR_LAST] = { > @@ -290,3 +315,37 @@ BuildAccessDeniedError(const char *file, unsigned int line, > return e; > } > } > + > +static struct method_info customerror_methods[] = { > + make_method(&type_CustomError, "custom_type", &CustomError::custom_type), > + METHODS_SENTINEL > +}; > + > +const struct type_info type_CustomError = > + make_type("CustomError", &type_ClientError, > + customerror_methods); 6. Make_type() perfectly fits in one line. > + > +CustomError::CustomError(const char *file, unsigned int line, > + const char *custom_type) > + :ClientError(&type_CustomError, file, line, ER_CUSTOM_ERROR) > +{ > + error_format_msg(this, tnt_errcode_desc(m_errcode), > + custom_type ?: ""); 7. custom_type is never NULL. > + > + if (custom_type) { > + strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1); > + m_custom_type[sizeof(m_custom_type) - 1] = '\0'; > + } else { > + m_custom_type[0] = '\0'; > + } > +} > + > +struct error * > +BuildCustomError(const char *file, unsigned int line, const char *custom_type) > +{ > + try { > + return new CustomError(file, line, custom_type); > + } catch (OutOfMemory *e) { > + return e; > + } > +} > diff --git a/src/box/error.h b/src/box/error.h > index ca5d5b2..5013488 100644 > --- a/src/box/error.h > +++ b/src/box/error.h > @@ -53,6 +53,9 @@ struct error * > BuildXlogGapError(const char *file, unsigned line, > const struct vclock *from, const struct vclock *to); > > +struct error * > +BuildCustomError(const char *file, unsigned int line, const char *custom_type); > + > /** \cond public */ > > struct error; > @@ -138,6 +141,14 @@ box_error_set(const char *file, unsigned line, uint32_t code, > /** \endcond public */ > > /** > + * Return the error custom type, > + * \param error 8. \param or @param takes two arguments - parameter name and its description. You provided only name, and the name does not match the actual parameter, which is 'e'. > + * \return pointer to custom error type. On error, return NULL 9. Please, try to start sentences from a capital letter, and finish them with a dot. > + */ > +const char * > +box_custom_error_type(const box_error_t *e); > + > +/** > * Add error to the diagnostic area. In contrast to box_error_set() > * it does not replace previous error being set, but rather link > * them into list. > @@ -155,15 +166,24 @@ box_error_add(const char *file, unsigned line, uint32_t code, > > /** > * Construct error object without setting it in the diagnostics > - * area. On the memory allocation fail returns NULL. > + * area. On the memory allocation fail returns OutOfMemory error. 10. Unneccessary diff. > */ > struct error * > box_error_new(const char *file, unsigned line, uint32_t code, > const char *fmt, ...); > > +/** > + * Construct error object without setting it in the diagnostics > + * area. On the memory allocation fail returns OutOfMemory error. 11. This is a copy-paste box box_error_new() comment. Better write here a couple of words how is it different. Or better merge box_error_new() and box_custom_error_new(). Because in Lua I can specify both code and type. Don't see why can't I do this in C. Honestly, I think we should use kind of struct error_args like you tried to do for lua/error.cc for the C API. Because with more arguments it will be hard to maintain them all as function paramters. But not now. > + */ > +struct error * > +box_custom_error_new(const char *file, unsigned line, > + const char *custom, const char *fmt, ...); > + > extern const struct type_info type_ClientError; > extern const struct type_info type_XlogError; > extern const struct type_info type_AccessDeniedError; > +extern const struct type_info type_CustomError; > > #if defined(__cplusplus) > } /* extern "C" */ > @@ -290,6 +310,22 @@ struct XlogGapError: public XlogError > virtual void raise() { throw this; } > }; > > +class CustomError: public ClientError > +{ > +public: > + CustomError(const char *file, unsigned int line, > + const char *custom_type); > + > + const char* > + custom_type() > + { > + return m_custom_type; > + } > +private: > + /** Custom type name */ > + char m_custom_type[64]; > +}; > + > #endif /* defined(__cplusplus) */ > > #endif /* TARANTOOL_BOX_ERROR_H_INCLUDED */ > diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc > index 4432bc8..1bcce1f 100644 > --- a/src/box/lua/error.cc > +++ b/src/box/lua/error.cc > @@ -42,64 +42,101 @@ extern "C" { > #include "lua/utils.h" > #include "box/error.h" > > -/** > - * Parse Lua arguments (they can come as single table > - * f({code : number, reason : string}) or as separate members > - * f(code, reason)) and construct struct error with given values. > - * In case one of arguments is missing its corresponding field > - * in struct error is filled with default value. > - */ > -static struct error * 12. Most of changes in this file does not really realate to the feature you are trying to introduce. It is just refactoring. If you want it, it should be done in a separate commit, because otherwise it is hard to see which changes are functional and need more attention. > -luaT_error_create(lua_State *L, int top_base) > +struct error_args { > + uint32_t code; > + const char *reason; > + const char *custom; > + bool tb_mode; > + bool tb_parsed; 13. All structures and their attributes usually have a comment explaining their purpose. In this structure it is hard to understand what is 'tb', what is 'tb_mode', what is 'tb_parsed', if you are not in the context of the patch. > +}; > + > +static int > +luaT_error_parse_args(struct lua_State *L, int top_base, > + struct error_args *args) > { > - uint32_t code = 0; > - const char *reason = NULL; > - bool tb_parsed = false; > - bool tb_mode = false; > - const char *file = ""; > - unsigned line = 0; > - lua_Debug info; > int top = lua_gettop(L); > - if (top >= top_base && lua_type(L, top_base) == LUA_TNUMBER) { > - code = lua_tonumber(L, top_base); > - reason = tnt_errcode_desc(code); > + int top_type = lua_type(L, top_base); > + if (top >= top_base && > + (top_type == LUA_TNUMBER || top_type == LUA_TSTRING)) { > + if (top_type == LUA_TNUMBER) { > + args->code = lua_tonumber(L, top_base); > + } else if (top_type == LUA_TSTRING) { > + args->code = ER_CUSTOM_ERROR; > + args->custom = lua_tostring(L, top_base); > + } else { > + return -1; > + } > + args->reason = tnt_errcode_desc(args->code); > if (top > top_base) { > /* Call string.format(reason, ...) to format message */ > lua_getglobal(L, "string"); > if (lua_isnil(L, -1)) > - goto raise; > + return 0; > lua_getfield(L, -1, "format"); > if (lua_isnil(L, -1)) > - goto raise; > - lua_pushstring(L, reason); > + return 0; > + lua_pushstring(L, args->reason); > for (int i = top_base + 1; i <= top; i++) > lua_pushvalue(L, i); > lua_call(L, top - top_base + 1, 1); > - reason = lua_tostring(L, -1); > - } else if (strchr(reason, '%') != NULL) { > + args->reason = lua_tostring(L, -1); > + } else if (strchr(args->reason, '%') != NULL) { > /* Missing arguments to format string */ > - return NULL; > + return -1; > } > - } else if (top == top_base && lua_istable(L, top_base)) { > + } else if (top == top_base && top_type == LUA_TTABLE) { > lua_getfield(L, top_base, "code"); > - code = lua_tonumber(L, -1); > + if (lua_isnil(L, -1) == 0) > + args->code = lua_tonumber(L, -1); 14. I reverted this change and didn't get any problems in the tests. Why? > lua_pop(L, 1); > lua_getfield(L, top_base, "reason"); > - reason = lua_tostring(L, -1); > - if (reason == NULL) > - reason = ""; > + if (lua_isnil(L, -1) == 0) > + args->reason = lua_tostring(L, -1); 15. Previously it was not checked for nil, and all worked fine. Why did you change that? > + lua_pop(L, 1); > + lua_getfield(L, top_base, "type"); > + if (lua_isnil(L, -1) == 0) > + args->custom = lua_tostring(L, -1); > lua_pop(L, 1); > lua_getfield(L, top_base, "traceback"); > if (lua_isboolean(L, -1)) { > - tb_parsed = true; > - tb_mode = lua_toboolean(L, -1); > + args->tb_parsed = true; > + args->tb_mode = lua_toboolean(L, -1); > } > lua_pop(L, -1); > } else { > + return -1; > + } > + > + return 0; > +} > + > +/** > + * Parse Lua arguments (they can come as single table > + * f({code : number, reason : string}) or as separate members > + * f(code, reason)) and construct struct error with given values. 16. The comment does not describe new syntax with 'type' argument. > + * In case one of arguments is missing its corresponding field > + * in struct error is filled with default value. > + */ > +static struct error * > +luaT_error_create(lua_State *L, int top_base) > +{ > + struct error_args args; > + args.code = UINT32_MAX; 17. Before your patch default error code was 0. Why is it changed to UINT32_MAX? > + args.reason = NULL; > + args.custom = NULL; > + args.tb_mode = false; > + args.tb_parsed = false; > + > + if (luaT_error_parse_args(L, top_base, &args) != 0) { > return NULL; > } > > -raise: > + if (args.reason == NULL) > + args.reason = ""; > + > + const char *file = ""; > + unsigned line = 0; > + lua_Debug info; > if (lua_getstack(L, 1, &info) && lua_getinfo(L, "Sl", &info)) { > if (*info.short_src) { > file = info.short_src; > @@ -111,9 +148,16 @@ raise: > line = info.currentline; > } > > - struct error *err = box_error_new(file, line, code, "%s", reason); > - if (tb_parsed) > - err->traceback_mode = tb_mode; > + struct error *err = NULL; > + if (args.custom) { > + err = box_custom_error_new(file, line, args.custom, > + "%s", args.reason); 18. Code argument is ignored here. Although I am not sure it shouldn't be ignored. From what I remember, we decided that code should stay, but I may be wrong. > + } else { > + err = box_error_new(file, line, args.code, "%s", args.reason); > + } > + > + if (args.tb_parsed) > + err->traceback_mode = args.tb_mode; > > return err; > } > @@ -162,17 +206,36 @@ luaT_error_last(lua_State *L) > static int > luaT_error_new(lua_State *L) > { > - if (lua_gettop(L) == 0) > - return luaL_error(L, "Usage: box.error.new(code, args)"); > + if (lua_gettop(L) == 0) { > + return luaL_error(L, "Usage: box.error.new(code, args) or "\ > + "box.error.new(type, args)"); > + } > struct error *e = luaT_error_create(L, 1); > - if (e == NULL) > - return luaL_error(L, "Usage: box.error.new(code, args)"); > + if (e == NULL) { > + return luaL_error(L, "Usage: box.error.new(code, args) or "\ > + "box.error.new(type, args)"); > + } > lua_settop(L, 0); > luaT_pusherror(L, e); > return 1; > } > > static int > +luaT_error_custom_type(lua_State *L) 19. This method is not needed. We don't have 'global' getters like box.error.prev, or box.error.type, and so on. Every error object provides API to get its members without calling global functions. > +{ > + struct error *e = luaL_checkerror(L, -1); > + > + const char *custom_type = box_custom_error_type(e); > + if (custom_type == NULL) { > + lua_pushfstring(L, "The error has't custom type"); > + return 1; > + } > + > + lua_pushstring(L, custom_type); > + return 1; > +} > + > +static int > luaT_error_clear(lua_State *L) > { > if (lua_gettop(L) >= 1) > diff --git a/src/lua/error.lua b/src/lua/error.lua > index 46d2866..5b32607 100644 > --- a/src/lua/error.lua > +++ b/src/lua/error.lua > @@ -78,6 +78,16 @@ local function reflection_get(err, method) > end > > local function error_type(err) > + local res > + if ffi.string(err._type.name) == 'CustomError' then > + res = box.error.custom_type(err) > + else > + res = ffi.string(err._type.name) 20. ffi.string() is called on one string 2 times. But it is not free, it allocates a new string each time. > + end > + return res > +end 21. There is again not a single test. Consider my review fixes below and on a new branch in a separate commit. Note, it is not entirely separated from the original commit in a couple of places, since I had rebase conflicts. Branch is: lvasiliev/gh-4398-expose-error-module-4-review ==================== Review fixes New commit message proposal: error: add custom error type Part of #4398 @TarantoolBot document Title: Custom error types for Lua errors Errors can be created in 2 ways: `box.error.new()` and `box.error()`. Both used to take either `code, reason, <reason string args>` or `{code = code, reason = reason, ...}` arguments. Now in the first option instead of code a user can specify a string as its own error type. In the second option a user can specify both code and type. For example: ```Lua box.error('MyErrorType', 'Message') box.error({type = 'MyErrorType', code = 1024, reason = 'Message'}) ``` Or no-throw version: ```Lua box.error.new('MyErrorType', 'Message') box.error.new({type = 'MyErrorType', code = 1024, reason = 'Message'}) ``` When a custom type is specified, it is shown in `err.type` attribute. When it is not specified, `err.type` shows one of built-in errors such as 'ClientError', 'OurOfMemory', etc. Name length limit on the custom type is 63 bytes. All is longer is truncated. Original error type can be checked using `err.base_type` member, although normally it should not be used. For user-defined types base type is 'CustomError'. For example: ``` tarantool> e = box.error.new({type = 'MyErrorType', code = 1024, reason = 'Message'}) --- ... tarantool> e:unpack() --- - code: 1024 trace: - file: '[string "e = box.error.new({type = ''MyErrorType'', code..."]' line: 1 type: MyErrorType custom_type: MyErrorType message: Message base_type: CustomError ... ``` diff --git a/extra/exports b/extra/exports index ffd11145d..a5ebe0884 100644 --- a/extra/exports +++ b/extra/exports @@ -234,6 +234,7 @@ box_index_min box_index_max box_index_count box_error_type +box_error_custom_type box_error_code box_error_message box_error_last diff --git a/src/box/error.cc b/src/box/error.cc index 8179e5287..d854b86e9 100644 --- a/src/box/error.cc +++ b/src/box/error.cc @@ -86,35 +86,51 @@ box_error_set(const char *file, unsigned line, uint32_t code, return -1; } +static struct error * +box_error_new_va(const char *file, unsigned line, uint32_t code, + const char *custom_type, const char *fmt, va_list ap) +{ + if (custom_type == NULL) { + struct error *e = BuildClientError(file, line, ER_UNKNOWN); + ClientError *client_error = type_cast(ClientError, e); + if (client_error != NULL) { + client_error->m_errcode = code; + error_vformat_msg(e, fmt, ap); + } + return e; + } else { + struct error *e = BuildCustomError(file, line, custom_type); + CustomError *custom_error = type_cast(CustomError, e); + if (custom_error != NULL) { + custom_error->m_errcode = code; + error_vformat_msg(e, fmt, ap); + } + return e; + } +} + struct error * box_error_new(const char *file, unsigned line, uint32_t code, - const char *fmt, ...) + const char *custom_type, const char *fmt, ...) { - struct error *e = BuildClientError(file, line, ER_UNKNOWN); - ClientError *client_error = type_cast(ClientError, e); - if (client_error != NULL) { - client_error->m_errcode = code; - va_list ap; - va_start(ap, fmt); - error_vformat_msg(e, fmt, ap); - va_end(ap); - } + va_list ap; + va_start(ap, fmt); + struct error *e = box_error_new_va(file, line, code, custom_type, + fmt, ap); + va_end(ap); return e; } int box_error_add(const char *file, unsigned line, uint32_t code, - const char *fmt, ...) + const char *custom_type, const char *fmt, ...) { - struct error *e = BuildClientError(file, line, ER_UNKNOWN); - ClientError *client_error = type_cast(ClientError, e); - if (client_error) { - client_error->m_errcode = code; - va_list ap; - va_start(ap, fmt); - error_vformat_msg(e, fmt, ap); - va_end(ap); - } + va_list ap; + va_start(ap, fmt); + struct error *e = box_error_new_va(file, line, code, custom_type, + fmt, ap); + va_end(ap); + struct diag *d = &fiber()->diag; if (diag_is_empty(d)) diag_set_error(d, e); @@ -126,7 +142,7 @@ box_error_add(const char *file, unsigned line, uint32_t code, /* }}} */ const char * -box_custom_error_type(const box_error_t *e) +box_error_custom_type(const struct error *e) { CustomError *custom_error = type_cast(CustomError, e); if (custom_error) @@ -135,21 +151,6 @@ box_custom_error_type(const box_error_t *e) return NULL; } -struct error * -box_custom_error_new(const char *file, unsigned line, - const char *custom, const char *fmt, ...) -{ - struct error *e = BuildCustomError(file, line, custom); - CustomError *custom_error = type_cast(CustomError, e); - if (custom_error != NULL) { - va_list ap; - va_start(ap, fmt); - error_vformat_msg(e, fmt, ap); - va_end(ap); - } - return e; -} - struct rmean *rmean_error = NULL; const char *rmean_error_strings[RMEAN_ERROR_LAST] = { @@ -322,22 +323,15 @@ static struct method_info customerror_methods[] = { }; const struct type_info type_CustomError = - make_type("CustomError", &type_ClientError, - customerror_methods); + make_type("CustomError", &type_ClientError, customerror_methods); CustomError::CustomError(const char *file, unsigned int line, const char *custom_type) :ClientError(&type_CustomError, file, line, ER_CUSTOM_ERROR) { - error_format_msg(this, tnt_errcode_desc(m_errcode), - custom_type ?: ""); - - if (custom_type) { - strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1); - m_custom_type[sizeof(m_custom_type) - 1] = '\0'; - } else { - m_custom_type[0] = '\0'; - } + error_format_msg(this, tnt_errcode_desc(m_errcode), custom_type); + strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1); + m_custom_type[sizeof(m_custom_type) - 1] = '\0'; } struct error * diff --git a/src/box/error.h b/src/box/error.h index 501348885..540008f35 100644 --- a/src/box/error.h +++ b/src/box/error.h @@ -142,11 +142,11 @@ box_error_set(const char *file, unsigned line, uint32_t code, /** * Return the error custom type, - * \param error - * \return pointer to custom error type. On error, return NULL + * \param e Error object. + * \return Pointer to custom error type. On error return NULL. */ const char * -box_custom_error_type(const box_error_t *e); +box_error_custom_type(const struct error *e); /** * Add error to the diagnostic area. In contrast to box_error_set() @@ -154,6 +154,8 @@ box_custom_error_type(const box_error_t *e); * them into list. * * \param code IPROTO error code (enum \link box_error_code \endlink) + * \param custom_type User-defined error type which will be + * displayed instead of ClientError. * \param format (const char * ) - printf()-like format string * \param ... - format arguments * \returns -1 for convention use @@ -162,23 +164,15 @@ box_custom_error_type(const box_error_t *e); */ int box_error_add(const char *file, unsigned line, uint32_t code, - const char *fmt, ...); + const char *custom_type, const char *fmt, ...); /** * Construct error object without setting it in the diagnostics - * area. On the memory allocation fail returns OutOfMemory error. + * area. On the memory allocation fail returns NULL. */ struct error * box_error_new(const char *file, unsigned line, uint32_t code, - const char *fmt, ...); - -/** - * Construct error object without setting it in the diagnostics - * area. On the memory allocation fail returns OutOfMemory error. - */ -struct error * -box_custom_error_new(const char *file, unsigned line, - const char *custom, const char *fmt, ...); + const char *custom_type, const char *fmt, ...); extern const struct type_info type_ClientError; extern const struct type_info type_XlogError; diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc index dd7068a83..cf771935e 100644 --- a/src/box/lua/error.cc +++ b/src/box/lua/error.cc @@ -42,95 +42,83 @@ extern "C" { #include "lua/utils.h" #include "box/error.h" -struct error_args { - uint32_t code; - const char *reason; - const char *custom; - const char *traceback; -}; - -static int -luaT_error_parse_args(struct lua_State *L, int top_base, - struct error_args *args) +/** + * Parse Lua arguments (they can come as single table + * f({code : number, reason : string}) or as separate members + * f(code, reason)) and construct struct error with given values. + * + * Instead of 'code' it is possible to specify a string name of + * the error object's type: + * + * box.error(type, reason, ...) + * box.error({type = string, reason = string, ...}) + * + * In case one of arguments is missing its corresponding field + * in struct error is filled with default value. + */ +static struct error * +luaT_error_create(lua_State *L, int top_base) { + uint32_t code = 0; + const char *custom_type = NULL; + const char *reason = NULL; + bool is_traceback_enabled = false; + bool is_traceback_specified = false; + const char *file = ""; + unsigned line = 0; + lua_Debug info; int top = lua_gettop(L); int top_type = lua_type(L, top_base); - if (top >= top_base && - (top_type == LUA_TNUMBER || top_type == LUA_TSTRING)) { + if (top >= top_base && (top_type == LUA_TNUMBER || + top_type == LUA_TSTRING)) { if (top_type == LUA_TNUMBER) { - args->code = lua_tonumber(L, top_base); - } else if (top_type == LUA_TSTRING) { - args->code = ER_CUSTOM_ERROR; - args->custom = lua_tostring(L, top_base); + code = lua_tonumber(L, top_base); } else { - return -1; + code = ER_CUSTOM_ERROR; + custom_type = lua_tostring(L, top_base); } - args->reason = tnt_errcode_desc(args->code); + reason = tnt_errcode_desc(code); if (top > top_base) { /* Call string.format(reason, ...) to format message */ lua_getglobal(L, "string"); if (lua_isnil(L, -1)) - return 0; + goto raise; lua_getfield(L, -1, "format"); if (lua_isnil(L, -1)) - return 0; - lua_pushstring(L, args->reason); + goto raise; + lua_pushstring(L, reason); for (int i = top_base + 1; i <= top; i++) lua_pushvalue(L, i); lua_call(L, top - top_base + 1, 1); - args->reason = lua_tostring(L, -1); - } else if (strchr(args->reason, '%') != NULL) { + reason = lua_tostring(L, -1); + } else if (strchr(reason, '%') != NULL) { /* Missing arguments to format string */ - return -1; + return NULL; } } else if (top == top_base && top_type == LUA_TTABLE) { lua_getfield(L, top_base, "code"); - if (lua_isnil(L, -1) == 0) - args->code = lua_tonumber(L, -1); + if (!lua_isnil(L, -1)) + code = lua_tonumber(L, -1); + else + code = ER_CUSTOM_ERROR; lua_getfield(L, top_base, "reason"); - if (lua_isnil(L, -1) == 0) - args->reason = lua_tostring(L, -1); + reason = lua_tostring(L, -1); + if (reason == NULL) + reason = ""; lua_getfield(L, top_base, "type"); - if (lua_isnil(L, -1) == 0) - args->custom = lua_tostring(L, -1); + if (!lua_isnil(L, -1)) + custom_type = lua_tostring(L, -1); lua_getfield(L, top_base, "traceback"); - if (lua_isboolean(L, -1) && lua_toboolean(L, -1)) { - luaL_traceback(L, L, NULL, 0); - if (lua_isstring(L, -1)) - args->traceback = lua_tostring(L, -1); + if (lua_isboolean(L, -1)) { + is_traceback_enabled = lua_toboolean(L, -1); + is_traceback_specified = true; } lua_pop(L, 1); } else { - return -1; - } - return 0; -} - -/** - * Parse Lua arguments (they can come as single table - * f({code : number, reason : string}) or as separate members - * f(code, reason)) and construct struct error with given values. - * In case one of arguments is missing its corresponding field - * in struct error is filled with default value. - */ -static struct error * -luaT_error_create(lua_State *L, int top_base) -{ - struct error_args args; - args.code = UINT32_MAX; - args.reason = NULL; - args.custom = NULL; - args.traceback = NULL; - - if (luaT_error_parse_args(L, top_base, &args) != 0) return NULL; + } - if (args.reason == NULL) - args.reason = ""; - - const char *file = ""; - unsigned line = 0; - lua_Debug info; +raise: if (lua_getstack(L, 1, &info) && lua_getinfo(L, "Sl", &info)) { if (*info.short_src) { file = info.short_src; @@ -142,15 +130,13 @@ luaT_error_create(lua_State *L, int top_base) line = info.currentline; } - struct error *err; - if (args.custom) { - err = box_custom_error_new(file, line, args.custom, - "%s", args.reason); - } else { - err = box_error_new(file, line, args.code, "%s", args.reason); - } - if (args.traceback != NULL && err != NULL) - error_set_traceback(err, args.traceback); + struct error *err = box_error_new(file, line, code, custom_type, + "%s", reason); + /* + * Explicit traceback option overrides the global setting. + */ + if (err != NULL && is_traceback_specified) + err->is_traceback_enabled = is_traceback_enabled; return err; } @@ -198,35 +184,16 @@ luaT_error_last(lua_State *L) static int luaT_error_new(lua_State *L) { - if (lua_gettop(L) == 0) { - return luaL_error(L, "Usage: box.error.new(code, args) or "\ - "box.error.new(type, args)"); - } - struct error *e = luaT_error_create(L, 1); - if (e == NULL) { + struct error *e; + if (lua_gettop(L) == 0 || (e = luaT_error_create(L, 1)) == NULL) { return luaL_error(L, "Usage: box.error.new(code, args) or "\ - "box.error.new(type, args)"); + "box.error.new(type, args)"); } lua_settop(L, 0); luaT_pusherror(L, e); return 1; } -static int -luaT_error_custom_type(lua_State *L) -{ - struct error *e = luaL_checkerror(L, -1); - - const char *custom_type = box_custom_error_type(e); - if (custom_type == NULL) { - lua_pushfstring(L, "The error has't custom type"); - return 1; - } - - lua_pushstring(L, custom_type); - return 1; -} - static int luaT_error_clear(lua_State *L) { @@ -381,10 +348,6 @@ box_lua_error_init(struct lua_State *L) { lua_pushcfunction(L, luaT_error_cfg); lua_setfield(L, -2, "cfg"); } - { - lua_pushcfunction(L, luaT_error_custom_type); - lua_setfield(L, -2, "custom_type"); - } lua_setfield(L, -2, "__index"); } lua_setmetatable(L, -2); diff --git a/src/box/xrow.c b/src/box/xrow.c index a494d1f46..5494b41cd 100644 --- a/src/box/xrow.c +++ b/src/box/xrow.c @@ -1123,7 +1123,7 @@ iproto_decode_error_stack(const char **pos) continue; } } - box_error_add(__FILE__, __LINE__, code, reason); + box_error_add(__FILE__, __LINE__, code, NULL, reason); } return 0; } diff --git a/src/lua/error.lua b/src/lua/error.lua index 1f9ae9375..1d3413b19 100644 --- a/src/lua/error.lua +++ b/src/lua/error.lua @@ -37,6 +37,9 @@ exception_get_int(struct error *e, const struct method_info *method); int error_set_prev(struct error *e, struct error *prev); + +const char * +box_error_custom_type(const struct error *e); ]] local REFLECTION_CACHE = {} @@ -77,20 +80,18 @@ local function reflection_get(err, method) end end -local function error_type(err) - local res - if ffi.string(err._type.name) == 'CustomError' then - res = box.error.custom_type(err) - else - res = ffi.string(err._type.name) - end - return res -end - local function error_base_type(err) return ffi.string(err._type.name) end +local function error_type(err) + local res = ffi.C.box_error_custom_type(err) + if res ~= nil then + return ffi.string(res) + end + return error_base_type(err) +end + local function error_message(err) return ffi.string(err._errmsg) end @@ -148,7 +149,7 @@ local error_fields = { ["errno"] = error_errno; ["prev"] = error_prev; ["traceback"] = error_traceback; - ["base_type"] = error_base_type + ["base_type"] = error_base_type, } local function error_unpack(err) diff --git a/test/box/error.result b/test/box/error.result index 2b837e434..b717a4ff4 100644 --- a/test/box/error.result +++ b/test/box/error.result @@ -431,11 +431,8 @@ t; | 210: box.error.SQL_PREPARE | 211: box.error.WRONG_QUERY_ID | 212: box.error.SEQUENCE_NOT_STARTED -<<<<<<< HEAD | 213: box.error.NO_SUCH_SESSION_SETTING -======= - | 213: box.error.CUSTOM_ERROR ->>>>>>> error: Add the custom error type + | 214: box.error.CUSTOM_ERROR | ... test_run:cmd("setopt delimiter ''"); @@ -895,3 +892,60 @@ check_trace(e:unpack().traceback) | --- | - true | ... + +-- +-- gh-4398: custom error type. +-- +-- Try no code. +e = box.error.new({type = 'TestType', reason = 'Test reason'}) + | --- + | ... +e:unpack() + | --- + | - code: 214 + | trace: + | - file: '[string "e = box.error.new({type = ''TestType'', reason ..."]' + | line: 1 + | type: TestType + | custom_type: TestType + | message: Test reason + | base_type: CustomError + | ... +-- Try code not the same as used by default. +e = box.error.new({type = 'TestType', reason = 'Test reason', code = 123}) + | --- + | ... +e:unpack() + | --- + | - code: 123 + | trace: + | - file: '[string "e = box.error.new({type = ''TestType'', reason ..."]' + | line: 1 + | type: TestType + | custom_type: TestType + | message: Test reason + | base_type: CustomError + | ... +-- Try to omit message. +e = box.error.new({type = 'TestType'}) + | --- + | ... +e:unpack() + | --- + | - code: 214 + | trace: + | - file: '[string "e = box.error.new({type = ''TestType''}) "]' + | line: 1 + | type: TestType + | custom_type: TestType + | message: + | base_type: CustomError + | ... +-- Try too long type name. +e = box.error.new({type = string.rep('a', 128)}) + | --- + | ... +#e.type + | --- + | - 63 + | ... diff --git a/test/box/error.test.lua b/test/box/error.test.lua index 6f1271630..fe4051899 100644 --- a/test/box/error.test.lua +++ b/test/box/error.test.lua @@ -262,3 +262,19 @@ check_trace(t3(nil, false):unpack().traceback) box.error.cfg{traceback_enable = false} _, e = pcall(t3, true, true) check_trace(e:unpack().traceback) + +-- +-- gh-4398: custom error type. +-- +-- Try no code. +e = box.error.new({type = 'TestType', reason = 'Test reason'}) +e:unpack() +-- Try code not the same as used by default. +e = box.error.new({type = 'TestType', reason = 'Test reason', code = 123}) +e:unpack() +-- Try to omit message. +e = box.error.new({type = 'TestType'}) +e:unpack() +-- Try too long type name. +e = box.error.new({type = string.rep('a', 128)}) +#e.type ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type 2020-04-14 1:11 ` Vladislav Shpilevoy @ 2020-04-15 9:25 ` lvasiliev 2020-04-16 0:02 ` Vladislav Shpilevoy 0 siblings, 1 reply; 37+ messages in thread From: lvasiliev @ 2020-04-15 9:25 UTC (permalink / raw) To: Vladislav Shpilevoy; +Cc: tarantool-patches Hi! Thanks for the review. I applied your patch. Since I applied your patch, I did not write fixed on every comment. But I wrote a few questions where I did not understand your approach. On 14.04.2020 4:11, Vladislav Shpilevoy wrote: > Thanks for the patch! > > Sorry, some of my comments may contain typos, because it is > very late and I can't continue polishing it today. > > See 21 comments below, review fixes in the end of the email, > and on a new branch in a separate commit. > > On 10/04/2020 10:10, Leonid Vasiliev wrote: >> A possibility to create an error with a custom subtype was added. >> >> In accordance with https://github.com/tarantool/tarantool/issues/4398 >> a custom error type has been added to the box.error. >> Now, it's possible to create an error with a custom subtype (string value) >> for use it in applications. >> >> @TarantoolBot document >> Title: error.custom_type > > 1. custom_type field does not exist in any user visible API. Even > after this patch. > >> A custom error type has been added to the box.error. >> Now, it's possible to create an error with a custom subtype (string value) >> for use it in applications. > > 2. When docbot duplicates commit message, you can omit the latter. > >> >> To API has been added: >> - For create CustomError: box.error.new(type(str), format, ...) >> box.error.new({type = str, >> reason = str}) >> >> - error.type - CustomType error return custom type >> other errors return "base" error tybe >> - base_type - return "base" errror type >> Example: >> >> err_custom = box.error.new("My Custom Type", "Reason") >> Now: >> err_custom.type == "My Custom Type" >> err_custom.base_type == "CustomType" >> err_custom.message == "Reason" > > 3. There is no a single word about name limit in 63 bytes. > >> >> Needed for #4398 >> --- >> src/box/errcode.h | 1 + >> src/box/error.cc | 59 +++++++++++++++++ >> src/box/error.h | 38 ++++++++++- >> src/box/lua/error.cc | 147 ++++++++++++++++++++++++++++++------------ >> src/lua/error.lua | 11 ++++ >> test/app/fiber.result | 5 +- >> test/box/error.result | 10 +-- >> test/engine/func_index.result | 18 +++--- >> 8 files changed, 234 insertions(+), 55 deletions(-) >> >> diff --git a/src/box/error.cc b/src/box/error.cc >> index 233b312..8179e52 100644 >> --- a/src/box/error.cc >> +++ b/src/box/error.cc >> @@ -125,6 +125,31 @@ box_error_add(const char *file, unsigned line, uint32_t code, >> >> /* }}} */ >> >> +const char * >> +box_custom_error_type(const box_error_t *e) > > 4. box_error_t is a type for the public API. This API is not public and > therefore should use 'struct error' type name. > > 5. Naming policy for functions is that there should be a prefix > with the object type name in all functions. Or at least the prefix > should be the same on all methods of one type. > > Here the prefix should be box_error_*, so this method is > box_error_custom_type(), and the method below is box_error_custom_new(). > >> +{ >> + CustomError *custom_error = type_cast(CustomError, e); >> + if (custom_error) >> + return custom_error->custom_type(); >> + >> + return NULL; >> +} >> + >> +struct error * >> +box_custom_error_new(const char *file, unsigned line, >> + const char *custom, const char *fmt, ...) >> +{ >> + struct error *e = BuildCustomError(file, line, custom); >> + CustomError *custom_error = type_cast(CustomError, e); >> + if (custom_error != NULL) { >> + va_list ap; >> + va_start(ap, fmt); >> + error_vformat_msg(e, fmt, ap); >> + va_end(ap); >> + } >> + return e; >> +} >> + >> struct rmean *rmean_error = NULL; >> >> const char *rmean_error_strings[RMEAN_ERROR_LAST] = { >> @@ -290,3 +315,37 @@ BuildAccessDeniedError(const char *file, unsigned int line, >> return e; >> } >> } >> + >> +static struct method_info customerror_methods[] = { >> + make_method(&type_CustomError, "custom_type", &CustomError::custom_type), >> + METHODS_SENTINEL >> +}; >> + >> +const struct type_info type_CustomError = >> + make_type("CustomError", &type_ClientError, >> + customerror_methods); > > 6. Make_type() perfectly fits in one line. > >> + >> +CustomError::CustomError(const char *file, unsigned int line, >> + const char *custom_type) >> + :ClientError(&type_CustomError, file, line, ER_CUSTOM_ERROR) >> +{ >> + error_format_msg(this, tnt_errcode_desc(m_errcode), >> + custom_type ?: ""); > > 7. custom_type is never NULL. Why? It's a constructor argument. > >> + >> + if (custom_type) { >> + strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1); >> + m_custom_type[sizeof(m_custom_type) - 1] = '\0'; >> + } else { >> + m_custom_type[0] = '\0'; >> + } >> +} >> + >> +struct error * >> +BuildCustomError(const char *file, unsigned int line, const char *custom_type) >> +{ >> + try { >> + return new CustomError(file, line, custom_type); >> + } catch (OutOfMemory *e) { >> + return e; >> + } >> +} >> diff --git a/src/box/error.h b/src/box/error.h >> index ca5d5b2..5013488 100644 >> --- a/src/box/error.h >> +++ b/src/box/error.h >> @@ -53,6 +53,9 @@ struct error * >> BuildXlogGapError(const char *file, unsigned line, >> const struct vclock *from, const struct vclock *to); >> >> +struct error * >> +BuildCustomError(const char *file, unsigned int line, const char *custom_type); >> + >> /** \cond public */ >> >> struct error; >> @@ -138,6 +141,14 @@ box_error_set(const char *file, unsigned line, uint32_t code, >> /** \endcond public */ >> >> /** >> + * Return the error custom type, >> + * \param error > > 8. \param or @param takes two arguments - parameter name and its > description. You provided only name, and the name does not match > the actual parameter, which is 'e'. > >> + * \return pointer to custom error type. On error, return NULL > > 9. Please, try to start sentences from a capital letter, and finish > them with a dot. > >> + */ >> +const char * >> +box_custom_error_type(const box_error_t *e); >> + >> +/** >> * Add error to the diagnostic area. In contrast to box_error_set() >> * it does not replace previous error being set, but rather link >> * them into list. >> @@ -155,15 +166,24 @@ box_error_add(const char *file, unsigned line, uint32_t code, >> >> /** >> * Construct error object without setting it in the diagnostics >> - * area. On the memory allocation fail returns NULL. >> + * area. On the memory allocation fail returns OutOfMemory error. > > 10. Unneccessary diff. The comment is wrong. How I can fix it? > >> */ >> struct error * >> box_error_new(const char *file, unsigned line, uint32_t code, >> const char *fmt, ...); >> >> +/** >> + * Construct error object without setting it in the diagnostics >> + * area. On the memory allocation fail returns OutOfMemory error. > > 11. This is a copy-paste box box_error_new() comment. Better write > here a couple of words how is it different. Or better merge box_error_new() > and box_custom_error_new(). Because in Lua I can specify both code > and type. Don't see why can't I do this in C. I thought we can’t change public API. > > Honestly, I think we should use kind of struct error_args like you > tried to do for lua/error.cc for the C API. Because with more > arguments it will be hard to maintain them all as function paramters. > But not now. > >> + */ >> +struct error * >> +box_custom_error_new(const char *file, unsigned line, >> + const char *custom, const char *fmt, ...); >> + >> extern const struct type_info type_ClientError; >> extern const struct type_info type_XlogError; >> extern const struct type_info type_AccessDeniedError; >> +extern const struct type_info type_CustomError; >> >> #if defined(__cplusplus) >> } /* extern "C" */ >> @@ -290,6 +310,22 @@ struct XlogGapError: public XlogError >> virtual void raise() { throw this; } >> }; >> >> +class CustomError: public ClientError >> +{ >> +public: >> + CustomError(const char *file, unsigned int line, >> + const char *custom_type); >> + >> + const char* >> + custom_type() >> + { >> + return m_custom_type; >> + } >> +private: >> + /** Custom type name */ >> + char m_custom_type[64]; >> +}; >> + >> #endif /* defined(__cplusplus) */ >> >> #endif /* TARANTOOL_BOX_ERROR_H_INCLUDED */ >> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc >> index 4432bc8..1bcce1f 100644 >> --- a/src/box/lua/error.cc >> +++ b/src/box/lua/error.cc >> @@ -42,64 +42,101 @@ extern "C" { >> #include "lua/utils.h" >> #include "box/error.h" >> >> -/** >> - * Parse Lua arguments (they can come as single table >> - * f({code : number, reason : string}) or as separate members >> - * f(code, reason)) and construct struct error with given values. >> - * In case one of arguments is missing its corresponding field >> - * in struct error is filled with default value. >> - */ >> -static struct error * > > 12. Most of changes in this file does not really realate to the > feature you are trying to introduce. It is just refactoring. If > you want it, it should be done in a separate commit, because > otherwise it is hard to see which changes are functional and need > more attention. Discarded > >> -luaT_error_create(lua_State *L, int top_base) >> +struct error_args { >> + uint32_t code; >> + const char *reason; >> + const char *custom; >> + bool tb_mode; >> + bool tb_parsed; > > 13. All structures and their attributes usually have a comment > explaining their purpose. In this structure it is hard to > understand what is 'tb', what is 'tb_mode', what is 'tb_parsed', > if you are not in the context of the patch. > >> +}; >> + >> +static int >> +luaT_error_parse_args(struct lua_State *L, int top_base, >> + struct error_args *args) >> { >> - uint32_t code = 0; >> - const char *reason = NULL; >> - bool tb_parsed = false; >> - bool tb_mode = false; >> - const char *file = ""; >> - unsigned line = 0; >> - lua_Debug info; >> int top = lua_gettop(L); >> - if (top >= top_base && lua_type(L, top_base) == LUA_TNUMBER) { >> - code = lua_tonumber(L, top_base); >> - reason = tnt_errcode_desc(code); >> + int top_type = lua_type(L, top_base); >> + if (top >= top_base && >> + (top_type == LUA_TNUMBER || top_type == LUA_TSTRING)) { >> + if (top_type == LUA_TNUMBER) { >> + args->code = lua_tonumber(L, top_base); >> + } else if (top_type == LUA_TSTRING) { >> + args->code = ER_CUSTOM_ERROR; >> + args->custom = lua_tostring(L, top_base); >> + } else { >> + return -1; >> + } >> + args->reason = tnt_errcode_desc(args->code); >> if (top > top_base) { >> /* Call string.format(reason, ...) to format message */ >> lua_getglobal(L, "string"); >> if (lua_isnil(L, -1)) >> - goto raise; >> + return 0; >> lua_getfield(L, -1, "format"); >> if (lua_isnil(L, -1)) >> - goto raise; >> - lua_pushstring(L, reason); >> + return 0; >> + lua_pushstring(L, args->reason); >> for (int i = top_base + 1; i <= top; i++) >> lua_pushvalue(L, i); >> lua_call(L, top - top_base + 1, 1); >> - reason = lua_tostring(L, -1); >> - } else if (strchr(reason, '%') != NULL) { >> + args->reason = lua_tostring(L, -1); >> + } else if (strchr(args->reason, '%') != NULL) { >> /* Missing arguments to format string */ >> - return NULL; >> + return -1; >> } >> - } else if (top == top_base && lua_istable(L, top_base)) { >> + } else if (top == top_base && top_type == LUA_TTABLE) { >> lua_getfield(L, top_base, "code"); >> - code = lua_tonumber(L, -1); >> + if (lua_isnil(L, -1) == 0) >> + args->code = lua_tonumber(L, -1); > > 14. I reverted this change and didn't get any problems > in the tests. Why? > >> lua_pop(L, 1); >> lua_getfield(L, top_base, "reason"); >> - reason = lua_tostring(L, -1); >> - if (reason == NULL) >> - reason = ""; >> + if (lua_isnil(L, -1) == 0) >> + args->reason = lua_tostring(L, -1); > > 15. Previously it was not checked for nil, and all worked fine. > Why did you change that? > >> + lua_pop(L, 1); >> + lua_getfield(L, top_base, "type"); >> + if (lua_isnil(L, -1) == 0) >> + args->custom = lua_tostring(L, -1); >> lua_pop(L, 1); >> lua_getfield(L, top_base, "traceback"); >> if (lua_isboolean(L, -1)) { >> - tb_parsed = true; >> - tb_mode = lua_toboolean(L, -1); >> + args->tb_parsed = true; >> + args->tb_mode = lua_toboolean(L, -1); >> } >> lua_pop(L, -1); >> } else { >> + return -1; >> + } >> + >> + return 0; >> +} >> + >> +/** >> + * Parse Lua arguments (they can come as single table >> + * f({code : number, reason : string}) or as separate members >> + * f(code, reason)) and construct struct error with given values. > > 16. The comment does not describe new syntax with 'type' argument. > >> + * In case one of arguments is missing its corresponding field >> + * in struct error is filled with default value. >> + */ >> +static struct error * >> +luaT_error_create(lua_State *L, int top_base) >> +{ >> + struct error_args args; >> + args.code = UINT32_MAX; > > 17. Before your patch default error code was 0. Why is it > changed to UINT32_MAX? > >> + args.reason = NULL; >> + args.custom = NULL; >> + args.tb_mode = false; >> + args.tb_parsed = false; >> + >> + if (luaT_error_parse_args(L, top_base, &args) != 0) { >> return NULL; >> } >> >> -raise: >> + if (args.reason == NULL) >> + args.reason = ""; >> + >> + const char *file = ""; >> + unsigned line = 0; >> + lua_Debug info; >> if (lua_getstack(L, 1, &info) && lua_getinfo(L, "Sl", &info)) { >> if (*info.short_src) { >> file = info.short_src; >> @@ -111,9 +148,16 @@ raise: >> line = info.currentline; >> } >> >> - struct error *err = box_error_new(file, line, code, "%s", reason); >> - if (tb_parsed) >> - err->traceback_mode = tb_mode; >> + struct error *err = NULL; >> + if (args.custom) { >> + err = box_custom_error_new(file, line, args.custom, >> + "%s", args.reason); > > 18. Code argument is ignored here. Although I am not sure it shouldn't > be ignored. From what I remember, we decided that code should stay, > but I may be wrong. CustomError type has a predefinded code. Why it shouldn't be ignored? > >> + } else { >> + err = box_error_new(file, line, args.code, "%s", args.reason); >> + } >> + >> + if (args.tb_parsed) >> + err->traceback_mode = args.tb_mode; >> >> return err; >> } >> @@ -162,17 +206,36 @@ luaT_error_last(lua_State *L) >> static int >> luaT_error_new(lua_State *L) >> { >> - if (lua_gettop(L) == 0) >> - return luaL_error(L, "Usage: box.error.new(code, args)"); >> + if (lua_gettop(L) == 0) { >> + return luaL_error(L, "Usage: box.error.new(code, args) or "\ >> + "box.error.new(type, args)"); >> + } >> struct error *e = luaT_error_create(L, 1); >> - if (e == NULL) >> - return luaL_error(L, "Usage: box.error.new(code, args)"); >> + if (e == NULL) { >> + return luaL_error(L, "Usage: box.error.new(code, args) or "\ >> + "box.error.new(type, args)"); >> + } >> lua_settop(L, 0); >> luaT_pusherror(L, e); >> return 1; >> } >> >> static int >> +luaT_error_custom_type(lua_State *L) > > 19. This method is not needed. We don't have 'global' getters > like box.error.prev, or box.error.type, and so on. Every > error object provides API to get its members without calling > global functions. > >> +{ >> + struct error *e = luaL_checkerror(L, -1); >> + >> + const char *custom_type = box_custom_error_type(e); >> + if (custom_type == NULL) { >> + lua_pushfstring(L, "The error has't custom type"); >> + return 1; >> + } >> + >> + lua_pushstring(L, custom_type); >> + return 1; >> +} >> + >> +static int >> luaT_error_clear(lua_State *L) >> { >> if (lua_gettop(L) >= 1) >> diff --git a/src/lua/error.lua b/src/lua/error.lua >> index 46d2866..5b32607 100644 >> --- a/src/lua/error.lua >> +++ b/src/lua/error.lua >> @@ -78,6 +78,16 @@ local function reflection_get(err, method) >> end >> >> local function error_type(err) >> + local res >> + if ffi.string(err._type.name) == 'CustomError' then >> + res = box.error.custom_type(err) >> + else >> + res = ffi.string(err._type.name) > > 20. ffi.string() is called on one string 2 times. But it > is not free, it allocates a new string each time. > >> + end >> + return res >> +end > > 21. There is again not a single test. That's my mistake. After our discussion for testing I used the test added in the last commit and deleted the ones added before in misc. > > Consider my review fixes below and on a new branch in a > separate commit. Note, it is not entirely separated > from the original commit in a couple of places, since I had > rebase conflicts. I applied your patch and added you to Co-authored. > > Branch is: > > lvasiliev/gh-4398-expose-error-module-4-review > > ==================== > Review fixes > > New commit message proposal: > > error: add custom error type > > Part of #4398 > > @TarantoolBot document > Title: Custom error types for Lua errors > > Errors can be created in 2 ways: `box.error.new()` and `box.error()`. > > Both used to take either `code, reason, <reason string args>` or > `{code = code, reason = reason, ...}` arguments. > > Now in the first option instead of code a user can specify a > string as its own error type. In the second option a user can > specify both code and type. For example: > > ```Lua > box.error('MyErrorType', 'Message') > box.error({type = 'MyErrorType', code = 1024, reason = 'Message'}) > ``` > Or no-throw version: > ```Lua > box.error.new('MyErrorType', 'Message') > box.error.new({type = 'MyErrorType', code = 1024, reason = 'Message'}) > ``` > When a custom type is specified, it is shown in `err.type` > attribute. When it is not specified, `err.type` shows one of > built-in errors such as 'ClientError', 'OurOfMemory', etc. > > Name length limit on the custom type is 63 bytes. All is longer > is truncated. > > Original error type can be checked using `err.base_type` member, > although normally it should not be used. For user-defined types > base type is 'CustomError'. > > For example: > ``` > tarantool> e = box.error.new({type = 'MyErrorType', code = 1024, reason = 'Message'}) > --- > ... > > tarantool> e:unpack() > --- > - code: 1024 > trace: > - file: '[string "e = box.error.new({type = ''MyErrorType'', code..."]' > line: 1 > type: MyErrorType > custom_type: MyErrorType > message: Message > base_type: CustomError > ... > ``` > > diff --git a/extra/exports b/extra/exports > index ffd11145d..a5ebe0884 100644 > --- a/extra/exports > +++ b/extra/exports > @@ -234,6 +234,7 @@ box_index_min > box_index_max > box_index_count > box_error_type > +box_error_custom_type > box_error_code > box_error_message > box_error_last > diff --git a/src/box/error.cc b/src/box/error.cc > index 8179e5287..d854b86e9 100644 > --- a/src/box/error.cc > +++ b/src/box/error.cc > @@ -86,35 +86,51 @@ box_error_set(const char *file, unsigned line, uint32_t code, > return -1; > } > > +static struct error * > +box_error_new_va(const char *file, unsigned line, uint32_t code, > + const char *custom_type, const char *fmt, va_list ap) > +{ > + if (custom_type == NULL) { > + struct error *e = BuildClientError(file, line, ER_UNKNOWN); > + ClientError *client_error = type_cast(ClientError, e); > + if (client_error != NULL) { > + client_error->m_errcode = code; > + error_vformat_msg(e, fmt, ap); > + } > + return e; > + } else { > + struct error *e = BuildCustomError(file, line, custom_type); > + CustomError *custom_error = type_cast(CustomError, e); > + if (custom_error != NULL) { > + custom_error->m_errcode = code; > + error_vformat_msg(e, fmt, ap); > + } > + return e; > + } > +} > + > struct error * > box_error_new(const char *file, unsigned line, uint32_t code, > - const char *fmt, ...) > + const char *custom_type, const char *fmt, ...) > { > - struct error *e = BuildClientError(file, line, ER_UNKNOWN); > - ClientError *client_error = type_cast(ClientError, e); > - if (client_error != NULL) { > - client_error->m_errcode = code; > - va_list ap; > - va_start(ap, fmt); > - error_vformat_msg(e, fmt, ap); > - va_end(ap); > - } > + va_list ap; > + va_start(ap, fmt); > + struct error *e = box_error_new_va(file, line, code, custom_type, > + fmt, ap); > + va_end(ap); > return e; > } > > int > box_error_add(const char *file, unsigned line, uint32_t code, > - const char *fmt, ...) > + const char *custom_type, const char *fmt, ...) > { > - struct error *e = BuildClientError(file, line, ER_UNKNOWN); > - ClientError *client_error = type_cast(ClientError, e); > - if (client_error) { > - client_error->m_errcode = code; > - va_list ap; > - va_start(ap, fmt); > - error_vformat_msg(e, fmt, ap); > - va_end(ap); > - } > + va_list ap; > + va_start(ap, fmt); > + struct error *e = box_error_new_va(file, line, code, custom_type, > + fmt, ap); > + va_end(ap); > + > struct diag *d = &fiber()->diag; > if (diag_is_empty(d)) > diag_set_error(d, e); > @@ -126,7 +142,7 @@ box_error_add(const char *file, unsigned line, uint32_t code, > /* }}} */ > > const char * > -box_custom_error_type(const box_error_t *e) > +box_error_custom_type(const struct error *e) > { > CustomError *custom_error = type_cast(CustomError, e); > if (custom_error) > @@ -135,21 +151,6 @@ box_custom_error_type(const box_error_t *e) > return NULL; > } > > -struct error * > -box_custom_error_new(const char *file, unsigned line, > - const char *custom, const char *fmt, ...) > -{ > - struct error *e = BuildCustomError(file, line, custom); > - CustomError *custom_error = type_cast(CustomError, e); > - if (custom_error != NULL) { > - va_list ap; > - va_start(ap, fmt); > - error_vformat_msg(e, fmt, ap); > - va_end(ap); > - } > - return e; > -} > - > struct rmean *rmean_error = NULL; > > const char *rmean_error_strings[RMEAN_ERROR_LAST] = { > @@ -322,22 +323,15 @@ static struct method_info customerror_methods[] = { > }; > > const struct type_info type_CustomError = > - make_type("CustomError", &type_ClientError, > - customerror_methods); > + make_type("CustomError", &type_ClientError, customerror_methods); > > CustomError::CustomError(const char *file, unsigned int line, > const char *custom_type) > :ClientError(&type_CustomError, file, line, ER_CUSTOM_ERROR) > { > - error_format_msg(this, tnt_errcode_desc(m_errcode), > - custom_type ?: ""); > - > - if (custom_type) { > - strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1); > - m_custom_type[sizeof(m_custom_type) - 1] = '\0'; > - } else { > - m_custom_type[0] = '\0'; > - } > + error_format_msg(this, tnt_errcode_desc(m_errcode), custom_type); > + strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1); > + m_custom_type[sizeof(m_custom_type) - 1] = '\0'; > } > > struct error * > diff --git a/src/box/error.h b/src/box/error.h > index 501348885..540008f35 100644 > --- a/src/box/error.h > +++ b/src/box/error.h > @@ -142,11 +142,11 @@ box_error_set(const char *file, unsigned line, uint32_t code, > > /** > * Return the error custom type, > - * \param error > - * \return pointer to custom error type. On error, return NULL > + * \param e Error object. > + * \return Pointer to custom error type. On error return NULL. > */ > const char * > -box_custom_error_type(const box_error_t *e); > +box_error_custom_type(const struct error *e); > > /** > * Add error to the diagnostic area. In contrast to box_error_set() > @@ -154,6 +154,8 @@ box_custom_error_type(const box_error_t *e); > * them into list. > * > * \param code IPROTO error code (enum \link box_error_code \endlink) > + * \param custom_type User-defined error type which will be > + * displayed instead of ClientError. > * \param format (const char * ) - printf()-like format string > * \param ... - format arguments > * \returns -1 for convention use > @@ -162,23 +164,15 @@ box_custom_error_type(const box_error_t *e); > */ > int > box_error_add(const char *file, unsigned line, uint32_t code, > - const char *fmt, ...); > + const char *custom_type, const char *fmt, ...); > > /** > * Construct error object without setting it in the diagnostics > - * area. On the memory allocation fail returns OutOfMemory error. > + * area. On the memory allocation fail returns NULL. > */ > struct error * > box_error_new(const char *file, unsigned line, uint32_t code, > - const char *fmt, ...); > - > -/** > - * Construct error object without setting it in the diagnostics > - * area. On the memory allocation fail returns OutOfMemory error. > - */ > -struct error * > -box_custom_error_new(const char *file, unsigned line, > - const char *custom, const char *fmt, ...); > + const char *custom_type, const char *fmt, ...); > > extern const struct type_info type_ClientError; > extern const struct type_info type_XlogError; > diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc > index dd7068a83..cf771935e 100644 > --- a/src/box/lua/error.cc > +++ b/src/box/lua/error.cc > @@ -42,95 +42,83 @@ extern "C" { > #include "lua/utils.h" > #include "box/error.h" > > -struct error_args { > - uint32_t code; > - const char *reason; > - const char *custom; > - const char *traceback; > -}; > - > -static int > -luaT_error_parse_args(struct lua_State *L, int top_base, > - struct error_args *args) > +/** > + * Parse Lua arguments (they can come as single table > + * f({code : number, reason : string}) or as separate members > + * f(code, reason)) and construct struct error with given values. > + * > + * Instead of 'code' it is possible to specify a string name of > + * the error object's type: > + * > + * box.error(type, reason, ...) > + * box.error({type = string, reason = string, ...}) > + * > + * In case one of arguments is missing its corresponding field > + * in struct error is filled with default value. > + */ > +static struct error * > +luaT_error_create(lua_State *L, int top_base) > { > + uint32_t code = 0; > + const char *custom_type = NULL; > + const char *reason = NULL; > + bool is_traceback_enabled = false; > + bool is_traceback_specified = false; > + const char *file = ""; > + unsigned line = 0; > + lua_Debug info; > int top = lua_gettop(L); > int top_type = lua_type(L, top_base); > - if (top >= top_base && > - (top_type == LUA_TNUMBER || top_type == LUA_TSTRING)) { > + if (top >= top_base && (top_type == LUA_TNUMBER || > + top_type == LUA_TSTRING)) { > if (top_type == LUA_TNUMBER) { > - args->code = lua_tonumber(L, top_base); > - } else if (top_type == LUA_TSTRING) { > - args->code = ER_CUSTOM_ERROR; > - args->custom = lua_tostring(L, top_base); > + code = lua_tonumber(L, top_base); > } else { > - return -1; > + code = ER_CUSTOM_ERROR; > + custom_type = lua_tostring(L, top_base); > } > - args->reason = tnt_errcode_desc(args->code); > + reason = tnt_errcode_desc(code); > if (top > top_base) { > /* Call string.format(reason, ...) to format message */ > lua_getglobal(L, "string"); > if (lua_isnil(L, -1)) > - return 0; > + goto raise; > lua_getfield(L, -1, "format"); > if (lua_isnil(L, -1)) > - return 0; > - lua_pushstring(L, args->reason); > + goto raise; > + lua_pushstring(L, reason); > for (int i = top_base + 1; i <= top; i++) > lua_pushvalue(L, i); > lua_call(L, top - top_base + 1, 1); > - args->reason = lua_tostring(L, -1); > - } else if (strchr(args->reason, '%') != NULL) { > + reason = lua_tostring(L, -1); > + } else if (strchr(reason, '%') != NULL) { > /* Missing arguments to format string */ > - return -1; > + return NULL; > } > } else if (top == top_base && top_type == LUA_TTABLE) { > lua_getfield(L, top_base, "code"); > - if (lua_isnil(L, -1) == 0) > - args->code = lua_tonumber(L, -1); > + if (!lua_isnil(L, -1)) > + code = lua_tonumber(L, -1); > + else > + code = ER_CUSTOM_ERROR; > lua_getfield(L, top_base, "reason"); > - if (lua_isnil(L, -1) == 0) > - args->reason = lua_tostring(L, -1); > + reason = lua_tostring(L, -1); > + if (reason == NULL) > + reason = ""; > lua_getfield(L, top_base, "type"); > - if (lua_isnil(L, -1) == 0) > - args->custom = lua_tostring(L, -1); > + if (!lua_isnil(L, -1)) > + custom_type = lua_tostring(L, -1); > lua_getfield(L, top_base, "traceback"); > - if (lua_isboolean(L, -1) && lua_toboolean(L, -1)) { > - luaL_traceback(L, L, NULL, 0); > - if (lua_isstring(L, -1)) > - args->traceback = lua_tostring(L, -1); > + if (lua_isboolean(L, -1)) { > + is_traceback_enabled = lua_toboolean(L, -1); > + is_traceback_specified = true; > } > lua_pop(L, 1); > } else { > - return -1; > - } > - return 0; > -} > - > -/** > - * Parse Lua arguments (they can come as single table > - * f({code : number, reason : string}) or as separate members > - * f(code, reason)) and construct struct error with given values. > - * In case one of arguments is missing its corresponding field > - * in struct error is filled with default value. > - */ > -static struct error * > -luaT_error_create(lua_State *L, int top_base) > -{ > - struct error_args args; > - args.code = UINT32_MAX; > - args.reason = NULL; > - args.custom = NULL; > - args.traceback = NULL; > - > - if (luaT_error_parse_args(L, top_base, &args) != 0) > return NULL; > + } > > - if (args.reason == NULL) > - args.reason = ""; > - > - const char *file = ""; > - unsigned line = 0; > - lua_Debug info; > +raise: > if (lua_getstack(L, 1, &info) && lua_getinfo(L, "Sl", &info)) { > if (*info.short_src) { > file = info.short_src; > @@ -142,15 +130,13 @@ luaT_error_create(lua_State *L, int top_base) > line = info.currentline; > } > > - struct error *err; > - if (args.custom) { > - err = box_custom_error_new(file, line, args.custom, > - "%s", args.reason); > - } else { > - err = box_error_new(file, line, args.code, "%s", args.reason); > - } > - if (args.traceback != NULL && err != NULL) > - error_set_traceback(err, args.traceback); > + struct error *err = box_error_new(file, line, code, custom_type, > + "%s", reason); > + /* > + * Explicit traceback option overrides the global setting. > + */ > + if (err != NULL && is_traceback_specified) > + err->is_traceback_enabled = is_traceback_enabled; > return err; > } > > @@ -198,35 +184,16 @@ luaT_error_last(lua_State *L) > static int > luaT_error_new(lua_State *L) > { > - if (lua_gettop(L) == 0) { > - return luaL_error(L, "Usage: box.error.new(code, args) or "\ > - "box.error.new(type, args)"); > - } > - struct error *e = luaT_error_create(L, 1); > - if (e == NULL) { > + struct error *e; > + if (lua_gettop(L) == 0 || (e = luaT_error_create(L, 1)) == NULL) { > return luaL_error(L, "Usage: box.error.new(code, args) or "\ > - "box.error.new(type, args)"); > + "box.error.new(type, args)"); > } > lua_settop(L, 0); > luaT_pusherror(L, e); > return 1; > } > > -static int > -luaT_error_custom_type(lua_State *L) > -{ > - struct error *e = luaL_checkerror(L, -1); > - > - const char *custom_type = box_custom_error_type(e); > - if (custom_type == NULL) { > - lua_pushfstring(L, "The error has't custom type"); > - return 1; > - } > - > - lua_pushstring(L, custom_type); > - return 1; > -} > - > static int > luaT_error_clear(lua_State *L) > { > @@ -381,10 +348,6 @@ box_lua_error_init(struct lua_State *L) { > lua_pushcfunction(L, luaT_error_cfg); > lua_setfield(L, -2, "cfg"); > } > - { > - lua_pushcfunction(L, luaT_error_custom_type); > - lua_setfield(L, -2, "custom_type"); > - } > lua_setfield(L, -2, "__index"); > } > lua_setmetatable(L, -2); > diff --git a/src/box/xrow.c b/src/box/xrow.c > index a494d1f46..5494b41cd 100644 > --- a/src/box/xrow.c > +++ b/src/box/xrow.c > @@ -1123,7 +1123,7 @@ iproto_decode_error_stack(const char **pos) > continue; > } > } > - box_error_add(__FILE__, __LINE__, code, reason); > + box_error_add(__FILE__, __LINE__, code, NULL, reason); > } > return 0; > } > diff --git a/src/lua/error.lua b/src/lua/error.lua > index 1f9ae9375..1d3413b19 100644 > --- a/src/lua/error.lua > +++ b/src/lua/error.lua > @@ -37,6 +37,9 @@ exception_get_int(struct error *e, const struct method_info *method); > > int > error_set_prev(struct error *e, struct error *prev); > + > +const char * > +box_error_custom_type(const struct error *e); > ]] > > local REFLECTION_CACHE = {} > @@ -77,20 +80,18 @@ local function reflection_get(err, method) > end > end > > -local function error_type(err) > - local res > - if ffi.string(err._type.name) == 'CustomError' then > - res = box.error.custom_type(err) > - else > - res = ffi.string(err._type.name) > - end > - return res > -end > - > local function error_base_type(err) > return ffi.string(err._type.name) > end > > +local function error_type(err) > + local res = ffi.C.box_error_custom_type(err) > + if res ~= nil then > + return ffi.string(res) > + end > + return error_base_type(err) > +end > + > local function error_message(err) > return ffi.string(err._errmsg) > end > @@ -148,7 +149,7 @@ local error_fields = { > ["errno"] = error_errno; > ["prev"] = error_prev; > ["traceback"] = error_traceback; > - ["base_type"] = error_base_type > + ["base_type"] = error_base_type, > } > > local function error_unpack(err) > diff --git a/test/box/error.result b/test/box/error.result > index 2b837e434..b717a4ff4 100644 > --- a/test/box/error.result > +++ b/test/box/error.result > @@ -431,11 +431,8 @@ t; > | 210: box.error.SQL_PREPARE > | 211: box.error.WRONG_QUERY_ID > | 212: box.error.SEQUENCE_NOT_STARTED > -<<<<<<< HEAD > | 213: box.error.NO_SUCH_SESSION_SETTING > -======= > - | 213: box.error.CUSTOM_ERROR > ->>>>>>> error: Add the custom error type > + | 214: box.error.CUSTOM_ERROR > | ... > > test_run:cmd("setopt delimiter ''"); > @@ -895,3 +892,60 @@ check_trace(e:unpack().traceback) > | --- > | - true > | ... > + > +-- > +-- gh-4398: custom error type. > +-- > +-- Try no code. > +e = box.error.new({type = 'TestType', reason = 'Test reason'}) > + | --- > + | ... > +e:unpack() > + | --- > + | - code: 214 > + | trace: > + | - file: '[string "e = box.error.new({type = ''TestType'', reason ..."]' > + | line: 1 > + | type: TestType > + | custom_type: TestType > + | message: Test reason > + | base_type: CustomError > + | ... > +-- Try code not the same as used by default. > +e = box.error.new({type = 'TestType', reason = 'Test reason', code = 123}) > + | --- > + | ... > +e:unpack() > + | --- > + | - code: 123 > + | trace: > + | - file: '[string "e = box.error.new({type = ''TestType'', reason ..."]' > + | line: 1 > + | type: TestType > + | custom_type: TestType > + | message: Test reason > + | base_type: CustomError > + | ... > +-- Try to omit message. > +e = box.error.new({type = 'TestType'}) > + | --- > + | ... > +e:unpack() > + | --- > + | - code: 214 > + | trace: > + | - file: '[string "e = box.error.new({type = ''TestType''}) "]' > + | line: 1 > + | type: TestType > + | custom_type: TestType > + | message: > + | base_type: CustomError > + | ... > +-- Try too long type name. > +e = box.error.new({type = string.rep('a', 128)}) > + | --- > + | ... > +#e.type > + | --- > + | - 63 > + | ... > diff --git a/test/box/error.test.lua b/test/box/error.test.lua > index 6f1271630..fe4051899 100644 > --- a/test/box/error.test.lua > +++ b/test/box/error.test.lua > @@ -262,3 +262,19 @@ check_trace(t3(nil, false):unpack().traceback) > box.error.cfg{traceback_enable = false} > _, e = pcall(t3, true, true) > check_trace(e:unpack().traceback) > + > +-- > +-- gh-4398: custom error type. > +-- > +-- Try no code. > +e = box.error.new({type = 'TestType', reason = 'Test reason'}) > +e:unpack() > +-- Try code not the same as used by default. > +e = box.error.new({type = 'TestType', reason = 'Test reason', code = 123}) > +e:unpack() > +-- Try to omit message. > +e = box.error.new({type = 'TestType'}) > +e:unpack() > +-- Try too long type name. > +e = box.error.new({type = string.rep('a', 128)}) > +#e.type > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type 2020-04-15 9:25 ` lvasiliev @ 2020-04-16 0:02 ` Vladislav Shpilevoy 2020-04-16 9:18 ` lvasiliev 0 siblings, 1 reply; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-16 0:02 UTC (permalink / raw) To: lvasiliev; +Cc: tarantool-patches Thanks for the fixes! >> 6. Make_type() perfectly fits in one line. >> >>> + >>> +CustomError::CustomError(const char *file, unsigned int line, >>> + const char *custom_type) >>> + :ClientError(&type_CustomError, file, line, ER_CUSTOM_ERROR) >>> +{ >>> + error_format_msg(this, tnt_errcode_desc(m_errcode), >>> + custom_type ?: ""); >> >> 7. custom_type is never NULL. > Why? It's a constructor argument. And still it is never NULL. I couldn't find any place, where you passed NULL here, and anyway that would be incorrect. Type can't be just NULL. How to present it in Lua then? nil? Doubt it. >>> + */ >>> +const char * >>> +box_custom_error_type(const box_error_t *e); >>> + >>> +/** >>> * Add error to the diagnostic area. In contrast to box_error_set() >>> * it does not replace previous error being set, but rather link >>> * them into list. >>> @@ -155,15 +166,24 @@ box_error_add(const char *file, unsigned line, uint32_t code, >>> /** >>> * Construct error object without setting it in the diagnostics >>> - * area. On the memory allocation fail returns NULL. >>> + * area. On the memory allocation fail returns OutOfMemory error. >> >> 10. Unneccessary diff. > The comment is wrong. How I can fix it? Yeah, I just realized that after you said box_error_new() never returns NULL. Then probably it is ok to change it. >> >>> */ >>> struct error * >>> box_error_new(const char *file, unsigned line, uint32_t code, >>> const char *fmt, ...); >>> +/** >>> + * Construct error object without setting it in the diagnostics >>> + * area. On the memory allocation fail returns OutOfMemory error. >> >> 11. This is a copy-paste box box_error_new() comment. Better write >> here a couple of words how is it different. Or better merge box_error_new() >> and box_custom_error_new(). Because in Lua I can specify both code >> and type. Don't see why can't I do this in C. > I thought we can’t change public API. This is not public API. If something is not compiled into module.h, it is not public (in C. For Lua rules are different). >>> @@ -111,9 +148,16 @@ raise: >>> line = info.currentline; >>> } >>> - struct error *err = box_error_new(file, line, code, "%s", reason); >>> - if (tb_parsed) >>> - err->traceback_mode = tb_mode; >>> + struct error *err = NULL; >>> + if (args.custom) { >>> + err = box_custom_error_new(file, line, args.custom, >>> + "%s", args.reason); >> >> 18. Code argument is ignored here. Although I am not sure it shouldn't >> be ignored. From what I remember, we decided that code should stay, >> but I may be wrong. > CustomError type has a predefinded code. Why it shouldn't be ignored? Because it is not a 'ClientError' object from user's point of view. The fact that we implemented it as a descendant of 'ClientError' class should not leak into the API. User can't assume anything about error hierarchies. He does not even see them. From user's point of view there are separate built-in 'ClientError'. And separate 'CustomError'. Both accept error code now. But 'CustomError' ignores it somewhy. Does not look right. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type 2020-04-16 0:02 ` Vladislav Shpilevoy @ 2020-04-16 9:18 ` lvasiliev 2020-04-16 21:03 ` Vladislav Shpilevoy 0 siblings, 1 reply; 37+ messages in thread From: lvasiliev @ 2020-04-16 9:18 UTC (permalink / raw) To: Vladislav Shpilevoy; +Cc: tarantool-patches Hi! Thanks you for the feedback. On 16.04.2020 3:02, Vladislav Shpilevoy wrote: > Thanks for the fixes! > >>> 6. Make_type() perfectly fits in one line. >>> >>>> + >>>> +CustomError::CustomError(const char *file, unsigned int line, >>>> + const char *custom_type) >>>> + :ClientError(&type_CustomError, file, line, ER_CUSTOM_ERROR) >>>> +{ >>>> + error_format_msg(this, tnt_errcode_desc(m_errcode), >>>> + custom_type ?: ""); >>> >>> 7. custom_type is never NULL. >> Why? It's a constructor argument. > > And still it is never NULL. I couldn't find any place, where > you passed NULL here, and anyway that would be incorrect. Type > can't be just NULL. How to present it in Lua then? nil? Doubt > it. Hmm, ok. > >>>> + */ >>>> +const char * >>>> +box_custom_error_type(const box_error_t *e); >>>> + >>>> +/** >>>> * Add error to the diagnostic area. In contrast to box_error_set() >>>> * it does not replace previous error being set, but rather link >>>> * them into list. >>>> @@ -155,15 +166,24 @@ box_error_add(const char *file, unsigned line, uint32_t code, >>>> /** >>>> * Construct error object without setting it in the diagnostics >>>> - * area. On the memory allocation fail returns NULL. >>>> + * area. On the memory allocation fail returns OutOfMemory error. >>> >>> 10. Unneccessary diff. >> The comment is wrong. How I can fix it? > > Yeah, I just realized that after you said box_error_new() never > returns NULL. Then probably it is ok to change it. Ok. I will replace it in the new version of the patchset. > >>> >>>> */ >>>> struct error * >>>> box_error_new(const char *file, unsigned line, uint32_t code, >>>> const char *fmt, ...); >>>> +/** >>>> + * Construct error object without setting it in the diagnostics >>>> + * area. On the memory allocation fail returns OutOfMemory error. >>> >>> 11. This is a copy-paste box box_error_new() comment. Better write >>> here a couple of words how is it different. Or better merge box_error_new() >>> and box_custom_error_new(). Because in Lua I can specify both code >>> and type. Don't see why can't I do this in C. >> I thought we can’t change public API. > > This is not public API. If something is not compiled into module.h, > it is not public (in C. For Lua rules are different). > >>>> @@ -111,9 +148,16 @@ raise: >>>> line = info.currentline; >>>> } >>>> - struct error *err = box_error_new(file, line, code, "%s", reason); >>>> - if (tb_parsed) >>>> - err->traceback_mode = tb_mode; >>>> + struct error *err = NULL; >>>> + if (args.custom) { >>>> + err = box_custom_error_new(file, line, args.custom, >>>> + "%s", args.reason); >>> >>> 18. Code argument is ignored here. Although I am not sure it shouldn't >>> be ignored. From what I remember, we decided that code should stay, >>> but I may be wrong. >> CustomError type has a predefinded code. Why it shouldn't be ignored? > > Because it is not a 'ClientError' object from user's point of view. The > fact that we implemented it as a descendant of 'ClientError' class should > not leak into the API. User can't assume anything about error hierarchies. > He does not even see them. > > From user's point of view there are separate built-in 'ClientError'. And > separate 'CustomError'. Both accept error code now. But 'CustomError' > ignores it somewhy. Does not look right. > No, from user's point of view it have only type without any codes (In accordance with the task). Besides, I think it's strange to have an AccessDeniedError with code "ER_SQL_PREPARE" (other branch of hierarchy). And user doesn't have an access to "code" of error. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type 2020-04-16 9:18 ` lvasiliev @ 2020-04-16 21:03 ` Vladislav Shpilevoy 0 siblings, 0 replies; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-16 21:03 UTC (permalink / raw) To: lvasiliev; +Cc: tarantool-patches Hi! Thanks for the answers! >>>>> @@ -111,9 +148,16 @@ raise: >>>>> line = info.currentline; >>>>> } >>>>> - struct error *err = box_error_new(file, line, code, "%s", reason); >>>>> - if (tb_parsed) >>>>> - err->traceback_mode = tb_mode; >>>>> + struct error *err = NULL; >>>>> + if (args.custom) { >>>>> + err = box_custom_error_new(file, line, args.custom, >>>>> + "%s", args.reason); >>>> >>>> 18. Code argument is ignored here. Although I am not sure it shouldn't >>>> be ignored. From what I remember, we decided that code should stay, >>>> but I may be wrong. >>> CustomError type has a predefinded code. Why it shouldn't be ignored? >> >> Because it is not a 'ClientError' object from user's point of view. The >> fact that we implemented it as a descendant of 'ClientError' class should >> not leak into the API. User can't assume anything about error hierarchies. >> He does not even see them. >> >> From user's point of view there are separate built-in 'ClientError'. And >> separate 'CustomError'. Both accept error code now. But 'CustomError' >> ignores it somewhy. Does not look right. >> > No, from user's point of view it have only type without any codes (In accordance with the task). The task says, I cite: "Ability to skip error codes if we don't need them". Not drop them. But skip when don't need. So essentially - make the code an optional parameter. > Besides, I think it's strange to have an AccessDeniedError with code "ER_SQL_PREPARE" (other branch of hierarchy). How AccessDeniedError is related to that? It is a different error type. Not ClientError, not CustomError. It does not have a code at all. > And user doesn't have an access to "code" of error. Well, they actually have. This is master branch: tarantool> e = box.error.new(1000, 'Message') --- ... tarantool> e.code --- - 1000 ... Talking of "user's point of view" regarding codes: ClientError has one meaning for error code. CustomError can have another. When you will add payload, users will add their codes for sure. Error code is something very basic for any more or less general error type. And if some of them will match our codes, it does not mean they have the same meaning. Some error types don't have a code - they type is enough. Such as AccessDeniedError. box.error.* codes only valid for ClientError objects. CustomError does not have any mapping of error codes. A user can define it. ^ permalink raw reply [flat|nested] 37+ messages in thread
* [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO 2020-04-10 8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error Leonid Vasiliev 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type Leonid Vasiliev @ 2020-04-10 8:10 ` Leonid Vasiliev 2020-04-14 1:12 ` Vladislav Shpilevoy 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO Leonid Vasiliev ` (2 subsequent siblings) 5 siblings, 1 reply; 37+ messages in thread From: Leonid Vasiliev @ 2020-04-10 8:10 UTC (permalink / raw) To: v.shpilevoy; +Cc: tarantool-patches Has been added IPROTO_ERROR_TRACEBACK and IPROTO_ERROR_CUSTOM_TYPE fields to IPROTO_ERROR_STACK for transfering through network a backtrace and custom type. Needed for #4398 --- src/box/iproto_constants.h | 2 ++ src/box/lua/error.cc | 23 +++++++++++++++++++++++ src/box/lua/net_box.lua | 21 ++++++++++++++++++++- src/box/xrow.c | 19 ++++++++++++++++++- 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h index 7ed8296..7daa980 100644 --- a/src/box/iproto_constants.h +++ b/src/box/iproto_constants.h @@ -154,6 +154,8 @@ enum iproto_ballot_key { enum iproto_error_key { IPROTO_ERROR_CODE = 0x01, IPROTO_ERROR_MESSAGE = 0x02, + IPROTO_ERROR_TRACEBACK = 0x03, + IPROTO_ERROR_CUSTOM_TYPE = 0x04, }; #define bit(c) (1ULL<<IPROTO_##c) diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc index 1bcce1f..27dee7a 100644 --- a/src/box/lua/error.cc +++ b/src/box/lua/error.cc @@ -236,6 +236,25 @@ luaT_error_custom_type(lua_State *L) } static int +luaT_error_set_lua_traceback(lua_State *L) +{ + if (lua_gettop(L) < 2) + return luaL_error(L, "Usage: box.error.set_lua_traceback"\ + "(error, traceback)"); + + struct error *e = luaL_checkerror(L, 1); + + if (lua_type(L, 2) == LUA_TSTRING) { + error_set_lua_traceback(e, lua_tostring(L, 2)); + } else { + return luaL_error(L, "Usage: box.error.set_lua_traceback"\ + "(error, traceback)"); + } + + return 0; +} + +static int luaT_error_clear(lua_State *L) { if (lua_gettop(L) >= 1) @@ -395,6 +414,10 @@ box_lua_error_init(struct lua_State *L) { lua_pushcfunction(L, luaT_error_custom_type); lua_setfield(L, -2, "custom_type"); } + { + lua_pushcfunction(L, luaT_error_set_lua_traceback); + lua_setfield(L, -2, "set_lua_traceback"); + } lua_setfield(L, -2, "__index"); } lua_setmetatable(L, -2); diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 07fa54c..1e0cd7a 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -47,6 +47,8 @@ local IPROTO_ERROR_KEY = 0x31 local IPROTO_ERROR_STACK = 0x52 local IPROTO_ERROR_CODE = 0x01 local IPROTO_ERROR_MESSAGE = 0x02 +local IPROTO_ERROR_TRACEBACK = 0x03 +local IPROTO_ERROR_CUSTOM_TYPE = 0x04 local IPROTO_GREETING_SIZE = 128 local IPROTO_CHUNK_KEY = 128 local IPROTO_OK_KEY = 0 @@ -287,7 +289,24 @@ local function create_transport(host, port, user, password, callback, local error = self.response[i] local code = error[IPROTO_ERROR_CODE] local msg = error[IPROTO_ERROR_MESSAGE] - local new_err = box.error.new({code = code, reason = msg}) + local custom_type = error[IPROTO_ERROR_CUSTOM_TYPE] + local traceback = error[IPROTO_ERROR_TRACEBACK] + + local new_err + if custom_type then + new_err = box.error.new({type = custom_type, + reason = msg, + traceback = false}) + else + new_err = box.error.new({code = code, + reason = msg, + traceback = false}) + end + + if traceback then + box.error.set_lua_traceback(new_err, traceback) + end + new_err:set_prev(prev) prev = new_err end diff --git a/src/box/xrow.c b/src/box/xrow.c index be026a4..cd88e49 100644 --- a/src/box/xrow.c +++ b/src/box/xrow.c @@ -494,11 +494,28 @@ mpstream_iproto_encode_error(struct mpstream *stream, const struct error *error) mpstream_encode_uint(stream, IPROTO_ERROR_STACK); mpstream_encode_array(stream, err_cnt); for (const struct error *it = error; it != NULL; it = it->cause) { - mpstream_encode_map(stream, 2); + /* Code and message are necessary fields */ + uint32_t map_size = 2; + const char *custom_type = NULL; + if (it->lua_traceback) + ++map_size; + if (strcmp(box_error_type(it), "CustomError") == 0) { + ++map_size; + custom_type = box_custom_error_type(it); + } + mpstream_encode_map(stream, map_size); mpstream_encode_uint(stream, IPROTO_ERROR_CODE); mpstream_encode_uint(stream, box_error_code(it)); mpstream_encode_uint(stream, IPROTO_ERROR_MESSAGE); mpstream_encode_str(stream, it->errmsg); + if (it->lua_traceback) { + mpstream_encode_uint(stream, IPROTO_ERROR_TRACEBACK); + mpstream_encode_str(stream, it->lua_traceback); + } + if (custom_type) { + mpstream_encode_uint(stream, IPROTO_ERROR_CUSTOM_TYPE); + mpstream_encode_str(stream, custom_type); + } } } -- 2.7.4 ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO Leonid Vasiliev @ 2020-04-14 1:12 ` Vladislav Shpilevoy 2020-04-15 9:25 ` lvasiliev 0 siblings, 1 reply; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-14 1:12 UTC (permalink / raw) To: Leonid Vasiliev; +Cc: tarantool-patches Thanks for the patch! Sorry, some of my comments may contain typos, because it is very late and I can't continue polishing it today. See 4 comments below, review fixes in the end of the email, and on a new branch in a separate commit. On 10/04/2020 10:10, Leonid Vasiliev wrote: > Has been added IPROTO_ERROR_TRACEBACK and IPROTO_ERROR_CUSTOM_TYPE > fields to IPROTO_ERROR_STACK for transfering through network > a backtrace and custom type. 1. The new IProto codes are part of the public API as well, and should be documented. > > Needed for #4398 > --- > src/box/iproto_constants.h | 2 ++ > src/box/lua/error.cc | 23 +++++++++++++++++++++++ > src/box/lua/net_box.lua | 21 ++++++++++++++++++++- > src/box/xrow.c | 19 ++++++++++++++++++- > 4 files changed, 63 insertions(+), 2 deletions(-) > > diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc > index 1bcce1f..27dee7a 100644 > --- a/src/box/lua/error.cc > +++ b/src/box/lua/error.cc > @@ -236,6 +236,25 @@ luaT_error_custom_type(lua_State *L) > } > > static int > +luaT_error_set_lua_traceback(lua_State *L) 2. This is not necessary as well as box.error.custom_type was not needed. Can be done via FFI. > +{ > + if (lua_gettop(L) < 2) > + return luaL_error(L, "Usage: box.error.set_lua_traceback"\ > + "(error, traceback)"); > + > + struct error *e = luaL_checkerror(L, 1); > + > + if (lua_type(L, 2) == LUA_TSTRING) { > + error_set_lua_traceback(e, lua_tostring(L, 2)); > + } else { > + return luaL_error(L, "Usage: box.error.set_lua_traceback"\ > + "(error, traceback)"); > + } > + > + return 0; > +} > + > diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua > index 07fa54c..1e0cd7a 100644 > --- a/src/box/lua/net_box.lua > +++ b/src/box/lua/net_box.lua > @@ -287,7 +289,24 @@ local function create_transport(host, port, user, password, callback, > local error = self.response[i] > local code = error[IPROTO_ERROR_CODE] > local msg = error[IPROTO_ERROR_MESSAGE] > - local new_err = box.error.new({code = code, reason = msg}) > + local custom_type = error[IPROTO_ERROR_CUSTOM_TYPE] > + local traceback = error[IPROTO_ERROR_TRACEBACK] > + > + local new_err > + if custom_type then > + new_err = box.error.new({type = custom_type, > + reason = msg, > + traceback = false}) > + else > + new_err = box.error.new({code = code, > + reason = msg, > + traceback = false}) 3. You can write all arguments in one table, box.error.new() should handle that fine. > + end > + > + if traceback then > + box.error.set_lua_traceback(new_err, traceback) > + end > + > new_err:set_prev(prev) > prev = new_err > end > diff --git a/src/box/xrow.c b/src/box/xrow.c > index be026a4..cd88e49 100644 > --- a/src/box/xrow.c > +++ b/src/box/xrow.c > @@ -494,11 +494,28 @@ mpstream_iproto_encode_error(struct mpstream *stream, const struct error *error) > mpstream_encode_uint(stream, IPROTO_ERROR_STACK); > mpstream_encode_array(stream, err_cnt); > for (const struct error *it = error; it != NULL; it = it->cause) { > - mpstream_encode_map(stream, 2); > + /* Code and message are necessary fields */ > + uint32_t map_size = 2; > + const char *custom_type = NULL; > + if (it->lua_traceback) > + ++map_size; > + if (strcmp(box_error_type(it), "CustomError") == 0) { > + ++map_size; > + custom_type = box_custom_error_type(it); > + } > + mpstream_encode_map(stream, map_size); > mpstream_encode_uint(stream, IPROTO_ERROR_CODE); > mpstream_encode_uint(stream, box_error_code(it)); > mpstream_encode_uint(stream, IPROTO_ERROR_MESSAGE); > mpstream_encode_str(stream, it->errmsg); > + if (it->lua_traceback) { > + mpstream_encode_uint(stream, IPROTO_ERROR_TRACEBACK); > + mpstream_encode_str(stream, it->lua_traceback); > + } > + if (custom_type) { > + mpstream_encode_uint(stream, IPROTO_ERROR_CUSTOM_TYPE); > + mpstream_encode_str(stream, custom_type); > + } > } > } 4. No any test. I wonder how did you check that it actually works? After I glanced at the last patch, I realized that looks like you implemented everything in a single huge commit, and then split it up, correct? Consider my review fixes below and on a new branch in a separate commit. Branch is: lvasiliev/gh-4398-expose-error-module-4-review ==================== ==================== Review fixes New commit message proposal: error: send traceback and custom type in IProto Error traceback and custom type features were added to the public Lua API in the previous commits. This one makes the new attributes being sent in IProto. @TarantoolBot document Title: New error object attributes in IProto Error objects in IProto already have 2 fields: `IPROTO_ERROR_CODE = 0x01` and `IPROTO_ERROR_MESSAGE = 0x02`. Now there are 2 more: `IPROTO_ERROR_TRACEBACK = 0x03` and `IPROTO_ERROR_CUSTOM_TYPE = 0x04`. Both are optional, have MP_STR type, and speak for themselves. Traceback is whatever the error object managed to collect from the caller's stack when was pushed on it. It is visible in Lua via `err_object.traceback`. Custom error type is another error object attribute. This is what a user specifies in `box.error.new({type = <custom_type>})` or `box.error.new(<custom_type>)`. diff --git a/extra/exports b/extra/exports index a5ebe0884..967e994c9 100644 --- a/extra/exports +++ b/extra/exports @@ -241,6 +241,7 @@ box_error_last box_error_clear box_error_set error_set_prev +error_set_traceback box_latch_new box_latch_delete box_latch_lock diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 1e0cd7aba..5c07bb07c 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -10,6 +10,11 @@ local urilib = require('uri') local internal = require('net.box.lib') local trigger = require('internal.trigger') +ffi.cdef[[ +void +error_set_traceback(struct error *e, const char *traceback); +]] + local band = bit.band local max = math.max local fiber_clock = fiber.clock @@ -287,26 +292,16 @@ local function create_transport(host, port, user, password, callback, local prev = nil for i = #self.response, 1, -1 do local error = self.response[i] - local code = error[IPROTO_ERROR_CODE] - local msg = error[IPROTO_ERROR_MESSAGE] - local custom_type = error[IPROTO_ERROR_CUSTOM_TYPE] local traceback = error[IPROTO_ERROR_TRACEBACK] - - local new_err - if custom_type then - new_err = box.error.new({type = custom_type, - reason = msg, - traceback = false}) - else - new_err = box.error.new({code = code, - reason = msg, - traceback = false}) + local new_err = box.error.new({ + type = error[IPROTO_ERROR_CUSTOM_TYPE], + code = error[IPROTO_ERROR_CODE], + reason = error[IPROTO_ERROR_MESSAGE], + traceback = false + }) + if traceback ~= nil then + ffi.C.error_set_traceback(new_err, traceback) end - - if traceback then - box.error.set_lua_traceback(new_err, traceback) - end - new_err:set_prev(prev) prev = new_err end diff --git a/src/box/xrow.c b/src/box/xrow.c index 04775d8ce..a9a6a6c75 100644 --- a/src/box/xrow.c +++ b/src/box/xrow.c @@ -494,7 +494,6 @@ mpstream_iproto_encode_error(struct mpstream *stream, const struct error *error) mpstream_encode_uint(stream, IPROTO_ERROR_STACK); mpstream_encode_array(stream, err_cnt); for (const struct error *it = error; it != NULL; it = it->cause) { - /* Code and message are necessary fields */ uint32_t map_size = 2; const char *custom_type = box_error_custom_type(it); map_size += (it->traceback != NULL); diff --git a/test/box/error.result b/test/box/error.result index b717a4ff4..6db8813b4 100644 --- a/test/box/error.result +++ b/test/box/error.result @@ -949,3 +949,59 @@ e = box.error.new({type = string.rep('a', 128)}) | --- | - 63 | ... + +-- +-- Check how traceback and custom error type are passed through +-- IProto. +-- +netbox = require('net.box') + | --- + | ... +box.schema.user.grant('guest', 'super') + | --- + | ... +c = netbox.connect(box.cfg.listen) + | --- + | ... + +_, e = pcall(c.call, c, 't3', {true, true}) + | --- + | ... +check_trace(e:unpack().traceback) + | --- + | - true + | ... + +_, e = pcall(c.call, c, 'box.error', { \ + {code = 123, type = 'TestType', traceback = true, reason = 'Test reason'} \ +}) + | --- + | ... +e = e:unpack() + | --- + | ... +e.traceback ~= nil or e.traceback + | --- + | - true + | ... +e.traceback = nil + | --- + | ... +e.trace = nil + | --- + | ... +e + | --- + | - code: 123 + | base_type: CustomError + | type: TestType + | custom_type: TestType + | message: Test reason + | ... + +c:close() + | --- + | ... +box.schema.user.revoke('guest', 'super') + | --- + | ... diff --git a/test/box/error.test.lua b/test/box/error.test.lua index fe4051899..0821fa0a8 100644 --- a/test/box/error.test.lua +++ b/test/box/error.test.lua @@ -278,3 +278,26 @@ e:unpack() -- Try too long type name. e = box.error.new({type = string.rep('a', 128)}) #e.type + +-- +-- Check how traceback and custom error type are passed through +-- IProto. +-- +netbox = require('net.box') +box.schema.user.grant('guest', 'super') +c = netbox.connect(box.cfg.listen) + +_, e = pcall(c.call, c, 't3', {true, true}) +check_trace(e:unpack().traceback) + +_, e = pcall(c.call, c, 'box.error', { \ + {code = 123, type = 'TestType', traceback = true, reason = 'Test reason'} \ +}) +e = e:unpack() +e.traceback ~= nil or e.traceback +e.traceback = nil +e.trace = nil +e + +c:close() +box.schema.user.revoke('guest', 'super') ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO 2020-04-14 1:12 ` Vladislav Shpilevoy @ 2020-04-15 9:25 ` lvasiliev 2020-04-16 0:02 ` Vladislav Shpilevoy 0 siblings, 1 reply; 37+ messages in thread From: lvasiliev @ 2020-04-15 9:25 UTC (permalink / raw) To: Vladislav Shpilevoy; +Cc: tarantool-patches Hi! Thanks for the review. On 14.04.2020 4:12, Vladislav Shpilevoy wrote: > Thanks for the patch! > > Sorry, some of my comments may contain typos, because it is > very late and I can't continue polishing it today. > > See 4 comments below, review fixes in the end of the email, > and on a new branch in a separate commit. > > On 10/04/2020 10:10, Leonid Vasiliev wrote: >> Has been added IPROTO_ERROR_TRACEBACK and IPROTO_ERROR_CUSTOM_TYPE >> fields to IPROTO_ERROR_STACK for transfering through network >> a backtrace and custom type. > > 1. The new IProto codes are part of the public API as well, and should > be documented. Ok > >> >> Needed for #4398 >> --- >> src/box/iproto_constants.h | 2 ++ >> src/box/lua/error.cc | 23 +++++++++++++++++++++++ >> src/box/lua/net_box.lua | 21 ++++++++++++++++++++- >> src/box/xrow.c | 19 ++++++++++++++++++- >> 4 files changed, 63 insertions(+), 2 deletions(-) >> >> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc >> index 1bcce1f..27dee7a 100644 >> --- a/src/box/lua/error.cc >> +++ b/src/box/lua/error.cc >> @@ -236,6 +236,25 @@ luaT_error_custom_type(lua_State *L) >> } >> >> static int >> +luaT_error_set_lua_traceback(lua_State *L) > > 2. This is not necessary as well as box.error.custom_type was not > needed. Can be done via FFI. Ok > >> +{ >> + if (lua_gettop(L) < 2) >> + return luaL_error(L, "Usage: box.error.set_lua_traceback"\ >> + "(error, traceback)"); >> + >> + struct error *e = luaL_checkerror(L, 1); >> + >> + if (lua_type(L, 2) == LUA_TSTRING) { >> + error_set_lua_traceback(e, lua_tostring(L, 2)); >> + } else { >> + return luaL_error(L, "Usage: box.error.set_lua_traceback"\ >> + "(error, traceback)"); >> + } >> + >> + return 0; >> +} >> + >> diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua >> index 07fa54c..1e0cd7a 100644 >> --- a/src/box/lua/net_box.lua >> +++ b/src/box/lua/net_box.lua >> @@ -287,7 +289,24 @@ local function create_transport(host, port, user, password, callback, >> local error = self.response[i] >> local code = error[IPROTO_ERROR_CODE] >> local msg = error[IPROTO_ERROR_MESSAGE] >> - local new_err = box.error.new({code = code, reason = msg}) >> + local custom_type = error[IPROTO_ERROR_CUSTOM_TYPE] >> + local traceback = error[IPROTO_ERROR_TRACEBACK] After we decided don't concatenate the traceback, I decided that it would be inconsistent to transmit the traceback in the case of "throw" over the network, since when creating a new error, the "trace" is set new and it always has been. Furthermore, an error can change the type. So, I delete IPROTO_ERROR_TRACEBACK.If you consider it wrong decision, I can return IPROTO_ERROR_TRACEBACK. >> + >> + local new_err >> + if custom_type then >> + new_err = box.error.new({type = custom_type, >> + reason = msg, >> + traceback = false}) >> + else >> + new_err = box.error.new({code = code, >> + reason = msg, >> + traceback = false}) > > 3. You can write all arguments in one table, box.error.new() should > handle that fine. > >> + end >> + >> + if traceback then >> + box.error.set_lua_traceback(new_err, traceback) >> + end >> + >> new_err:set_prev(prev) >> prev = new_err >> end >> diff --git a/src/box/xrow.c b/src/box/xrow.c >> index be026a4..cd88e49 100644 >> --- a/src/box/xrow.c >> +++ b/src/box/xrow.c >> @@ -494,11 +494,28 @@ mpstream_iproto_encode_error(struct mpstream *stream, const struct error *error) >> mpstream_encode_uint(stream, IPROTO_ERROR_STACK); >> mpstream_encode_array(stream, err_cnt); >> for (const struct error *it = error; it != NULL; it = it->cause) { >> - mpstream_encode_map(stream, 2); >> + /* Code and message are necessary fields */ >> + uint32_t map_size = 2; >> + const char *custom_type = NULL; >> + if (it->lua_traceback) >> + ++map_size; >> + if (strcmp(box_error_type(it), "CustomError") == 0) { >> + ++map_size; >> + custom_type = box_custom_error_type(it); >> + } >> + mpstream_encode_map(stream, map_size); >> mpstream_encode_uint(stream, IPROTO_ERROR_CODE); >> mpstream_encode_uint(stream, box_error_code(it)); >> mpstream_encode_uint(stream, IPROTO_ERROR_MESSAGE); >> mpstream_encode_str(stream, it->errmsg); >> + if (it->lua_traceback) { >> + mpstream_encode_uint(stream, IPROTO_ERROR_TRACEBACK); >> + mpstream_encode_str(stream, it->lua_traceback); >> + } >> + if (custom_type) { >> + mpstream_encode_uint(stream, IPROTO_ERROR_CUSTOM_TYPE); >> + mpstream_encode_str(stream, custom_type); >> + } >> } >> } > > 4. No any test. I wonder how did you check that it actually works? That's my mistake. After our discussion for testing I used the test added in the last commit > After I glanced at the last patch, I realized that looks like you > implemented everything in a single huge commit, and then split it > up, correct? It's kind of true. After our discussion the patchset had to be heavily redesigned and this was done in one commit, which has been split after) > > Consider my review fixes below and on a new branch in a > separate commit. > I applied your patch and added you to Co-authored. > Branch is: > > lvasiliev/gh-4398-expose-error-module-4-review > > ==================== > > ==================== > Review fixes > > New commit message proposal: > > error: send traceback and custom type in IProto > > Error traceback and custom type features were added to the public > Lua API in the previous commits. This one makes the new attributes > being sent in IProto. > > @TarantoolBot document > Title: New error object attributes in IProto > > Error objects in IProto already have 2 fields: > `IPROTO_ERROR_CODE = 0x01` and `IPROTO_ERROR_MESSAGE = 0x02`. > > Now there are 2 more: > > `IPROTO_ERROR_TRACEBACK = 0x03` and > `IPROTO_ERROR_CUSTOM_TYPE = 0x04`. > > Both are optional, have MP_STR type, and speak for themselves. > Traceback is whatever the error object managed to collect from > the caller's stack when was pushed on it. It is visible in Lua via > `err_object.traceback`. Custom error type is another error object > attribute. This is what a user specifies in > `box.error.new({type = <custom_type>})` or > `box.error.new(<custom_type>)`. > > diff --git a/extra/exports b/extra/exports > index a5ebe0884..967e994c9 100644 > --- a/extra/exports > +++ b/extra/exports > @@ -241,6 +241,7 @@ box_error_last > box_error_clear > box_error_set > error_set_prev > +error_set_traceback > box_latch_new > box_latch_delete > box_latch_lock > diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua > index 1e0cd7aba..5c07bb07c 100644 > --- a/src/box/lua/net_box.lua > +++ b/src/box/lua/net_box.lua > @@ -10,6 +10,11 @@ local urilib = require('uri') > local internal = require('net.box.lib') > local trigger = require('internal.trigger') > > +ffi.cdef[[ > +void > +error_set_traceback(struct error *e, const char *traceback); > +]] > + > local band = bit.band > local max = math.max > local fiber_clock = fiber.clock > @@ -287,26 +292,16 @@ local function create_transport(host, port, user, password, callback, > local prev = nil > for i = #self.response, 1, -1 do > local error = self.response[i] > - local code = error[IPROTO_ERROR_CODE] > - local msg = error[IPROTO_ERROR_MESSAGE] > - local custom_type = error[IPROTO_ERROR_CUSTOM_TYPE] > local traceback = error[IPROTO_ERROR_TRACEBACK] > - > - local new_err > - if custom_type then > - new_err = box.error.new({type = custom_type, > - reason = msg, > - traceback = false}) > - else > - new_err = box.error.new({code = code, > - reason = msg, > - traceback = false}) > + local new_err = box.error.new({ > + type = error[IPROTO_ERROR_CUSTOM_TYPE], > + code = error[IPROTO_ERROR_CODE], > + reason = error[IPROTO_ERROR_MESSAGE], > + traceback = false > + }) > + if traceback ~= nil then > + ffi.C.error_set_traceback(new_err, traceback) > end > - > - if traceback then > - box.error.set_lua_traceback(new_err, traceback) > - end > - > new_err:set_prev(prev) > prev = new_err > end > diff --git a/src/box/xrow.c b/src/box/xrow.c > index 04775d8ce..a9a6a6c75 100644 > --- a/src/box/xrow.c > +++ b/src/box/xrow.c > @@ -494,7 +494,6 @@ mpstream_iproto_encode_error(struct mpstream *stream, const struct error *error) > mpstream_encode_uint(stream, IPROTO_ERROR_STACK); > mpstream_encode_array(stream, err_cnt); > for (const struct error *it = error; it != NULL; it = it->cause) { > - /* Code and message are necessary fields */ > uint32_t map_size = 2; > const char *custom_type = box_error_custom_type(it); > map_size += (it->traceback != NULL); > diff --git a/test/box/error.result b/test/box/error.result > index b717a4ff4..6db8813b4 100644 > --- a/test/box/error.result > +++ b/test/box/error.result > @@ -949,3 +949,59 @@ e = box.error.new({type = string.rep('a', 128)}) > | --- > | - 63 > | ... > + > +-- > +-- Check how traceback and custom error type are passed through > +-- IProto. > +-- > +netbox = require('net.box') > + | --- > + | ... > +box.schema.user.grant('guest', 'super') > + | --- > + | ... > +c = netbox.connect(box.cfg.listen) > + | --- > + | ... > + > +_, e = pcall(c.call, c, 't3', {true, true}) > + | --- > + | ... > +check_trace(e:unpack().traceback) > + | --- > + | - true > + | ... > + > +_, e = pcall(c.call, c, 'box.error', { \ > + {code = 123, type = 'TestType', traceback = true, reason = 'Test reason'} \ > +}) > + | --- > + | ... > +e = e:unpack() > + | --- > + | ... > +e.traceback ~= nil or e.traceback > + | --- > + | - true > + | ... > +e.traceback = nil > + | --- > + | ... > +e.trace = nil > + | --- > + | ... > +e > + | --- > + | - code: 123 > + | base_type: CustomError > + | type: TestType > + | custom_type: TestType > + | message: Test reason > + | ... > + > +c:close() > + | --- > + | ... > +box.schema.user.revoke('guest', 'super') > + | --- > + | ... > diff --git a/test/box/error.test.lua b/test/box/error.test.lua > index fe4051899..0821fa0a8 100644 > --- a/test/box/error.test.lua > +++ b/test/box/error.test.lua > @@ -278,3 +278,26 @@ e:unpack() > -- Try too long type name. > e = box.error.new({type = string.rep('a', 128)}) > #e.type > + > +-- > +-- Check how traceback and custom error type are passed through > +-- IProto. > +-- > +netbox = require('net.box') > +box.schema.user.grant('guest', 'super') > +c = netbox.connect(box.cfg.listen) > + > +_, e = pcall(c.call, c, 't3', {true, true}) > +check_trace(e:unpack().traceback) > + > +_, e = pcall(c.call, c, 'box.error', { \ > + {code = 123, type = 'TestType', traceback = true, reason = 'Test reason'} \ > +}) > +e = e:unpack() > +e.traceback ~= nil or e.traceback > +e.traceback = nil > +e.trace = nil > +e > + > +c:close() > +box.schema.user.revoke('guest', 'super') > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO 2020-04-15 9:25 ` lvasiliev @ 2020-04-16 0:02 ` Vladislav Shpilevoy 0 siblings, 0 replies; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-16 0:02 UTC (permalink / raw) To: lvasiliev; +Cc: tarantool-patches Thanks for the fixes! >>> diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua >>> index 07fa54c..1e0cd7a 100644 >>> --- a/src/box/lua/net_box.lua >>> +++ b/src/box/lua/net_box.lua >>> @@ -287,7 +289,24 @@ local function create_transport(host, port, user, password, callback, >>> local error = self.response[i] >>> local code = error[IPROTO_ERROR_CODE] >>> local msg = error[IPROTO_ERROR_MESSAGE] >>> - local new_err = box.error.new({code = code, reason = msg}) >>> + local custom_type = error[IPROTO_ERROR_CUSTOM_TYPE] >>> + local traceback = error[IPROTO_ERROR_TRACEBACK] > After we decided don't concatenate the traceback, I decided that it would be inconsistent to transmit the traceback in the case of "throw" over the network, since when creating a new error, the "trace" is set new and it always has been. Furthermore, an error can change the type. So, I delete IPROTO_ERROR_TRACEBACK.If you consider it wrong decision, I can return IPROTO_ERROR_TRACEBACK. I am ok with that. As I said, traceback is not really a kind of critical and stable feature anyway. ^ permalink raw reply [flat|nested] 37+ messages in thread
* [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO 2020-04-10 8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev ` (2 preceding siblings ...) 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO Leonid Vasiliev @ 2020-04-10 8:10 ` Leonid Vasiliev 2020-04-14 1:12 ` Vladislav Shpilevoy 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding Leonid Vasiliev 2020-04-14 1:10 ` [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Vladislav Shpilevoy 5 siblings, 1 reply; 37+ messages in thread From: Leonid Vasiliev @ 2020-04-10 8:10 UTC (permalink / raw) To: v.shpilevoy; +Cc: tarantool-patches IPROTO session settings for error transmission in various formats depending on session settings have been added. This is required for backward compatibility. @TarantoolBot document Title: Add session_setting iproto_error_format setting has been added to _session_settings Used to set the error transmission format in the session. Old format: transmits as string at IPROTO_BODY New format: transmits as error object at IPROTO_BODY Needed for #4398 --- src/box/iproto.cc | 97 ++++++++++++++++++++++++++++++++++++++++++++++ src/box/lua/net_box.lua | 12 ++++++ src/box/session.cc | 3 ++ src/box/session.h | 3 ++ src/box/session_settings.h | 1 + src/lua/utils.h | 20 ++++++++++ 6 files changed, 136 insertions(+) diff --git a/src/box/iproto.cc b/src/box/iproto.cc index 9dad43b..92be645 100644 --- a/src/box/iproto.cc +++ b/src/box/iproto.cc @@ -55,6 +55,7 @@ #include "call.h" #include "tuple_convert.h" #include "session.h" +#include "session_settings.h" #include "xrow.h" #include "schema.h" /* schema_version */ #include "replication.h" /* instance_uuid */ @@ -2183,6 +2184,100 @@ iproto_session_push(struct session *session, uint64_t sync, struct port *port) /** }}} */ +enum { + IPROTO_SESSION_SETTING_ERR_FORMAT = 0, + iproto_session_setting_MAX, +}; + +static const char *iproto_session_setting_strs[iproto_session_setting_MAX] = { + "iproto_error_format", +}; + +static int iproto_session_field_type[] = { + /** IPROTO_SESSION_SETTING_ERR_FORMAT */ + FIELD_TYPE_UNSIGNED, +}; + +static void +iproto_session_setting_get(int id, const char **mp_pair, + const char **mp_pair_end) +{ + if (id < 0 || id >= iproto_session_setting_MAX) { + diag_set(ClientError, ER_ILLEGAL_PARAMS, + "unknown session setting"); + return; + } + struct session *session = current_session(); + + const char *name = iproto_session_setting_strs[id]; + size_t name_len = strlen(name); + + /* Now we have only one iproto session setting. */ + size_t size = mp_sizeof_array(2) + mp_sizeof_str(name_len) + + mp_sizeof_uint(session->serializer_ctx.err_format_ver); + + char *pos = (char*)static_alloc(size); + assert(pos != NULL); + char *pos_end = mp_encode_array(pos, 2); + pos_end = mp_encode_str(pos_end, name, name_len); + pos_end = mp_encode_uint(pos_end, + session->serializer_ctx.err_format_ver); + *mp_pair = pos; + *mp_pair_end = pos_end; +} + +static int +iproto_session_setting_set(int id, const char *mp_value) +{ + if (id < 0 || id >= iproto_session_setting_MAX) { + diag_set(ClientError, ER_ILLEGAL_PARAMS, + "unknown session setting"); + return -1; + } + /*Current IPROTO session settings are used only for BINARY session */ + if (current_session()->type != SESSION_TYPE_BINARY) + return -1; + + enum mp_type mtype = mp_typeof(*mp_value); + int stype = iproto_session_field_type[id]; + switch(stype) { + case FIELD_TYPE_UNSIGNED: { + if (mtype != MP_UINT) + break; + int val = mp_decode_uint(&mp_value); + switch (id) { + case IPROTO_SESSION_SETTING_ERR_FORMAT: + if (val >= ERR_FORMAT_UNK) + break; + current_session()->serializer_ctx.err_format_ver = val; + return 0; + default: + diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE, + iproto_session_setting_strs[id], + field_type_strs[stype]); + return -1; + } + break; + } + default: + unreachable(); + } + diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE, + iproto_session_setting_strs[id], field_type_strs[stype]); + return -1; +} + +void +iproto_session_settings_init() +{ + struct session_setting_module *module = + &session_setting_modules[SESSION_SETTING_IPROTO]; + module->settings = iproto_session_setting_strs; + module->setting_count = iproto_session_setting_MAX; + module->get = iproto_session_setting_get; + module->set = iproto_session_setting_set; +} + /** Initialize the iproto subsystem and start network io thread */ void iproto_init() @@ -2201,6 +2296,8 @@ iproto_init() /* .sync = */ iproto_session_sync, }; session_vtab_registry[SESSION_TYPE_BINARY] = iproto_session_vtab; + + iproto_session_settings_init(); } /** Available iproto configuration changes. */ diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 1e0cd7a..c8f76b0 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -1047,6 +1047,18 @@ local function new_sm(host, port, opts, connection, greeting) if opts.wait_connected ~= false then remote._transport.wait_state('active', tonumber(opts.wait_connected)) end + + -- Set extended error format for session. + if opts.error_extended then + local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1) + if not ext_err_supported then + box.error(box.error.PROC_LUA, + "Server doesn't support extended error format") + end + remote.space._session_settings:update('iproto_error_format', + {{'=', 2, 1}}) + end + return remote end diff --git a/src/box/session.cc b/src/box/session.cc index 8813182..0cd8675 100644 --- a/src/box/session.cc +++ b/src/box/session.cc @@ -144,6 +144,9 @@ session_create(enum session_type type) session->sql_default_engine = SQL_STORAGE_ENGINE_MEMTX; session->sql_stmts = NULL; + /* Set default Lua serializer context */ + session->serializer_ctx.err_format_ver = ERR_FORMAT_DEF; + /* For on_connect triggers. */ credentials_create(&session->credentials, guest_user); struct mh_i64ptr_node_t node; diff --git a/src/box/session.h b/src/box/session.h index 6dfc7cb..a8903aa 100644 --- a/src/box/session.h +++ b/src/box/session.h @@ -36,6 +36,7 @@ #include "fiber.h" #include "user.h" #include "authentication.h" +#include "lua/utils.h" #if defined(__cplusplus) extern "C" { @@ -110,6 +111,8 @@ struct session { struct credentials credentials; /** Trigger for fiber on_stop to cleanup created on-demand session */ struct trigger fiber_on_stop; + /** Session Lua serializer context */ + struct luaL_serializer_ctx serializer_ctx; }; struct session_vtab { diff --git a/src/box/session_settings.h b/src/box/session_settings.h index 25490a7..c139d30 100644 --- a/src/box/session_settings.h +++ b/src/box/session_settings.h @@ -41,6 +41,7 @@ * type list is used by setting iterators. */ enum session_setting_type { + SESSION_SETTING_IPROTO, SESSION_SETTING_SQL, session_setting_type_MAX, }; diff --git a/src/lua/utils.h b/src/lua/utils.h index 0b36727..5875ba3 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -266,6 +266,26 @@ struct luaL_serializer { struct rlist on_update; }; +/** + * An error serialization formats + */ +enum error_formats { + /** Default(old) format */ + ERR_FORMAT_DEF, + /** Extended format */ + ERR_FORMAT_EX, + /** The max version of error format */ + ERR_FORMAT_UNK +}; + +/** + * A serializer context (additional settings for a serializer) + */ +struct luaL_serializer_ctx { + /** Version of a format for an error transmission */ + uint8_t err_format_ver; +}; + extern int luaL_nil_ref; extern int luaL_map_metatable_ref; extern int luaL_array_metatable_ref; -- 2.7.4 ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO Leonid Vasiliev @ 2020-04-14 1:12 ` Vladislav Shpilevoy 2020-04-15 9:26 ` lvasiliev 0 siblings, 1 reply; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-14 1:12 UTC (permalink / raw) To: Leonid Vasiliev; +Cc: tarantool-patches Thanks for the patch! Sorry, some of my comments may contain typos, because it is very late and I can't continue polishing it today. See 5 comments below, review fixes in the end of the email, and on a new branch in a separate commit. On 10/04/2020 10:10, Leonid Vasiliev wrote: > IPROTO session settings for error transmission in various formats > depending on session settings have been added. > This is required for backward compatibility. What backward compatibility? Keep in mind, that the team consists not only of you, me, and Alexander T. Other people don't know what a backward compatibility is being tried to be preserved here. Just 'backward compatibility' does not tell anything at all. > @TarantoolBot document > Title: Add session_setting The doc request is corrupted, docbot won't understand that because of leading whitespaces. > iproto_error_format setting has been added to _session_settings Looks like there is a lack of global setting similar to what we had for tracebacks. Currently, when the option is false (by default), and I want to use the new format everywhere, I need to find every single place where I create a new session, and put there code saying box.session.settings.error_format = new/old/whatever I think there should be a global option when a user's application is ready to switch to the new format completely. Otherwise it is going to be hell to find all places where a new session is created, and patch them. Just a reminder - every fiber.new(), fiber.create() creates a session, every iproto connection is a session. > Used to set the error transmission format in the session. > Old format: transmits as string at IPROTO_BODY > New format: transmits as error object at IPROTO_BODY > > Needed for #4398 > --- > src/box/iproto.cc | 97 ++++++++++++++++++++++++++++++++++++++++++++++ > src/box/lua/net_box.lua | 12 ++++++ > src/box/session.cc | 3 ++ > src/box/session.h | 3 ++ > src/box/session_settings.h | 1 + > src/lua/utils.h | 20 ++++++++++ > 6 files changed, 136 insertions(+) > > diff --git a/src/box/iproto.cc b/src/box/iproto.cc > index 9dad43b..92be645 100644 > --- a/src/box/iproto.cc > +++ b/src/box/iproto.cc > @@ -2183,6 +2184,100 @@ iproto_session_push(struct session *session, uint64_t sync, struct port *port) > > /** }}} */ > > +enum { > + IPROTO_SESSION_SETTING_ERR_FORMAT = 0, > + iproto_session_setting_MAX, > +}; 1. The new setting is not about iproto only. It is about error serialization to MessagePack. MessagePack != IProto. So iproto.cc is not a good place for the new setting. > diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua > index 1e0cd7a..c8f76b0 100644 > --- a/src/box/lua/net_box.lua > +++ b/src/box/lua/net_box.lua > @@ -1047,6 +1047,18 @@ local function new_sm(host, port, opts, connection, greeting) > if opts.wait_connected ~= false then > remote._transport.wait_state('active', tonumber(opts.wait_connected)) > end > + > + -- Set extended error format for session. > + if opts.error_extended then 2. You are inconsistent in what an option do you want. In _session_setting you call it 'iproto_error_format' assuming it is a format. In practice it is a number, which, I assume, means version number. In the public API it is called 'error_extended' assuming it is a flag - either true or false. So what is it? In my review fixes I made it a flag. Because there is no way we will support multiple 'degrees' of error extension. > + local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1) 3. This won't work in case it is an instance bootstrapped from the master branch before 2.4.1 was released. I don't know how to fix it now. > + if not ext_err_supported then > + box.error(box.error.PROC_LUA, > + "Server doesn't support extended error format") > + end > + remote.space._session_settings:update('iproto_error_format', > + {{'=', 2, 1}}) 4. This is additional network hop. I don't think it should be done automatically. I would let users do that. Besides, you didn't document that option, so how were they supposed to know about it? > + end > + > return remote > end > > diff --git a/src/box/session.h b/src/box/session.h > index 6dfc7cb..a8903aa 100644 > --- a/src/box/session.h > +++ b/src/box/session.h > @@ -36,6 +36,7 @@ > #include "fiber.h" > #include "user.h" > #include "authentication.h" > +#include "lua/utils.h" 5. This is clearly broken dependency. lua/utils.h is from src/lua/, and it obviously depends on Lua language. Session is from src/box/. So it is from different hierarchy, and is not related to any language. Consider my review fixes below and on a new branch in a separate commit. Branch is: lvasiliev/gh-4398-expose-error-module-4-review ==================== Review fixes New commit message proposal: error: add session setting for error type marshaling Errors are encoded as a string when serialized to MessagePack to be sent over IProto or when just saved into a buffer via Lua modules msgpackffi and msgpack. That is not very useful on client-side, because most of the error metadata is lost: code, type, trace - everything except the message. Next commits are going to dedicate a new MP_EXT type to error objects so as everything could be encoded, and on client side it would be possible to restore types. But this is a breaking change in case some users use old connectors when work with newer Tarantool instances. So to smooth the upgrade there is a new session setting - 'error_marshaling_enabled'. By default it is false. When it is true, all fibers of the given session will serialize error objects as MP_EXT. diff --git a/src/box/iproto.cc b/src/box/iproto.cc index 92be645d2..9dad43b0b 100644 --- a/src/box/iproto.cc +++ b/src/box/iproto.cc @@ -55,7 +55,6 @@ #include "call.h" #include "tuple_convert.h" #include "session.h" -#include "session_settings.h" #include "xrow.h" #include "schema.h" /* schema_version */ #include "replication.h" /* instance_uuid */ @@ -2184,100 +2183,6 @@ iproto_session_push(struct session *session, uint64_t sync, struct port *port) /** }}} */ -enum { - IPROTO_SESSION_SETTING_ERR_FORMAT = 0, - iproto_session_setting_MAX, -}; - -static const char *iproto_session_setting_strs[iproto_session_setting_MAX] = { - "iproto_error_format", -}; - -static int iproto_session_field_type[] = { - /** IPROTO_SESSION_SETTING_ERR_FORMAT */ - FIELD_TYPE_UNSIGNED, -}; - -static void -iproto_session_setting_get(int id, const char **mp_pair, - const char **mp_pair_end) -{ - if (id < 0 || id >= iproto_session_setting_MAX) { - diag_set(ClientError, ER_ILLEGAL_PARAMS, - "unknown session setting"); - return; - } - struct session *session = current_session(); - - const char *name = iproto_session_setting_strs[id]; - size_t name_len = strlen(name); - - /* Now we have only one iproto session setting. */ - size_t size = mp_sizeof_array(2) + mp_sizeof_str(name_len) - + mp_sizeof_uint(session->serializer_ctx.err_format_ver); - - char *pos = (char*)static_alloc(size); - assert(pos != NULL); - char *pos_end = mp_encode_array(pos, 2); - pos_end = mp_encode_str(pos_end, name, name_len); - pos_end = mp_encode_uint(pos_end, - session->serializer_ctx.err_format_ver); - *mp_pair = pos; - *mp_pair_end = pos_end; -} - -static int -iproto_session_setting_set(int id, const char *mp_value) -{ - if (id < 0 || id >= iproto_session_setting_MAX) { - diag_set(ClientError, ER_ILLEGAL_PARAMS, - "unknown session setting"); - return -1; - } - /*Current IPROTO session settings are used only for BINARY session */ - if (current_session()->type != SESSION_TYPE_BINARY) - return -1; - - enum mp_type mtype = mp_typeof(*mp_value); - int stype = iproto_session_field_type[id]; - switch(stype) { - case FIELD_TYPE_UNSIGNED: { - if (mtype != MP_UINT) - break; - int val = mp_decode_uint(&mp_value); - switch (id) { - case IPROTO_SESSION_SETTING_ERR_FORMAT: - if (val >= ERR_FORMAT_UNK) - break; - current_session()->serializer_ctx.err_format_ver = val; - return 0; - default: - diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE, - iproto_session_setting_strs[id], - field_type_strs[stype]); - return -1; - } - break; - } - default: - unreachable(); - } - diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE, - iproto_session_setting_strs[id], field_type_strs[stype]); - return -1; -} - -void -iproto_session_settings_init() -{ - struct session_setting_module *module = - &session_setting_modules[SESSION_SETTING_IPROTO]; - module->settings = iproto_session_setting_strs; - module->setting_count = iproto_session_setting_MAX; - module->get = iproto_session_setting_get; - module->set = iproto_session_setting_set; -} - /** Initialize the iproto subsystem and start network io thread */ void iproto_init() @@ -2296,8 +2201,6 @@ iproto_init() /* .sync = */ iproto_session_sync, }; session_vtab_registry[SESSION_TYPE_BINARY] = iproto_session_vtab; - - iproto_session_settings_init(); } /** Available iproto configuration changes. */ diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index a1e9ee745..5c07bb07c 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -1042,18 +1042,6 @@ local function new_sm(host, port, opts, connection, greeting) if opts.wait_connected ~= false then remote._transport.wait_state('active', tonumber(opts.wait_connected)) end - - -- Set extended error format for session. - if opts.error_extended then - local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1) - if not ext_err_supported then - box.error(box.error.PROC_LUA, - "Server doesn't support extended error format") - end - remote.space._session_settings:update('iproto_error_format', - {{'=', 2, 1}}) - end - return remote end diff --git a/src/box/session.cc b/src/box/session.cc index 89435ee0c..08a10924a 100644 --- a/src/box/session.cc +++ b/src/box/session.cc @@ -144,9 +144,6 @@ session_create(enum session_type type) session->sql_default_engine = SQL_STORAGE_ENGINE_MEMTX; session->sql_stmts = NULL; - /* Set default Lua serializer context */ - session->serializer_ctx.err_format_ver = ERR_FORMAT_DEF; - /* For on_connect triggers. */ credentials_create(&session->credentials, guest_user); struct mh_i64ptr_node_t node; @@ -278,6 +275,9 @@ session_find(uint64_t sid) mh_i64ptr_node(session_registry, k)->val; } +extern "C" void +session_settings_init(void); + void session_init() { @@ -286,7 +286,7 @@ session_init() panic("out of memory"); mempool_create(&session_pool, &cord()->slabc, sizeof(struct session)); credentials_create(&admin_credentials, admin_user); - sql_session_settings_init(); + session_settings_init(); } void diff --git a/src/box/session.h b/src/box/session.h index fdc9f03d7..500a88b22 100644 --- a/src/box/session.h +++ b/src/box/session.h @@ -36,14 +36,12 @@ #include "fiber.h" #include "user.h" #include "authentication.h" -#include "lua/utils.h" +#include "serializer_opts.h" #if defined(__cplusplus) extern "C" { #endif /* defined(__cplusplus) */ -extern void sql_session_settings_init(); - struct port; struct session_vtab; @@ -91,6 +89,8 @@ struct session_meta { }; /** Console output format. */ enum output_format output_format; + /** Session-specific serialization options. */ + struct serializer_opts serializer_opts; }; /** @@ -123,8 +123,6 @@ struct session { struct credentials credentials; /** Trigger for fiber on_stop to cleanup created on-demand session */ struct trigger fiber_on_stop; - /** Session Lua serializer context */ - struct luaL_serializer_ctx serializer_ctx; }; struct session_vtab { diff --git a/src/box/session_settings.c b/src/box/session_settings.c index 79c4b8d3c..dbbbf2461 100644 --- a/src/box/session_settings.c +++ b/src/box/session_settings.c @@ -42,6 +42,7 @@ struct session_setting session_settings[SESSION_SETTING_COUNT] = {}; /** Corresponding names of session settings. */ const char *session_setting_strs[SESSION_SETTING_COUNT] = { + "error_marshaling_enabled", "sql_default_engine", "sql_defer_foreign_keys", "sql_full_column_names", @@ -449,3 +450,58 @@ session_setting_find(const char *name) { else return -1; } + +/* Module independent session settings. */ + +static void +session_setting_error_marshaling_enabled_get(int id, const char **mp_pair, + const char **mp_pair_end) +{ + assert(id == SESSION_SETTING_ERROR_MARSHALING_ENABLED); + struct session *session = current_session(); + const char *name = session_setting_strs[id]; + size_t name_len = strlen(name); + bool value = session->meta.serializer_opts.error_marshaling_enabled; + size_t size = mp_sizeof_array(2) + mp_sizeof_str(name_len) + + mp_sizeof_bool(value); + + char *pos = (char*)static_alloc(size); + assert(pos != NULL); + char *pos_end = mp_encode_array(pos, 2); + pos_end = mp_encode_str(pos_end, name, name_len); + pos_end = mp_encode_bool(pos_end, value); + *mp_pair = pos; + *mp_pair_end = pos_end; +} + +static int +session_setting_error_marshaling_enabled_set(int id, const char *mp_value) +{ + assert(id == SESSION_SETTING_ERROR_MARSHALING_ENABLED); + enum mp_type mtype = mp_typeof(*mp_value); + enum field_type stype = session_settings[id].field_type; + if (mtype != MP_BOOL) { + diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE, + session_setting_strs[id], field_type_strs[stype]); + return -1; + } + struct session *session = current_session(); + session->meta.serializer_opts.error_marshaling_enabled = + mp_decode_bool(&mp_value); + return 0; +} + +extern void +sql_session_settings_init(); + +void +session_settings_init(void) +{ + struct session_setting *s = + &session_settings[SESSION_SETTING_ERROR_MARSHALING_ENABLED]; + s->field_type = FIELD_TYPE_BOOLEAN; + s->get = session_setting_error_marshaling_enabled_get; + s->set = session_setting_error_marshaling_enabled_set; + + sql_session_settings_init(); +} diff --git a/src/lua/utils.h b/src/lua/utils.h index cac9c57b8..4bc041796 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -270,26 +270,6 @@ struct luaL_serializer { struct rlist on_update; }; -/** - * An error serialization formats - */ -enum error_formats { - /** Default(old) format */ - ERR_FORMAT_DEF, - /** Extended format */ - ERR_FORMAT_EX, - /** The max version of error format */ - ERR_FORMAT_UNK -}; - -/** - * A serializer context (additional settings for a serializer) - */ -struct luaL_serializer_ctx { - /** Version of a format for an error transmission */ - uint8_t err_format_ver; -}; - extern int luaL_nil_ref; extern int luaL_map_metatable_ref; extern int luaL_array_metatable_ref; diff --git a/src/serializer_opts.h b/src/serializer_opts.h new file mode 100644 index 000000000..9e2c15eff --- /dev/null +++ b/src/serializer_opts.h @@ -0,0 +1,44 @@ +#pragma once +/* + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/** + * Serializer options which can regulate how to serialize + * something in scope of one session. + */ +struct serializer_opts { + /** + * When enabled, error objects get their own MP_EXT + * MessagePack type and therefore can be type-safely + * transmitted over the network. + */ + bool error_marshaling_enabled; +}; diff --git a/test/box/session_settings.result b/test/box/session_settings.result index ea6302dff..149cc4bd5 100644 --- a/test/box/session_settings.result +++ b/test/box/session_settings.result @@ -52,7 +52,8 @@ s:replace({'sql_defer_foreign_keys', true}) -- s:select() | --- - | - - ['sql_default_engine', 'memtx'] + | - - ['error_marshaling_enabled', false] + | - ['sql_default_engine', 'memtx'] | - ['sql_defer_foreign_keys', false] | - ['sql_full_column_names', false] | - ['sql_full_metadata', false] ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO 2020-04-14 1:12 ` Vladislav Shpilevoy @ 2020-04-15 9:26 ` lvasiliev 2020-04-16 0:06 ` Vladislav Shpilevoy 0 siblings, 1 reply; 37+ messages in thread From: lvasiliev @ 2020-04-15 9:26 UTC (permalink / raw) To: Vladislav Shpilevoy; +Cc: tarantool-patches Hi! Thanks for the review. On 14.04.2020 4:12, Vladislav Shpilevoy wrote: > Thanks for the patch! > > Sorry, some of my comments may contain typos, because it is > very late and I can't continue polishing it today. > > See 5 comments below, review fixes in the end of the email, > and on a new branch in a separate commit. > > On 10/04/2020 10:10, Leonid Vasiliev wrote: >> IPROTO session settings for error transmission in various formats >> depending on session settings have been added. >> This is required for backward compatibility. > > What backward compatibility? Keep in mind, that the team consists not > only of you, me, and Alexander T. Other people don't know what a > backward compatibility is being tried to be preserved here. Just > 'backward compatibility' does not tell anything at all. > >> @TarantoolBot document >> Title: Add session_setting > > The doc request is corrupted, docbot won't understand that because > of leading whitespaces. > >> iproto_error_format setting has been added to _session_settings > > Looks like there is a lack of global setting similar to what we had > for tracebacks. Currently, when the option is false (by default), and > I want to use the new format everywhere, I need to find every single > place where I create a new session, and put there code saying > > box.session.settings.error_format = new/old/whatever > > I think there should be a global option when a user's application is > ready to switch to the new format completely. Otherwise it is going > to be hell to find all places where a new session is created, and patch > them. > > Just a reminder - every fiber.new(), fiber.create() creates a session, > every iproto connection is a session. This was discussed in TPT chat with Mons and Nazarov, and after that with you, Turenko, Mons ... in zoom. I was orienteted by the net.box session and I might not know something. Where do you suggest storing the setting? > >> Used to set the error transmission format in the session. >> Old format: transmits as string at IPROTO_BODY >> New format: transmits as error object at IPROTO_BODY >> >> Needed for #4398 >> --- >> src/box/iproto.cc | 97 ++++++++++++++++++++++++++++++++++++++++++++++ >> src/box/lua/net_box.lua | 12 ++++++ >> src/box/session.cc | 3 ++ >> src/box/session.h | 3 ++ >> src/box/session_settings.h | 1 + >> src/lua/utils.h | 20 ++++++++++ >> 6 files changed, 136 insertions(+) >> >> diff --git a/src/box/iproto.cc b/src/box/iproto.cc >> index 9dad43b..92be645 100644 >> --- a/src/box/iproto.cc >> +++ b/src/box/iproto.cc >> @@ -2183,6 +2184,100 @@ iproto_session_push(struct session *session, uint64_t sync, struct port *port) >> >> /** }}} */ >> >> +enum { >> + IPROTO_SESSION_SETTING_ERR_FORMAT = 0, >> + iproto_session_setting_MAX, >> +}; > > 1. The new setting is not about iproto only. It is about > error serialization to MessagePack. MessagePack != IProto. > So iproto.cc is not a good place for the new setting. ok > >> diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua >> index 1e0cd7a..c8f76b0 100644 >> --- a/src/box/lua/net_box.lua >> +++ b/src/box/lua/net_box.lua >> @@ -1047,6 +1047,18 @@ local function new_sm(host, port, opts, connection, greeting) >> if opts.wait_connected ~= false then >> remote._transport.wait_state('active', tonumber(opts.wait_connected)) >> end >> + >> + -- Set extended error format for session. >> + if opts.error_extended then > > 2. You are inconsistent in what an option do you want. In _session_setting > you call it 'iproto_error_format' assuming it is a format. In practice it > is a number, which, I assume, means version number. In the public API it > is called 'error_extended' assuming it is a flag - either true or false. > > So what is it? > > In my review fixes I made it a flag. Because there is no way we will support > multiple 'degrees' of error extension. I don't mind. > >> + local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1) > > 3. This won't work in case it is an instance bootstrapped from the master > branch before 2.4.1 was released. I don't know how to fix it now. Sorry, I think this should not work until the release. > >> + if not ext_err_supported then >> + box.error(box.error.PROC_LUA, >> + "Server doesn't support extended error format") >> + end >> + remote.space._session_settings:update('iproto_error_format', >> + {{'=', 2, 1}}) > > 4. This is additional network hop. I don't think it should be done > automatically. I would let users do that. It's network hop only if option will be set.As optimization it can be included to auth packet in future. > > Besides, you didn't document that option, so how were they supposed > to know about it? > >> + end >> + >> return remote >> end >> >> diff --git a/src/box/session.h b/src/box/session.h >> index 6dfc7cb..a8903aa 100644 >> --- a/src/box/session.h >> +++ b/src/box/session.h >> @@ -36,6 +36,7 @@ >> #include "fiber.h" >> #include "user.h" >> #include "authentication.h" >> +#include "lua/utils.h" > > 5. This is clearly broken dependency. lua/utils.h is from src/lua/, > and it obviously depends on Lua language. > > Session is from src/box/. So it is from different hierarchy, and > is not related to any language. > > > Consider my review fixes below and on a new branch in a > separate commit. > I applied your patch and added you to Co-authored. > Branch is: > > lvasiliev/gh-4398-expose-error-module-4-review > > ==================== > Review fixes > > New commit message proposal: > > error: add session setting for error type marshaling > > Errors are encoded as a string when serialized to MessagePack to > be sent over IProto or when just saved into a buffer via Lua > modules msgpackffi and msgpack. > > That is not very useful on client-side, because most of the error > metadata is lost: code, type, trace - everything except the > message. > > Next commits are going to dedicate a new MP_EXT type to error > objects so as everything could be encoded, and on client side it > would be possible to restore types. > > But this is a breaking change in case some users use old > connectors when work with newer Tarantool instances. So to smooth > the upgrade there is a new session setting - > 'error_marshaling_enabled'. > > By default it is false. When it is true, all fibers of the given > session will serialize error objects as MP_EXT. > > diff --git a/src/box/iproto.cc b/src/box/iproto.cc > index 92be645d2..9dad43b0b 100644 > --- a/src/box/iproto.cc > +++ b/src/box/iproto.cc > @@ -55,7 +55,6 @@ > #include "call.h" > #include "tuple_convert.h" > #include "session.h" > -#include "session_settings.h" > #include "xrow.h" > #include "schema.h" /* schema_version */ > #include "replication.h" /* instance_uuid */ > @@ -2184,100 +2183,6 @@ iproto_session_push(struct session *session, uint64_t sync, struct port *port) > > /** }}} */ > > -enum { > - IPROTO_SESSION_SETTING_ERR_FORMAT = 0, > - iproto_session_setting_MAX, > -}; > - > -static const char *iproto_session_setting_strs[iproto_session_setting_MAX] = { > - "iproto_error_format", > -}; > - > -static int iproto_session_field_type[] = { > - /** IPROTO_SESSION_SETTING_ERR_FORMAT */ > - FIELD_TYPE_UNSIGNED, > -}; > - > -static void > -iproto_session_setting_get(int id, const char **mp_pair, > - const char **mp_pair_end) > -{ > - if (id < 0 || id >= iproto_session_setting_MAX) { > - diag_set(ClientError, ER_ILLEGAL_PARAMS, > - "unknown session setting"); > - return; > - } > - struct session *session = current_session(); > - > - const char *name = iproto_session_setting_strs[id]; > - size_t name_len = strlen(name); > - > - /* Now we have only one iproto session setting. */ > - size_t size = mp_sizeof_array(2) + mp_sizeof_str(name_len) > - + mp_sizeof_uint(session->serializer_ctx.err_format_ver); > - > - char *pos = (char*)static_alloc(size); > - assert(pos != NULL); > - char *pos_end = mp_encode_array(pos, 2); > - pos_end = mp_encode_str(pos_end, name, name_len); > - pos_end = mp_encode_uint(pos_end, > - session->serializer_ctx.err_format_ver); > - *mp_pair = pos; > - *mp_pair_end = pos_end; > -} > - > -static int > -iproto_session_setting_set(int id, const char *mp_value) > -{ > - if (id < 0 || id >= iproto_session_setting_MAX) { > - diag_set(ClientError, ER_ILLEGAL_PARAMS, > - "unknown session setting"); > - return -1; > - } > - /*Current IPROTO session settings are used only for BINARY session */ > - if (current_session()->type != SESSION_TYPE_BINARY) > - return -1; > - > - enum mp_type mtype = mp_typeof(*mp_value); > - int stype = iproto_session_field_type[id]; > - switch(stype) { > - case FIELD_TYPE_UNSIGNED: { > - if (mtype != MP_UINT) > - break; > - int val = mp_decode_uint(&mp_value); > - switch (id) { > - case IPROTO_SESSION_SETTING_ERR_FORMAT: > - if (val >= ERR_FORMAT_UNK) > - break; > - current_session()->serializer_ctx.err_format_ver = val; > - return 0; > - default: > - diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE, > - iproto_session_setting_strs[id], > - field_type_strs[stype]); > - return -1; > - } > - break; > - } > - default: > - unreachable(); > - } > - diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE, > - iproto_session_setting_strs[id], field_type_strs[stype]); > - return -1; > -} > - > -void > -iproto_session_settings_init() > -{ > - struct session_setting_module *module = > - &session_setting_modules[SESSION_SETTING_IPROTO]; > - module->settings = iproto_session_setting_strs; > - module->setting_count = iproto_session_setting_MAX; > - module->get = iproto_session_setting_get; > - module->set = iproto_session_setting_set; > -} > - > /** Initialize the iproto subsystem and start network io thread */ > void > iproto_init() > @@ -2296,8 +2201,6 @@ iproto_init() > /* .sync = */ iproto_session_sync, > }; > session_vtab_registry[SESSION_TYPE_BINARY] = iproto_session_vtab; > - > - iproto_session_settings_init(); > } > > /** Available iproto configuration changes. */ > diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua > index a1e9ee745..5c07bb07c 100644 > --- a/src/box/lua/net_box.lua > +++ b/src/box/lua/net_box.lua > @@ -1042,18 +1042,6 @@ local function new_sm(host, port, opts, connection, greeting) > if opts.wait_connected ~= false then > remote._transport.wait_state('active', tonumber(opts.wait_connected)) > end > - > - -- Set extended error format for session. > - if opts.error_extended then > - local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1) > - if not ext_err_supported then > - box.error(box.error.PROC_LUA, > - "Server doesn't support extended error format") > - end > - remote.space._session_settings:update('iproto_error_format', > - {{'=', 2, 1}}) > - end > - > return remote > end > > diff --git a/src/box/session.cc b/src/box/session.cc > index 89435ee0c..08a10924a 100644 > --- a/src/box/session.cc > +++ b/src/box/session.cc > @@ -144,9 +144,6 @@ session_create(enum session_type type) > session->sql_default_engine = SQL_STORAGE_ENGINE_MEMTX; > session->sql_stmts = NULL; > > - /* Set default Lua serializer context */ > - session->serializer_ctx.err_format_ver = ERR_FORMAT_DEF; > - > /* For on_connect triggers. */ > credentials_create(&session->credentials, guest_user); > struct mh_i64ptr_node_t node; > @@ -278,6 +275,9 @@ session_find(uint64_t sid) > mh_i64ptr_node(session_registry, k)->val; > } > > +extern "C" void > +session_settings_init(void); > + > void > session_init() > { > @@ -286,7 +286,7 @@ session_init() > panic("out of memory"); > mempool_create(&session_pool, &cord()->slabc, sizeof(struct session)); > credentials_create(&admin_credentials, admin_user); > - sql_session_settings_init(); > + session_settings_init(); > } > > void > diff --git a/src/box/session.h b/src/box/session.h > index fdc9f03d7..500a88b22 100644 > --- a/src/box/session.h > +++ b/src/box/session.h > @@ -36,14 +36,12 @@ > #include "fiber.h" > #include "user.h" > #include "authentication.h" > -#include "lua/utils.h" > +#include "serializer_opts.h" > > #if defined(__cplusplus) > extern "C" { > #endif /* defined(__cplusplus) */ > > -extern void sql_session_settings_init(); > - > struct port; > struct session_vtab; > > @@ -91,6 +89,8 @@ struct session_meta { > }; > /** Console output format. */ > enum output_format output_format; > + /** Session-specific serialization options. */ > + struct serializer_opts serializer_opts; > }; > > /** > @@ -123,8 +123,6 @@ struct session { > struct credentials credentials; > /** Trigger for fiber on_stop to cleanup created on-demand session */ > struct trigger fiber_on_stop; > - /** Session Lua serializer context */ > - struct luaL_serializer_ctx serializer_ctx; > }; > > struct session_vtab { > diff --git a/src/box/session_settings.c b/src/box/session_settings.c > index 79c4b8d3c..dbbbf2461 100644 > --- a/src/box/session_settings.c > +++ b/src/box/session_settings.c > @@ -42,6 +42,7 @@ struct session_setting session_settings[SESSION_SETTING_COUNT] = {}; > > /** Corresponding names of session settings. */ > const char *session_setting_strs[SESSION_SETTING_COUNT] = { > + "error_marshaling_enabled", > "sql_default_engine", > "sql_defer_foreign_keys", > "sql_full_column_names", > @@ -449,3 +450,58 @@ session_setting_find(const char *name) { > else > return -1; > } > + > +/* Module independent session settings. */ > + > +static void > +session_setting_error_marshaling_enabled_get(int id, const char **mp_pair, > + const char **mp_pair_end) > +{ > + assert(id == SESSION_SETTING_ERROR_MARSHALING_ENABLED); > + struct session *session = current_session(); > + const char *name = session_setting_strs[id]; > + size_t name_len = strlen(name); > + bool value = session->meta.serializer_opts.error_marshaling_enabled; > + size_t size = mp_sizeof_array(2) + mp_sizeof_str(name_len) + > + mp_sizeof_bool(value); > + > + char *pos = (char*)static_alloc(size); > + assert(pos != NULL); > + char *pos_end = mp_encode_array(pos, 2); > + pos_end = mp_encode_str(pos_end, name, name_len); > + pos_end = mp_encode_bool(pos_end, value); > + *mp_pair = pos; > + *mp_pair_end = pos_end; > +} > + > +static int > +session_setting_error_marshaling_enabled_set(int id, const char *mp_value) > +{ > + assert(id == SESSION_SETTING_ERROR_MARSHALING_ENABLED); > + enum mp_type mtype = mp_typeof(*mp_value); > + enum field_type stype = session_settings[id].field_type; > + if (mtype != MP_BOOL) { > + diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE, > + session_setting_strs[id], field_type_strs[stype]); > + return -1; > + } > + struct session *session = current_session(); > + session->meta.serializer_opts.error_marshaling_enabled = > + mp_decode_bool(&mp_value); > + return 0; > +} > + > +extern void > +sql_session_settings_init(); > + > +void > +session_settings_init(void) > +{ > + struct session_setting *s = > + &session_settings[SESSION_SETTING_ERROR_MARSHALING_ENABLED]; > + s->field_type = FIELD_TYPE_BOOLEAN; > + s->get = session_setting_error_marshaling_enabled_get; > + s->set = session_setting_error_marshaling_enabled_set; > + > + sql_session_settings_init(); > +} > diff --git a/src/lua/utils.h b/src/lua/utils.h > index cac9c57b8..4bc041796 100644 > --- a/src/lua/utils.h > +++ b/src/lua/utils.h > @@ -270,26 +270,6 @@ struct luaL_serializer { > struct rlist on_update; > }; > > -/** > - * An error serialization formats > - */ > -enum error_formats { > - /** Default(old) format */ > - ERR_FORMAT_DEF, > - /** Extended format */ > - ERR_FORMAT_EX, > - /** The max version of error format */ > - ERR_FORMAT_UNK > -}; > - > -/** > - * A serializer context (additional settings for a serializer) > - */ > -struct luaL_serializer_ctx { > - /** Version of a format for an error transmission */ > - uint8_t err_format_ver; > -}; > - > extern int luaL_nil_ref; > extern int luaL_map_metatable_ref; > extern int luaL_array_metatable_ref; > diff --git a/src/serializer_opts.h b/src/serializer_opts.h > new file mode 100644 > index 000000000..9e2c15eff > --- /dev/null > +++ b/src/serializer_opts.h > @@ -0,0 +1,44 @@ > +#pragma once > +/* > + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. > + * > + * Redistribution and use in source and binary forms, with or > + * without modification, are permitted provided that the following > + * conditions are met: > + * > + * 1. Redistributions of source code must retain the above > + * copyright notice, this list of conditions and the > + * following disclaimer. > + * > + * 2. Redistributions in binary form must reproduce the above > + * copyright notice, this list of conditions and the following > + * disclaimer in the documentation and/or other materials > + * provided with the distribution. > + * > + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND > + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED > + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR > + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL > + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, > + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL > + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF > + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR > + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF > + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT > + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF > + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF > + * SUCH DAMAGE. > + */ > + > +/** > + * Serializer options which can regulate how to serialize > + * something in scope of one session. > + */ > +struct serializer_opts { > + /** > + * When enabled, error objects get their own MP_EXT > + * MessagePack type and therefore can be type-safely > + * transmitted over the network. > + */ > + bool error_marshaling_enabled; > +}; > diff --git a/test/box/session_settings.result b/test/box/session_settings.result > index ea6302dff..149cc4bd5 100644 > --- a/test/box/session_settings.result > +++ b/test/box/session_settings.result > @@ -52,7 +52,8 @@ s:replace({'sql_defer_foreign_keys', true}) > -- > s:select() > | --- > - | - - ['sql_default_engine', 'memtx'] > + | - - ['error_marshaling_enabled', false] > + | - ['sql_default_engine', 'memtx'] > | - ['sql_defer_foreign_keys', false] > | - ['sql_full_column_names', false] > | - ['sql_full_metadata', false] > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO 2020-04-15 9:26 ` lvasiliev @ 2020-04-16 0:06 ` Vladislav Shpilevoy 2020-04-16 9:36 ` lvasiliev 0 siblings, 1 reply; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-16 0:06 UTC (permalink / raw) To: lvasiliev; +Cc: tarantool-patches Hi! Thanks for the fixes! >>> @TarantoolBot document >>> Title: Add session_setting >> >> The doc request is corrupted, docbot won't understand that because >> of leading whitespaces. >> >>> iproto_error_format setting has been added to _session_settings >> >> Looks like there is a lack of global setting similar to what we had >> for tracebacks. Currently, when the option is false (by default), and >> I want to use the new format everywhere, I need to find every single >> place where I create a new session, and put there code saying >> >> box.session.settings.error_format = new/old/whatever >> >> I think there should be a global option when a user's application is >> ready to switch to the new format completely. Otherwise it is going >> to be hell to find all places where a new session is created, and patch >> them. >> >> Just a reminder - every fiber.new(), fiber.create() creates a session, >> every iproto connection is a session. > This was discussed in TPT chat with Mons and Nazarov, and after that with you, Turenko, Mons ... in zoom. I was orienteted by the net.box session and I might not know something. Where do you suggest storing the setting? Looks like you didn't understood me. I don't propose to remove the session local option. I propose to provide an ability to override it with a global option if necessary. So we have session option by default with the old format, and global option unset. If someone is ready to start using new errors everywhere, he just turns the global option on. If someone is not ready, he continues using session local option. Why that may be needed - I said above. Turning that option on for every session is going to be very tricky. But maybe it can be moved to the next release (the global option). Don't know. The issue is already a huge blackhole which absorbed lots of independent initially unplanned features. The more we move for later, the better. You should try to ask users in the red chat. It helps sometimes, they can share their opinions and experience of upgrades. Just formulate your question in a short but detailed manner, provide a couple of ready-to-choose answers, probably a poll. >>> + local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1) >> >> 3. This won't work in case it is an instance bootstrapped from the master >> branch before 2.4.1 was released. I don't know how to fix it now. > Sorry, I think this should not work until the release. Exactly for 'between-releases' users we introduced the new versioning schema with 4 numbers. I don't think we can just say fuck off to them now. I would better drop this from the patchset and added when somebody explicitly asks for that, with good design and planning. >> >>> + if not ext_err_supported then >>> + box.error(box.error.PROC_LUA, >>> + "Server doesn't support extended error format") >>> + end >>> + remote.space._session_settings:update('iproto_error_format', >>> + {{'=', 2, 1}}) >> >> 4. This is additional network hop. I don't think it should be done >> automatically. I would let users do that. > It's network hop only if option will be set.As optimization it can be included to auth packet in future. Auth packets have nothing to do with session settings. It would be encapsulation violation. Moreover, in addition to settings we may want to configure more things in future. What if an encryption will be added to Tarantool? What if we will add key exchange? I think we should not add a new option + 1 network hop for every 'negotiation' thing. Better omit this for now and design this properly when we have more time. I don't propose to remove your code. I just propose to finish some non-critical parts of it later. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO 2020-04-16 0:06 ` Vladislav Shpilevoy @ 2020-04-16 9:36 ` lvasiliev 2020-04-16 21:04 ` Vladislav Shpilevoy 0 siblings, 1 reply; 37+ messages in thread From: lvasiliev @ 2020-04-16 9:36 UTC (permalink / raw) To: Vladislav Shpilevoy; +Cc: tarantool-patches Hi! Thanks you for the feedback. On 16.04.2020 3:06, Vladislav Shpilevoy wrote: > Hi! Thanks for the fixes! > >>>> @TarantoolBot document >>>> Title: Add session_setting >>> >>> The doc request is corrupted, docbot won't understand that because >>> of leading whitespaces. >>> >>>> iproto_error_format setting has been added to _session_settings >>> >>> Looks like there is a lack of global setting similar to what we had >>> for tracebacks. Currently, when the option is false (by default), and >>> I want to use the new format everywhere, I need to find every single >>> place where I create a new session, and put there code saying >>> >>> box.session.settings.error_format = new/old/whatever >>> >>> I think there should be a global option when a user's application is >>> ready to switch to the new format completely. Otherwise it is going >>> to be hell to find all places where a new session is created, and patch >>> them. >>> >>> Just a reminder - every fiber.new(), fiber.create() creates a session, >>> every iproto connection is a session. >> This was discussed in TPT chat with Mons and Nazarov, and after that with you, Turenko, Mons ... in zoom. I was orienteted by the net.box session and I might not know something. Where do you suggest storing the setting? > > Looks like you didn't understood me. I don't propose to remove the > session local option. I propose to provide an ability to override it > with a global option if necessary. So we have session option by > default with the old format, and global option unset. > > If someone is ready to start using new errors everywhere, he just turns > the global option on. > > If someone is not ready, he continues using session local option. > > Why that may be needed - I said above. Turning that option on for every > session is going to be very tricky. But maybe it can be moved to the next > release (the global option). Don't know. The issue is already a huge > blackhole which absorbed lots of independent initially unplanned > features. The more we move for later, the better. > > You should try to ask users in the red chat. It helps sometimes, they > can share their opinions and experience of upgrades. Just formulate your > question in a short but detailed manner, provide a couple of > ready-to-choose answers, probably a poll. > Ok. But for beginning, I need to understand where this flag can be located. I will think about that but if you have a proposal, please, write it. >>>> + local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1) >>> >>> 3. This won't work in case it is an instance bootstrapped from the master >>> branch before 2.4.1 was released. I don't know how to fix it now. >> Sorry, I think this should not work until the release. > > Exactly for 'between-releases' users we introduced the new versioning schema > with 4 numbers. I don't think we can just say fuck off to them now. I > would better drop this from the patchset and added when somebody explicitly > asks for that, with good design and planning. > Ok.I have remove it, but:"Don't have 'between-releases' users a version more when previous release?" >>> >>>> + if not ext_err_supported then >>>> + box.error(box.error.PROC_LUA, >>>> + "Server doesn't support extended error format") >>>> + end >>>> + remote.space._session_settings:update('iproto_error_format', >>>> + {{'=', 2, 1}}) >>> >>> 4. This is additional network hop. I don't think it should be done >>> automatically. I would let users do that. >> It's network hop only if option will be set.As optimization it can be included to auth packet in future. > > Auth packets have nothing to do with session settings. It would be > encapsulation violation. Moreover, in addition to settings we may > want to configure more things in future. What if an encryption will > be added to Tarantool? What if we will add key exchange? I think we > should not add a new option + 1 network hop for every 'negotiation' > thing. Better omit this for now and design this properly when we have > more time. > > I don't propose to remove your code. I just propose to finish some > non-critical parts of it later. > ok ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO 2020-04-16 9:36 ` lvasiliev @ 2020-04-16 21:04 ` Vladislav Shpilevoy 0 siblings, 0 replies; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-16 21:04 UTC (permalink / raw) To: lvasiliev; +Cc: tarantool-patches Thanks for the answers! >>>>> @TarantoolBot document >>>>> Title: Add session_setting >>>> >>>> The doc request is corrupted, docbot won't understand that because >>>> of leading whitespaces. >>>> >>>>> iproto_error_format setting has been added to _session_settings >>>> >>>> Looks like there is a lack of global setting similar to what we had >>>> for tracebacks. Currently, when the option is false (by default), and >>>> I want to use the new format everywhere, I need to find every single >>>> place where I create a new session, and put there code saying >>>> >>>> box.session.settings.error_format = new/old/whatever >>>> >>>> I think there should be a global option when a user's application is >>>> ready to switch to the new format completely. Otherwise it is going >>>> to be hell to find all places where a new session is created, and patch >>>> them. >>>> >>>> Just a reminder - every fiber.new(), fiber.create() creates a session, >>>> every iproto connection is a session. >>> This was discussed in TPT chat with Mons and Nazarov, and after that with you, Turenko, Mons ... in zoom. I was orienteted by the net.box session and I might not know something. Where do you suggest storing the setting? >> >> Looks like you didn't understood me. I don't propose to remove the >> session local option. I propose to provide an ability to override it >> with a global option if necessary. So we have session option by >> default with the old format, and global option unset. >> >> If someone is ready to start using new errors everywhere, he just turns >> the global option on. >> >> If someone is not ready, he continues using session local option. >> >> Why that may be needed - I said above. Turning that option on for every >> session is going to be very tricky. But maybe it can be moved to the next >> release (the global option). Don't know. The issue is already a huge >> blackhole which absorbed lots of independent initially unplanned >> features. The more we move for later, the better. >> >> You should try to ask users in the red chat. It helps sometimes, they >> can share their opinions and experience of upgrades. Just formulate your >> question in a short but detailed manner, provide a couple of >> ready-to-choose answers, probably a poll. >> > Ok. But for beginning, I need to understand where this flag can be > located. I will think about that but if you have a proposal, please, > write it. box.cfg is a first option, but naive - error objects exist before box.cfg is called. I would go for box.error.cfg(). It would look fine - one place for configuring traceback and serialization format. >>>>> + local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1) >>>> >>>> 3. This won't work in case it is an instance bootstrapped from the master >>>> branch before 2.4.1 was released. I don't know how to fix it now. >>> Sorry, I think this should not work until the release. >> >> Exactly for 'between-releases' users we introduced the new versioning schema >> with 4 numbers. I don't think we can just say fuck off to them now. I >> would better drop this from the patchset and added when somebody explicitly >> asks for that, with good design and planning. >> > Ok.I have remove it, but:"Don't have 'between-releases' users a version more when previous release?" First 3 numbers stay the same, and 4th is not transmitted to clients (from what I remember). If correctly parsed your question, because I am not sure I did. ^ permalink raw reply [flat|nested] 37+ messages in thread
* [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding 2020-04-10 8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev ` (3 preceding siblings ...) 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO Leonid Vasiliev @ 2020-04-10 8:10 ` Leonid Vasiliev 2020-04-14 1:12 ` Vladislav Shpilevoy 2020-04-14 1:10 ` [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Vladislav Shpilevoy 5 siblings, 1 reply; 37+ messages in thread From: Leonid Vasiliev @ 2020-04-10 8:10 UTC (permalink / raw) To: v.shpilevoy; +Cc: tarantool-patches New MsgPack encode format for error (MP_ERROR) has been added. If the extended error format is enabled using iproto session settings MP_ERROR type will be used for transferring error through network, MP_STR was used before. Needed for #4398 --- src/box/CMakeLists.txt | 1 + src/box/error.cc | 28 ++- src/box/error.h | 11 +- src/box/lua/call.c | 29 ++- src/box/lua/execute.c | 2 +- src/box/lua/init.c | 56 +++++ src/box/lua/mp_error.cc | 454 +++++++++++++++++++++++++++++++++++ src/box/lua/mp_error.h | 49 ++++ src/box/lua/net_box.lua | 2 +- src/box/lua/tuple.c | 28 +-- src/box/sql/func.c | 4 +- src/lib/core/mp_extension_types.h | 5 +- src/lua/error.c | 2 - src/lua/error.h | 3 +- src/lua/msgpack.c | 28 ++- src/lua/msgpack.h | 8 +- src/lua/utils.c | 16 +- src/lua/utils.h | 6 +- test/box-tap/extended_error.test.lua | 164 +++++++++++++ 19 files changed, 821 insertions(+), 75 deletions(-) create mode 100644 src/box/lua/mp_error.cc create mode 100644 src/box/lua/mp_error.h create mode 100755 test/box-tap/extended_error.test.lua diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 56758bd..32415b5 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -151,6 +151,7 @@ add_library(box STATIC lua/stat.c lua/ctl.c lua/error.cc + lua/mp_error.cc lua/session.c lua/net_box.c lua/xlog.c diff --git a/src/box/error.cc b/src/box/error.cc index 8179e52..f2e60c1 100644 --- a/src/box/error.cc +++ b/src/box/error.cc @@ -253,6 +253,13 @@ XlogGapError::XlogGapError(const char *file, unsigned line, (long long) vclock_sum(to), s_to ? s_to : ""); } +XlogGapError::XlogGapError(const char *file, unsigned line, + const char *msg) + : XlogError(&type_XlogGapError, file, line) +{ + error_format_msg(this, "%s", msg); +} + struct error * BuildXlogGapError(const char *file, unsigned line, const struct vclock *from, const struct vclock *to) @@ -264,6 +271,16 @@ BuildXlogGapError(const char *file, unsigned line, } } +struct error * +ReBuildXlogGapError(const char *file, unsigned line, const char *msg) +{ + try { + return new XlogGapError(file, line, msg); + } catch (OutOfMemory *e) { + return e; + } +} + struct rlist on_access_denied = RLIST_HEAD_INITIALIZER(on_access_denied); static struct method_info accessdeniederror_methods[] = { @@ -289,15 +306,8 @@ AccessDeniedError::AccessDeniedError(const char *file, unsigned int line, struct on_access_denied_ctx ctx = {access_type, object_type, object_name}; trigger_run(&on_access_denied, (void *) &ctx); - /* - * We want to use ctx parameters as error parameters - * later, so we have to alloc space for it. - * As m_access_type and m_object_type are constant - * literals they are statically allocated. We must copy - * only m_object_name. - */ - m_object_type = object_type; - m_access_type = access_type; + m_object_type = strdup(object_type); + m_access_type = strdup(access_type); m_object_name = strdup(object_name); } diff --git a/src/box/error.h b/src/box/error.h index 5013488..d4d064b 100644 --- a/src/box/error.h +++ b/src/box/error.h @@ -54,6 +54,9 @@ BuildXlogGapError(const char *file, unsigned line, const struct vclock *from, const struct vclock *to); struct error * +ReBuildXlogGapError(const char *file, unsigned line, const char *msg); + +struct error * BuildCustomError(const char *file, unsigned int line, const char *custom_type); /** \cond public */ @@ -250,6 +253,8 @@ public: ~AccessDeniedError() { free(m_object_name); + free(m_object_type); + free(m_access_type); } const char * @@ -272,11 +277,11 @@ public: private: /** Type of object the required access was denied to */ - const char *m_object_type; + char *m_object_type; /** Name of object the required access was denied to */ char *m_object_name; /** Type of declined access */ - const char *m_access_type; + char *m_access_type; }; /** @@ -306,6 +311,8 @@ struct XlogGapError: public XlogError { XlogGapError(const char *file, unsigned line, const struct vclock *from, const struct vclock *to); + XlogGapError(const char *file, unsigned line, + const char *msg); virtual void raise() { throw this; } }; diff --git a/src/box/lua/call.c b/src/box/lua/call.c index 5d3579e..6d1d247 100644 --- a/src/box/lua/call.c +++ b/src/box/lua/call.c @@ -46,6 +46,7 @@ #include "small/obuf.h" #include "trivia/util.h" #include "mpstream.h" +#include "box/session.h" /** * A helper to find a Lua function by name and put it @@ -174,7 +175,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, */ for (int i = 1; i <= nrets; ++i) { struct luaL_field field; - if (luaL_tofield(L, cfg, i, &field) < 0) + if (luaL_tofield(L, cfg, NULL, i, &field) < 0) return luaT_error(L); struct tuple *tuple; if (field.type == MP_EXT && @@ -188,11 +189,11 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, */ lua_pushvalue(L, i); mpstream_encode_array(stream, 1); - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); lua_pop(L, 1); } else { /* `return ..., array, ...` */ - luamp_encode(L, cfg, stream, i); + luamp_encode(L, cfg, NULL, stream, i); } } return nrets; @@ -203,7 +204,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, * Inspect the first result */ struct luaL_field root; - if (luaL_tofield(L, cfg, 1, &root) < 0) + if (luaL_tofield(L, cfg, NULL, 1, &root) < 0) return luaT_error(L); struct tuple *tuple; if (root.type == MP_EXT && (tuple = luaT_istuple(L, 1)) != NULL) { @@ -217,7 +218,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, */ mpstream_encode_array(stream, 1); assert(lua_gettop(L) == 1); - luamp_encode_r(L, cfg, stream, &root, 0); + luamp_encode_r(L, cfg, NULL, stream, &root, 0); return 1; } @@ -233,7 +234,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, for (uint32_t t = 1; t <= root.size; t++) { lua_rawgeti(L, 1, t); struct luaL_field field; - if (luaL_tofield(L, cfg, -1, &field) < 0) + if (luaL_tofield(L, cfg, NULL, -1, &field) < 0) return luaT_error(L); if (field.type == MP_EXT && (tuple = luaT_istuple(L, -1))) { tuple_to_mpstream(tuple, stream); @@ -249,13 +250,13 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, * Encode the first field of tuple using * existing information from luaL_tofield */ - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); lua_pop(L, 1); assert(lua_gettop(L) == 1); /* Encode remaining fields as usual */ for (uint32_t f = 2; f <= root.size; f++) { lua_rawgeti(L, 1, f); - luamp_encode(L, cfg, stream, -1); + luamp_encode(L, cfg, NULL, stream, -1); lua_pop(L, 1); } return 1; @@ -265,10 +266,10 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, * { tuple/array, ..., { scalar }, ... }` */ mpstream_encode_array(stream, 1); - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); } else { /* `return { tuple/array, ..., tuple/array, ... }` */ - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); } lua_pop(L, 1); assert(lua_gettop(L) == 1); @@ -379,6 +380,7 @@ execute_lua_eval(lua_State *L) struct encode_lua_ctx { struct port_lua *port; struct mpstream *stream; + struct luaL_serializer_ctx *serializer_ctx; }; static int @@ -393,8 +395,10 @@ encode_lua_call(lua_State *L) */ struct luaL_serializer *cfg = luaL_msgpack_default; int size = lua_gettop(ctx->port->L); - for (int i = 1; i <= size; ++i) - luamp_encode(ctx->port->L, cfg, ctx->stream, i); + for (int i = 1; i <= size; ++i) { + luamp_encode(ctx->port->L, cfg, ctx->serializer_ctx, + ctx->stream, i); + } ctx->port->size = size; mpstream_flush(ctx->stream); return 0; @@ -429,6 +433,7 @@ port_lua_do_dump(struct port *base, struct mpstream *stream, struct encode_lua_ctx ctx; ctx.port = port; ctx.stream = stream; + ctx.serializer_ctx = ¤t_session()->serializer_ctx; struct lua_State *L = tarantool_L; int top = lua_gettop(L); if (lua_cpcall(L, handler, &ctx) != 0) { diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c index b4843b2..b5db3c9 100644 --- a/src/box/lua/execute.c +++ b/src/box/lua/execute.c @@ -328,7 +328,7 @@ lua_sql_bind_decode(struct lua_State *L, struct sql_bind *bind, int idx, int i) bind->name = NULL; bind->name_len = 0; } - if (luaL_tofield(L, luaL_msgpack_default, -1, &field) < 0) + if (luaL_tofield(L, luaL_msgpack_default, NULL, -1, &field) < 0) return -1; switch (field.type) { case MP_UINT: diff --git a/src/box/lua/init.c b/src/box/lua/init.c index 63e8b82..b57df4c 100644 --- a/src/box/lua/init.c +++ b/src/box/lua/init.c @@ -36,11 +36,13 @@ #include "lua/utils.h" /* luaT_error() */ #include "lua/trigger.h" +#include "lua/msgpack.h" #include "box/box.h" #include "box/txn.h" #include "box/func.h" #include "box/vclock.h" +#include "box/session.h" #include "box/lua/error.h" #include "box/lua/tuple.h" @@ -62,6 +64,9 @@ #include "box/lua/execute.h" #include "box/lua/key_def.h" #include "box/lua/merger.h" +#include "box/lua/mp_error.h" + +#include "mpstream.h" static uint32_t CTID_STRUCT_TXN_SAVEPOINT_PTR = 0; @@ -386,6 +391,54 @@ static const struct luaL_Reg boxlib_backup[] = { {NULL, NULL} }; +/** + * A MsgPack extensions handler that supports tuples and errors encode. + */ +static enum mp_type +luamp_encode_extension_box(struct lua_State *L, int idx, + struct mpstream *stream) +{ + struct tuple *tuple = luaT_istuple(L, idx); + if (tuple != NULL) { + tuple_to_mpstream(tuple, stream); + return MP_ARRAY; + } + + if (current_session()->serializer_ctx.err_format_ver == ERR_FORMAT_EX) { + struct error *err = luaL_iserror(L, idx); + if (err != NULL) + error_to_mpstream(err, stream); + } + + return MP_EXT; +} + +/** + * A MsgPack extensions handler that supports errors decode. + */ +static void +luamp_decode_extension_box(struct lua_State *L, const char **data) +{ + assert(mp_typeof(**data) == MP_EXT); + int8_t ext_type; + uint32_t len = mp_decode_extl(data, &ext_type); + + if(ext_type != MP_ERROR) { + luaL_error(L, "Unsuported MsgPack extension type: %u", + ext_type); + return; + } + + struct error *err = error_unpack(data, len); + if (err == NULL) { + luaL_error(L, "Can't parse an error from MsgPack"); + return; + } + + luaT_pusherror(L, err); + return; +} + #include "say.h" void @@ -426,6 +479,9 @@ box_lua_init(struct lua_State *L) luaopen_merger(L); lua_pop(L, 1); + luamp_set_encode_extension(luamp_encode_extension_box); + luamp_set_decode_extension(luamp_decode_extension_box); + /* Load Lua extension */ for (const char **s = lua_sources; *s; s += 2) { const char *modname = *s; diff --git a/src/box/lua/mp_error.cc b/src/box/lua/mp_error.cc new file mode 100644 index 0000000..f99da0f --- /dev/null +++ b/src/box/lua/mp_error.cc @@ -0,0 +1,454 @@ +/* + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "box/lua/mp_error.h" +#include "box/error.h" +#include "mpstream.h" +#include "msgpuck.h" +#include "mp_extension_types.h" + +enum mp_error_details { + MP_ERROR_DET_TYPE, + MP_ERROR_DET_FILE, + MP_ERROR_DET_LINE, + MP_ERROR_DET_REASON, + MP_ERROR_DET_ERRNO, + MP_ERROR_DET_CODE, + MP_ERROR_DET_BACKTRACE, + MP_ERROR_DET_CUSTOM_TYPE, + MP_ERROR_DET_AD_OBJ_TYPE, + MP_ERROR_DET_AD_OBJ_NAME, + MP_ERROR_DET_AD_ACCESS_TYPE +}; + +enum mp_error_types { + MP_ERROR_TYPE_UNKNOWN, + MP_ERROR_TYPE_CLIENT, + MP_ERROR_TYPE_CUSTOM, + MP_ERROR_TYPE_ACCESS_DENIED, + MP_ERROR_TYPE_XLOG, + MP_ERROR_TYPE_XLOG_GAP, + MP_ERROR_TyPE_SYSTEM, + MP_ERROR_TyPE_SOCKET, + MP_ERROR_TyPE_OOM, + MP_ERROR_TyPE_TIMED_OUT, + MP_ERROR_TyPE_CHANNEL_IS_CLOSED, + MP_ERROR_TyPE_FIBER_IS_CANCELLED, + MP_ERROR_TyPE_LUAJIT, + MP_ERROR_TyPE_ILLEGAL_PARAMS, + MP_ERROR_TyPE_COLLATION, + MP_ERROR_TyPE_SWIM, + MP_ERROR_TyPE_CRYPTO +}; + +struct mp_error { + uint32_t error_code; + uint8_t error_type; + uint32_t line; + uint32_t saved_errno; + char *file; + char *backtrace; + char *reason; + char *custom_type; + char *ad_obj_type; + char *ad_obj_name; + char *ad_access_type; +}; + +static void +mp_error_init(struct mp_error *mp_error) +{ + mp_error->error_type = MP_ERROR_TYPE_UNKNOWN; + mp_error->file = NULL; + mp_error->backtrace = NULL; + mp_error->reason = NULL; + mp_error->custom_type = NULL; + mp_error->ad_obj_type = NULL; + mp_error->ad_obj_name = NULL; + mp_error->ad_access_type = NULL; +} + +static void +mp_error_cleanup(struct mp_error *mp_error) +{ + mp_error->error_type = MP_ERROR_TYPE_UNKNOWN; + free(mp_error->file); + free(mp_error->backtrace); + free(mp_error->reason); + free(mp_error->custom_type); + free(mp_error->ad_obj_type); + free(mp_error->ad_obj_name); + free(mp_error->ad_access_type); +} + +static uint8_t +mp_error_type_from_str(const char *type_str) +{ + if (type_str == NULL) { + return MP_ERROR_TYPE_UNKNOWN; + } else if (strcmp(type_str, "ClientError") == 0) { + return MP_ERROR_TYPE_CLIENT; + } else if (strcmp(type_str, "CustomError") == 0) { + return MP_ERROR_TYPE_CUSTOM; + } else if (strcmp(type_str, "AccessDeniedError") == 0) { + return MP_ERROR_TYPE_ACCESS_DENIED; + } else if (strcmp(type_str, "XlogError") == 0) { + return MP_ERROR_TYPE_XLOG; + } else if (strcmp(type_str, "XlogGapError") == 0) { + return MP_ERROR_TYPE_XLOG_GAP; + } else if (strcmp(type_str, "SystemError") == 0) { + return MP_ERROR_TyPE_SYSTEM; + } else if (strcmp(type_str, "SocketError") == 0) { + return MP_ERROR_TyPE_SOCKET; + } else if (strcmp(type_str, "OutOfMemory") == 0) { + return MP_ERROR_TyPE_OOM; + } else if (strcmp(type_str, "TimedOut") == 0) { + return MP_ERROR_TyPE_TIMED_OUT; + } else if (strcmp(type_str, "ChannelIsClosed") == 0) { + return MP_ERROR_TyPE_CHANNEL_IS_CLOSED; + } else if (strcmp(type_str, "FiberIsCancelled") == 0) { + return MP_ERROR_TyPE_FIBER_IS_CANCELLED; + } else if (strcmp(type_str, "LuajitError") == 0) { + return MP_ERROR_TyPE_LUAJIT; + } else if (strcmp(type_str, "IllegalParams") == 0) { + return MP_ERROR_TyPE_ILLEGAL_PARAMS; + } else if (strcmp(type_str, "CollationError") == 0) { + return MP_ERROR_TyPE_COLLATION; + } else if (strcmp(type_str, "SwimError") == 0) { + return MP_ERROR_TyPE_SWIM; + } else if (strcmp(type_str, "CryptoError") == 0) { + return MP_ERROR_TyPE_CRYPTO; + } + + return MP_ERROR_TYPE_UNKNOWN; +} + +void +error_to_mpstream(struct error *error, struct mpstream *stream) +{ + uint8_t err_type = mp_error_type_from_str(box_error_type(error)); + + uint32_t errcode; + const char *custom_type = NULL; + const char *ad_obj_type = NULL; + const char *ad_obj_name = NULL; + const char *ad_access_type = NULL; + + /* Error type, reason, errno, file and line are the necessary fields */ + uint32_t details_num = 5; + + uint32_t data_size = 0; + + data_size += mp_sizeof_uint(MP_ERROR_DET_TYPE); + data_size += mp_sizeof_uint(err_type); + data_size += mp_sizeof_uint(MP_ERROR_DET_LINE); + data_size += mp_sizeof_uint(error->line); + data_size += mp_sizeof_uint(MP_ERROR_DET_FILE); + data_size += mp_sizeof_str(strlen(error->file)); + data_size += mp_sizeof_uint(MP_ERROR_DET_REASON); + data_size += mp_sizeof_str(strlen(error->errmsg)); + data_size += mp_sizeof_uint(MP_ERROR_DET_ERRNO); + data_size += mp_sizeof_uint(error->saved_errno); + + if (error->lua_traceback) { + ++details_num; + data_size += mp_sizeof_uint(MP_ERROR_DET_BACKTRACE); + data_size += mp_sizeof_str(strlen(error->lua_traceback)); + } + + if (err_type == MP_ERROR_TYPE_CLIENT || + err_type == MP_ERROR_TYPE_ACCESS_DENIED || + err_type == MP_ERROR_TYPE_CUSTOM) { + ++details_num; + errcode = box_error_code(error); + data_size += mp_sizeof_uint(MP_ERROR_DET_CODE); + data_size += mp_sizeof_uint(errcode); + if (err_type == MP_ERROR_TYPE_CUSTOM) { + ++details_num; + data_size += mp_sizeof_uint(MP_ERROR_DET_CUSTOM_TYPE); + custom_type = box_custom_error_type(error); + data_size += mp_sizeof_str(strlen(custom_type)); + } else if (err_type == MP_ERROR_TYPE_ACCESS_DENIED) { + AccessDeniedError *ad_err = type_cast(AccessDeniedError, + error); + details_num += 3; + ad_obj_type = ad_err->object_type(); + ad_obj_name = ad_err->object_name(); + ad_access_type = ad_err->access_type(); + data_size += mp_sizeof_uint(MP_ERROR_DET_AD_OBJ_TYPE); + data_size += mp_sizeof_str(strlen(ad_obj_type)); + data_size += mp_sizeof_uint(MP_ERROR_DET_AD_OBJ_NAME); + data_size += mp_sizeof_str(strlen(ad_obj_name)); + data_size += mp_sizeof_uint(MP_ERROR_DET_AD_ACCESS_TYPE); + data_size += mp_sizeof_str(strlen(ad_access_type)); + } + } + + data_size += mp_sizeof_map(details_num); + uint32_t data_size_ext = mp_sizeof_ext(data_size); + char *ptr = mpstream_reserve(stream, data_size_ext); + + char *data = ptr; + data = mp_encode_extl(data, MP_ERROR, data_size); + data = mp_encode_map(data, details_num); + data = mp_encode_uint(data, MP_ERROR_DET_TYPE); + data = mp_encode_uint(data, err_type); + data = mp_encode_uint(data, MP_ERROR_DET_LINE); + data = mp_encode_uint(data, err_type); + data = mp_encode_uint(data, MP_ERROR_DET_FILE); + data = mp_encode_str(data, error->file, strlen(error->file)); + data = mp_encode_uint(data, MP_ERROR_DET_REASON); + data = mp_encode_str(data, error->errmsg, strlen(error->errmsg)); + data = mp_encode_uint(data, MP_ERROR_DET_ERRNO); + data = mp_encode_uint(data, error->saved_errno); + if(error->lua_traceback) { + data = mp_encode_uint(data, MP_ERROR_DET_BACKTRACE); + data = mp_encode_str(data, error->lua_traceback, + strlen(error->lua_traceback)); + } + + if (err_type == MP_ERROR_TYPE_CLIENT || + err_type == MP_ERROR_TYPE_ACCESS_DENIED || + err_type == MP_ERROR_TYPE_CUSTOM) { + data = mp_encode_uint(data, MP_ERROR_DET_CODE); + data = mp_encode_uint(data, errcode); + if (err_type == MP_ERROR_TYPE_CUSTOM) { + data = mp_encode_uint(data, MP_ERROR_DET_CUSTOM_TYPE); + data = mp_encode_str(data, custom_type, + strlen(custom_type)); + } else if (err_type == MP_ERROR_TYPE_ACCESS_DENIED) { + data = mp_encode_uint(data, MP_ERROR_DET_AD_OBJ_TYPE); + data = mp_encode_str(data, ad_obj_type, + strlen(ad_obj_type)); + data = mp_encode_uint(data, MP_ERROR_DET_AD_OBJ_NAME); + data = mp_encode_str(data, ad_obj_name, + strlen(ad_obj_name)); + data = mp_encode_uint(data, MP_ERROR_DET_AD_ACCESS_TYPE); + data = mp_encode_str(data, ad_access_type, + strlen(ad_access_type)); + } + } + + assert(data == ptr + data_size_ext); + mpstream_advance(stream, data_size_ext); +} + +static struct error * +build_error(struct mp_error *mp_error) +{ + struct error *err; + switch (mp_error->error_type) { + case MP_ERROR_TYPE_UNKNOWN: + err = NULL; + break; + case MP_ERROR_TYPE_CLIENT: + { + ClientError *e = new ClientError(mp_error->file, mp_error->line, + ER_UNKNOWN); + e->m_errcode = mp_error->error_code; + err = (struct error *)e; + break; + } + case MP_ERROR_TYPE_CUSTOM: + err = BuildCustomError(mp_error->file, mp_error->line, + mp_error->custom_type); + break; + case MP_ERROR_TYPE_ACCESS_DENIED: + err = BuildAccessDeniedError(mp_error->file, mp_error->line, + mp_error->ad_access_type, + mp_error->ad_obj_type, + mp_error->ad_obj_name, ""); + break; + case MP_ERROR_TYPE_XLOG: + err = BuildXlogError(mp_error->file, mp_error->line, + "%s", mp_error->reason); + break; + case MP_ERROR_TYPE_XLOG_GAP: + err = ReBuildXlogGapError(mp_error->file, mp_error->line, + mp_error->reason); + break; + case MP_ERROR_TyPE_SYSTEM: + err = BuildSystemError(mp_error->file, mp_error->line, + "%s", mp_error->reason); + break; + case MP_ERROR_TyPE_SOCKET: + err = BuildSocketError(mp_error->file, mp_error->line, "", ""); + error_format_msg(err, "", mp_error->reason); + break; + case MP_ERROR_TyPE_OOM: + err = BuildOutOfMemory(mp_error->file, mp_error->line, + 0, "", ""); + error_format_msg(err, "%s", mp_error->reason); + break; + case MP_ERROR_TyPE_TIMED_OUT: + err = BuildTimedOut(mp_error->file, mp_error->line); + break; + case MP_ERROR_TyPE_CHANNEL_IS_CLOSED: + err = BuildChannelIsClosed(mp_error->file, mp_error->line); + break; + case MP_ERROR_TyPE_FIBER_IS_CANCELLED: + err = BuildFiberIsCancelled(mp_error->file, mp_error->line); + break; + case MP_ERROR_TyPE_LUAJIT: + err = BuildLuajitError(mp_error->file, mp_error->line, + mp_error->reason); + break; + case MP_ERROR_TyPE_ILLEGAL_PARAMS: + err = BuildIllegalParams(mp_error->file, mp_error->line, + "%s", mp_error->reason); + break; + case MP_ERROR_TyPE_COLLATION: + err = BuildCollationError(mp_error->file, mp_error->line, + "%s", mp_error->reason); + break; + case MP_ERROR_TyPE_SWIM: + err = BuildSwimError(mp_error->file, mp_error->line, + "%s", mp_error->reason); + break; + case MP_ERROR_TyPE_CRYPTO: + err = BuildCryptoError(mp_error->file, mp_error->line, + "%s", mp_error->reason); + break; + default: + break; + } + + err->traceback_mode = false; + err->saved_errno = mp_error->saved_errno; + error_format_msg(err, "%s", mp_error->reason); + + return err; +} + +struct error * +error_unpack(const char **data, uint32_t len) +{ + const char *svp = *data; + if (mp_typeof(**data) != MP_MAP) { + diag_set(ClientError, ER_INVALID_MSGPACK, + "Invalid MP_ERROR format"); + return NULL; + } + + struct mp_error mp_err; + mp_error_init(&mp_err); + + uint32_t map_size = mp_decode_map(data); + + struct error *err = NULL; + for (uint32_t i = 0; i < map_size; ++i) { + if (mp_typeof(**data) != MP_UINT) { + diag_set(ClientError, ER_INVALID_MSGPACK, + "Invalid MP_ERROR MsgPack format"); + return NULL; + } + + uint8_t key = mp_decode_uint(data); + const char *str; + uint32_t str_len; + switch(key) { + case MP_ERROR_DET_TYPE: + if (mp_typeof(**data) != MP_UINT) + goto error; + mp_err.error_type = mp_decode_uint(data); + break; + case MP_ERROR_DET_FILE: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.file = strndup(str, str_len); + break; + case MP_ERROR_DET_LINE: + if (mp_typeof(**data) != MP_UINT) + goto error; + mp_err.line = mp_decode_uint(data); + break; + case MP_ERROR_DET_REASON: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.reason = strndup(str, str_len); + break; + case MP_ERROR_DET_ERRNO: + if (mp_typeof(**data) != MP_UINT) + goto error; + mp_err.saved_errno = mp_decode_uint(data); + break; + case MP_ERROR_DET_CODE: + if (mp_typeof(**data) != MP_UINT) + goto error; + mp_err.error_code = mp_decode_uint(data); + break; + case MP_ERROR_DET_BACKTRACE: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.backtrace = strndup(str, str_len); + break; + case MP_ERROR_DET_CUSTOM_TYPE: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.custom_type = strndup(str, str_len); + break; + case MP_ERROR_DET_AD_OBJ_TYPE: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.ad_obj_type = strndup(str, str_len); + break; + case MP_ERROR_DET_AD_OBJ_NAME: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.ad_obj_name = strndup(str, str_len); + break; + case MP_ERROR_DET_AD_ACCESS_TYPE: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.ad_access_type = strndup(str, str_len); + break; + default: + mp_next(data); + } + } + + assert(*data == svp + len); + + err = build_error(&mp_err); + mp_error_cleanup(&mp_err); + return err; + +error: + diag_set(ClientError, ER_INVALID_MSGPACK, + "Invalid MP_ERROR MsgPack format"); + return NULL; +} diff --git a/src/box/lua/mp_error.h b/src/box/lua/mp_error.h new file mode 100644 index 0000000..9eab213 --- /dev/null +++ b/src/box/lua/mp_error.h @@ -0,0 +1,49 @@ +#pragma once +/* + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +#include "stdint.h" + +struct mpstream; + +void +error_to_mpstream(struct error *error, struct mpstream *stream); + +struct error * +error_unpack(const char **data, uint32_t len); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index c8f76b0..d3997af 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -1050,7 +1050,7 @@ local function new_sm(host, port, opts, connection, greeting) -- Set extended error format for session. if opts.error_extended then - local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1) + local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 0) if not ext_err_supported then box.error(box.error.PROC_LUA, "Server doesn't support extended error format") diff --git a/src/box/lua/tuple.c b/src/box/lua/tuple.c index 1e3c3d6..af2c5b4 100644 --- a/src/box/lua/tuple.c +++ b/src/box/lua/tuple.c @@ -120,7 +120,7 @@ luaT_tuple_new(struct lua_State *L, int idx, box_tuple_format_t *format) int argc = lua_gettop(L); mpstream_encode_array(&stream, argc); for (int k = 1; k <= argc; ++k) { - luamp_encode(L, luaL_msgpack_default, &stream, k); + luamp_encode(L, luaL_msgpack_default, NULL, &stream, k); } } else { /* Create the tuple from a Lua table. */ @@ -252,18 +252,18 @@ luamp_convert_key(struct lua_State *L, struct luaL_serializer *cfg, return tuple_to_mpstream(tuple, stream); struct luaL_field field; - if (luaL_tofield(L, cfg, index, &field) < 0) + if (luaL_tofield(L, cfg, NULL, index, &field) < 0) luaT_error(L); if (field.type == MP_ARRAY) { lua_pushvalue(L, index); - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); lua_pop(L, 1); } else if (field.type == MP_NIL) { mpstream_encode_array(stream, 0); } else { mpstream_encode_array(stream, 1); lua_pushvalue(L, index); - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); lua_pop(L, 1); } } @@ -275,7 +275,7 @@ luamp_encode_tuple(struct lua_State *L, struct luaL_serializer *cfg, struct tuple *tuple = luaT_istuple(L, index); if (tuple != NULL) { return tuple_to_mpstream(tuple, stream); - } else if (luamp_encode(L, cfg, stream, index) != MP_ARRAY) { + } else if (luamp_encode(L, cfg, NULL, stream, index) != MP_ARRAY) { diag_set(ClientError, ER_TUPLE_NOT_ARRAY); luaT_error(L); } @@ -290,20 +290,6 @@ tuple_to_mpstream(struct tuple *tuple, struct mpstream *stream) mpstream_advance(stream, bsize); } -/* A MsgPack extensions handler that supports tuples */ -static enum mp_type -luamp_encode_extension_box(struct lua_State *L, int idx, - struct mpstream *stream) -{ - struct tuple *tuple = luaT_istuple(L, idx); - if (tuple != NULL) { - tuple_to_mpstream(tuple, stream); - return MP_ARRAY; - } - - return MP_EXT; -} - /** * Convert a tuple into lua table. Named fields are stored as * {name = value} pairs. Not named fields are stored as @@ -435,7 +421,7 @@ lbox_tuple_transform(struct lua_State *L) mpstream_encode_array(&stream, 3); mpstream_encode_str(&stream, "!"); mpstream_encode_uint(&stream, offset); - luamp_encode(L, luaL_msgpack_default, &stream, i); + luamp_encode(L, luaL_msgpack_default, NULL, &stream, i); } mpstream_flush(&stream); @@ -582,8 +568,6 @@ box_lua_tuple_init(struct lua_State *L) luaL_register_module(L, tuplelib_name, lbox_tuplelib); lua_pop(L, 1); - luamp_set_encode_extension(luamp_encode_extension_box); - tuple_serializer_update_options(); trigger_create(&tuple_serializer.update_trigger, on_msgpack_serializer_update, NULL, NULL); diff --git a/src/box/sql/func.c b/src/box/sql/func.c index 6e724c8..bbc1f6f 100644 --- a/src/box/sql/func.c +++ b/src/box/sql/func.c @@ -257,8 +257,10 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size) return NULL; for (int i = 0; i < argc; i++) { struct luaL_field field; - if (luaL_tofield(L, luaL_msgpack_default, -1 - i, &field) < 0) + if (luaL_tofield(L, luaL_msgpack_default, + NULL, -1 - i, &field) < 0) { goto error; + } switch (field.type) { case MP_BOOL: mem_set_bool(&val[i], field.bval); diff --git a/src/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h index bc9873f..a2a5079 100644 --- a/src/lib/core/mp_extension_types.h +++ b/src/lib/core/mp_extension_types.h @@ -40,8 +40,9 @@ * You may assign values in range [0, 127] */ enum mp_extension_type { - MP_UNKNOWN_EXTENSION = 0, - MP_DECIMAL = 1, + MP_UNKNOWN_EXTENSION = 0, + MP_DECIMAL = 1, + MP_ERROR = 2 }; #endif diff --git a/src/lua/error.c b/src/lua/error.c index cd6ab54..109f947 100644 --- a/src/lua/error.c +++ b/src/lua/error.c @@ -34,8 +34,6 @@ #include <fiber.h> #include "utils.h" -static int CTID_CONST_STRUCT_ERROR_REF = 0; - struct error * luaL_iserror(struct lua_State *L, int narg) { diff --git a/src/lua/error.h b/src/lua/error.h index 16cdaf7..4e4dc04 100644 --- a/src/lua/error.h +++ b/src/lua/error.h @@ -37,7 +37,6 @@ extern "C" { #endif /* defined(__cplusplus) */ - /** \cond public */ struct error; @@ -62,6 +61,8 @@ void luaT_pusherror(struct lua_State *L, struct error *e); /** \endcond public */ +extern uint32_t CTID_CONST_STRUCT_ERROR_REF; + struct error * luaL_iserror(struct lua_State *L, int narg); diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c index edbc15b..6e0cf15 100644 --- a/src/lua/msgpack.c +++ b/src/lua/msgpack.c @@ -108,8 +108,8 @@ luamp_set_decode_extension(luamp_decode_extension_f handler) enum mp_type luamp_encode_r(struct lua_State *L, struct luaL_serializer *cfg, - struct mpstream *stream, struct luaL_field *field, - int level) + struct luaL_serializer_ctx *ctx, struct mpstream *stream, + struct luaL_field *field, int level) { int top = lua_gettop(L); enum mp_type type; @@ -154,13 +154,13 @@ restart: /* used by MP_EXT */ lua_pushnil(L); /* first key */ while (lua_next(L, top) != 0) { lua_pushvalue(L, -2); /* push a copy of key to top */ - if (luaL_tofield(L, cfg, lua_gettop(L), field) < 0) + if (luaL_tofield(L, cfg, ctx, lua_gettop(L), field) < 0) return luaT_error(L); - luamp_encode_r(L, cfg, stream, field, level + 1); + luamp_encode_r(L, cfg, ctx, stream, field, level + 1); lua_pop(L, 1); /* pop a copy of key */ - if (luaL_tofield(L, cfg, lua_gettop(L), field) < 0) + if (luaL_tofield(L, cfg, ctx, lua_gettop(L), field) < 0) return luaT_error(L); - luamp_encode_r(L, cfg, stream, field, level + 1); + luamp_encode_r(L, cfg, ctx, stream, field, level + 1); lua_pop(L, 1); /* pop value */ } assert(lua_gettop(L) == top); @@ -179,9 +179,9 @@ restart: /* used by MP_EXT */ mpstream_encode_array(stream, size); for (uint32_t i = 0; i < size; i++) { lua_rawgeti(L, top, i + 1); - if (luaL_tofield(L, cfg, top + 1, field) < 0) + if (luaL_tofield(L, cfg, ctx, top + 1, field) < 0) return luaT_error(L); - luamp_encode_r(L, cfg, stream, field, level + 1); + luamp_encode_r(L, cfg, ctx, stream, field, level + 1); lua_pop(L, 1); } assert(lua_gettop(L) == top); @@ -191,6 +191,8 @@ restart: /* used by MP_EXT */ case MP_DECIMAL: mpstream_encode_decimal(stream, field->decval); return MP_EXT; + case MP_ERROR: + return luamp_encode_extension(L, top, stream); default: /* Run trigger if type can't be encoded */ type = luamp_encode_extension(L, top, stream); @@ -209,7 +211,8 @@ restart: /* used by MP_EXT */ enum mp_type luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, - struct mpstream *stream, int index) + struct luaL_serializer_ctx *ctx, struct mpstream *stream, + int index) { int top = lua_gettop(L); if (index < 0) @@ -221,9 +224,9 @@ luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, } struct luaL_field field; - if (luaL_tofield(L, cfg, lua_gettop(L), &field) < 0) + if (luaL_tofield(L, cfg, ctx, lua_gettop(L), &field) < 0) return luaT_error(L); - enum mp_type top_type = luamp_encode_r(L, cfg, stream, &field, 0); + enum mp_type top_type = luamp_encode_r(L, cfg, ctx, stream, &field, 0); if (!on_top) { lua_remove(L, top + 1); /* remove a value copy */ @@ -232,6 +235,7 @@ luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, return top_type; } + void luamp_decode(struct lua_State *L, struct luaL_serializer *cfg, const char **data) @@ -354,7 +358,7 @@ lua_msgpack_encode(lua_State *L) mpstream_init(&stream, buf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - luamp_encode(L, cfg, &stream, 1); + luamp_encode(L, cfg, NULL, &stream, 1); mpstream_flush(&stream); if (index > 1) { diff --git a/src/lua/msgpack.h b/src/lua/msgpack.h index d0ade30..e5c7a94 100644 --- a/src/lua/msgpack.h +++ b/src/lua/msgpack.h @@ -43,6 +43,7 @@ extern "C" { struct luaL_serializer; struct mpstream; +struct luaL_serializer_ctx; /** * Default instance of msgpack serializer (msgpack = require('msgpack')). @@ -63,12 +64,13 @@ enum { LUAMP_ALLOC_FACTOR = 256 }; /* low-level function needed for execute_lua_call() */ enum mp_type luamp_encode_r(struct lua_State *L, struct luaL_serializer *cfg, - struct mpstream *stream, struct luaL_field *field, - int level); + struct luaL_serializer_ctx *ctx, struct mpstream *stream, + struct luaL_field *field, int level); enum mp_type luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, - struct mpstream *stream, int index); + struct luaL_serializer_ctx *ctx, struct mpstream *stream, + int index); void luamp_decode(struct lua_State *L, struct luaL_serializer *cfg, diff --git a/src/lua/utils.c b/src/lua/utils.c index 54d18ac..c0f173b 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -46,6 +46,7 @@ static uint32_t CTID_STRUCT_IBUF_PTR; static uint32_t CTID_CHAR_PTR; static uint32_t CTID_CONST_CHAR_PTR; uint32_t CTID_DECIMAL; +uint32_t CTID_CONST_STRUCT_ERROR_REF; void * @@ -445,7 +446,7 @@ lua_field_inspect_ucdata(struct lua_State *L, struct luaL_serializer *cfg, lua_pcall(L, 1, 1, 0); /* replace obj with the unpacked value */ lua_replace(L, idx); - if (luaL_tofield(L, cfg, idx, field) < 0) + if (luaL_tofield(L, cfg, NULL, idx, field) < 0) luaT_error(L); } /* else ignore lua_gettable exceptions */ lua_settop(L, top); /* remove temporary objects */ @@ -508,7 +509,7 @@ lua_field_try_serialize(struct lua_State *L) /* copy object itself */ lua_pushvalue(L, 1); lua_call(L, 1, 1); - s->is_error = (luaL_tofield(L, cfg, -1, field) != 0); + s->is_error = (luaL_tofield(L, cfg, NULL, -1, field) != 0); s->is_value_returned = true; return 1; } @@ -634,12 +635,13 @@ lua_field_tostring(struct lua_State *L, struct luaL_serializer *cfg, int idx, lua_call(L, 1, 1); lua_replace(L, idx); lua_settop(L, top); - if (luaL_tofield(L, cfg, idx, field) < 0) + if (luaL_tofield(L, cfg, NULL, idx, field) < 0) luaT_error(L); } int -luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index, +luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, + struct luaL_serializer_ctx *ctx, int index, struct luaL_field *field) { if (index < 0) @@ -746,6 +748,10 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index, if (cd->ctypeid == CTID_DECIMAL) { field->ext_type = MP_DECIMAL; field->decval = (decimal_t *) cdata; + } else if (cd->ctypeid == CTID_CONST_STRUCT_ERROR_REF && + ctx && + ctx->err_format_ver == ERR_FORMAT_EX) { + field->ext_type = MP_ERROR; } else { field->ext_type = MP_UNKNOWN_EXTENSION; } @@ -792,8 +798,8 @@ luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx, { if (idx < 0) idx = lua_gettop(L) + idx + 1; - assert(field->type == MP_EXT && field->ext_type == MP_UNKNOWN_EXTENSION); /* must be called after tofield() */ + assert(field->type == MP_EXT && field->ext_type == MP_UNKNOWN_EXTENSION); /* must be called after tofield() */ if (cfg->encode_load_metatables) { int type = lua_type(L, idx); if (type == LUA_TCDATA) { diff --git a/src/lua/utils.h b/src/lua/utils.h index 5875ba3..4ccce03 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -381,6 +381,7 @@ struct luaL_field { * * @param L stack * @param cfg configuration + * @param serializer_ctx the Lua serializer context * @param index stack index * @param field conversion result * @@ -388,7 +389,8 @@ struct luaL_field { * @retval -1 Error. */ int -luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index, +luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, + struct luaL_serializer_ctx *ctx, int index, struct luaL_field *field); /** @@ -427,7 +429,7 @@ static inline void luaL_checkfield(struct lua_State *L, struct luaL_serializer *cfg, int idx, struct luaL_field *field) { - if (luaL_tofield(L, cfg, idx, field) < 0) + if (luaL_tofield(L, cfg, NULL, idx, field) < 0) luaT_error(L); if (field->type != MP_EXT || field->ext_type != MP_UNKNOWN_EXTENSION) return; diff --git a/test/box-tap/extended_error.test.lua b/test/box-tap/extended_error.test.lua new file mode 100755 index 0000000..45c200f --- /dev/null +++ b/test/box-tap/extended_error.test.lua @@ -0,0 +1,164 @@ +#! /usr/bin/env tarantool + +local netbox = require('net.box') +local os = require('os') +local tap = require('tap') +local uri = require('uri') + +local test = tap.test('check an extended error') +test:plan(4) + + +function error_func() + box.error(box.error.PROC_LUA, "An old good error") +end + +function custom_error_func() + box.error("My Custom Type", "A modern custom error") +end + +function custom_error_func_2() + local err = box.error.new("My Custom Type", "A modern custom error") + return err +end + +local function version_at_least(peer_version_str, major, minor, patch) + local major_p, minor_p, patch_p = string.match(peer_version_str, + "(%d)%.(%d)%.(%d)") + major_p = tonumber(major_p) + minor_p = tonumber(minor_p) + patch_p = tonumber(patch_p) + + if major_p < major + or (major_p == major and minor_p < minor) + or (major_p == major and minor_p == minor and patch_p < patch) then + return false + end + + return true +end + +local function grant_func(user, name) + box.schema.func.create(name, {if_not_exists = true}) + box.schema.user.grant(user, 'execute', 'function', name, { + if_not_exists = true + }) +end + +local function check_error(err, check_list) + if type(check_list) ~= 'table' then + return false + end + + for k, v in pairs(check_list) do + if k == 'type' then + if err.base_type ~= v then + return false + end + elseif k == 'custom_type' then + if err.type ~= v then + return false + end + elseif k == 'message' then + if err.message ~= v then + return false + end + elseif k == 'trace' then + local trace = "File " .. err.trace[1]['file'] + .. "\nLine " .. err.trace[1]['line'] + if not string.match(trace, v) then + return false + end + elseif k == 'errno' then + if err.errno ~= v then + return false + end + elseif k == 'is_custom' then + if (err.base_type == 'CustomError') ~= v then + return false + end + else + return false + end + end + + return true +end + +local function test_old_transmission(host, port) + grant_func('guest', 'error_func') + grant_func('guest', 'custom_error_func_2') + + local connection = netbox.connect(host, port) + box.error.cfg({traceback_supplementation = true}) + local _, err = pcall(connection.call, connection, 'error_func') + local err_2 = connection:call('custom_error_func_2') + + + local check_list = { + type = 'ClientError', + message = 'An old good error', + trace = '^File builtin/box/net_box.lua\nLine %d+$', + is_custom = false + } + + local check_result = check_error(err, check_list) + local check_result_2 = type(err_2) == 'string' and err_2 == 'A modern custom error' + + test:ok(check_result, 'Check the old transmission type(IPROTO_ERROR)') + test:ok(check_result_2, 'Check the old transmission type(IPROTO_OK)') + + connection:close() +end + +local function test_extended_transmission(host, port) + grant_func('guest', 'custom_error_func') + grant_func('guest', 'custom_error_func_2') + box.schema.user.grant('guest','read,write', 'space', '_session_settings') + + local connection = netbox.connect(host, port, {error_extended = true}) + local _, err = pcall(connection.call, connection, 'custom_error_func') + local err_2 = connection:call('custom_error_func_2') + + local check_list = { + type = 'CustomError', + custom_type = 'My Custom Type', + message = 'A modern custom error', + trace = '^File builtin/box/net_box.lua\nLine %d+$', + is_custom = true + } + + local check_list_2 = { + type = 'CustomError', + custom_type = 'My Custom Type', + message = 'A modern custom error', + trace = '.*extended_error.test.lua\nLine 2$', + is_custom = true + } + + local check_result = check_error(err, check_list) + local check_result_2 = check_error(err_2, check_list_2) + test:ok(check_result, 'Check the extended transmission type(IPROTO_ERROR)') + test:ok(check_result_2, 'Check the extended transmission type(IPROTO_OK)') + + connection:close() +end + +box.cfg{ + listen = os.getenv('LISTEN') +} +local tarantool_ver = string.match(box.info.version, "%d%.%d%.%d") +local host= uri.parse(box.cfg.listen).host or 'localhost' +local port = uri.parse(box.cfg.listen).service + +if version_at_least(box.info.version, 2, 4, 0) then + test_extended_transmission(host, port) +else + test:ok(true, 'Current version of tarantool(' .. tarantool_ver .. ')' .. + ' don\'t support extended transmission') + test:ok(true, 'Current version of tarantool(' .. tarantool_ver .. ')' .. + ' don\'t support extended transmission') +end +test_old_transmission(host, port) + +os.exit(test:check() and 0 or 1) -- 2.7.4 ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding Leonid Vasiliev @ 2020-04-14 1:12 ` Vladislav Shpilevoy 2020-04-15 9:25 ` lvasiliev 0 siblings, 1 reply; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-14 1:12 UTC (permalink / raw) To: Leonid Vasiliev; +Cc: tarantool-patches Thanks for the patch! Sorry, some of my comments may contain typos, because it is very late and I can't continue polishing it today. I didn't have time to review it properly yet. Just some first comments below. See 12 of them below. On 10/04/2020 10:10, Leonid Vasiliev wrote: > New MsgPack encode format for error (MP_ERROR) has been added. > If the extended error format is enabled using iproto session settings > MP_ERROR type will be used for transferring error through network, > MP_STR was used before. 1. For such a big feature and not a single word of documentation. > Needed for #4398 2. Shouldn't that be 'Closes'? > diff --git a/src/box/error.cc b/src/box/error.cc > index 8179e52..f2e60c1 100644 > --- a/src/box/error.cc > +++ b/src/box/error.cc > @@ -253,6 +253,13 @@ XlogGapError::XlogGapError(const char *file, unsigned line, > (long long) vclock_sum(to), s_to ? s_to : ""); > } > > +XlogGapError::XlogGapError(const char *file, unsigned line, > + const char *msg) > + : XlogError(&type_XlogGapError, file, line) > +{ > + error_format_msg(this, "%s", msg); > +} > + > struct error * > BuildXlogGapError(const char *file, unsigned line, > const struct vclock *from, const struct vclock *to) > @@ -264,6 +271,16 @@ BuildXlogGapError(const char *file, unsigned line, > } > } > > +struct error * > +ReBuildXlogGapError(const char *file, unsigned line, const char *msg) 3. Wtf is Rebuild? Why normal Build does not work? If that is because of vclock, then just change vclock to const char * in the original Build in a separate preparatory commit. > +{ > + try { > + return new XlogGapError(file, line, msg); > + } catch (OutOfMemory *e) { > + return e; > + } > +} > + > struct rlist on_access_denied = RLIST_HEAD_INITIALIZER(on_access_denied); > > diff --git a/src/box/lua/call.c b/src/box/lua/call.c > index 5d3579e..6d1d247 100644 > --- a/src/box/lua/call.c > +++ b/src/box/lua/call.c > @@ -174,7 +175,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, > */ > for (int i = 1; i <= nrets; ++i) { > struct luaL_field field; > - if (luaL_tofield(L, cfg, i, &field) < 0) > + if (luaL_tofield(L, cfg, NULL, i, &field) < 0) 4. I think addition of that parameter should be a part of the previous commit. So as not to pollute this commit with such minor changes. In the previous commit it can be added and ignored inside luamp_encode_r(). But it will absorb all these not interesting changes. > return luaT_error(L); > struct tuple *tuple; > if (field.type == MP_EXT && > @@ -379,6 +380,7 @@ execute_lua_eval(lua_State *L) > struct encode_lua_ctx { > struct port_lua *port; > struct mpstream *stream; > + struct luaL_serializer_ctx *serializer_ctx; 5. Not. A. Single. Word. Please, start writing descriptive comments. > }; > > static int > diff --git a/src/box/lua/mp_error.cc b/src/box/lua/mp_error.cc > new file mode 100644 > index 0000000..f99da0f > --- /dev/null > +++ b/src/box/lua/mp_error.cc > @@ -0,0 +1,454 @@ > +/* > + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. > + * > + * Redistribution and use in source and binary forms, with or > + * without modification, are permitted provided that the following > + * conditions are met: > + * > + * 1. Redistributions of source code must retain the above > + * copyright notice, this list of conditions and the > + * following disclaimer. > + * > + * 2. Redistributions in binary form must reproduce the above > + * copyright notice, this list of conditions and the following > + * disclaimer in the documentation and/or other materials > + * provided with the distribution. > + * > + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND > + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED > + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR > + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL > + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, > + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL > + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF > + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR > + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF > + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT > + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF > + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF > + * SUCH DAMAGE. > + */ > +#include "box/lua/mp_error.h" > +#include "box/error.h" > +#include "mpstream.h" > +#include "msgpuck.h" > +#include "mp_extension_types.h" > + > +enum mp_error_details { > + MP_ERROR_DET_TYPE, > + MP_ERROR_DET_FILE, > + MP_ERROR_DET_LINE, > + MP_ERROR_DET_REASON, > + MP_ERROR_DET_ERRNO, > + MP_ERROR_DET_CODE, > + MP_ERROR_DET_BACKTRACE, > + MP_ERROR_DET_CUSTOM_TYPE, > + MP_ERROR_DET_AD_OBJ_TYPE, > + MP_ERROR_DET_AD_OBJ_NAME, > + MP_ERROR_DET_AD_ACCESS_TYPE > +}; > + > +enum mp_error_types { > + MP_ERROR_TYPE_UNKNOWN, > + MP_ERROR_TYPE_CLIENT, > + MP_ERROR_TYPE_CUSTOM, > + MP_ERROR_TYPE_ACCESS_DENIED, > + MP_ERROR_TYPE_XLOG, > + MP_ERROR_TYPE_XLOG_GAP, > + MP_ERROR_TyPE_SYSTEM, 6. I assume you use some kind of smart editor with autocompletion so as you missed this typo: TyPE -> TYPE 7. Why are these values in a enum? I thought we decided to use string types? > + MP_ERROR_TyPE_SOCKET, > + MP_ERROR_TyPE_OOM, > + MP_ERROR_TyPE_TIMED_OUT, > + MP_ERROR_TyPE_CHANNEL_IS_CLOSED, > + MP_ERROR_TyPE_FIBER_IS_CANCELLED, > + MP_ERROR_TyPE_LUAJIT, > + MP_ERROR_TyPE_ILLEGAL_PARAMS, > + MP_ERROR_TyPE_COLLATION, > + MP_ERROR_TyPE_SWIM, > + MP_ERROR_TyPE_CRYPTO > +}; > + > +struct mp_error { > + uint32_t error_code; > + uint8_t error_type; 8. If you assign enum to a variable, that variable should have type of that enum, usually. > + uint32_t line; > + uint32_t saved_errno; > + char *file; > + char *backtrace; > + char *reason; > + char *custom_type; > + char *ad_obj_type; > + char *ad_obj_name; > + char *ad_access_type; > +}; > diff --git a/src/box/lua/mp_error.h b/src/box/lua/mp_error.h > new file mode 100644 > index 0000000..9eab213 > --- /dev/null > +++ b/src/box/lua/mp_error.h > @@ -0,0 +1,49 @@ > +#pragma once > +/* > + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. > + * > + * Redistribution and use in source and binary forms, with or > + * without modification, are permitted provided that the following > + * conditions are met: > + * > + * 1. Redistributions of source code must retain the above > + * copyright notice, this list of conditions and the > + * following disclaimer. > + * > + * 2. Redistributions in binary form must reproduce the above > + * copyright notice, this list of conditions and the following > + * disclaimer in the documentation and/or other materials > + * provided with the distribution. > + * > + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND > + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED > + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR > + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL > + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, > + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL > + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF > + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR > + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF > + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT > + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF > + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF > + * SUCH DAMAGE. > + */ > + > +#if defined(__cplusplus) > +extern "C" { > +#endif /* defined(__cplusplus) */ > + > +#include "stdint.h" 9. Standard and other external headers should be in <>, not "". > + > +struct mpstream; > + > +void > +error_to_mpstream(struct error *error, struct mpstream *stream); > + > +struct error * > +error_unpack(const char **data, uint32_t len); > + > +#if defined(__cplusplus) > +} /* extern "C" */ > +#endif /* defined(__cplusplus) */ > diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua > index c8f76b0..d3997af 100644 > --- a/src/box/lua/net_box.lua > +++ b/src/box/lua/net_box.lua > @@ -1050,7 +1050,7 @@ local function new_sm(host, port, opts, connection, greeting) > > -- Set extended error format for session. > if opts.error_extended then > - local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1) > + local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 0) 10. Why? > if not ext_err_supported then > box.error(box.error.PROC_LUA, > "Server doesn't support extended error format") > diff --git a/src/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h > index bc9873f..a2a5079 100644 > --- a/src/lib/core/mp_extension_types.h > +++ b/src/lib/core/mp_extension_types.h > @@ -40,8 +40,9 @@ > * You may assign values in range [0, 127] > */ > enum mp_extension_type { > - MP_UNKNOWN_EXTENSION = 0, > - MP_DECIMAL = 1, > + MP_UNKNOWN_EXTENSION = 0, > + MP_DECIMAL = 1, > + MP_ERROR = 2 > }; 11. Please, rebase before applying any review fixes. This code is changed in the master. As well as session settings. > #endif > diff --git a/src/lua/error.c b/src/lua/error.c > index cd6ab54..109f947 100644 > --- a/src/lua/error.c > +++ b/src/lua/error.c > @@ -34,8 +34,6 @@ > #include <fiber.h> > #include "utils.h" > > -static int CTID_CONST_STRUCT_ERROR_REF = 0; > - > struct error * > luaL_iserror(struct lua_State *L, int narg) > { > diff --git a/src/lua/error.h b/src/lua/error.h > index 16cdaf7..4e4dc04 100644 > --- a/src/lua/error.h > +++ b/src/lua/error.h > @@ -37,7 +37,6 @@ > extern "C" { > #endif /* defined(__cplusplus) */ > > - 12. Please, remove all unnecessary diff. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding 2020-04-14 1:12 ` Vladislav Shpilevoy @ 2020-04-15 9:25 ` lvasiliev 2020-04-16 0:11 ` Vladislav Shpilevoy 0 siblings, 1 reply; 37+ messages in thread From: lvasiliev @ 2020-04-15 9:25 UTC (permalink / raw) To: Vladislav Shpilevoy; +Cc: tarantool-patches Hi! Thanks you for the review. On 14.04.2020 4:12, Vladislav Shpilevoy wrote: > Thanks for the patch! > > Sorry, some of my comments may contain typos, because it is > very late and I can't continue polishing it today. > > I didn't have time to review it properly yet. Just some > first comments below. See 12 of them below. > > On 10/04/2020 10:10, Leonid Vasiliev wrote: >> New MsgPack encode format for error (MP_ERROR) has been added. >> If the extended error format is enabled using iproto session settings >> MP_ERROR type will be used for transferring error through network, >> MP_STR was used before. > > 1. For such a big feature and not a single word of documentation. fixed in new patch > >> Needed for #4398 > > 2. Shouldn't that be 'Closes'? fixed in new patch > >> diff --git a/src/box/error.cc b/src/box/error.cc >> index 8179e52..f2e60c1 100644 >> --- a/src/box/error.cc >> +++ b/src/box/error.cc >> @@ -253,6 +253,13 @@ XlogGapError::XlogGapError(const char *file, unsigned line, >> (long long) vclock_sum(to), s_to ? s_to : ""); >> } >> >> +XlogGapError::XlogGapError(const char *file, unsigned line, >> + const char *msg) >> + : XlogError(&type_XlogGapError, file, line) >> +{ >> + error_format_msg(this, "%s", msg); >> +} >> + >> struct error * >> BuildXlogGapError(const char *file, unsigned line, >> const struct vclock *from, const struct vclock *to) >> @@ -264,6 +271,16 @@ BuildXlogGapError(const char *file, unsigned line, >> } >> } >> >> +struct error * >> +ReBuildXlogGapError(const char *file, unsigned line, const char *msg) > > 3. Wtf is Rebuild? Why normal Build does not work? If that is > because of vclock, then just change vclock to const char * in > the original Build in a separate preparatory commit. fixed in new patch > >> +{ >> + try { >> + return new XlogGapError(file, line, msg); >> + } catch (OutOfMemory *e) { >> + return e; >> + } >> +} >> + >> struct rlist on_access_denied = RLIST_HEAD_INITIALIZER(on_access_denied); >> > diff --git a/src/box/lua/call.c b/src/box/lua/call.c >> index 5d3579e..6d1d247 100644 >> --- a/src/box/lua/call.c >> +++ b/src/box/lua/call.c >> @@ -174,7 +175,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, >> */ >> for (int i = 1; i <= nrets; ++i) { >> struct luaL_field field; >> - if (luaL_tofield(L, cfg, i, &field) < 0) >> + if (luaL_tofield(L, cfg, NULL, i, &field) < 0) > > 4. I think addition of that parameter should be a part of the previous > commit. So as not to pollute this commit with such minor changes. > > In the previous commit it can be added and ignored inside luamp_encode_r(). > But it will absorb all these not interesting changes. fixed in new patch > >> return luaT_error(L); >> struct tuple *tuple; >> if (field.type == MP_EXT && >> @@ -379,6 +380,7 @@ execute_lua_eval(lua_State *L) >> struct encode_lua_ctx { >> struct port_lua *port; >> struct mpstream *stream; >> + struct luaL_serializer_ctx *serializer_ctx; > > 5. Not. A. Single. Word. Please, start writing descriptive > comments. Ok. I will try. > >> }; >> >> static int >> diff --git a/src/box/lua/mp_error.cc b/src/box/lua/mp_error.cc >> new file mode 100644 >> index 0000000..f99da0f >> --- /dev/null >> +++ b/src/box/lua/mp_error.cc >> @@ -0,0 +1,454 @@ >> +/* >> + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. >> + * >> + * Redistribution and use in source and binary forms, with or >> + * without modification, are permitted provided that the following >> + * conditions are met: >> + * >> + * 1. Redistributions of source code must retain the above >> + * copyright notice, this list of conditions and the >> + * following disclaimer. >> + * >> + * 2. Redistributions in binary form must reproduce the above >> + * copyright notice, this list of conditions and the following >> + * disclaimer in the documentation and/or other materials >> + * provided with the distribution. >> + * >> + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND >> + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED >> + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >> + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL >> + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, >> + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL >> + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF >> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR >> + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF >> + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF >> + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF >> + * SUCH DAMAGE. >> + */ >> +#include "box/lua/mp_error.h" >> +#include "box/error.h" >> +#include "mpstream.h" >> +#include "msgpuck.h" >> +#include "mp_extension_types.h" >> + >> +enum mp_error_details { >> + MP_ERROR_DET_TYPE, >> + MP_ERROR_DET_FILE, >> + MP_ERROR_DET_LINE, >> + MP_ERROR_DET_REASON, >> + MP_ERROR_DET_ERRNO, >> + MP_ERROR_DET_CODE, >> + MP_ERROR_DET_BACKTRACE, >> + MP_ERROR_DET_CUSTOM_TYPE, >> + MP_ERROR_DET_AD_OBJ_TYPE, >> + MP_ERROR_DET_AD_OBJ_NAME, >> + MP_ERROR_DET_AD_ACCESS_TYPE >> +}; >> + >> +enum mp_error_types { >> + MP_ERROR_TYPE_UNKNOWN, >> + MP_ERROR_TYPE_CLIENT, >> + MP_ERROR_TYPE_CUSTOM, >> + MP_ERROR_TYPE_ACCESS_DENIED, >> + MP_ERROR_TYPE_XLOG, >> + MP_ERROR_TYPE_XLOG_GAP, >> + MP_ERROR_TyPE_SYSTEM, > > 6. I assume you use some kind of smart editor with > autocompletion so as you missed this typo: > > TyPE -> TYPE Fixed. > > 7. Why are these values in a enum? I thought we > decided to use string types? Now we compare a string with error type when encode and use number further. If we encode error type as string we still have to compare the strings when decoding to create the error of the desired type. But number takes up less space versus string when transmitting over a network and makes it possible to use a switch when create an error after decoding (which looks much nicer). > >> + MP_ERROR_TyPE_SOCKET, >> + MP_ERROR_TyPE_OOM, >> + MP_ERROR_TyPE_TIMED_OUT, >> + MP_ERROR_TyPE_CHANNEL_IS_CLOSED, >> + MP_ERROR_TyPE_FIBER_IS_CANCELLED, >> + MP_ERROR_TyPE_LUAJIT, >> + MP_ERROR_TyPE_ILLEGAL_PARAMS, >> + MP_ERROR_TyPE_COLLATION, >> + MP_ERROR_TyPE_SWIM, >> + MP_ERROR_TyPE_CRYPTO >> +}; >> + >> +struct mp_error { >> + uint32_t error_code; >> + uint8_t error_type; > > 8. If you assign enum to a variable, that variable should > have type of that enum, usually. fixed > >> + uint32_t line; >> + uint32_t saved_errno; >> + char *file; >> + char *backtrace; >> + char *reason; >> + char *custom_type; >> + char *ad_obj_type; >> + char *ad_obj_name; >> + char *ad_access_type; >> +}; >> diff --git a/src/box/lua/mp_error.h b/src/box/lua/mp_error.h >> new file mode 100644 >> index 0000000..9eab213 >> --- /dev/null >> +++ b/src/box/lua/mp_error.h >> @@ -0,0 +1,49 @@ >> +#pragma once >> +/* >> + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. >> + * >> + * Redistribution and use in source and binary forms, with or >> + * without modification, are permitted provided that the following >> + * conditions are met: >> + * >> + * 1. Redistributions of source code must retain the above >> + * copyright notice, this list of conditions and the >> + * following disclaimer. >> + * >> + * 2. Redistributions in binary form must reproduce the above >> + * copyright notice, this list of conditions and the following >> + * disclaimer in the documentation and/or other materials >> + * provided with the distribution. >> + * >> + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND >> + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED >> + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >> + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL >> + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, >> + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL >> + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF >> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR >> + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF >> + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF >> + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF >> + * SUCH DAMAGE. >> + */ >> + >> +#if defined(__cplusplus) >> +extern "C" { >> +#endif /* defined(__cplusplus) */ >> + >> +#include "stdint.h" > > 9. Standard and other external headers should be in <>, not "". fixed > >> + >> +struct mpstream; >> + >> +void >> +error_to_mpstream(struct error *error, struct mpstream *stream); >> + >> +struct error * >> +error_unpack(const char **data, uint32_t len); >> + >> +#if defined(__cplusplus) >> +} /* extern "C" */ >> +#endif /* defined(__cplusplus) */ >> diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua >> index c8f76b0..d3997af 100644 >> --- a/src/box/lua/net_box.lua >> +++ b/src/box/lua/net_box.lua >> @@ -1050,7 +1050,7 @@ local function new_sm(host, port, opts, connection, greeting) >> >> -- Set extended error format for session. >> if opts.error_extended then >> - local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1) >> + local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 0) > > 10. Why? It's mistake. Now deleted. > >> if not ext_err_supported then >> box.error(box.error.PROC_LUA, >> "Server doesn't support extended error format") >> diff --git a/src/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h >> index bc9873f..a2a5079 100644 >> --- a/src/lib/core/mp_extension_types.h >> +++ b/src/lib/core/mp_extension_types.h >> @@ -40,8 +40,9 @@ >> * You may assign values in range [0, 127] >> */ >> enum mp_extension_type { >> - MP_UNKNOWN_EXTENSION = 0, >> - MP_DECIMAL = 1, >> + MP_UNKNOWN_EXTENSION = 0, >> + MP_DECIMAL = 1, >> + MP_ERROR = 2 >> }; > > 11. Please, rebase before applying any review fixes. This code is > changed in the master. As well as session settings. > >> #endif >> diff --git a/src/lua/error.c b/src/lua/error.c >> index cd6ab54..109f947 100644 >> --- a/src/lua/error.c >> +++ b/src/lua/error.c >> @@ -34,8 +34,6 @@ >> #include <fiber.h> >> #include "utils.h" >> >> -static int CTID_CONST_STRUCT_ERROR_REF = 0; >> - >> struct error * >> luaL_iserror(struct lua_State *L, int narg) >> { >> diff --git a/src/lua/error.h b/src/lua/error.h >> index 16cdaf7..4e4dc04 100644 >> --- a/src/lua/error.h >> +++ b/src/lua/error.h >> @@ -37,7 +37,6 @@ >> extern "C" { >> #endif /* defined(__cplusplus) */ >> >> - > > 12. Please, remove all unnecessary diff. fixed I have a some additional question: "Do I understand correctly that MP_ERROR should not be added to field_def.h/field_def.c or I am wrong?" > ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding 2020-04-15 9:25 ` lvasiliev @ 2020-04-16 0:11 ` Vladislav Shpilevoy 2020-04-16 10:04 ` lvasiliev 0 siblings, 1 reply; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-16 0:11 UTC (permalink / raw) To: lvasiliev; +Cc: tarantool-patches Hi! Thanks for the fixes! >> 7. Why are these values in a enum? I thought we >> decided to use string types? > Now we compare a string with error type when encode and use number further. If we encode error type as string we still have to compare the strings when decoding to create the error of the desired type. But number takes up less space versus string when transmitting over a network and makes it possible to use a switch when create an error after decoding (which looks much nicer). Wait, so this is inside the MessagePack too? This is not a good idea really. I thought you just converted strings to enums and then enums to classes. First about 'less space'. These are error objects. It does not really matter if every one of them becomes +10 or even +20 bytes if we use string types instead of numbers. They are not critical in any aspect, not in perf nor in space. Second about switch, 'which looks nicer'. Nicely formatted if-else sequence also look fine. This is subjective though. I won't argue. Thirdly, about why I think string types are better. The problem is that I don't want us to document all the error types as numbers and support them forever. When we use strings, we easily can remove old errors, add new errors. When we use numbers, we 1) will need to document what every number means. Strings are self-documenting. OutOfMemory means out of memory, obviously. And so on. 2) will one day have holes in these numbers left from removed errors, this won't look nice, trust me. 3) that complicates compatibility. What if some error type was added to a newer tarantool version, and an old connector connected to the instance? How will it handle the new error types? With string types the problem does not exist. Numbers are fine for error codes. For example, SQL drivers define certain error codes as kind of a standard, and that simplifies support. Also an error code can describe actually a pretty big problem, which would be impossible to say in a short string. This is not so for types. I hope you follow my idea, I don't want you to just blindly agree, if you actually don't agree and don't want to argue either. >>> diff --git a/src/lua/error.h b/src/lua/error.h >>> index 16cdaf7..4e4dc04 100644 >>> --- a/src/lua/error.h >>> +++ b/src/lua/error.h >>> @@ -37,7 +37,6 @@ >>> extern "C" { >>> #endif /* defined(__cplusplus) */ >>> - >> >> 12. Please, remove all unnecessary diff. > fixed > > I have a some additional question: > "Do I understand correctly that MP_ERROR should not be added to field_def.h/field_def.c or I am wrong?" I didn't read to that place, but sounds strange. We can't create a field of type 'error' in a space. We can't store errors anywhere. So it looks incorrect for field_def to depend on MP_ERROR. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding 2020-04-16 0:11 ` Vladislav Shpilevoy @ 2020-04-16 10:04 ` lvasiliev 2020-04-16 21:06 ` Vladislav Shpilevoy 0 siblings, 1 reply; 37+ messages in thread From: lvasiliev @ 2020-04-16 10:04 UTC (permalink / raw) To: Vladislav Shpilevoy; +Cc: tarantool-patches On 16.04.2020 3:11, Vladislav Shpilevoy wrote: > Hi! Thanks for the fixes! > >>> 7. Why are these values in a enum? I thought we >>> decided to use string types? >> Now we compare a string with error type when encode and use number further. If we encode error type as string we still have to compare the strings when decoding to create the error of the desired type. But number takes up less space versus string when transmitting over a network and makes it possible to use a switch when create an error after decoding (which looks much nicer). > > Wait, so this is inside the MessagePack too? This is not a good > idea really. I thought you just converted strings to enums and > then enums to classes. Yes. It's not inside the MessagePack. MP_ERROR decodes by external handler as we discussed. It registered by luamp_set_decode_extension(). > > First about 'less space'. These are error objects. It does not > really matter if every one of them becomes +10 or even +20 bytes > if we use string types instead of numbers. They are not critical > in any aspect, not in perf nor in space. > > Second about switch, 'which looks nicer'. Nicely formatted if-else > sequence also look fine. This is subjective though. I won't argue. > > Thirdly, about why I think string types are better. The problem is > that I don't want us to document all the error types as numbers > and support them forever. When we use strings, we easily can remove > old errors, add new errors. When we use numbers, we > > 1) will need to document what every number means. Strings are > self-documenting. OutOfMemory means out of memory, obviously. > And so on. Ok. > > 2) will one day have holes in these numbers left from removed > errors, this won't look nice, trust me. Ok. > > 3) that complicates compatibility. What if some error type was > added to a newer tarantool version, and an old connector connected > to the instance? How will it handle the new error types? With > string types the problem does not exist. If I understand you correctly, you will have the same problem with string, because you can't create an error of unknown type. > > Numbers are fine for error codes. For example, SQL drivers define > certain error codes as kind of a standard, and that simplifies > support. Also an error code can describe actually a pretty big > problem, which would be impossible to say in a short string. This > is not so for types. > > I hope you follow my idea, I don't want you to just blindly agree, > if you actually don't agree and don't want to argue either. I will update this in the future version of the patchset. > >>>> diff --git a/src/lua/error.h b/src/lua/error.h >>>> index 16cdaf7..4e4dc04 100644 >>>> --- a/src/lua/error.h >>>> +++ b/src/lua/error.h >>>> @@ -37,7 +37,6 @@ >>>> extern "C" { >>>> #endif /* defined(__cplusplus) */ >>>> - >>> >>> 12. Please, remove all unnecessary diff. >> fixed >> >> I have a some additional question: >> "Do I understand correctly that MP_ERROR should not be added to field_def.h/field_def.c or I am wrong?" > > I didn't read to that place, but sounds strange. We can't create a field > of type 'error' in a space. We can't store errors anywhere. So it looks > incorrect for field_def to depend on MP_ERROR. > As I understand, you haven't reviewed this patch. Please see version from patchset V3. I will apply all updates after the review, I assume there will be questions and objections. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding 2020-04-16 10:04 ` lvasiliev @ 2020-04-16 21:06 ` Vladislav Shpilevoy 0 siblings, 0 replies; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-16 21:06 UTC (permalink / raw) To: lvasiliev; +Cc: tarantool-patches Thanks for the answers! >>>> 7. Why are these values in a enum? I thought we >>>> decided to use string types? >>> Now we compare a string with error type when encode and use number further. If we encode error type as string we still have to compare the strings when decoding to create the error of the desired type. But number takes up less space versus string when transmitting over a network and makes it possible to use a switch when create an error after decoding (which looks much nicer). >> >> Wait, so this is inside the MessagePack too? This is not a good >> idea really. I thought you just converted strings to enums and >> then enums to classes. > Yes. It's not inside the MessagePack. MP_ERROR decodes by > external handler as we discussed. It registered by > luamp_set_decode_extension(). I meant it is inside MessagePack protocol, not library. >> First about 'less space'. These are error objects. It does not >> really matter if every one of them becomes +10 or even +20 bytes >> if we use string types instead of numbers. They are not critical >> in any aspect, not in perf nor in space. >> >> Second about switch, 'which looks nicer'. Nicely formatted if-else >> sequence also look fine. This is subjective though. I won't argue. >> >> Thirdly, about why I think string types are better. The problem is >> that I don't want us to document all the error types as numbers >> and support them forever. When we use strings, we easily can remove >> old errors, add new errors. When we use numbers, we >> >> 1) will need to document what every number means. Strings are >> self-documenting. OutOfMemory means out of memory, obviously. >> And so on. > Ok. >> >> 2) will one day have holes in these numbers left from removed >> errors, this won't look nice, trust me. > Ok. >> >> 3) that complicates compatibility. What if some error type was >> added to a newer tarantool version, and an old connector connected >> to the instance? How will it handle the new error types? With >> string types the problem does not exist. > If I understand you correctly, you will have the same problem > with string, because you can't create an error of unknown type. String is the type. Client's connector don't need to keep a track of all existing error types, and validate every incoming string. The connector just need to be able to decode MP_STR, and that is all. When you have numbers, client can't tell error type, if a new unknown number is received. Instead of a nice human readable error type it will be just an abstract number like '252'. With string types whatever new errors are added in newer versions, all client connectors will be able to decode them without necessity to validate. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 0/5] Extending error functionality 2020-04-10 8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev ` (4 preceding siblings ...) 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding Leonid Vasiliev @ 2020-04-14 1:10 ` Vladislav Shpilevoy 2020-04-15 9:48 ` lvasiliev 5 siblings, 1 reply; 37+ messages in thread From: Vladislav Shpilevoy @ 2020-04-14 1:10 UTC (permalink / raw) To: Leonid Vasiliev; +Cc: tarantool-patches Hi! Thanks for the patchset! On 10/04/2020 10:10, Leonid Vasiliev wrote: > https://github.com/tarantool/tarantool/issues/4398 > https://github.com/tarantool/tarantool/tree/lvasiliev/gh-4398-expose-error-module-4 The branch build fails on Travis. ^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [Tarantool-patches] [PATCH v2 0/5] Extending error functionality 2020-04-14 1:10 ` [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Vladislav Shpilevoy @ 2020-04-15 9:48 ` lvasiliev 0 siblings, 0 replies; 37+ messages in thread From: lvasiliev @ 2020-04-15 9:48 UTC (permalink / raw) To: Vladislav Shpilevoy; +Cc: tarantool-patches On 14.04.2020 4:10, Vladislav Shpilevoy wrote: > Hi! Thanks for the patchset! > > On 10/04/2020 10:10, Leonid Vasiliev wrote: >> https://github.com/tarantool/tarantool/issues/4398 >> https://github.com/tarantool/tarantool/tree/lvasiliev/gh-4398-expose-error-module-4 > > The branch build fails on Travis. Thanks, a bug in session setting module was a reason. > ^ permalink raw reply [flat|nested] 37+ messages in thread
end of thread, other threads:[~2020-04-16 21:06 UTC | newest] Thread overview: 37+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2020-04-10 8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error Leonid Vasiliev 2020-04-14 1:11 ` Vladislav Shpilevoy 2020-04-15 9:25 ` lvasiliev 2020-04-16 0:00 ` Vladislav Shpilevoy 2020-04-16 1:11 ` Alexander Turenko 2020-04-16 8:58 ` lvasiliev 2020-04-16 9:30 ` Alexander Turenko 2020-04-16 12:27 ` lvasiliev 2020-04-16 12:45 ` Alexander Turenko 2020-04-16 17:48 ` lvasiliev 2020-04-16 8:40 ` lvasiliev 2020-04-16 9:04 ` lvasiliev 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type Leonid Vasiliev 2020-04-14 1:11 ` Vladislav Shpilevoy 2020-04-15 9:25 ` lvasiliev 2020-04-16 0:02 ` Vladislav Shpilevoy 2020-04-16 9:18 ` lvasiliev 2020-04-16 21:03 ` Vladislav Shpilevoy 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO Leonid Vasiliev 2020-04-14 1:12 ` Vladislav Shpilevoy 2020-04-15 9:25 ` lvasiliev 2020-04-16 0:02 ` Vladislav Shpilevoy 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO Leonid Vasiliev 2020-04-14 1:12 ` Vladislav Shpilevoy 2020-04-15 9:26 ` lvasiliev 2020-04-16 0:06 ` Vladislav Shpilevoy 2020-04-16 9:36 ` lvasiliev 2020-04-16 21:04 ` Vladislav Shpilevoy 2020-04-10 8:10 ` [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding Leonid Vasiliev 2020-04-14 1:12 ` Vladislav Shpilevoy 2020-04-15 9:25 ` lvasiliev 2020-04-16 0:11 ` Vladislav Shpilevoy 2020-04-16 10:04 ` lvasiliev 2020-04-16 21:06 ` Vladislav Shpilevoy 2020-04-14 1:10 ` [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Vladislav Shpilevoy 2020-04-15 9:48 ` lvasiliev
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox