[tarantool-patches] [PATCH v1 3/3] box: extend ffi error object API
Kirill Shcherbatov
kshcherbatov at tarantool.org
Thu Aug 1 14:13:28 MSK 2019
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<struct error *> 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
More information about the Tarantool-patches
mailing list