Tarantool development patches archive
 help / color / mirror / Atom feed
From: Kirill Shcherbatov <kshcherbatov@tarantool.org>
To: tarantool-patches@freelists.org, v.shpilevoy@tarantool.org
Cc: alexander.turenko@tarantool.org, kostja@tarantool.org,
	Kirill Shcherbatov <kshcherbatov@tarantool.org>
Subject: [tarantool-patches] [PATCH v1 3/3] box: extend ffi error object API
Date: Thu,  1 Aug 2019 14:13:28 +0300	[thread overview]
Message-ID: <47d9072e60bdc563c7466a4e51db1a61bc71a610.1564657285.git.kshcherbatov@tarantool.org> (raw)
In-Reply-To: <cover.1564657285.git.kshcherbatov@tarantool.org>

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

  parent reply	other threads:[~2019-08-01 11:13 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-08-01 11:13 [tarantool-patches] [PATCH v1 0/3] box: stacked diagnostics area in fiber Kirill Shcherbatov
2019-08-01 11:13 ` [tarantool-patches] [PATCH v1 1/3] box: rfc for stacked diagnostic area in Tarantool Kirill Shcherbatov
2019-08-05 21:16   ` [tarantool-patches] " Vladislav Shpilevoy
     [not found]     ` <06bd2140-3d2b-4bc3-7bc4-5f3d293bf891@tarantool.org>
2019-08-06 20:50       ` Vladislav Shpilevoy
2019-08-07 23:27   ` Alexander Turenko
2019-08-08 20:46     ` Vladislav Shpilevoy
2019-08-08 23:29       ` Alexander Turenko
2019-08-09 19:25         ` Vladislav Shpilevoy
2019-08-12 20:35         ` Konstantin Osipov
2019-08-01 11:13 ` [tarantool-patches] [PATCH v1 2/3] box: stacked diagnostics area in fiber Kirill Shcherbatov
2019-08-05 21:16   ` [tarantool-patches] " Vladislav Shpilevoy
2019-08-01 11:13 ` Kirill Shcherbatov [this message]
2019-08-05 21:18   ` [tarantool-patches] Re: [PATCH v1 3/3] box: extend ffi error object API Vladislav Shpilevoy
2019-08-06  7:56     ` Kirill Shcherbatov
2019-08-06 20:50       ` Vladislav Shpilevoy
2019-08-08 23:33     ` Alexander Turenko
2019-08-09 19:27       ` Vladislav Shpilevoy

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=47d9072e60bdc563c7466a4e51db1a61bc71a610.1564657285.git.kshcherbatov@tarantool.org \
    --to=kshcherbatov@tarantool.org \
    --cc=alexander.turenko@tarantool.org \
    --cc=kostja@tarantool.org \
    --cc=tarantool-patches@freelists.org \
    --cc=v.shpilevoy@tarantool.org \
    --subject='Re: [tarantool-patches] [PATCH v1 3/3] box: extend ffi error object API' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox