From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from localhost (localhost [127.0.0.1]) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTP id DF40F20615 for ; Thu, 1 Aug 2019 07:13:34 -0400 (EDT) Received: from turing.freelists.org ([127.0.0.1]) by localhost (turing.freelists.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 5DAlEHNUOFTT for ; Thu, 1 Aug 2019 07:13:34 -0400 (EDT) Received: from smtpng3.m.smailru.net (smtpng3.m.smailru.net [94.100.177.149]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTPS id 57109250A6 for ; Thu, 1 Aug 2019 07:13:34 -0400 (EDT) From: Kirill Shcherbatov Subject: [tarantool-patches] [PATCH v1 3/3] box: extend ffi error object API Date: Thu, 1 Aug 2019 14:13:28 +0300 Message-Id: <47d9072e60bdc563c7466a4e51db1a61bc71a610.1564657285.git.kshcherbatov@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Sender: tarantool-patches-bounce@freelists.org Errors-to: tarantool-patches-bounce@freelists.org Reply-To: tarantool-patches@freelists.org List-Help: List-Unsubscribe: List-software: Ecartis version 1.0.0 List-Id: tarantool-patches List-Subscribe: List-Owner: List-post: List-Archive: To: tarantool-patches@freelists.org, v.shpilevoy@tarantool.org Cc: alexander.turenko@tarantool.org, kostja@tarantool.org, Kirill Shcherbatov Closes #1148 @TarantoolBot document Title: Stacked diagnostics area in fiber Tarantool errors subsystem had been refactored to support stacked errors. Errors in Tarantool are represented as a cdata objects with following fields and methods error.type - get top-level error type error.message - get top-level error description string error.trace - get top-level file and line trace for error object error.code - get top-level error code error:raise() - raise error exception error:match() - match a string in error message with given pattern. Required to uniformly handle string error messages and cdata error objects: f1() return nil, "err1" end f2() return nil, box.error.new(555, "err2") end _, err1 = f1() -, err2 = f2() err1:match("err1") == true err2:match("err2") == true -- Methods with stacked error semantics error:unpack() - return error's detailed stack diagnostics: a table of errors in their historical order with all related information error:has(code)- test whether a given error code is met in error's stacked representation err1 = err1:wrap(err) - add err error object as a reason for err1 object this call modifies err1 object and doesn't modify err object. err1_parent = err1:unwrap() - remove the most recent error in error object, return it's parent. the call has no effect when there is no parent in given error object. Example: errors = {ERR_IO = 1001, ERR_USER_CREATE = 1002} e0 = box.error.new(errors.ERR_IO, 'IO error') e1 = box.error.new(errors.ERR_USER_CREATE, 'Can\'t create a user') e1 = e1:wrap(e0) e1:unpack() - - type: ClientError message: Unknown error code: 1001 trace: - file: '[string "e0 = box.error.new(errors.ERR_IO, ''IO error'')"]' line: 1 - type: ClientError message: Unknown error code: 1002 trace: - file: '[string "e1 = box.error.new(errors.ERR_USER_CREATE, ''C..."]' line: 1 e1:has(errors.ERR_IO) - true e1:has(errors.ERR_USER_CREATE) - true --- src/box/error.h | 23 ++++ extra/exports | 2 + src/box/error.cc | 25 ++++ src/lua/error.lua | 50 ++++++++ test/box/errors.result | 265 +++++++++++++++++++++++++++++++++++++++ test/box/errors.test.lua | 53 ++++++++ 6 files changed, 418 insertions(+) create mode 100644 test/box/errors.result create mode 100644 test/box/errors.test.lua diff --git a/src/box/error.h b/src/box/error.h index b8c7cf73d..f45a0a661 100644 --- a/src/box/error.h +++ b/src/box/error.h @@ -85,6 +85,29 @@ box_error_code(const box_error_t *error); const char * box_error_message(const box_error_t *error); +/** + * Wrap reason error object into given error. + * This API replaces box.error.last() value with an updated + * error object. + * \param error wrapper error object + * \param reason error object + * \return a pointer to the updated error object + */ +struct error * +box_error_wrap(box_error_t *error, box_error_t *reason); + +/** + * Removes the element's parent and returns the + * unwrapped reason error object. + * This API replases box.error.last() value with an unwrapped + * reason reason error object. The original error object is + * modified an has no reason anymore. + * \param error error object + * \return a pointer to the updated error object + */ +struct error * +box_error_unwrap(box_error_t *error); + /** * Get the information about the last API call error. * diff --git a/extra/exports b/extra/exports index b8c42c0df..17294ed8c 100644 --- a/extra/exports +++ b/extra/exports @@ -225,6 +225,8 @@ box_index_count box_error_type box_error_code box_error_message +box_error_wrap +box_error_unwrap box_error_last box_error_clear box_error_set diff --git a/src/box/error.cc b/src/box/error.cc index 7dfe1b3ee..4ed2a995b 100644 --- a/src/box/error.cc +++ b/src/box/error.cc @@ -57,6 +57,31 @@ box_error_message(const box_error_t *error) return error->errmsg; } +struct error * +box_error_wrap(box_error_t *error, box_error_t *reason) +{ + if (reason == NULL) { + diag_set_error(diag_get(), error); + return error; + } + assert(reason != NULL && error->reason == NULL); + error_ref(reason); + diag_set_error(diag_get(), error); + error->reason = reason; + return error; +} + +struct error * +box_error_unwrap(box_error_t *error) +{ + struct error *reason = error->reason; + assert(reason != NULL); + diag_set_error(diag_get(), reason); + error_unref(reason); + error->reason = NULL; + return reason; +} + box_error_t * box_error_last(void) { diff --git a/src/lua/error.lua b/src/lua/error.lua index 2bb8ccfe0..9b8b22a64 100644 --- a/src/lua/error.lua +++ b/src/lua/error.lua @@ -31,6 +31,15 @@ char * exception_get_string(struct error *e, const struct method_info *method); int exception_get_int(struct error *e, const struct method_info *method); + +typedef struct error box_error_t; + +uint32_t +box_error_code(const box_error_t *error); +box_error_t * +box_error_unwrap(box_error_t *error); +box_error_t * +box_error_wrap(box_error_t *error, box_error_t *reason); ]] local REFLECTION_CACHE = {} @@ -92,6 +101,7 @@ local error_fields = { ["type"] = error_type; ["message"] = error_message; ["trace"] = error_trace; + ["code"] = ffi.C.box_error_code; } local function error_unpack_one(err) @@ -126,6 +136,43 @@ local function error_unpack(err) return result end +local function error_has(err, errcode) + if not ffi.istype('struct error', err) then + error("Usage: error:has()") + end + while err ~= nil do + if ffi.C.box_error_code(err) == errcode then + return true + end + err = err._reason + end + return false +end + +local function error_wrap(err, reason) + if not ffi.istype('struct error', err) then + error("Usage: error:wrap()") + end + if err._reason ~= nil then + error("The error:wrap() method is only applicable for ".. + "errors with no reason defined") + end + if reason == nil then + return err + end + return ffi.C.box_error_wrap(err, reason) +end + +local function error_unwrap(err) + if not ffi.istype('struct error', err) then + error("Usage: error:unwrap()") + end + if err._reason == nil then + return err + end + return ffi.C.box_error_unwrap(err) +end + local function error_raise(err) if not ffi.istype('struct error', err) then error("Usage: error:raise()") @@ -149,6 +196,9 @@ local error_methods = { ["unpack"] = error_unpack; ["raise"] = error_raise; ["match"] = error_match; -- Tarantool 1.6 backward compatibility + ["has"] = error_has; + ["wrap"] = error_wrap; + ["unwrap"] = error_unwrap; ["__serialize"] = error_serialize; } diff --git a/test/box/errors.result b/test/box/errors.result new file mode 100644 index 000000000..b0fb2f38d --- /dev/null +++ b/test/box/errors.result @@ -0,0 +1,265 @@ +-- test-run result file version 2 +env = require('test_run') + | --- + | ... +test_run = env.new() + | --- + | ... + +-- +-- gh-1148: Stacked diagnostics area in fiber +-- +s = box.schema.space.create('withdata') + | --- + | ... +pk = s:create_index('pk') + | --- + | ... +lua_code = [[function(tuple) return {{"hello", "world"}, {1, 2}} end]] + | --- + | ... +box.schema.func.create('invalidreturn3', {body = lua_code, is_deterministic = true, is_sandboxed = true, opts = {is_multikey = true}}) + | --- + | ... +idx = s:create_index('idx', {func = box.func.invalidreturn3.id, parts = {{1, 'unsigned'}, {2, 'unsigned'}}}) + | --- + | ... +tuple, err2 = pcall(box.internal.insert, s.id, {1}) + | --- + | ... +assert(tuple == false) + | --- + | - true + | ... +err2:has(box.error.KEY_PART_TYPE) + | --- + | - true + | ... +err2:has(box.error.FUNC_INDEX_FORMAT) + | --- + | - true + | ... +err2:has(box.error.PROC_LUA) + | --- + | - false + | ... +err2.trace + | --- + | - - file: /home/kir/WORK/tarantool/src/box/key_list.c + | line: 175 + | ... + +-- Test wrap/unwrap cases and garbage collection +err3 = box.error.new(box.error.PROC_LUA, "Can't initialize storage") + | --- + | ... +err3:wrap(err2) + | --- + | - Can't initialize storage + | ... +err2:has(box.error.PROC_LUA) + | --- + | - false + | ... +err4 = box.error.new(box.error.PROC_LUA, "Can't initialize storage 2") + | --- + | ... +err4:wrap(nil) + | --- + | - Can't initialize storage 2 + | ... +err4:unpack() + | --- + | - - type: ClientError + | message: Can't initialize storage 2 + | code: 32 + | trace: + | - file: '[string "err4 = box.error.new(box.error.PROC_LUA, "Can..."]' + | line: 1 + | ... +err4:wrap(err3) + | --- + | - Can't initialize storage 2 + | ... +err4:wrap(nil) + | --- + | - error: 'builtin/error.lua:157: The error:wrap() method is only applicable for errors + | with no reason defined' + | ... +collectgarbage() + | --- + | - 0 + | ... +box.error.clear() + | --- + | ... +err2:unpack() + | --- + | - - type: ClientError + | message: 'Supplied key type of part 0 does not match index part type: expected + | unsigned' + | code: 18 + | trace: + | - file: /home/kir/WORK/tarantool/src/box/key_def.h + | line: 534 + | - type: ClientError + | message: 'Key format doesn''t match one defined in functional index ''idx'' of + | space ''withdata'': key does not follow functional index key definition' + | code: 199 + | trace: + | - file: /home/kir/WORK/tarantool/src/box/key_list.c + | line: 175 + | ... +err3:unpack() + | --- + | - - type: ClientError + | message: 'Supplied key type of part 0 does not match index part type: expected + | unsigned' + | code: 18 + | trace: + | - file: /home/kir/WORK/tarantool/src/box/key_def.h + | line: 534 + | - type: ClientError + | message: 'Key format doesn''t match one defined in functional index ''idx'' of + | space ''withdata'': key does not follow functional index key definition' + | code: 199 + | trace: + | - file: /home/kir/WORK/tarantool/src/box/key_list.c + | line: 175 + | - type: ClientError + | message: Can't initialize storage + | code: 32 + | trace: + | - file: '[string "err3 = box.error.new(box.error.PROC_LUA, "Can..."]' + | line: 1 + | ... +err4:unpack() + | --- + | - - type: ClientError + | message: 'Supplied key type of part 0 does not match index part type: expected + | unsigned' + | code: 18 + | trace: + | - file: /home/kir/WORK/tarantool/src/box/key_def.h + | line: 534 + | - type: ClientError + | message: 'Key format doesn''t match one defined in functional index ''idx'' of + | space ''withdata'': key does not follow functional index key definition' + | code: 199 + | trace: + | - file: /home/kir/WORK/tarantool/src/box/key_list.c + | line: 175 + | - type: ClientError + | message: Can't initialize storage + | code: 32 + | trace: + | - file: '[string "err3 = box.error.new(box.error.PROC_LUA, "Can..."]' + | line: 1 + | - type: ClientError + | message: Can't initialize storage 2 + | code: 32 + | trace: + | - file: '[string "err4 = box.error.new(box.error.PROC_LUA, "Can..."]' + | line: 1 + | ... + +err4 = nil + | --- + | ... +collectgarbage() + | --- + | - 0 + | ... +err2:unpack() + | --- + | - - type: ClientError + | message: 'Supplied key type of part 0 does not match index part type: expected + | unsigned' + | code: 18 + | trace: + | - file: /home/kir/WORK/tarantool/src/box/key_def.h + | line: 534 + | - type: ClientError + | message: 'Key format doesn''t match one defined in functional index ''idx'' of + | space ''withdata'': key does not follow functional index key definition' + | code: 199 + | trace: + | - file: /home/kir/WORK/tarantool/src/box/key_list.c + | line: 175 + | ... +err3:unpack() + | --- + | - - type: ClientError + | message: 'Supplied key type of part 0 does not match index part type: expected + | unsigned' + | code: 18 + | trace: + | - file: /home/kir/WORK/tarantool/src/box/key_def.h + | line: 534 + | - type: ClientError + | message: 'Key format doesn''t match one defined in functional index ''idx'' of + | space ''withdata'': key does not follow functional index key definition' + | code: 199 + | trace: + | - file: /home/kir/WORK/tarantool/src/box/key_list.c + | line: 175 + | - type: ClientError + | message: Can't initialize storage + | code: 32 + | trace: + | - file: '[string "err3 = box.error.new(box.error.PROC_LUA, "Can..."]' + | line: 1 + | ... + +err3_head = err3 + | --- + | ... +err3 = err3:unwrap() + | --- + | ... +err3 == err2 + | --- + | - true + | ... +box.error.last() == err3 + | --- + | - true + | ... +err3_head:unpack() + | --- + | - - type: ClientError + | message: Can't initialize storage + | code: 32 + | trace: + | - file: '[string "err3 = box.error.new(box.error.PROC_LUA, "Can..."]' + | line: 1 + | ... +err3_head = nil + | --- + | ... +collectgarbage() + | --- + | - 0 + | ... + +box.error.clear() + | --- + | ... +err3 = nil + | --- + | ... +collectgarbage() + | --- + | - 0 + | ... +err2 = nil + | --- + | ... +collectgarbage() + | --- + | - 0 + | ... + +s:drop() + | --- + | ... diff --git a/test/box/errors.test.lua b/test/box/errors.test.lua new file mode 100644 index 000000000..fac9cfbc2 --- /dev/null +++ b/test/box/errors.test.lua @@ -0,0 +1,53 @@ +env = require('test_run') +test_run = env.new() + +-- +-- gh-1148: Stacked diagnostics area in fiber +-- +s = box.schema.space.create('withdata') +pk = s:create_index('pk') +lua_code = [[function(tuple) return {{"hello", "world"}, {1, 2}} end]] +box.schema.func.create('invalidreturn3', {body = lua_code, is_deterministic = true, is_sandboxed = true, opts = {is_multikey = true}}) +idx = s:create_index('idx', {func = box.func.invalidreturn3.id, parts = {{1, 'unsigned'}, {2, 'unsigned'}}}) +tuple, err2 = pcall(box.internal.insert, s.id, {1}) +assert(tuple == false) +err2:has(box.error.KEY_PART_TYPE) +err2:has(box.error.FUNC_INDEX_FORMAT) +err2:has(box.error.PROC_LUA) +err2.trace + +-- Test wrap/unwrap cases and garbage collection +err3 = box.error.new(box.error.PROC_LUA, "Can't initialize storage") +err3:wrap(err2) +err2:has(box.error.PROC_LUA) +err4 = box.error.new(box.error.PROC_LUA, "Can't initialize storage 2") +err4:wrap(nil) +err4:unpack() +err4:wrap(err3) +err4:wrap(nil) +collectgarbage() +box.error.clear() +err2:unpack() +err3:unpack() +err4:unpack() + +err4 = nil +collectgarbage() +err2:unpack() +err3:unpack() + +err3_head = err3 +err3 = err3:unwrap() +err3 == err2 +box.error.last() == err3 +err3_head:unpack() +err3_head = nil +collectgarbage() + +box.error.clear() +err3 = nil +collectgarbage() +err2 = nil +collectgarbage() + +s:drop() -- 2.22.0