* [Tarantool-patches] [PATCH V3 1/7] error: add a Lua traceback to error
2020-04-15 9:31 [Tarantool-patches] [PATCH V3 0/7] Extending error functionality Leonid Vasiliev
@ 2020-04-15 9:31 ` Leonid Vasiliev
2020-04-15 9:31 ` [Tarantool-patches] [PATCH V3 2/7] error: add custom error type Leonid Vasiliev
` (5 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Leonid Vasiliev @ 2020-04-15 9:31 UTC (permalink / raw)
To: v.shpilevoy; +Cc: tarantool-patches
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.
Co-authored-by: Vladislav Shpilevoy<v.shpilevoy@tarantool.org>
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
})
```
---
src/box/lua/error.cc | 33 +++++++++++++++++++++++++-
src/lib/core/diag.c | 16 +++++++++++++
src/lib/core/diag.h | 28 ++++++++++++++++++++++
src/lib/core/exception.cc | 1 +
src/lua/error.c | 9 ++++++++
src/lua/error.lua | 7 ++++++
test/box/error.result | 59 +++++++++++++++++++++++++++++++++++++++++++++++
test/box/error.test.lua | 33 ++++++++++++++++++++++++++
8 files changed, 185 insertions(+), 1 deletion(-)
diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
index b2625bf..a2facf0 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 is_traceback_enabled = false;
+ bool is_traceback_specified = 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)) {
+ is_traceback_enabled = lua_toboolean(L, -1);
+ is_traceback_specified = true;
+ }
+ lua_pop(L, 1);
} else {
return NULL;
}
@@ -102,7 +110,14 @@ raise:
}
line = info.currentline;
}
- return box_error_new(file, line, code, "%s", reason);
+
+ struct error *err = box_error_new(file, line, code, "%s", reason);
+ /*
+ * Explicit traceback option overrides the global setting.
+ */
+ if (is_traceback_specified)
+ err->is_traceback_enabled = is_traceback_enabled;
+ return err;
}
static int
@@ -180,6 +195,18 @@ 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_enable");
+ if (lua_isboolean(L, -1))
+ error_is_traceback_enabled = lua_toboolean(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..833454b 100644
--- a/src/lib/core/diag.c
+++ b/src/lib/core/diag.c
@@ -31,6 +31,8 @@
#include "diag.h"
#include "fiber.h"
+bool error_is_traceback_enabled = false;
+
int
error_set_prev(struct error *e, struct error *prev)
{
@@ -97,6 +99,8 @@ error_create(struct error *e,
e->errmsg[0] = '\0';
e->cause = NULL;
e->effect = NULL;
+ e->traceback = NULL;
+ e->is_traceback_enabled = error_is_traceback_enabled;
}
struct diag *
@@ -120,3 +124,15 @@ error_vformat_msg(struct error *e, const char *format, va_list ap)
vsnprintf(e->errmsg, sizeof(e->errmsg), format, ap);
}
+void
+error_set_traceback(struct error *e, const char *traceback)
+{
+ 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 7a5454d..e918d30 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,6 +117,19 @@ struct error {
*/
struct error *cause;
struct error *effect;
+ /**
+ * 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
@@ -172,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)
{
diff --git a/src/lib/core/exception.cc b/src/lib/core/exception.cc
index 180cb0e..1717b76 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->traceback);
delete (Exception *) e;
}
diff --git a/src/lua/error.c b/src/lua/error.c
index 18a990a..a21548c 100644
--- a/src/lua/error.c
+++ b/src/lua/error.c
@@ -85,6 +85,15 @@ luaT_pusherror(struct lua_State *L, struct error *e)
* then set the finalizer.
*/
error_ref(e);
+
+ 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_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..93fd1b9 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 *err_traceback;
+ bool is_traceback_enabled;
};
char *
@@ -92,6 +94,10 @@ local function error_trace(err)
}
end
+local function error_traceback(err)
+ return err.err_traceback ~= ffi.nullptr and ffi.string(err.err_traceback) or nil
+end
+
local function error_errno(err)
local e = err._saved_errno
if e == 0 then
@@ -131,6 +137,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/box/error.result b/test/box/error.result
index df6d0eb..2502d88 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -831,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 41baed5..6f12716 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)
--
2.7.4
^ permalink raw reply [flat|nested] 9+ messages in thread
* [Tarantool-patches] [PATCH V3 2/7] error: add custom error type
2020-04-15 9:31 [Tarantool-patches] [PATCH V3 0/7] Extending error functionality Leonid Vasiliev
2020-04-15 9:31 ` [Tarantool-patches] [PATCH V3 1/7] error: add a Lua traceback to error Leonid Vasiliev
@ 2020-04-15 9:31 ` Leonid Vasiliev
2020-04-15 9:31 ` [Tarantool-patches] [PATCH V3 3/7] error: send custom type in IProto Leonid Vasiliev
` (4 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Leonid Vasiliev @ 2020-04-15 9:31 UTC (permalink / raw)
To: v.shpilevoy; +Cc: tarantool-patches
Co-authored-by: Vladislav Shpilevoy<v.shpilevoy@tarantool.org>
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
...
```
---
| 1 +
src/box/errcode.h | 1 +
src/box/error.cc | 93 +++++++++++++++++++++++++++++++++----------
src/box/error.h | 34 +++++++++++++++-
src/box/lua/error.cc | 44 ++++++++++++++------
src/box/xrow.c | 2 +-
src/lua/error.lua | 14 ++++++-
test/app/fiber.result | 7 ++--
test/box/error.result | 69 +++++++++++++++++++++++++++++---
test/box/error.test.lua | 16 ++++++++
test/engine/func_index.result | 14 ++++---
11 files changed, 245 insertions(+), 50 deletions(-)
--git a/extra/exports b/extra/exports
index a9add2c..9467398 100644
--- a/extra/exports
+++ b/extra/exports
@@ -235,6 +235,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/errcode.h b/src/box/errcode.h
index d1e4d02..e37b7bd 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -266,6 +266,7 @@ struct errcode_record {
/*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_NO_SUCH_SESSION_SETTING, "Session setting %s doesn't exist") \
+ /*214 */_(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..d854b86 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);
@@ -125,6 +141,16 @@ box_error_add(const char *file, unsigned line, uint32_t code,
/* }}} */
+const char *
+box_error_custom_type(const struct error *e)
+{
+ CustomError *custom_error = type_cast(CustomError, e);
+ if (custom_error)
+ return custom_error->custom_type();
+
+ return NULL;
+}
+
struct rmean *rmean_error = NULL;
const char *rmean_error_strings[RMEAN_ERROR_LAST] = {
@@ -290,3 +316,30 @@ 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);
+ strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1);
+ m_custom_type[sizeof(m_custom_type) - 1] = '\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..540008f 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,11 +141,21 @@ box_error_set(const char *file, unsigned line, uint32_t code,
/** \endcond public */
/**
+ * Return the error custom type,
+ * \param e Error object.
+ * \return Pointer to custom error type. On error return NULL.
+ */
+const char *
+box_error_custom_type(const struct error *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.
*
* \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
@@ -151,7 +164,7 @@ box_error_set(const char *file, unsigned line, uint32_t code,
*/
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
@@ -159,11 +172,12 @@ box_error_add(const char *file, unsigned line, uint32_t code,
*/
struct error *
box_error_new(const char *file, unsigned line, uint32_t code,
- const char *fmt, ...);
+ const char *custom_type, 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 +304,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 a2facf0..2279d63 100644
--- a/src/box/lua/error.cc
+++ b/src/box/lua/error.cc
@@ -46,6 +46,13 @@ extern "C" {
* 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.
*/
@@ -53,6 +60,7 @@ 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;
@@ -60,8 +68,15 @@ luaT_error_create(lua_State *L, int top_base)
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);
+ 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) {
+ code = lua_tonumber(L, top_base);
+ } else {
+ code = ER_CUSTOM_ERROR;
+ custom_type = lua_tostring(L, top_base);
+ }
reason = tnt_errcode_desc(code);
if (top > top_base) {
/* Call string.format(reason, ...) to format message */
@@ -80,15 +95,19 @@ luaT_error_create(lua_State *L, int top_base)
/* Missing arguments to format string */
return NULL;
}
- } 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);
- lua_pop(L, 1);
+ if (!lua_isnil(L, -1))
+ code = lua_tonumber(L, -1);
+ else
+ code = ER_CUSTOM_ERROR;
lua_getfield(L, top_base, "reason");
reason = lua_tostring(L, -1);
if (reason == NULL)
reason = "";
- lua_pop(L, 1);
+ lua_getfield(L, top_base, "type");
+ if (!lua_isnil(L, -1))
+ custom_type = lua_tostring(L, -1);
lua_getfield(L, top_base, "traceback");
if (lua_isboolean(L, -1)) {
is_traceback_enabled = lua_toboolean(L, -1);
@@ -111,7 +130,8 @@ raise:
line = info.currentline;
}
- struct error *err = box_error_new(file, line, code, "%s", reason);
+ struct error *err = box_error_new(file, line, code, custom_type,
+ "%s", reason);
/*
* Explicit traceback option overrides the global setting.
*/
@@ -164,11 +184,11 @@ 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)");
- struct error *e = luaT_error_create(L, 1);
- if (e == NULL)
- return luaL_error(L, "Usage: box.error.new(code, args)");
+ 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)");
+ }
lua_settop(L, 0);
luaT_pusherror(L, e);
return 1;
diff --git a/src/box/xrow.c b/src/box/xrow.c
index a494d1f..5494b41 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 93fd1b9..80df657 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,10 +80,18 @@ local function reflection_get(err, method)
end
end
-local function error_type(err)
+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
@@ -138,6 +149,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 debfc67..fa7b20a 100644
--- a/test/app/fiber.result
+++ b/test/app/fiber.result
@@ -1038,12 +1038,13 @@ st;
...
e:unpack();
---
-- type: ClientError
- code: 1
- message: Illegal parameters, oh my
+- code: 1
trace:
- file: '[string "function err() box.error(box.error.ILLEGAL_PA..."]'
line: 1
+ type: ClientError
+ message: Illegal parameters, oh my
+ base_type: ClientError
...
flag = false;
---
diff --git a/test/box/error.result b/test/box/error.result
index 2502d88..b717a4f 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -34,12 +34,13 @@ e
| ...
e:unpack()
| ---
- | - type: ClientError
- | code: 1
- | message: Illegal parameters, bla bla
+ | - code: 1
| trace:
| - file: '[C]'
| line: 4294967295
+ | type: ClientError
+ | message: Illegal parameters, bla bla
+ | base_type: ClientError
| ...
e.type
| ---
@@ -105,7 +106,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;
| 211: box.error.WRONG_QUERY_ID
| 212: box.error.SEQUENCE_NOT_STARTED
| 213: box.error.NO_SUCH_SESSION_SETTING
+ | 214: 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.
@@ -890,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 6f12716..fe40518 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
diff --git a/test/engine/func_index.result b/test/engine/func_index.result
index a827c92..12fb0ea 100644
--- a/test/engine/func_index.result
+++ b/test/engine/func_index.result
@@ -281,22 +281,24 @@ e:unpack()
| - file: <filename>
| line: <line>
| 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'
+ | base_type: ClientError
| ...
e = e.prev
| ---
| ...
e:unpack()
| ---
- | - type: LuajitError
- | message: '[string "return function(tuple) local ..."]:1: attempt
- | to call global ''require'' (a nil value)'
- | trace:
+ | - trace:
| - file: <filename>
| line: <line>
+ | type: LuajitError
+ | message: '[string "return function(tuple) local ..."]:1: attempt
+ | to call global ''require'' (a nil value)'
+ | base_type: LuajitError
| ...
e = e.prev
| ---
--
2.7.4
^ permalink raw reply [flat|nested] 9+ messages in thread
* [Tarantool-patches] [PATCH V3 3/7] error: send custom type in IProto
2020-04-15 9:31 [Tarantool-patches] [PATCH V3 0/7] Extending error functionality Leonid Vasiliev
2020-04-15 9:31 ` [Tarantool-patches] [PATCH V3 1/7] error: add a Lua traceback to error Leonid Vasiliev
2020-04-15 9:31 ` [Tarantool-patches] [PATCH V3 2/7] error: add custom error type Leonid Vasiliev
@ 2020-04-15 9:31 ` Leonid Vasiliev
2020-04-15 9:31 ` [Tarantool-patches] [PATCH V3 4/7] session: add offset to SQL session settings array Leonid Vasiliev
` (3 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Leonid Vasiliev @ 2020-04-15 9:31 UTC (permalink / raw)
To: v.shpilevoy; +Cc: tarantool-patches
Error custom type features were added to the public
Lua API in the previous commits. This one makes the new attribute
being sent in IProto.
Co-authored-by: Vladislav Shpilevoy<v.shpilevoy@tarantool.org>
Needed for #4398
@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 added:
`IPROTO_ERROR_CUSTOM_TYPE = 0x03`.
It's optional, have MP_STR type, and speak for themselves.
Custom error type is error object attribute.
This is what a user specifies in
`box.error.new({type = <custom_type>})` or
`box.error.new(<custom_type>)`.
---
| 1 +
src/box/iproto_constants.h | 1 +
src/box/lua/net_box.lua | 14 +++++++++++---
src/box/xrow.c | 9 ++++++++-
test/box/error.result | 40 ++++++++++++++++++++++++++++++++++++++++
test/box/error.test.lua | 17 +++++++++++++++++
6 files changed, 78 insertions(+), 4 deletions(-)
--git a/extra/exports b/extra/exports
index 9467398..b01b437 100644
--- a/extra/exports
+++ b/extra/exports
@@ -242,6 +242,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/iproto_constants.h b/src/box/iproto_constants.h
index 7ed8296..1fca97b 100644
--- a/src/box/iproto_constants.h
+++ b/src/box/iproto_constants.h
@@ -154,6 +154,7 @@ enum iproto_ballot_key {
enum iproto_error_key {
IPROTO_ERROR_CODE = 0x01,
IPROTO_ERROR_MESSAGE = 0x02,
+ IPROTO_ERROR_CUSTOM_TYPE = 0x03,
};
#define bit(c) (1ULL<<IPROTO_##c)
diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index 07fa54c..95f9cf8 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
@@ -47,6 +52,7 @@ local IPROTO_ERROR_KEY = 0x31
local IPROTO_ERROR_STACK = 0x52
local IPROTO_ERROR_CODE = 0x01
local IPROTO_ERROR_MESSAGE = 0x02
+local IPROTO_ERROR_CUSTOM_TYPE = 0x03
local IPROTO_GREETING_SIZE = 128
local IPROTO_CHUNK_KEY = 128
local IPROTO_OK_KEY = 0
@@ -285,9 +291,11 @@ 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 new_err = box.error.new({code = code, reason = msg})
+ local new_err = box.error.new({
+ type = error[IPROTO_ERROR_CUSTOM_TYPE],
+ code = error[IPROTO_ERROR_CODE],
+ reason = error[IPROTO_ERROR_MESSAGE],
+ })
new_err:set_prev(prev)
prev = new_err
end
diff --git a/src/box/xrow.c b/src/box/xrow.c
index 5494b41..a717da6 100644
--- a/src/box/xrow.c
+++ b/src/box/xrow.c
@@ -494,11 +494,18 @@ 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);
+ uint32_t map_size = 2;
+ const char *custom_type = box_error_custom_type(it);
+ map_size += (custom_type != NULL);
+ 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 (custom_type != NULL) {
+ mpstream_encode_uint(stream, IPROTO_ERROR_CUSTOM_TYPE);
+ mpstream_encode_str(stream, custom_type);
+ }
}
}
diff --git a/test/box/error.result b/test/box/error.result
index b717a4f..ced227f 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -949,3 +949,43 @@ e = box.error.new({type = string.rep('a', 128)})
| ---
| - 63
| ...
+
+--
+-- Check how 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, 'box.error', { \
+ {code = 123, type = 'TestType', reason = 'Test reason'} \
+})
+ | ---
+ | ...
+e = e:unpack()
+ | ---
+ | ...
+e.trace = nil
+ | ---
+ | ...
+e
+ | ---
+ | - code: 123
+ | type: TestType
+ | custom_type: TestType
+ | message: Test reason
+ | base_type: CustomError
+ | ...
+
+c:close()
+ | ---
+ | ...
+box.schema.user.revoke('guest', 'super')
+ | ---
+ | ...
diff --git a/test/box/error.test.lua b/test/box/error.test.lua
index fe40518..b71ab52 100644
--- a/test/box/error.test.lua
+++ b/test/box/error.test.lua
@@ -278,3 +278,20 @@ e:unpack()
-- Try too long type name.
e = box.error.new({type = string.rep('a', 128)})
#e.type
+
+--
+-- Check how 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, 'box.error', { \
+ {code = 123, type = 'TestType', reason = 'Test reason'} \
+})
+e = e:unpack()
+e.trace = nil
+e
+
+c:close()
+box.schema.user.revoke('guest', 'super')
--
2.7.4
^ permalink raw reply [flat|nested] 9+ messages in thread
* [Tarantool-patches] [PATCH V3 4/7] session: add offset to SQL session settings array
2020-04-15 9:31 [Tarantool-patches] [PATCH V3 0/7] Extending error functionality Leonid Vasiliev
` (2 preceding siblings ...)
2020-04-15 9:31 ` [Tarantool-patches] [PATCH V3 3/7] error: send custom type in IProto Leonid Vasiliev
@ 2020-04-15 9:31 ` Leonid Vasiliev
2020-04-15 9:32 ` [Tarantool-patches] [PATCH V3 5/7] error: add session setting for error type marshaling Leonid Vasiliev
` (2 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Leonid Vasiliev @ 2020-04-15 9:31 UTC (permalink / raw)
To: v.shpilevoy; +Cc: tarantool-patches
From: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Session settings are stored in a monolithic array. Submodules
can define a range of settings in there. For example, SQL. It
occupies settings from 0 to 8. There is a second array of only
SQL settings in build.c, of the same size, and it uses the same
indexes.
But if something will be added before SQL settings, so as they
won't start from 0, it will break the equal indexes assumption.
SQL should normalize all setting identifiers by
SESSION_SETTING_SQL_BEGIN.
---
src/box/sql/build.c | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index aecba41..b1f9fed 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -3367,7 +3367,8 @@ sql_session_setting_get(int id, const char **mp_pair, const char **mp_pair_end)
assert(id >= SESSION_SETTING_SQL_BEGIN && id < SESSION_SETTING_SQL_END);
struct session *session = current_session();
uint32_t flags = session->sql_flags;
- struct sql_option_metadata *opt = &sql_session_opts[id];
+ struct sql_option_metadata *opt =
+ &sql_session_opts[id - SESSION_SETTING_SQL_BEGIN];
uint32_t mask = opt->mask;
const char *name = session_setting_strs[id];
size_t name_len = strlen(name);
@@ -3404,7 +3405,8 @@ static int
sql_set_boolean_option(int id, bool value)
{
struct session *session = current_session();
- struct sql_option_metadata *option = &sql_session_opts[id];
+ struct sql_option_metadata *option =
+ &sql_session_opts[id - SESSION_SETTING_SQL_BEGIN];
assert(option->field_type == FIELD_TYPE_BOOLEAN);
#ifdef NDEBUG
if ((session->sql_flags & SQL_SqlTrace) == 0) {
@@ -3431,7 +3433,8 @@ sql_set_boolean_option(int id, bool value)
static int
sql_set_string_option(int id, const char *value)
{
- assert(sql_session_opts[id].field_type = FIELD_TYPE_STRING);
+ assert(sql_session_opts[id - SESSION_SETTING_SQL_BEGIN].field_type =
+ FIELD_TYPE_STRING);
assert(id == SESSION_SETTING_SQL_DEFAULT_ENGINE);
(void)id;
enum sql_storage_engine engine = STR2ENUM(sql_storage_engine, value);
@@ -3448,7 +3451,8 @@ sql_session_setting_set(int id, const char *mp_value)
{
assert(id >= SESSION_SETTING_SQL_BEGIN && id < SESSION_SETTING_SQL_END);
enum mp_type mtype = mp_typeof(*mp_value);
- enum field_type stype = sql_session_opts[id].field_type;
+ enum field_type stype =
+ sql_session_opts[id - SESSION_SETTING_SQL_BEGIN].field_type;
uint32_t len;
const char *tmp;
switch(stype) {
@@ -3473,10 +3477,10 @@ sql_session_setting_set(int id, const char *mp_value)
void
sql_session_settings_init()
{
- for (int id = SESSION_SETTING_SQL_BEGIN; id < SESSION_SETTING_SQL_END;
- ++id) {
+ for (int i = 0, id = SESSION_SETTING_SQL_BEGIN;
+ id < SESSION_SETTING_SQL_END; ++id, ++i) {
struct session_setting *setting = &session_settings[id];
- setting->field_type = sql_session_opts[id].field_type;
+ setting->field_type = sql_session_opts[i].field_type;
setting->get = sql_session_setting_get;
setting->set = sql_session_setting_set;
}
--
2.7.4
^ permalink raw reply [flat|nested] 9+ messages in thread
* [Tarantool-patches] [PATCH V3 5/7] error: add session setting for error type marshaling
2020-04-15 9:31 [Tarantool-patches] [PATCH V3 0/7] Extending error functionality Leonid Vasiliev
` (3 preceding siblings ...)
2020-04-15 9:31 ` [Tarantool-patches] [PATCH V3 4/7] session: add offset to SQL session settings array Leonid Vasiliev
@ 2020-04-15 9:32 ` Leonid Vasiliev
2020-04-15 9:32 ` [Tarantool-patches] [PATCH V3 6/7] error: update constructors of some errors Leonid Vasiliev
2020-04-15 9:32 ` [Tarantool-patches] [PATCH V3 7/7] error: add error MsgPack encoding Leonid Vasiliev
6 siblings, 0 replies; 9+ messages in thread
From: Leonid Vasiliev @ 2020-04-15 9:32 UTC (permalink / raw)
To: v.shpilevoy; +Cc: tarantool-patches
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.
Co-authored-by: Vladislav Shpilevoy<v.shpilevoy@tarantool.org>
Needed for #4398
---
src/box/lua/call.c | 33 ++++++++++++++---------
src/box/lua/execute.c | 2 +-
src/box/lua/tuple.c | 12 ++++-----
src/box/session.cc | 5 +++-
src/box/session.h | 5 ++--
src/box/session_settings.c | 56 ++++++++++++++++++++++++++++++++++++++++
src/box/session_settings.h | 1 +
src/box/sql/func.c | 4 ++-
src/lua/msgpack.c | 25 +++++++++---------
src/lua/msgpack.h | 8 +++---
src/lua/utils.c | 11 +++++---
src/lua/utils.h | 8 ++++--
src/serializer_opts.h | 44 +++++++++++++++++++++++++++++++
test/box/session_settings.result | 3 ++-
14 files changed, 172 insertions(+), 45 deletions(-)
create mode 100644 src/serializer_opts.h
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index 1d29494..91e291c 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/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,11 @@ execute_lua_eval(lua_State *L)
struct encode_lua_ctx {
struct port_lua *port;
struct mpstream *stream;
+ /*
+ * Lua serializer additional options.
+ * Used for sets the session specific serialization options.
+ */
+ struct serializer_opts *serializer_opts;
};
static int
@@ -393,8 +399,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_opts,
+ ctx->stream, i);
+ }
ctx->port->size = size;
mpstream_flush(ctx->stream);
return 0;
@@ -429,6 +437,7 @@ port_lua_do_dump(struct port *base, struct mpstream *stream,
struct encode_lua_ctx ctx;
ctx.port = port;
ctx.stream = stream;
+ ctx.serializer_opts = ¤t_session()->meta.serializer_opts;
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/tuple.c b/src/box/lua/tuple.c
index 4b0701e..aba906d 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);
}
@@ -435,7 +435,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);
diff --git a/src/box/session.cc b/src/box/session.cc
index b557eed..08a1092 100644
--- a/src/box/session.cc
+++ b/src/box/session.cc
@@ -275,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()
{
@@ -283,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 8693799..500a88b 100644
--- a/src/box/session.h
+++ b/src/box/session.h
@@ -36,13 +36,12 @@
#include "fiber.h"
#include "user.h"
#include "authentication.h"
+#include "serializer_opts.h"
#if defined(__cplusplus)
extern "C" {
#endif /* defined(__cplusplus) */
-extern void sql_session_settings_init();
-
struct port;
struct session_vtab;
@@ -90,6 +89,8 @@ struct session_meta {
};
/** Console output format. */
enum output_format output_format;
+ /** Session-specific serialization options. */
+ struct serializer_opts serializer_opts;
};
/**
diff --git a/src/box/session_settings.c b/src/box/session_settings.c
index 79c4b8d..dbbbf24 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/box/session_settings.h b/src/box/session_settings.h
index e2adc52..6560b8c 100644
--- a/src/box/session_settings.h
+++ b/src/box/session_settings.h
@@ -42,6 +42,7 @@
* space iterator will not be sorted properly.
*/
enum {
+ SESSION_SETTING_ERROR_MARSHALING_ENABLED,
SESSION_SETTING_SQL_BEGIN,
SESSION_SETTING_SQL_DEFAULT_ENGINE = SESSION_SETTING_SQL_BEGIN,
SESSION_SETTING_SQL_DEFER_FOREIGN_KEYS,
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index e3a6e88..3768372 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/lua/msgpack.c b/src/lua/msgpack.c
index e4fb0cf..b14361f 100644
--- a/src/lua/msgpack.c
+++ b/src/lua/msgpack.c
@@ -109,8 +109,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 serializer_opts *opts, struct mpstream *stream,
+ struct luaL_field *field, int level)
{
int top = lua_gettop(L);
enum mp_type type;
@@ -155,13 +155,13 @@ restart: /* used by MP_EXT of unidentified subtype */
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, opts, lua_gettop(L), field) < 0)
return luaT_error(L);
- luamp_encode_r(L, cfg, stream, field, level + 1);
+ luamp_encode_r(L, cfg, opts, 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, opts, lua_gettop(L), field) < 0)
return luaT_error(L);
- luamp_encode_r(L, cfg, stream, field, level + 1);
+ luamp_encode_r(L, cfg, opts, stream, field, level + 1);
lua_pop(L, 1); /* pop value */
}
assert(lua_gettop(L) == top);
@@ -180,9 +180,9 @@ restart: /* used by MP_EXT of unidentified subtype */
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, opts, top + 1, field) < 0)
return luaT_error(L);
- luamp_encode_r(L, cfg, stream, field, level + 1);
+ luamp_encode_r(L, cfg, opts, stream, field, level + 1);
lua_pop(L, 1);
}
assert(lua_gettop(L) == top);
@@ -213,7 +213,8 @@ restart: /* used by MP_EXT of unidentified subtype */
enum mp_type
luamp_encode(struct lua_State *L, struct luaL_serializer *cfg,
- struct mpstream *stream, int index)
+ struct serializer_opts *opts, struct mpstream *stream,
+ int index)
{
int top = lua_gettop(L);
if (index < 0)
@@ -225,9 +226,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, opts, 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, opts, stream, &field, 0);
if (!on_top) {
lua_remove(L, top + 1); /* remove a value copy */
@@ -369,7 +370,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..279e056 100644
--- a/src/lua/msgpack.h
+++ b/src/lua/msgpack.h
@@ -43,6 +43,7 @@ extern "C" {
struct luaL_serializer;
struct mpstream;
+struct serializer_opts;
/**
* 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 serializer_opts *opts, 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 serializer_opts *opts, 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 667365f..bd320f3 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -452,7 +452,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 */
@@ -515,7 +515,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;
}
@@ -641,14 +641,17 @@ 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 serializer_opts *opts, int index,
struct luaL_field *field)
{
+ /* opts will be used for encode MP_ERROR in the future */
+ (void)opts;
if (index < 0)
index = lua_gettop(L) + index + 1;
diff --git a/src/lua/utils.h b/src/lua/utils.h
index 4bc0417..cf9ef76 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -59,6 +59,8 @@ extern "C" {
#include "lib/core/mp_extension_types.h"
#include "lib/core/decimal.h" /* decimal_t */
+#include "serializer_opts.h"
+
struct lua_State;
struct ibuf;
struct tt_uuid;
@@ -366,6 +368,7 @@ struct luaL_field {
*
* @param L stack
* @param cfg configuration
+ * @param serializer_opts the Lua serializer additional options
* @param index stack index
* @param field conversion result
*
@@ -373,7 +376,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 serializer_opts *opts, int index,
struct luaL_field *field);
/**
@@ -412,7 +416,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/src/serializer_opts.h b/src/serializer_opts.h
new file mode 100644
index 0000000..9e2c15e
--- /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 ea6302d..149cc4b 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]
--
2.7.4
^ permalink raw reply [flat|nested] 9+ messages in thread
* [Tarantool-patches] [PATCH V3 6/7] error: update constructors of some errors
2020-04-15 9:31 [Tarantool-patches] [PATCH V3 0/7] Extending error functionality Leonid Vasiliev
` (4 preceding siblings ...)
2020-04-15 9:32 ` [Tarantool-patches] [PATCH V3 5/7] error: add session setting for error type marshaling Leonid Vasiliev
@ 2020-04-15 9:32 ` Leonid Vasiliev
2020-04-15 9:32 ` [Tarantool-patches] [PATCH V3 7/7] error: add error MsgPack encoding Leonid Vasiliev
6 siblings, 0 replies; 9+ messages in thread
From: Leonid Vasiliev @ 2020-04-15 9:32 UTC (permalink / raw)
To: v.shpilevoy; +Cc: tarantool-patches
We want to have a transparent marshalling through net.box
for errors. To do this, we need to recreate the error
on the client side with the same parameters as on the server.
For convenience, we update AccessDeniedError constructor
which has points to static strings and add the XlogGapError
constructor that does not require vclock.
Needed for #4398
---
src/box/error.cc | 18 +++++++++---------
src/box/error.h | 8 ++++++--
2 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/src/box/error.cc b/src/box/error.cc
index d854b86..acfd2f7 100644
--- a/src/box/error.cc
+++ b/src/box/error.cc
@@ -254,6 +254,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)
@@ -290,15 +297,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 540008f..4388723 100644
--- a/src/box/error.h
+++ b/src/box/error.h
@@ -244,6 +244,8 @@ public:
~AccessDeniedError()
{
free(m_object_name);
+ free(m_object_type);
+ free(m_access_type);
}
const char *
@@ -266,11 +268,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;
};
/**
@@ -300,6 +302,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; }
};
--
2.7.4
^ permalink raw reply [flat|nested] 9+ messages in thread
* [Tarantool-patches] [PATCH V3 7/7] error: add error MsgPack encoding
2020-04-15 9:31 [Tarantool-patches] [PATCH V3 0/7] Extending error functionality Leonid Vasiliev
` (5 preceding siblings ...)
2020-04-15 9:32 ` [Tarantool-patches] [PATCH V3 6/7] error: update constructors of some errors Leonid Vasiliev
@ 2020-04-15 9:32 ` Leonid Vasiliev
2020-04-15 15:08 ` lvasiliev
6 siblings, 1 reply; 9+ messages in thread
From: Leonid Vasiliev @ 2020-04-15 9:32 UTC (permalink / raw)
To: v.shpilevoy; +Cc: tarantool-patches
Closes #4398
@TarantoolBot document
Title: msgpack format for MP_ERROR
For sent an error over IProto MP_ERROR(0x03) subtype
has been added to MsgPack MP_EXT.
Now, if session setting 'error_marshaling_enabled'
is true error will be encoded as:
```
+--------+----------+========+
| MP_EXT | MP_ERROR | MP_MAP |
+--------+----------+========+
```
At the map passes all necessary parameters (depending
from the error type) to create a copy of the error
on the client side.
The necessary fields:
- Error type
- Trace(file name and line)
- Error message
- Errno
Additional fields:
- ClientError code
- Traceback (now only Lua traceback)
- Custom type
- Access Denied object type
- Access Denied object name
- Access Denied access type
---
src/box/CMakeLists.txt | 1 +
src/box/lua/init.c | 56 +++++
src/box/lua/mp_error.cc | 476 +++++++++++++++++++++++++++++++++++
src/box/lua/mp_error.h | 49 ++++
src/box/lua/tuple.c | 16 --
src/lib/core/mp_extension_types.h | 1 +
src/lua/error.c | 2 -
src/lua/error.h | 3 +-
src/lua/msgpack.c | 3 +
src/lua/utils.c | 6 +-
test/box-tap/extended_error.test.lua | 182 ++++++++++++++
11 files changed, 774 insertions(+), 21 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 6688303..7581078 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/lua/init.c b/src/box/lua/init.c
index 63e8b82..d7e97a4 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/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()->meta.serializer_opts.error_marshaling_enabled) {
+ 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..f99ed2e
--- /dev/null
+++ b/src/box/lua/mp_error.cc
@@ -0,0 +1,476 @@
+/*
+ * 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/mpstream.h"
+#include "msgpuck.h"
+#include "mp_extension_types.h"
+
+/**
+ * Keys used for encode/decode MP_ERROR.
+ */
+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_TRACEBACK,
+ MP_ERROR_DET_CUSTOM_TYPE,
+ MP_ERROR_DET_AD_OBJ_TYPE,
+ MP_ERROR_DET_AD_OBJ_NAME,
+ MP_ERROR_DET_AD_ACCESS_TYPE
+};
+
+/**
+ * Types of errors that can be transmitted using MP_ERROR.
+ */
+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
+};
+
+/**
+ * The structure is used for storing parameters
+ * during decoding MP_ERROR.
+ */
+struct mp_error {
+ uint32_t error_code;
+ enum mp_error_types error_type;
+ uint32_t line;
+ uint32_t saved_errno;
+ char *file;
+ char *traceback;
+ 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->traceback = 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->traceback);
+ 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;
+
+ 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->traceback) {
+ ++details_num;
+ data_size += mp_sizeof_uint(MP_ERROR_DET_TRACEBACK);
+ data_size += mp_sizeof_str(strlen(error->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_error_custom_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, error->line);
+ 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->traceback) {
+ data = mp_encode_uint(data, MP_ERROR_DET_TRACEBACK);
+ data = mp_encode_str(data, error->traceback,
+ strlen(error->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)
+{
+ /*
+ * For create an error the "raw" constructor using
+ * because OOM error must be throwed in OOM case.
+ * Bulders returns a pointer to the static OOM error
+ * in OOM case.
+ */
+ 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 = e;
+ break;
+ }
+ case MP_ERROR_TYPE_CUSTOM:
+ err = new CustomError(mp_error->file, mp_error->line,
+ mp_error->custom_type);
+ break;
+ case MP_ERROR_TYPE_ACCESS_DENIED:
+ err = new AccessDeniedError(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 = new XlogError(&type_XlogError, mp_error->file,
+ mp_error->line);
+ error_format_msg(err, "%s", mp_error->reason);
+ break;
+ case MP_ERROR_TYPE_XLOG_GAP:
+ err = new XlogGapError(mp_error->file, mp_error->line,
+ mp_error->reason);
+ break;
+ case MP_ERROR_TYPE_SYSTEM:
+ err = new SystemError(mp_error->file, mp_error->line,
+ "%s", mp_error->reason);
+ break;
+ case MP_ERROR_TYPE_SOCKET:
+ err = new SocketError(mp_error->file, mp_error->line, "", "");
+ error_format_msg(err, "%s", mp_error->reason);
+ break;
+ case MP_ERROR_TYPE_OOM:
+ err = new OutOfMemory(mp_error->file, mp_error->line,
+ 0, "", "");
+ error_format_msg(err, "%s", mp_error->reason);
+ break;
+ case MP_ERROR_TYPE_TIMED_OUT:
+ err = new TimedOut(mp_error->file, mp_error->line);
+ break;
+ case MP_ERROR_TYPE_CHANNEL_IS_CLOSED:
+ err = new ChannelIsClosed(mp_error->file, mp_error->line);
+ break;
+ case MP_ERROR_TYPE_FIBER_IS_CANCELLED:
+ err = new FiberIsCancelled(mp_error->file, mp_error->line);
+ break;
+ case MP_ERROR_TYPE_LUAJIT:
+ err = new LuajitError(mp_error->file, mp_error->line,
+ mp_error->reason);
+ break;
+ case MP_ERROR_TYPE_ILLEGAL_PARAMS:
+ err = new IllegalParams(mp_error->file, mp_error->line,
+ "%s", mp_error->reason);
+ break;
+ case MP_ERROR_TYPE_COLLATION:
+ err = new CollationError(mp_error->file, mp_error->line,
+ "%s", mp_error->reason);
+ break;
+ case MP_ERROR_TYPE_SWIM:
+ err = new SwimError(mp_error->file, mp_error->line,
+ "%s", mp_error->reason);
+ break;
+ case MP_ERROR_TYPE_CRYPTO:
+ err = new CryptoError(mp_error->file, mp_error->line,
+ "%s", mp_error->reason);
+ break;
+ default:
+ unreachable();
+ break;
+ }
+
+ if (mp_error->traceback)
+ error_set_traceback(err, mp_error->traceback);
+ else
+ err->is_traceback_enabled = 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 = (enum mp_error_types)
+ 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_TRACEBACK:
+ if (mp_typeof(**data) != MP_STR)
+ goto error;
+ str = mp_decode_str(data, &str_len);
+ mp_err.traceback = 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..2aa5cb9
--- /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/tuple.c b/src/box/lua/tuple.c
index aba906d..03b4b8a 100644
--- a/src/box/lua/tuple.c
+++ b/src/box/lua/tuple.c
@@ -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
@@ -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/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h
index 7d42f21..e3ff9f5 100644
--- a/src/lib/core/mp_extension_types.h
+++ b/src/lib/core/mp_extension_types.h
@@ -43,6 +43,7 @@ enum mp_extension_type {
MP_UNKNOWN_EXTENSION = 0,
MP_DECIMAL = 1,
MP_UUID = 2,
+ MP_ERROR = 3,
mp_extension_type_MAX,
};
diff --git a/src/lua/error.c b/src/lua/error.c
index a21548c..192d537 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 b14361f..91560ef 100644
--- a/src/lua/msgpack.c
+++ b/src/lua/msgpack.c
@@ -195,6 +195,8 @@ restart: /* used by MP_EXT of unidentified subtype */
case MP_UUID:
mpstream_encode_uuid(stream, field->uuidval);
break;
+ 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);
@@ -237,6 +239,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)
diff --git a/src/lua/utils.c b/src/lua/utils.c
index bd320f3..aaa8a3f 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -47,6 +47,7 @@ static uint32_t CTID_CHAR_PTR;
static uint32_t CTID_CONST_CHAR_PTR;
static uint32_t CTID_UUID;
uint32_t CTID_DECIMAL;
+uint32_t CTID_CONST_STRUCT_ERROR_REF;
void *
@@ -650,8 +651,6 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg,
struct serializer_opts *opts, int index,
struct luaL_field *field)
{
- /* opts will be used for encode MP_ERROR in the future */
- (void)opts;
if (index < 0)
index = lua_gettop(L) + index + 1;
@@ -759,6 +758,9 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg,
} else if (cd->ctypeid == CTID_UUID) {
field->ext_type = MP_UUID;
field->uuidval = (struct tt_uuid *) cdata;
+ } else if (cd->ctypeid == CTID_CONST_STRUCT_ERROR_REF &&
+ opts && opts->error_marshaling_enabled) {
+ field->ext_type = MP_ERROR;
} else {
field->ext_type = MP_UNKNOWN_EXTENSION;
}
diff --git a/test/box-tap/extended_error.test.lua b/test/box-tap/extended_error.test.lua
new file mode 100755
index 0000000..41dd84e
--- /dev/null
+++ b/test/box-tap/extended_error.test.lua
@@ -0,0 +1,182 @@
+#! /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 == 'traceback' then
+ if not string.match(err.traceback, 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')
+ box.error.cfg({traceback_enable = true})
+
+ local connection = netbox.connect(host, port)
+ connection.space._session_settings:update('error_marshaling_enabled',
+ {{'=', 2, true}})
+ local _, err = pcall(connection.call, connection, 'custom_error_func')
+ local err_2 = connection:call('custom_error_func_2')
+
+ local tb_1 = "^stack traceback:\n" ..
+ "%s+%[C%]: in function 'new'\n" ..
+ "%s+builtin/box/net_box.lua:%d+:" ..
+ " in function 'perform_request'\n" ..
+ "%s+builtin/box/net_box.lua:%d+: in function '_request'\n" ..
+ "%s+builtin/box/net_box.lua:%d+:" ..
+ " in function <builtin/box/net_box.lua:%d+>\n" ..
+ "%s+%[C%]: in function 'pcall'\n" ..
+ ".*box%-tap/extended_error.test.lua:%d+:" ..
+ " in function 'test_extended_transmission'\n" ..
+ ".*box%-tap/extended_error.test.lua:%d+: in main chunk$"
+
+ local check_list = {
+ type = 'CustomError',
+ custom_type = 'My Custom Type',
+ message = 'A modern custom error',
+ trace = '^File builtin/box/net_box.lua\nLine %d+$',
+ traceback = tb_1,
+ is_custom = true
+ }
+
+ local tb_2 = "^stack traceback:" ..
+ "%s+%[C%]: in function 'new'\n" ..
+ ".*extended_error.test.lua:%d+:" ..
+ " in function <.*extended_error.test.lua:%d+>\n" ..
+ "%s+%[C%]: at 0x.*"
+
+ local check_list_2 = {
+ type = 'CustomError',
+ custom_type = 'My Custom Type',
+ message = 'A modern custom error',
+ trace = '.*extended_error.test.lua\nLine 21$',
+ traceback = tb_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 host= uri.parse(box.cfg.listen).host or 'localhost'
+local port = uri.parse(box.cfg.listen).service
+
+test_extended_transmission(host, port)
+test_old_transmission(host, port)
+
+os.exit(test:check() and 0 or 1)
--
2.7.4
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [Tarantool-patches] [PATCH V3 7/7] error: add error MsgPack encoding
2020-04-15 9:32 ` [Tarantool-patches] [PATCH V3 7/7] error: add error MsgPack encoding Leonid Vasiliev
@ 2020-04-15 15:08 ` lvasiliev
0 siblings, 0 replies; 9+ messages in thread
From: lvasiliev @ 2020-04-15 15:08 UTC (permalink / raw)
To: v.shpilevoy; +Cc: tarantool-patches
Fix release builds.
diff --git a/src/box/lua/mp_error.cc b/src/box/lua/mp_error.cc
index f99ed2e..8504fba 100644
--- a/src/box/lua/mp_error.cc
+++ b/src/box/lua/mp_error.cc
@@ -95,6 +95,9 @@ struct mp_error {
static void
mp_error_init(struct mp_error *mp_error)
{
+ mp_error->error_code = 0;
+ mp_error->line = 0;
+ mp_error->saved_errno = 0;
mp_error->error_type = MP_ERROR_TYPE_UNKNOWN;
mp_error->file = NULL;
mp_error->traceback = NULL;
@@ -165,7 +168,7 @@ 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;
+ uint32_t errcode = 0;
const char *custom_type = NULL;
const char *ad_obj_type = NULL;
const char *ad_obj_name = NULL;
@@ -371,7 +374,7 @@ build_error(struct mp_error *mp_error)
struct error *
error_unpack(const char **data, uint32_t len)
{
- const char *svp = *data;
+ const char *end = *data + len;
if (mp_typeof(**data) != MP_MAP) {
diag_set(ClientError, ER_INVALID_MSGPACK,
"Invalid MP_ERROR format");
@@ -463,7 +466,8 @@ error_unpack(const char **data, uint32_t len)
}
}
- assert(*data == svp + len);
+ (void)end;
+ assert(*data == end);
err = build_error(&mp_err);
mp_error_cleanup(&mp_err);
^ permalink raw reply [flat|nested] 9+ messages in thread