[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