Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v2 0/5] Extending error functionality
@ 2020-04-10  8:10 Leonid Vasiliev
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error Leonid Vasiliev
                   ` (5 more replies)
  0 siblings, 6 replies; 37+ messages in thread
From: Leonid Vasiliev @ 2020-04-10  8:10 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

https://github.com/tarantool/tarantool/issues/4398
https://github.com/tarantool/tarantool/tree/lvasiliev/gh-4398-expose-error-module-4

According to https://github.com/tarantool/tarantool/issues/4398
(and after some discussion) we would like box.error to have:
* Ability to create new error types
* Transparent marshalling through net.box
* Lua backtrace

@Changelog
Added:
Lua traceback to error.
Posibility to create errors of a custom user type
Transparent marshalling error through net.box(gh-4398)

Leonid Vasiliev (5):
  error: Add a Lua backtrace to error
  error: Add the custom error type
  error: Increase the number of fields transmitted through IPROTO
  iproto: Add session settings for IPROTO
  iproto: Update error MsgPack encoding

 src/box/CMakeLists.txt               |   1 +
 src/box/errcode.h                    |   1 +
 src/box/error.cc                     |  87 ++++++-
 src/box/error.h                      |  49 +++-
 src/box/iproto.cc                    |  97 ++++++++
 src/box/iproto_constants.h           |   2 +
 src/box/lua/call.c                   |  29 ++-
 src/box/lua/error.cc                 | 189 ++++++++++++---
 src/box/lua/execute.c                |   2 +-
 src/box/lua/init.c                   |  56 +++++
 src/box/lua/mp_error.cc              | 454 +++++++++++++++++++++++++++++++++++
 src/box/lua/mp_error.h               |  49 ++++
 src/box/lua/net_box.lua              |  33 ++-
 src/box/lua/tuple.c                  |  28 +--
 src/box/session.cc                   |   3 +
 src/box/session.h                    |   3 +
 src/box/session_settings.h           |   1 +
 src/box/sql/func.c                   |   4 +-
 src/box/xrow.c                       |  19 +-
 src/lib/core/diag.c                  |  32 +++
 src/lib/core/diag.h                  |  11 +
 src/lib/core/exception.cc            |   1 +
 src/lib/core/mp_extension_types.h    |   5 +-
 src/lua/error.c                      |  12 +-
 src/lua/error.h                      |   3 +-
 src/lua/error.lua                    |  23 +-
 src/lua/msgpack.c                    |  28 ++-
 src/lua/msgpack.h                    |   8 +-
 src/lua/utils.c                      |  16 +-
 src/lua/utils.h                      |  26 +-
 test/app/fiber.result                |   4 +-
 test/box-tap/extended_error.test.lua | 164 +++++++++++++
 test/box/error.result                |   9 +-
 test/engine/func_index.result        |  18 +-
 34 files changed, 1344 insertions(+), 123 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

-- 
2.7.4

^ permalink raw reply	[flat|nested] 37+ messages in thread

* [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error
  2020-04-10  8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev
@ 2020-04-10  8:10 ` Leonid Vasiliev
  2020-04-14  1:11   ` Vladislav Shpilevoy
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type Leonid Vasiliev
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 37+ messages in thread
From: Leonid Vasiliev @ 2020-04-10  8:10 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

In accordance with https://github.com/tarantool/tarantool/issues/4398
Lua traceback has been added for box.error.
Has been added a per server flag for turn on/off traceback adding
and ability to force it at creation time.

@TarantoolBot document
Title: error.traceback
Was added:
Per server flag for turn on/off adding a traceback to the errors.
box.error.cfg({traceback_supplementation = true/false})
Adding a traceback can be forced on creation.
box.error.new({type = "CustomType", reason = "reason", traceback = true/false})

Needed for #4398
---
 src/box/lua/error.cc          | 33 ++++++++++++++++++++++++++++++++-
 src/lib/core/diag.c           | 32 ++++++++++++++++++++++++++++++++
 src/lib/core/diag.h           | 11 +++++++++++
 src/lib/core/exception.cc     |  1 +
 src/lua/error.c               | 10 ++++++++++
 src/lua/error.lua             | 12 +++++++++++-
 test/app/fiber.result         |  5 +++--
 test/box/error.result         |  5 +++--
 test/engine/func_index.result | 10 ++++++----
 9 files changed, 109 insertions(+), 10 deletions(-)

diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
index b2625bf..4432bc8 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 tb_parsed = false;
+	bool tb_mode = 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)) {
+			tb_parsed = true;
+			tb_mode = lua_toboolean(L, -1);
+		}
+		lua_pop(L, -1);
 	} else {
 		return NULL;
 	}
@@ -102,7 +110,12 @@ raise:
 		}
 		line = info.currentline;
 	}
-	return box_error_new(file, line, code, "%s", reason);
+
+	struct error *err = box_error_new(file, line, code, "%s", reason);
+	if (tb_parsed)
+		err->traceback_mode = tb_mode;
+
+	return err;
 }
 
 static int
@@ -180,6 +193,20 @@ 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_supplementation");
+	if (lua_isnil(L, -1) == 0)
+		error_set_traceback_supplementation(lua_toboolean(L, -1));
+	lua_pop(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..1caa75e 100644
--- a/src/lib/core/diag.c
+++ b/src/lib/core/diag.c
@@ -31,6 +31,11 @@
 #include "diag.h"
 #include "fiber.h"
 
+/**
+ * Global flag to add or not backtrace to errors.
+ */
+static bool global_traceback_mode = false;
+
 int
 error_set_prev(struct error *e, struct error *prev)
 {
@@ -97,6 +102,8 @@ error_create(struct error *e,
 	e->errmsg[0] = '\0';
 	e->cause = NULL;
 	e->effect = NULL;
+	e->lua_traceback = NULL;
+	e->traceback_mode = global_traceback_mode;
 }
 
 struct diag *
@@ -120,3 +127,28 @@ error_vformat_msg(struct error *e, const char *format, va_list ap)
 	vsnprintf(e->errmsg, sizeof(e->errmsg), format, ap);
 }
 
+void
+error_set_lua_traceback(struct error *e, const char *lua_traceback)
+{
+	if (e == NULL)
+		return;
+
+	if (lua_traceback == NULL) {
+		free(e->lua_traceback);
+		e->lua_traceback = NULL;
+		return;
+	}
+
+	size_t tb_len = strlen(lua_traceback);
+	e->lua_traceback = realloc(e->lua_traceback, tb_len + 1);
+	if (e->lua_traceback == NULL)
+		return;
+	strcpy(e->lua_traceback, lua_traceback);
+	return;
+}
+
+void
+error_set_traceback_supplementation(bool traceback_mode)
+{
+	global_traceback_mode = traceback_mode;
+}
diff --git a/src/lib/core/diag.h b/src/lib/core/diag.h
index 7a5454d..f38009c 100644
--- a/src/lib/core/diag.h
+++ b/src/lib/core/diag.h
@@ -110,6 +110,8 @@ struct error {
 	 */
 	struct error *cause;
 	struct error *effect;
+	char *lua_traceback;
+	bool traceback_mode;
 };
 
 static inline void
@@ -197,6 +199,15 @@ error_format_msg(struct error *e, const char *format, ...);
 void
 error_vformat_msg(struct error *e, const char *format, va_list ap);
 
+void
+error_set_lua_traceback(struct error *e, const char *lua_traceback);
+
+/**
+* Sets the global flag to add or not backtrace to errors.
+*/
+void
+error_set_traceback_supplementation(bool traceback_mode);
+
 /**
  * Diagnostics Area - a container for errors
  */
diff --git a/src/lib/core/exception.cc b/src/lib/core/exception.cc
index 180cb0e..0e4b6ca 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->lua_traceback);
 	delete (Exception *) e;
 }
 
diff --git a/src/lua/error.c b/src/lua/error.c
index 18a990a..cd6ab54 100644
--- a/src/lua/error.c
+++ b/src/lua/error.c
@@ -85,6 +85,16 @@ luaT_pusherror(struct lua_State *L, struct error *e)
 	 * then set the finalizer.
 	 */
 	error_ref(e);
+
+	if (e->lua_traceback == NULL && e->traceback_mode) {
+		int top = lua_gettop(L);
+		luaL_traceback(L, L, NULL, 0);
+		if (lua_isstring(L, -1)) {
+			error_set_lua_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..46d2866 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 *lua_traceback;
+    bool traceback_mode;
 };
 
 char *
@@ -92,6 +94,14 @@ local function error_trace(err)
     }
 end
 
+local function error_traceback(err)
+    local result = "Traceback is absent"
+    if err.lua_traceback ~= ffi.nullptr then
+        result = ffi.string(err.lua_traceback)
+    end
+    return result
+end
+
 local function error_errno(err)
     local e = err._saved_errno
     if e == 0 then
@@ -122,7 +132,6 @@ local function error_set_prev(err, prev)
     if ok ~= 0 then
         error("Cycles are not allowed")
     end
-
 end
 
 local error_fields = {
@@ -131,6 +140,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/app/fiber.result b/test/app/fiber.result
index debfc67..fc6b3c9 100644
--- a/test/app/fiber.result
+++ b/test/app/fiber.result
@@ -1038,12 +1038,13 @@ st;
 ...
 e:unpack();
 ---
-- type: ClientError
+- traceback: Traceback is absent
   code: 1
-  message: Illegal parameters, oh my
   trace:
   - file: '[string "function err() box.error(box.error.ILLEGAL_PA..."]'
     line: 1
+  type: ClientError
+  message: Illegal parameters, oh my
 ...
 flag = false;
 ---
diff --git a/test/box/error.result b/test/box/error.result
index 234c263..22788a1 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -34,12 +34,13 @@ e
  | ...
 e:unpack()
  | ---
- | - type: ClientError
+ | - traceback: Traceback is absent
  |   code: 1
- |   message: Illegal parameters, bla bla
  |   trace:
  |   - file: '[C]'
  |     line: 4294967295
+ |   type: ClientError
+ |   message: Illegal parameters, bla bla
  | ...
 e.type
  | ---
diff --git a/test/engine/func_index.result b/test/engine/func_index.result
index a827c92..28befca 100644
--- a/test/engine/func_index.result
+++ b/test/engine/func_index.result
@@ -276,7 +276,8 @@ e = box.error.last()
  | ...
 e:unpack()
  | ---
- | - code: 198
+ | - traceback: Traceback is absent
+ |   code: 198
  |   trace:
  |   - file: <filename>
  |     line: <line>
@@ -291,12 +292,13 @@ e = e.prev
  | ...
 e:unpack()
  | ---
- | - type: LuajitError
- |   message: '[string "return function(tuple)                 local ..."]:1: attempt
- |     to call global ''require'' (a nil value)'
+ | - traceback: Traceback is absent
  |   trace:
  |   - file: <filename>
  |     line: <line>
+ |   type: LuajitError
+ |   message: '[string "return function(tuple)                 local ..."]:1: attempt
+ |     to call global ''require'' (a nil value)'
  | ...
 e = e.prev
  | ---
-- 
2.7.4

^ permalink raw reply	[flat|nested] 37+ messages in thread

* [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type
  2020-04-10  8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error Leonid Vasiliev
@ 2020-04-10  8:10 ` Leonid Vasiliev
  2020-04-14  1:11   ` Vladislav Shpilevoy
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO Leonid Vasiliev
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 37+ messages in thread
From: Leonid Vasiliev @ 2020-04-10  8:10 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

A possibility to create an error with a custom subtype was added.

In accordance with https://github.com/tarantool/tarantool/issues/4398
a custom error type has been added to the box.error.
Now, it's possible to create an error with a custom subtype (string value)
for use it in applications.

@TarantoolBot document
Title: error.custom_type
A custom error type has been added to the box.error.
Now, it's possible to create an error with a custom subtype (string value)
for use it in applications.

To API has been added:
- For create CustomError: box.error.new(type(str), format, ...)
                          box.error.new({type = str,
                                         reason = str})

- error.type - CustomType error return custom type
    other errors return "base" error tybe
- base_type - return "base" errror type
Example:

err_custom = box.error.new("My Custom Type", "Reason")
Now:
err_custom.type == "My Custom Type"
err_custom.base_type == "CustomType"
err_custom.message == "Reason"

Needed for #4398
---
 src/box/errcode.h             |   1 +
 src/box/error.cc              |  59 +++++++++++++++++
 src/box/error.h               |  38 ++++++++++-
 src/box/lua/error.cc          | 147 ++++++++++++++++++++++++++++++------------
 src/lua/error.lua             |  11 ++++
 test/app/fiber.result         |   5 +-
 test/box/error.result         |  10 +--
 test/engine/func_index.result |  18 +++---
 8 files changed, 234 insertions(+), 55 deletions(-)

diff --git a/src/box/errcode.h b/src/box/errcode.h
index 4441717..9f5f993 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -265,6 +265,7 @@ struct errcode_record {
 	/*210 */_(ER_SQL_PREPARE,		"Failed to prepare SQL statement: %s") \
 	/*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_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..8179e52 100644
--- a/src/box/error.cc
+++ b/src/box/error.cc
@@ -125,6 +125,31 @@ box_error_add(const char *file, unsigned line, uint32_t code,
 
 /* }}} */
 
+const char *
+box_custom_error_type(const box_error_t *e)
+{
+	CustomError *custom_error = type_cast(CustomError, e);
+	if (custom_error)
+		return custom_error->custom_type();
+
+	return NULL;
+}
+
+struct error *
+box_custom_error_new(const char *file, unsigned line,
+		     const char *custom, const char *fmt, ...)
+{
+	struct error *e = BuildCustomError(file, line, custom);
+	CustomError *custom_error = type_cast(CustomError, e);
+	if (custom_error != NULL) {
+		va_list ap;
+		va_start(ap, fmt);
+		error_vformat_msg(e, fmt, ap);
+		va_end(ap);
+	}
+	return e;
+}
+
 struct rmean *rmean_error = NULL;
 
 const char *rmean_error_strings[RMEAN_ERROR_LAST] = {
@@ -290,3 +315,37 @@ 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 ?: "");
+
+	if (custom_type) {
+		strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1);
+		m_custom_type[sizeof(m_custom_type) - 1] = '\0';
+	} else {
+		m_custom_type[0] = '\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..5013488 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,6 +141,14 @@ box_error_set(const char *file, unsigned line, uint32_t code,
 /** \endcond public */
 
 /**
+ * Return the error custom type,
+ * \param error
+ * \return pointer to custom error type. On error, return NULL
+ */
+const char *
+box_custom_error_type(const box_error_t *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.
@@ -155,15 +166,24 @@ box_error_add(const char *file, unsigned line, uint32_t code,
 
 /**
  * Construct error object without setting it in the diagnostics
- * area. On the memory allocation fail returns NULL.
+ * area. On the memory allocation fail returns  OutOfMemory error.
  */
 struct error *
 box_error_new(const char *file, unsigned line, uint32_t code,
 	      const char *fmt, ...);
 
+/**
+ * Construct error object without setting it in the diagnostics
+ * area. On the memory allocation fail returns OutOfMemory error.
+ */
+struct error *
+box_custom_error_new(const char *file, unsigned line,
+		     const char *custom, 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 +310,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 4432bc8..1bcce1f 100644
--- a/src/box/lua/error.cc
+++ b/src/box/lua/error.cc
@@ -42,64 +42,101 @@ extern "C" {
 #include "lua/utils.h"
 #include "box/error.h"
 
-/**
- * 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.
- * In case one of arguments is missing its corresponding field
- * in struct error is filled with default value.
- */
-static struct error *
-luaT_error_create(lua_State *L, int top_base)
+struct error_args {
+	uint32_t code;
+	const char *reason;
+	const char *custom;
+	bool tb_mode;
+	bool tb_parsed;
+};
+
+static int
+luaT_error_parse_args(struct lua_State *L, int top_base,
+		      struct error_args *args)
 {
-	uint32_t code = 0;
-	const char *reason = NULL;
-	bool tb_parsed = false;
-	bool tb_mode = false;
-	const char *file = "";
-	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);
-		reason = tnt_errcode_desc(code);
+	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) {
+			args->code = lua_tonumber(L, top_base);
+		} else if (top_type == LUA_TSTRING) {
+			args->code = ER_CUSTOM_ERROR;
+			args->custom = lua_tostring(L, top_base);
+		} else {
+			return -1;
+		}
+		args->reason = tnt_errcode_desc(args->code);
 		if (top > top_base) {
 			/* Call string.format(reason, ...) to format message */
 			lua_getglobal(L, "string");
 			if (lua_isnil(L, -1))
-				goto raise;
+				return 0;
 			lua_getfield(L, -1, "format");
 			if (lua_isnil(L, -1))
-				goto raise;
-			lua_pushstring(L, reason);
+				return 0;
+			lua_pushstring(L, args->reason);
 			for (int i = top_base + 1; i <= top; i++)
 				lua_pushvalue(L, i);
 			lua_call(L, top - top_base + 1, 1);
-			reason = lua_tostring(L, -1);
-		} else if (strchr(reason, '%') != NULL) {
+			args->reason = lua_tostring(L, -1);
+		} else if (strchr(args->reason, '%') != NULL) {
 			/* Missing arguments to format string */
-			return NULL;
+			return -1;
 		}
-	} 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);
+		if (lua_isnil(L, -1) == 0)
+			args->code = lua_tonumber(L, -1);
 		lua_pop(L, 1);
 		lua_getfield(L, top_base, "reason");
-		reason = lua_tostring(L, -1);
-		if (reason == NULL)
-			reason = "";
+		if (lua_isnil(L, -1) == 0)
+			args->reason = lua_tostring(L, -1);
+		lua_pop(L, 1);
+		lua_getfield(L, top_base, "type");
+		if (lua_isnil(L, -1) == 0)
+			args->custom = lua_tostring(L, -1);
 		lua_pop(L, 1);
 		lua_getfield(L, top_base, "traceback");
 		if (lua_isboolean(L, -1)) {
-			tb_parsed = true;
-			tb_mode = lua_toboolean(L, -1);
+			args->tb_parsed = true;
+			args->tb_mode = lua_toboolean(L, -1);
 		}
 		lua_pop(L, -1);
 	} else {
+		return -1;
+	}
+
+	return 0;
+}
+
+/**
+ * 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.
+ * In case one of arguments is missing its corresponding field
+ * in struct error is filled with default value.
+ */
+static struct error *
+luaT_error_create(lua_State *L, int top_base)
+{
+	struct error_args args;
+	args.code = UINT32_MAX;
+	args.reason = NULL;
+	args.custom = NULL;
+	args.tb_mode = false;
+	args.tb_parsed = false;
+
+	if (luaT_error_parse_args(L, top_base, &args) != 0) {
 		return NULL;
 	}
 
-raise:
+	if (args.reason == NULL)
+		args.reason = "";
+
+	const char *file = "";
+	unsigned line = 0;
+	lua_Debug info;
 	if (lua_getstack(L, 1, &info) && lua_getinfo(L, "Sl", &info)) {
 		if (*info.short_src) {
 			file = info.short_src;
@@ -111,9 +148,16 @@ raise:
 		line = info.currentline;
 	}
 
-	struct error *err = box_error_new(file, line, code, "%s", reason);
-	if (tb_parsed)
-		err->traceback_mode = tb_mode;
+	struct error *err = NULL;
+	if (args.custom) {
+		err = box_custom_error_new(file, line, args.custom,
+					   "%s", args.reason);
+	} else {
+		err = box_error_new(file, line, args.code, "%s", args.reason);
+	}
+
+	if (args.tb_parsed)
+		err->traceback_mode = args.tb_mode;
 
 	return err;
 }
@@ -162,17 +206,36 @@ 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)");
+	if (lua_gettop(L) == 0) {
+		return luaL_error(L, "Usage: box.error.new(code, args) or "\
+				     "box.error.new(type, args)");
+	}
 	struct error *e = luaT_error_create(L, 1);
-	if (e == NULL)
-		return luaL_error(L, "Usage: box.error.new(code, args)");
+	if (e == 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;
 }
 
 static int
+luaT_error_custom_type(lua_State *L)
+{
+	struct error *e = luaL_checkerror(L, -1);
+
+	const char *custom_type = box_custom_error_type(e);
+	if (custom_type == NULL) {
+		lua_pushfstring(L, "The error has't custom type");
+		return 1;
+	}
+
+	lua_pushstring(L, custom_type);
+	return 1;
+}
+
+static int
 luaT_error_clear(lua_State *L)
 {
 	if (lua_gettop(L) >= 1)
@@ -328,6 +391,10 @@ box_lua_error_init(struct lua_State *L) {
 			lua_pushcfunction(L, luaT_error_cfg);
 			lua_setfield(L, -2, "cfg");
 		}
+		{
+			lua_pushcfunction(L, luaT_error_custom_type);
+			lua_setfield(L, -2, "custom_type");
+		}
 		lua_setfield(L, -2, "__index");
 	}
 	lua_setmetatable(L, -2);
diff --git a/src/lua/error.lua b/src/lua/error.lua
index 46d2866..5b32607 100644
--- a/src/lua/error.lua
+++ b/src/lua/error.lua
@@ -78,6 +78,16 @@ local function reflection_get(err, method)
 end
 
 local function error_type(err)
+    local res
+    if ffi.string(err._type.name) == 'CustomError' then
+        res = box.error.custom_type(err)
+    else
+        res = ffi.string(err._type.name)
+    end
+    return res
+end
+
+local function error_base_type(err)
     return ffi.string(err._type.name)
 end
 
@@ -141,6 +151,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 fc6b3c9..00d915c 100644
--- a/test/app/fiber.result
+++ b/test/app/fiber.result
@@ -1040,11 +1040,12 @@ e:unpack();
 ---
 - traceback: Traceback is absent
   code: 1
+  base_type: ClientError
+  type: ClientError
+  message: Illegal parameters, oh my
   trace:
   - file: '[string "function err() box.error(box.error.ILLEGAL_PA..."]'
     line: 1
-  type: ClientError
-  message: Illegal parameters, oh my
 ...
 flag = false;
 ---
diff --git a/test/box/error.result b/test/box/error.result
index 22788a1..611044b 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -36,11 +36,12 @@ e:unpack()
  | ---
  | - traceback: Traceback is absent
  |   code: 1
+ |   base_type: ClientError
+ |   type: ClientError
+ |   message: Illegal parameters, bla bla
  |   trace:
  |   - file: '[C]'
  |     line: 4294967295
- |   type: ClientError
- |   message: Illegal parameters, bla bla
  | ...
 e.type
  | ---
@@ -106,7 +107,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;
  |   210: box.error.SQL_PREPARE
  |   211: box.error.WRONG_QUERY_ID
  |   212: box.error.SEQUENCE_NOT_STARTED
+ |   213: 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.
diff --git a/test/engine/func_index.result b/test/engine/func_index.result
index 28befca..aee3ca4 100644
--- a/test/engine/func_index.result
+++ b/test/engine/func_index.result
@@ -278,14 +278,15 @@ e:unpack()
  | ---
  | - traceback: Traceback is absent
  |   code: 198
- |   trace:
- |   - file: <filename>
- |     line: <line>
+ |   base_type: ClientError
  |   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'
+ |   trace:
+ |   - file: <filename>
+ |     line: <line>
  | ...
 e = e.prev
  | ---
@@ -293,12 +294,13 @@ e = e.prev
 e:unpack()
  | ---
  | - traceback: Traceback is absent
- |   trace:
- |   - file: <filename>
- |     line: <line>
+ |   base_type: LuajitError
  |   type: LuajitError
  |   message: '[string "return function(tuple)                 local ..."]:1: attempt
  |     to call global ''require'' (a nil value)'
+ |   trace:
+ |   - file: <filename>
+ |     line: <line>
  | ...
 e = e.prev
  | ---
-- 
2.7.4

^ permalink raw reply	[flat|nested] 37+ messages in thread

* [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO
  2020-04-10  8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error Leonid Vasiliev
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type Leonid Vasiliev
@ 2020-04-10  8:10 ` Leonid Vasiliev
  2020-04-14  1:12   ` Vladislav Shpilevoy
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO Leonid Vasiliev
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 37+ messages in thread
From: Leonid Vasiliev @ 2020-04-10  8:10 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

Has been added IPROTO_ERROR_TRACEBACK and IPROTO_ERROR_CUSTOM_TYPE
fields to IPROTO_ERROR_STACK for transfering through network
a backtrace and custom type.

Needed for #4398
---
 src/box/iproto_constants.h |  2 ++
 src/box/lua/error.cc       | 23 +++++++++++++++++++++++
 src/box/lua/net_box.lua    | 21 ++++++++++++++++++++-
 src/box/xrow.c             | 19 ++++++++++++++++++-
 4 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h
index 7ed8296..7daa980 100644
--- a/src/box/iproto_constants.h
+++ b/src/box/iproto_constants.h
@@ -154,6 +154,8 @@ enum iproto_ballot_key {
 enum iproto_error_key {
 	IPROTO_ERROR_CODE = 0x01,
 	IPROTO_ERROR_MESSAGE = 0x02,
+	IPROTO_ERROR_TRACEBACK = 0x03,
+	IPROTO_ERROR_CUSTOM_TYPE = 0x04,
 };
 
 #define bit(c) (1ULL<<IPROTO_##c)
diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
index 1bcce1f..27dee7a 100644
--- a/src/box/lua/error.cc
+++ b/src/box/lua/error.cc
@@ -236,6 +236,25 @@ luaT_error_custom_type(lua_State *L)
 }
 
 static int
+luaT_error_set_lua_traceback(lua_State *L)
+{
+	if (lua_gettop(L) < 2)
+		return luaL_error(L, "Usage: box.error.set_lua_traceback"\
+				     "(error, traceback)");
+
+	struct error *e = luaL_checkerror(L, 1);
+
+	if (lua_type(L, 2) == LUA_TSTRING) {
+		error_set_lua_traceback(e, lua_tostring(L, 2));
+	} else {
+		return luaL_error(L, "Usage: box.error.set_lua_traceback"\
+				     "(error, traceback)");
+	}
+
+	return 0;
+}
+
+static int
 luaT_error_clear(lua_State *L)
 {
 	if (lua_gettop(L) >= 1)
@@ -395,6 +414,10 @@ box_lua_error_init(struct lua_State *L) {
 			lua_pushcfunction(L, luaT_error_custom_type);
 			lua_setfield(L, -2, "custom_type");
 		}
+		{
+			lua_pushcfunction(L, luaT_error_set_lua_traceback);
+			lua_setfield(L, -2, "set_lua_traceback");
+		}
 		lua_setfield(L, -2, "__index");
 	}
 	lua_setmetatable(L, -2);
diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index 07fa54c..1e0cd7a 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -47,6 +47,8 @@ local IPROTO_ERROR_KEY     = 0x31
 local IPROTO_ERROR_STACK   = 0x52
 local IPROTO_ERROR_CODE    = 0x01
 local IPROTO_ERROR_MESSAGE = 0x02
+local IPROTO_ERROR_TRACEBACK = 0x03
+local IPROTO_ERROR_CUSTOM_TYPE = 0x04
 local IPROTO_GREETING_SIZE = 128
 local IPROTO_CHUNK_KEY     = 128
 local IPROTO_OK_KEY        = 0
@@ -287,7 +289,24 @@ local function create_transport(host, port, user, password, callback,
                     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 custom_type = error[IPROTO_ERROR_CUSTOM_TYPE]
+                    local traceback = error[IPROTO_ERROR_TRACEBACK]
+
+                    local new_err
+                    if custom_type then
+                        new_err = box.error.new({type = custom_type,
+                                                 reason = msg,
+                                                 traceback = false})
+                    else
+                        new_err = box.error.new({code = code,
+                                                 reason = msg,
+                                                 traceback = false})
+                    end
+
+                    if traceback then
+                        box.error.set_lua_traceback(new_err, traceback)
+                    end
+
                     new_err:set_prev(prev)
                     prev = new_err
                 end
diff --git a/src/box/xrow.c b/src/box/xrow.c
index be026a4..cd88e49 100644
--- a/src/box/xrow.c
+++ b/src/box/xrow.c
@@ -494,11 +494,28 @@ 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);
+		/* Code and message are necessary fields */
+		uint32_t map_size = 2;
+		const char *custom_type = NULL;
+		if (it->lua_traceback)
+			++map_size;
+		if (strcmp(box_error_type(it), "CustomError") == 0) {
+			++map_size;
+			custom_type = box_custom_error_type(it);
+		}
+		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 (it->lua_traceback) {
+			mpstream_encode_uint(stream, IPROTO_ERROR_TRACEBACK);
+			mpstream_encode_str(stream, it->lua_traceback);
+		}
+		if (custom_type) {
+			mpstream_encode_uint(stream, IPROTO_ERROR_CUSTOM_TYPE);
+			mpstream_encode_str(stream, custom_type);
+		}
 	}
 }
 
-- 
2.7.4

^ permalink raw reply	[flat|nested] 37+ messages in thread

* [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO
  2020-04-10  8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev
                   ` (2 preceding siblings ...)
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO Leonid Vasiliev
@ 2020-04-10  8:10 ` Leonid Vasiliev
  2020-04-14  1:12   ` Vladislav Shpilevoy
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding Leonid Vasiliev
  2020-04-14  1:10 ` [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Vladislav Shpilevoy
  5 siblings, 1 reply; 37+ messages in thread
From: Leonid Vasiliev @ 2020-04-10  8:10 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

IPROTO session settings for error transmission in various formats
depending on session settings have been added.
This is required for backward compatibility.

@TarantoolBot document
    Title: Add session_setting
iproto_error_format setting has been added to _session_settings
Used to set the error transmission format in the session.
Old format: transmits as string at IPROTO_BODY
New format: transmits as error object at IPROTO_BODY

Needed for #4398
---
 src/box/iproto.cc          | 97 ++++++++++++++++++++++++++++++++++++++++++++++
 src/box/lua/net_box.lua    | 12 ++++++
 src/box/session.cc         |  3 ++
 src/box/session.h          |  3 ++
 src/box/session_settings.h |  1 +
 src/lua/utils.h            | 20 ++++++++++
 6 files changed, 136 insertions(+)

diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index 9dad43b..92be645 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -55,6 +55,7 @@
 #include "call.h"
 #include "tuple_convert.h"
 #include "session.h"
+#include "session_settings.h"
 #include "xrow.h"
 #include "schema.h" /* schema_version */
 #include "replication.h" /* instance_uuid */
@@ -2183,6 +2184,100 @@ iproto_session_push(struct session *session, uint64_t sync, struct port *port)
 
 /** }}} */
 
+enum {
+	IPROTO_SESSION_SETTING_ERR_FORMAT = 0,
+	iproto_session_setting_MAX,
+};
+
+static const char *iproto_session_setting_strs[iproto_session_setting_MAX] = {
+	"iproto_error_format",
+};
+
+static int iproto_session_field_type[] = {
+	/** IPROTO_SESSION_SETTING_ERR_FORMAT */
+	FIELD_TYPE_UNSIGNED,
+};
+
+static void
+iproto_session_setting_get(int id, const char **mp_pair,
+			   const char **mp_pair_end)
+{
+	if (id < 0 || id >= iproto_session_setting_MAX) {
+		diag_set(ClientError, ER_ILLEGAL_PARAMS,
+			 "unknown session setting");
+		return;
+	}
+	struct session *session = current_session();
+
+	const char *name = iproto_session_setting_strs[id];
+	size_t name_len = strlen(name);
+
+	/* Now we have only one iproto session setting. */
+	size_t size = mp_sizeof_array(2) + mp_sizeof_str(name_len)
+		+ mp_sizeof_uint(session->serializer_ctx.err_format_ver);
+
+	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_uint(pos_end,
+				 session->serializer_ctx.err_format_ver);
+	*mp_pair = pos;
+	*mp_pair_end = pos_end;
+}
+
+static int
+iproto_session_setting_set(int id, const char *mp_value)
+{
+	if (id < 0 || id >= iproto_session_setting_MAX) {
+		diag_set(ClientError, ER_ILLEGAL_PARAMS,
+			 "unknown session setting");
+		return -1;
+	}
+	/*Current IPROTO session settings are used only for BINARY session */
+	if (current_session()->type != SESSION_TYPE_BINARY)
+		return -1;
+
+	enum mp_type mtype = mp_typeof(*mp_value);
+	int stype = iproto_session_field_type[id];
+	switch(stype) {
+	case FIELD_TYPE_UNSIGNED: {
+		if (mtype != MP_UINT)
+			break;
+		int val = mp_decode_uint(&mp_value);
+		switch (id) {
+		case IPROTO_SESSION_SETTING_ERR_FORMAT:
+			if (val >= ERR_FORMAT_UNK)
+				break;
+			current_session()->serializer_ctx.err_format_ver = val;
+			return 0;
+		default:
+			diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE,
+				 iproto_session_setting_strs[id],
+				 field_type_strs[stype]);
+			return -1;
+		}
+		break;
+	}
+	default:
+		unreachable();
+	}
+	diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE,
+		 iproto_session_setting_strs[id], field_type_strs[stype]);
+	return -1;
+}
+
+void
+iproto_session_settings_init()
+{
+	struct session_setting_module *module =
+		&session_setting_modules[SESSION_SETTING_IPROTO];
+	module->settings = iproto_session_setting_strs;
+	module->setting_count = iproto_session_setting_MAX;
+	module->get = iproto_session_setting_get;
+	module->set = iproto_session_setting_set;
+}
+
 /** Initialize the iproto subsystem and start network io thread */
 void
 iproto_init()
@@ -2201,6 +2296,8 @@ iproto_init()
 		/* .sync = */ iproto_session_sync,
 	};
 	session_vtab_registry[SESSION_TYPE_BINARY] = iproto_session_vtab;
+
+	iproto_session_settings_init();
 }
 
 /** Available iproto configuration changes. */
diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index 1e0cd7a..c8f76b0 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -1047,6 +1047,18 @@ local function new_sm(host, port, opts, connection, greeting)
     if opts.wait_connected ~= false then
         remote._transport.wait_state('active', tonumber(opts.wait_connected))
     end
+
+    -- Set extended error format for session.
+    if opts.error_extended then
+        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1)
+        if not ext_err_supported then
+            box.error(box.error.PROC_LUA,
+                      "Server doesn't support extended error format")
+        end
+        remote.space._session_settings:update('iproto_error_format',
+                                              {{'=', 2, 1}})
+    end
+
     return remote
 end
 
diff --git a/src/box/session.cc b/src/box/session.cc
index 8813182..0cd8675 100644
--- a/src/box/session.cc
+++ b/src/box/session.cc
@@ -144,6 +144,9 @@ session_create(enum session_type type)
 	session->sql_default_engine = SQL_STORAGE_ENGINE_MEMTX;
 	session->sql_stmts = NULL;
 
+	/* Set default Lua serializer context */
+	session->serializer_ctx.err_format_ver = ERR_FORMAT_DEF;
+
 	/* For on_connect triggers. */
 	credentials_create(&session->credentials, guest_user);
 	struct mh_i64ptr_node_t node;
diff --git a/src/box/session.h b/src/box/session.h
index 6dfc7cb..a8903aa 100644
--- a/src/box/session.h
+++ b/src/box/session.h
@@ -36,6 +36,7 @@
 #include "fiber.h"
 #include "user.h"
 #include "authentication.h"
+#include "lua/utils.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -110,6 +111,8 @@ struct session {
 	struct credentials credentials;
 	/** Trigger for fiber on_stop to cleanup created on-demand session */
 	struct trigger fiber_on_stop;
+	/** Session Lua serializer context */
+	struct luaL_serializer_ctx serializer_ctx;
 };
 
 struct session_vtab {
diff --git a/src/box/session_settings.h b/src/box/session_settings.h
index 25490a7..c139d30 100644
--- a/src/box/session_settings.h
+++ b/src/box/session_settings.h
@@ -41,6 +41,7 @@
  * type list is used by setting iterators.
  */
 enum session_setting_type {
+	SESSION_SETTING_IPROTO,
 	SESSION_SETTING_SQL,
 	session_setting_type_MAX,
 };
diff --git a/src/lua/utils.h b/src/lua/utils.h
index 0b36727..5875ba3 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -266,6 +266,26 @@ struct luaL_serializer {
 	struct rlist on_update;
 };
 
+/**
+ * An error serialization formats
+ */
+enum error_formats {
+	/** Default(old) format */
+	ERR_FORMAT_DEF,
+	/** Extended format */
+	ERR_FORMAT_EX,
+	/** The max version of error format */
+	ERR_FORMAT_UNK
+};
+
+/**
+ * A serializer context (additional settings for a serializer)
+ */
+struct luaL_serializer_ctx {
+	/** Version of a format for an error transmission */
+	uint8_t err_format_ver;
+};
+
 extern int luaL_nil_ref;
 extern int luaL_map_metatable_ref;
 extern int luaL_array_metatable_ref;
-- 
2.7.4

^ permalink raw reply	[flat|nested] 37+ messages in thread

* [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding
  2020-04-10  8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev
                   ` (3 preceding siblings ...)
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO Leonid Vasiliev
@ 2020-04-10  8:10 ` Leonid Vasiliev
  2020-04-14  1:12   ` Vladislav Shpilevoy
  2020-04-14  1:10 ` [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Vladislav Shpilevoy
  5 siblings, 1 reply; 37+ messages in thread
From: Leonid Vasiliev @ 2020-04-10  8:10 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

New MsgPack encode format for error (MP_ERROR) has been added.
If the extended error format is enabled using iproto session settings
MP_ERROR type will be used for transferring error through network,
MP_STR was used before.

Needed for #4398
---
 src/box/CMakeLists.txt               |   1 +
 src/box/error.cc                     |  28 ++-
 src/box/error.h                      |  11 +-
 src/box/lua/call.c                   |  29 ++-
 src/box/lua/execute.c                |   2 +-
 src/box/lua/init.c                   |  56 +++++
 src/box/lua/mp_error.cc              | 454 +++++++++++++++++++++++++++++++++++
 src/box/lua/mp_error.h               |  49 ++++
 src/box/lua/net_box.lua              |   2 +-
 src/box/lua/tuple.c                  |  28 +--
 src/box/sql/func.c                   |   4 +-
 src/lib/core/mp_extension_types.h    |   5 +-
 src/lua/error.c                      |   2 -
 src/lua/error.h                      |   3 +-
 src/lua/msgpack.c                    |  28 ++-
 src/lua/msgpack.h                    |   8 +-
 src/lua/utils.c                      |  16 +-
 src/lua/utils.h                      |   6 +-
 test/box-tap/extended_error.test.lua | 164 +++++++++++++
 19 files changed, 821 insertions(+), 75 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 56758bd..32415b5 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/error.cc b/src/box/error.cc
index 8179e52..f2e60c1 100644
--- a/src/box/error.cc
+++ b/src/box/error.cc
@@ -253,6 +253,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)
@@ -264,6 +271,16 @@ BuildXlogGapError(const char *file, unsigned line,
 	}
 }
 
+struct error *
+ReBuildXlogGapError(const char *file, unsigned line, const char *msg)
+{
+	try {
+		return new XlogGapError(file, line, msg);
+	} catch (OutOfMemory *e) {
+		return e;
+	}
+}
+
 struct rlist on_access_denied = RLIST_HEAD_INITIALIZER(on_access_denied);
 
 static struct method_info accessdeniederror_methods[] = {
@@ -289,15 +306,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 5013488..d4d064b 100644
--- a/src/box/error.h
+++ b/src/box/error.h
@@ -54,6 +54,9 @@ BuildXlogGapError(const char *file, unsigned line,
 		  const struct vclock *from, const struct vclock *to);
 
 struct error *
+ReBuildXlogGapError(const char *file, unsigned line, const char *msg);
+
+struct error *
 BuildCustomError(const char *file, unsigned int line, const char *custom_type);
 
 /** \cond public */
@@ -250,6 +253,8 @@ public:
 	~AccessDeniedError()
 	{
 		free(m_object_name);
+		free(m_object_type);
+		free(m_access_type);
 	}
 
 	const char *
@@ -272,11 +277,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;
 };
 
 /**
@@ -306,6 +311,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; }
 };
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index 5d3579e..6d1d247 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.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,7 @@ execute_lua_eval(lua_State *L)
 struct encode_lua_ctx {
 	struct port_lua *port;
 	struct mpstream *stream;
+	struct luaL_serializer_ctx *serializer_ctx;
 };
 
 static int
@@ -393,8 +395,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_ctx,
+			     ctx->stream, i);
+	}
 	ctx->port->size = size;
 	mpstream_flush(ctx->stream);
 	return 0;
@@ -429,6 +433,7 @@ port_lua_do_dump(struct port *base, struct mpstream *stream,
 	struct encode_lua_ctx ctx;
 	ctx.port = port;
 	ctx.stream = stream;
+	ctx.serializer_ctx = &current_session()->serializer_ctx;
 	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/init.c b/src/box/lua/init.c
index 63e8b82..b57df4c 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.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()->serializer_ctx.err_format_ver == ERR_FORMAT_EX) {
+		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..f99da0f
--- /dev/null
+++ b/src/box/lua/mp_error.cc
@@ -0,0 +1,454 @@
+/*
+ * 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.h"
+#include "msgpuck.h"
+#include "mp_extension_types.h"
+
+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_BACKTRACE,
+	MP_ERROR_DET_CUSTOM_TYPE,
+	MP_ERROR_DET_AD_OBJ_TYPE,
+	MP_ERROR_DET_AD_OBJ_NAME,
+	MP_ERROR_DET_AD_ACCESS_TYPE
+};
+
+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
+};
+
+struct mp_error {
+	uint32_t error_code;
+	uint8_t error_type;
+	uint32_t line;
+	uint32_t saved_errno;
+	char *file;
+	char *backtrace;
+	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->backtrace = 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->backtrace);
+	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;
+
+	/* Error type, reason, errno, file and line are the necessary fields */
+	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->lua_traceback) {
+		++details_num;
+		data_size += mp_sizeof_uint(MP_ERROR_DET_BACKTRACE);
+		data_size += mp_sizeof_str(strlen(error->lua_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_custom_error_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, err_type);
+	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->lua_traceback) {
+		data = mp_encode_uint(data, MP_ERROR_DET_BACKTRACE);
+		data = mp_encode_str(data, error->lua_traceback,
+				     strlen(error->lua_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)
+{
+	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 = (struct error *)e;
+		break;
+	}
+	case MP_ERROR_TYPE_CUSTOM:
+		err = BuildCustomError(mp_error->file, mp_error->line,
+				       mp_error->custom_type);
+		break;
+	case MP_ERROR_TYPE_ACCESS_DENIED:
+		err = BuildAccessDeniedError(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 = BuildXlogError(mp_error->file, mp_error->line,
+				     "%s", mp_error->reason);
+		break;
+	case MP_ERROR_TYPE_XLOG_GAP:
+		err = ReBuildXlogGapError(mp_error->file, mp_error->line,
+					  mp_error->reason);
+		break;
+	case MP_ERROR_TyPE_SYSTEM:
+		err = BuildSystemError(mp_error->file, mp_error->line,
+				       "%s", mp_error->reason);
+		break;
+	case MP_ERROR_TyPE_SOCKET:
+		err = BuildSocketError(mp_error->file, mp_error->line, "", "");
+		error_format_msg(err, "", mp_error->reason);
+		break;
+	case MP_ERROR_TyPE_OOM:
+		err = BuildOutOfMemory(mp_error->file, mp_error->line,
+				       0, "", "");
+		error_format_msg(err, "%s", mp_error->reason);
+		break;
+	case MP_ERROR_TyPE_TIMED_OUT:
+		err = BuildTimedOut(mp_error->file, mp_error->line);
+		break;
+	case MP_ERROR_TyPE_CHANNEL_IS_CLOSED:
+		err = BuildChannelIsClosed(mp_error->file, mp_error->line);
+		break;
+	case MP_ERROR_TyPE_FIBER_IS_CANCELLED:
+		err = BuildFiberIsCancelled(mp_error->file, mp_error->line);
+		break;
+	case MP_ERROR_TyPE_LUAJIT:
+		err = BuildLuajitError(mp_error->file, mp_error->line,
+				       mp_error->reason);
+		break;
+	case MP_ERROR_TyPE_ILLEGAL_PARAMS:
+		err = BuildIllegalParams(mp_error->file, mp_error->line,
+					 "%s", mp_error->reason);
+		break;
+	case MP_ERROR_TyPE_COLLATION:
+		err = BuildCollationError(mp_error->file, mp_error->line,
+					  "%s", mp_error->reason);
+		break;
+	case MP_ERROR_TyPE_SWIM:
+		err = BuildSwimError(mp_error->file, mp_error->line,
+				     "%s", mp_error->reason);
+		break;
+	case MP_ERROR_TyPE_CRYPTO:
+		err = BuildCryptoError(mp_error->file, mp_error->line,
+				       "%s", mp_error->reason);
+		break;
+	default:
+		break;
+	}
+
+	err->traceback_mode = 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 = 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_BACKTRACE:
+			if (mp_typeof(**data) != MP_STR)
+				goto error;
+			str = mp_decode_str(data, &str_len);
+			mp_err.backtrace = 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..9eab213
--- /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/net_box.lua b/src/box/lua/net_box.lua
index c8f76b0..d3997af 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -1050,7 +1050,7 @@ local function new_sm(host, port, opts, connection, greeting)
 
     -- Set extended error format for session.
     if opts.error_extended then
-        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1)
+        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 0)
         if not ext_err_supported then
             box.error(box.error.PROC_LUA,
                       "Server doesn't support extended error format")
diff --git a/src/box/lua/tuple.c b/src/box/lua/tuple.c
index 1e3c3d6..af2c5b4 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);
 	}
@@ -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
@@ -435,7 +421,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);
 
@@ -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/box/sql/func.c b/src/box/sql/func.c
index 6e724c8..bbc1f6f 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/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h
index bc9873f..a2a5079 100644
--- a/src/lib/core/mp_extension_types.h
+++ b/src/lib/core/mp_extension_types.h
@@ -40,8 +40,9 @@
  * You may assign values in range [0, 127]
  */
 enum mp_extension_type {
-    MP_UNKNOWN_EXTENSION = 0,
-    MP_DECIMAL = 1,
+	MP_UNKNOWN_EXTENSION = 0,
+	MP_DECIMAL = 1,
+	MP_ERROR = 2
 };
 
 #endif
diff --git a/src/lua/error.c b/src/lua/error.c
index cd6ab54..109f947 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 edbc15b..6e0cf15 100644
--- a/src/lua/msgpack.c
+++ b/src/lua/msgpack.c
@@ -108,8 +108,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 luaL_serializer_ctx *ctx, struct mpstream *stream,
+	       struct luaL_field *field, int level)
 {
 	int top = lua_gettop(L);
 	enum mp_type type;
@@ -154,13 +154,13 @@ restart: /* used by MP_EXT */
 		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, ctx, lua_gettop(L), field) < 0)
 				return luaT_error(L);
-			luamp_encode_r(L, cfg, stream, field, level + 1);
+			luamp_encode_r(L, cfg, ctx, 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, ctx, lua_gettop(L), field) < 0)
 				return luaT_error(L);
-			luamp_encode_r(L, cfg, stream, field, level + 1);
+			luamp_encode_r(L, cfg, ctx, stream, field, level + 1);
 			lua_pop(L, 1); /* pop value */
 		}
 		assert(lua_gettop(L) == top);
@@ -179,9 +179,9 @@ restart: /* used by MP_EXT */
 		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, ctx, top + 1, field) < 0)
 				return luaT_error(L);
-			luamp_encode_r(L, cfg, stream, field, level + 1);
+			luamp_encode_r(L, cfg, ctx, stream, field, level + 1);
 			lua_pop(L, 1);
 		}
 		assert(lua_gettop(L) == top);
@@ -191,6 +191,8 @@ restart: /* used by MP_EXT */
 		case MP_DECIMAL:
 			mpstream_encode_decimal(stream, field->decval);
 			return MP_EXT;
+		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);
@@ -209,7 +211,8 @@ restart: /* used by MP_EXT */
 
 enum mp_type
 luamp_encode(struct lua_State *L, struct luaL_serializer *cfg,
-	     struct mpstream *stream, int index)
+	     struct luaL_serializer_ctx *ctx, struct mpstream *stream,
+	     int index)
 {
 	int top = lua_gettop(L);
 	if (index < 0)
@@ -221,9 +224,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, ctx, 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, ctx, stream, &field, 0);
 
 	if (!on_top) {
 		lua_remove(L, top + 1); /* remove a value copy */
@@ -232,6 +235,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)
@@ -354,7 +358,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..e5c7a94 100644
--- a/src/lua/msgpack.h
+++ b/src/lua/msgpack.h
@@ -43,6 +43,7 @@ extern "C" {
 
 struct luaL_serializer;
 struct mpstream;
+struct luaL_serializer_ctx;
 
 /**
  * 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 luaL_serializer_ctx *ctx, 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 luaL_serializer_ctx *ctx, 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 54d18ac..c0f173b 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -46,6 +46,7 @@ static uint32_t CTID_STRUCT_IBUF_PTR;
 static uint32_t CTID_CHAR_PTR;
 static uint32_t CTID_CONST_CHAR_PTR;
 uint32_t CTID_DECIMAL;
+uint32_t CTID_CONST_STRUCT_ERROR_REF;
 
 
 void *
@@ -445,7 +446,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 */
@@ -508,7 +509,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;
 	}
@@ -634,12 +635,13 @@ 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 luaL_serializer_ctx *ctx, int index,
 	     struct luaL_field *field)
 {
 	if (index < 0)
@@ -746,6 +748,10 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index,
 			if (cd->ctypeid == CTID_DECIMAL) {
 				field->ext_type = MP_DECIMAL;
 				field->decval = (decimal_t *) cdata;
+			} else if (cd->ctypeid == CTID_CONST_STRUCT_ERROR_REF &&
+				   ctx &&
+				   ctx->err_format_ver == ERR_FORMAT_EX) {
+				field->ext_type = MP_ERROR;
 			} else {
 				field->ext_type = MP_UNKNOWN_EXTENSION;
 			}
@@ -792,8 +798,8 @@ luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx,
 {
 	if (idx < 0)
 		idx = lua_gettop(L) + idx + 1;
-	assert(field->type == MP_EXT && field->ext_type == MP_UNKNOWN_EXTENSION); /* must be called after tofield() */
 
+	assert(field->type == MP_EXT && field->ext_type == MP_UNKNOWN_EXTENSION); /* must be called after tofield() */
 	if (cfg->encode_load_metatables) {
 		int type = lua_type(L, idx);
 		if (type == LUA_TCDATA) {
diff --git a/src/lua/utils.h b/src/lua/utils.h
index 5875ba3..4ccce03 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -381,6 +381,7 @@ struct luaL_field {
  *
  * @param L stack
  * @param cfg configuration
+ * @param serializer_ctx the Lua serializer context
  * @param index stack index
  * @param field conversion result
  *
@@ -388,7 +389,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 luaL_serializer_ctx *ctx, int index,
 	     struct luaL_field *field);
 
 /**
@@ -427,7 +429,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/test/box-tap/extended_error.test.lua b/test/box-tap/extended_error.test.lua
new file mode 100755
index 0000000..45c200f
--- /dev/null
+++ b/test/box-tap/extended_error.test.lua
@@ -0,0 +1,164 @@
+#! /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 == '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')
+
+    local connection = netbox.connect(host, port, {error_extended = true})
+    local _, err = pcall(connection.call, connection, 'custom_error_func')
+    local err_2 = connection:call('custom_error_func_2')
+
+    local check_list = {
+        type = 'CustomError',
+        custom_type = 'My Custom Type',
+        message = 'A modern custom error',
+        trace = '^File builtin/box/net_box.lua\nLine %d+$',
+        is_custom = true
+    }
+
+    local check_list_2 = {
+        type = 'CustomError',
+        custom_type = 'My Custom Type',
+        message = 'A modern custom error',
+        trace = '.*extended_error.test.lua\nLine 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 tarantool_ver = string.match(box.info.version, "%d%.%d%.%d")
+local host= uri.parse(box.cfg.listen).host or 'localhost'
+local port = uri.parse(box.cfg.listen).service 
+
+if version_at_least(box.info.version, 2, 4, 0) then
+    test_extended_transmission(host, port)
+else
+    test:ok(true, 'Current version of tarantool(' .. tarantool_ver .. ')' ..
+            ' don\'t support extended transmission')
+    test:ok(true, 'Current version of tarantool(' .. tarantool_ver .. ')' ..
+            ' don\'t support extended transmission')
+end
+test_old_transmission(host, port)
+
+os.exit(test:check() and 0 or 1)
-- 
2.7.4

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 0/5] Extending error functionality
  2020-04-10  8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev
                   ` (4 preceding siblings ...)
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding Leonid Vasiliev
@ 2020-04-14  1:10 ` Vladislav Shpilevoy
  2020-04-15  9:48   ` lvasiliev
  5 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-14  1:10 UTC (permalink / raw)
  To: Leonid Vasiliev; +Cc: tarantool-patches

Hi! Thanks for the patchset!

On 10/04/2020 10:10, Leonid Vasiliev wrote:
> https://github.com/tarantool/tarantool/issues/4398
> https://github.com/tarantool/tarantool/tree/lvasiliev/gh-4398-expose-error-module-4

The branch build fails on Travis.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error Leonid Vasiliev
@ 2020-04-14  1:11   ` Vladislav Shpilevoy
  2020-04-15  9:25     ` lvasiliev
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-14  1:11 UTC (permalink / raw)
  To: Leonid Vasiliev; +Cc: tarantool-patches

Thanks for the patch!

Sorry, some of my comments may contain typos, because it is
very late and I can't continue polishing it today.

First of all there are some major issues with the
patch, which IMO should be clarified before it is pushed.

1) Why do we need the traceback in scope of 4398? It has
nothing to do with marshaling through IProto, nor with
custom error types.

2) What to do with stacked errors? Currently only the first
error in the stack gets a traceback, because luaT_pusherror() is
called only on the top error in the stack. Consider this test:

    box.cfg{}
    lua_code = [[function(tuple)
                    local json = require('json')
                    return json.encode(tuple)
                 end]]
    box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
    s = box.schema.space.create('withdata')
    pk = s:create_index('pk')
    idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})

    function test_func() return pcall(s.insert, s, {1}) end
    box.error.cfg{traceback_enable = true}
    ok, err = test_func()


    tarantool> err:unpack()
    ---
    - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string
        \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382:
        in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725:
        in function <builtin/box/console.lua:713>"
    ... <snipped>

    tarantool> err.prev:unpack()
    ---
    - type: LuajitError
      message: '[string "return function(tuple)..."]:2: attempt to call global ''require''
        (a nil value)'
    ... <snipped>

The second error does not have a traceback at all.


See 10 comments below. Also I started a new branch with review
fixes to speed up the process. The branch is called

    lvasiliev/gh-4398-expose-error-module-4-review

You can find review fixes to this commit in the end of the
email and on this branch in a separate commit. Note, my branch
is rebased on master already. So some changes are related to
cleaning the mess after it.

On 10/04/2020 10:10, Leonid Vasiliev wrote:
> In accordance with https://github.com/tarantool/tarantool/issues/4398

1. We don't put links when issue is in the same repository.
We always use '#<number>' reference. This concerns this and all
next commits.

> Lua traceback has been added for box.error.
> Has been added a per server flag for turn on/off traceback adding
> and ability to force it at creation time.
> 
> @TarantoolBot document
> Title: error.traceback
> Was added:
> Per server flag for turn on/off adding a traceback to the errors.
> box.error.cfg({traceback_supplementation = true/false})

2. The option name is utterly overcomplicated. Just use

    traceback_enable

> Adding a traceback can be forced on creation.
> box.error.new({type = "CustomType", reason = "reason", traceback = true/false})

3. CustomType errors are not defined here, you can't use it in
the request description.

> Needed for #4398

4. Please, don't put issue references inside docbot requests. This
concerns this and all next commits.

> ---
>  src/box/lua/error.cc          | 33 ++++++++++++++++++++++++++++++++-
>  src/lib/core/diag.c           | 32 ++++++++++++++++++++++++++++++++
>  src/lib/core/diag.h           | 11 +++++++++++
>  src/lib/core/exception.cc     |  1 +
>  src/lua/error.c               | 10 ++++++++++
>  src/lua/error.lua             | 12 +++++++++++-
>  test/app/fiber.result         |  5 +++--
>  test/box/error.result         |  5 +++--
>  test/engine/func_index.result | 10 ++++++----
>  9 files changed, 109 insertions(+), 10 deletions(-)
> 
> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
> index b2625bf..4432bc8 100644
> --- a/src/box/lua/error.cc
> +++ b/src/box/lua/error.cc> @@ -180,6 +193,20 @@ 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_supplementation");
> +	if (lua_isnil(L, -1) == 0)

5. If something is not nil, it does not mean it is a boolean.
Better check lua_isboolean().

> +		error_set_traceback_supplementation(lua_toboolean(L, -1));
> +	lua_pop(L, 1);
> +
> +	return 0;
> +}
> diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c
> index e143db1..1caa75e 100644
> --- a/src/lib/core/diag.c
> +++ b/src/lib/core/diag.c
> @@ -120,3 +127,28 @@ error_vformat_msg(struct error *e, const char *format, va_list ap)
>  	vsnprintf(e->errmsg, sizeof(e->errmsg), format, ap);
>  }
>  
> +void
> +error_set_lua_traceback(struct error *e, const char *lua_traceback)
> +{
> +	if (e == NULL)
> +		return;

6. It makes no sense to call this function on a NULL 'e'. This
should be an assertion. As well as it is not possible to 'reset'
the trace, so e->lua_traceback should be always NULL here.

7. Also I don't think it is a good idea to call traceback attribute
'lua_traceback'. Because we won't have separated 'lua_traceback'
and 'c_traceback'. Anyway there will be always only one. Probably
with C and Lua mixed (it is possible via space triggers to mix Lua
and C in layers).

> +
> +	if (lua_traceback == NULL) {
> +		free(e->lua_traceback);
> +		e->lua_traceback = NULL;
> +		return;
> +	}
> +
> +	size_t tb_len = strlen(lua_traceback);
> +	e->lua_traceback = realloc(e->lua_traceback, tb_len + 1);
> +	if (e->lua_traceback == NULL)
> +		return;
> +	strcpy(e->lua_traceback, lua_traceback);
> +	return;
> +}
> +
> +void
> +error_set_traceback_supplementation(bool traceback_mode)
> +{
> +	global_traceback_mode = traceback_mode;
> +}
> diff --git a/src/lua/error.lua b/src/lua/error.lua
> index bdc9c71..46d2866 100644
> --- a/src/lua/error.lua
> +++ b/src/lua/error.lua
> @@ -92,6 +94,14 @@ local function error_trace(err)
>      }
>  end
>  
> +local function error_traceback(err)
> +    local result = "Traceback is absent"

8. I don't think it is a good idea to add this to
every error object when there is no a trace. Just
leave it nil.

> +    if err.lua_traceback ~= ffi.nullptr then
> +        result = ffi.string(err.lua_traceback)
> +    end
> +    return result
> +end
> +
>  local function error_errno(err)
>      local e = err._saved_errno
>      if e == 0 then
> @@ -122,7 +132,6 @@ local function error_set_prev(err, prev)
>      if ok ~= 0 then
>          error("Cycles are not allowed")
>      end
> -

9. Please, avoid unnecessary diff.

>  end
>  
>  local error_fields = {
> diff --git a/test/engine/func_index.result b/test/engine/func_index.result
> index a827c92..28befca 100644
> --- a/test/engine/func_index.result
> +++ b/test/engine/func_index.result
> @@ -291,12 +292,13 @@ e = e.prev
>   | ...
>  e:unpack()
>   | ---
> - | - type: LuajitError
> - |   message: '[string "return function(tuple)                 local ..."]:1: attempt
> - |     to call global ''require'' (a nil value)'
> + | - traceback: Traceback is absent
>   |   trace:
>   |   - file: <filename>
>   |     line: <line>
> + |   type: LuajitError
> + |   message: '[string "return function(tuple)                 local ..."]:1: attempt
> + |     to call global ''require'' (a nil value)'
>   | ...
>  e = e.prev
>   | ---

10. Usually every commit should add tests on the feature it
introduces. You didn't add any single test. Why?

Consider my review fixes below and on a new branch in a
separate commit. It solves all the code-related problems,
but I still don't know what to do with the major issues
described in the beginning.

Branch is:

    lvasiliev/gh-4398-expose-error-module-4-review

====================
    Review fixes
    
    New commit message proposal:
    
    error: add a Lua traceback to error
    
    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.
    
    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
        })
    ```

diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
index 4432bc89b..a616e87a4 100644
--- a/src/box/lua/error.cc
+++ b/src/box/lua/error.cc
@@ -54,8 +54,8 @@ luaT_error_create(lua_State *L, int top_base)
 {
 	uint32_t code = 0;
 	const char *reason = NULL;
-	bool tb_parsed = false;
-	bool tb_mode = false;
+	bool is_traceback_enabled = false;
+	bool is_traceback_specified = false;
 	const char *file = "";
 	unsigned line = 0;
 	lua_Debug info;
@@ -91,10 +91,10 @@ luaT_error_create(lua_State *L, int top_base)
 		lua_pop(L, 1);
 		lua_getfield(L, top_base, "traceback");
 		if (lua_isboolean(L, -1)) {
-			tb_parsed = true;
-			tb_mode = lua_toboolean(L, -1);
+			is_traceback_enabled = lua_toboolean(L, -1);
+			is_traceback_specified = true;
 		}
-		lua_pop(L, -1);
+		lua_pop(L, 1);
 	} else {
 		return NULL;
 	}
@@ -112,9 +112,11 @@ raise:
 	}
 
 	struct error *err = box_error_new(file, line, code, "%s", reason);
-	if (tb_parsed)
-		err->traceback_mode = tb_mode;
-
+	/*
+	 * Explicit traceback option overrides the global setting.
+	 */
+	if (err != NULL && is_traceback_specified)
+		err->is_traceback_enabled = is_traceback_enabled;
 	return err;
 }
 
@@ -198,11 +200,9 @@ 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_supplementation");
-	if (lua_isnil(L, -1) == 0)
-		error_set_traceback_supplementation(lua_toboolean(L, -1));
-	lua_pop(L, 1);
-
+	lua_getfield(L, 1, "traceback_enable");
+	if (lua_isboolean(L, -1))
+		error_is_traceback_enabled = lua_toboolean(L, -1);
 	return 0;
 }
 
diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c
index 1caa75ee9..833454bac 100644
--- a/src/lib/core/diag.c
+++ b/src/lib/core/diag.c
@@ -31,10 +31,7 @@
 #include "diag.h"
 #include "fiber.h"
 
-/**
- * Global flag to add or not backtrace to errors.
- */
-static bool global_traceback_mode = false;
+bool error_is_traceback_enabled = false;
 
 int
 error_set_prev(struct error *e, struct error *prev)
@@ -102,8 +99,8 @@ error_create(struct error *e,
 	e->errmsg[0] = '\0';
 	e->cause = NULL;
 	e->effect = NULL;
-	e->lua_traceback = NULL;
-	e->traceback_mode = global_traceback_mode;
+	e->traceback = NULL;
+	e->is_traceback_enabled = error_is_traceback_enabled;
 }
 
 struct diag *
@@ -128,27 +125,14 @@ error_vformat_msg(struct error *e, const char *format, va_list ap)
 }
 
 void
-error_set_lua_traceback(struct error *e, const char *lua_traceback)
-{
-	if (e == NULL)
-		return;
-
-	if (lua_traceback == NULL) {
-		free(e->lua_traceback);
-		e->lua_traceback = NULL;
-		return;
-	}
-
-	size_t tb_len = strlen(lua_traceback);
-	e->lua_traceback = realloc(e->lua_traceback, tb_len + 1);
-	if (e->lua_traceback == NULL)
-		return;
-	strcpy(e->lua_traceback, lua_traceback);
-	return;
-}
-
-void
-error_set_traceback_supplementation(bool traceback_mode)
+error_set_traceback(struct error *e, const char *traceback)
 {
-	global_traceback_mode = traceback_mode;
+	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 f38009c54..e918d3089 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,8 +117,19 @@ struct error {
 	 */
 	struct error *cause;
 	struct error *effect;
-	char *lua_traceback;
-	bool traceback_mode;
+	/**
+	 * 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
@@ -174,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)
 {
@@ -199,15 +225,6 @@ error_format_msg(struct error *e, const char *format, ...);
 void
 error_vformat_msg(struct error *e, const char *format, va_list ap);
 
-void
-error_set_lua_traceback(struct error *e, const char *lua_traceback);
-
-/**
-* Sets the global flag to add or not backtrace to errors.
-*/
-void
-error_set_traceback_supplementation(bool traceback_mode);
-
 /**
  * Diagnostics Area - a container for errors
  */
diff --git a/src/lib/core/exception.cc b/src/lib/core/exception.cc
index 0e4b6ca8b..1717b76fb 100644
--- a/src/lib/core/exception.cc
+++ b/src/lib/core/exception.cc
@@ -42,7 +42,7 @@ extern "C" {
 static void
 exception_destroy(struct error *e)
 {
-	free(e->lua_traceback);
+	free(e->traceback);
 	delete (Exception *) e;
 }
 
diff --git a/src/lua/error.c b/src/lua/error.c
index cd6ab54b1..a21548ce6 100644
--- a/src/lua/error.c
+++ b/src/lua/error.c
@@ -86,12 +86,11 @@ luaT_pusherror(struct lua_State *L, struct error *e)
 	 */
 	error_ref(e);
 
-	if (e->lua_traceback == NULL && e->traceback_mode) {
+	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_lua_traceback(e, lua_tostring(L, -1));
-		}
+		if (lua_isstring(L, -1))
+			error_set_traceback(e, lua_tostring(L, -1));
 		lua_settop(L, top);
 	}
 
diff --git a/src/lua/error.lua b/src/lua/error.lua
index 46d28667f..535110588 100644
--- a/src/lua/error.lua
+++ b/src/lua/error.lua
@@ -26,8 +26,8 @@ struct error {
     char _errmsg[DIAG_ERRMSG_MAX];
     struct error *_cause;
     struct error *_effect;
-    char *lua_traceback;
-    bool traceback_mode;
+    char *traceback;
+    bool is_traceback_enabled;
 };
 
 char *
@@ -95,11 +95,7 @@ local function error_trace(err)
 end
 
 local function error_traceback(err)
-    local result = "Traceback is absent"
-    if err.lua_traceback ~= ffi.nullptr then
-        result = ffi.string(err.lua_traceback)
-    end
-    return result
+    return err.traceback ~= ffi.nullptr and ffi.string(err.traceback) or nil
 end
 
 local function error_errno(err)
@@ -132,6 +128,7 @@ local function error_set_prev(err, prev)
     if ok ~= 0 then
         error("Cycles are not allowed")
     end
+
 end
 
 local error_fields = {
diff --git a/test/app/fiber.result b/test/app/fiber.result
index fc6b3c92d..debfc6718 100644
--- a/test/app/fiber.result
+++ b/test/app/fiber.result
@@ -1038,13 +1038,12 @@ st;
 ...
 e:unpack();
 ---
-- traceback: Traceback is absent
+- type: ClientError
   code: 1
+  message: Illegal parameters, oh my
   trace:
   - file: '[string "function err() box.error(box.error.ILLEGAL_PA..."]'
     line: 1
-  type: ClientError
-  message: Illegal parameters, oh my
 ...
 flag = false;
 ---
diff --git a/test/box/error.result b/test/box/error.result
index 574bc481f..2502d88c4 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -34,13 +34,12 @@ e
  | ...
 e:unpack()
  | ---
- | - traceback: Traceback is absent
+ | - type: ClientError
  |   code: 1
+ |   message: Illegal parameters, bla bla
  |   trace:
  |   - file: '[C]'
  |     line: 4294967295
- |   type: ClientError
- |   message: Illegal parameters, bla bla
  | ...
 e.type
  | ---
@@ -832,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 41baed52d..6f1271630 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)
diff --git a/test/engine/func_index.result b/test/engine/func_index.result
index 28befca39..a827c929f 100644
--- a/test/engine/func_index.result
+++ b/test/engine/func_index.result
@@ -276,8 +276,7 @@ e = box.error.last()
  | ...
 e:unpack()
  | ---
- | - traceback: Traceback is absent
- |   code: 198
+ | - code: 198
  |   trace:
  |   - file: <filename>
  |     line: <line>
@@ -292,13 +291,12 @@ e = e.prev
  | ...
 e:unpack()
  | ---
- | - traceback: Traceback is absent
+ | - type: LuajitError
+ |   message: '[string "return function(tuple)                 local ..."]:1: attempt
+ |     to call global ''require'' (a nil value)'
  |   trace:
  |   - file: <filename>
  |     line: <line>
- |   type: LuajitError
- |   message: '[string "return function(tuple)                 local ..."]:1: attempt
- |     to call global ''require'' (a nil value)'
  | ...
 e = e.prev
  | ---

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type Leonid Vasiliev
@ 2020-04-14  1:11   ` Vladislav Shpilevoy
  2020-04-15  9:25     ` lvasiliev
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-14  1:11 UTC (permalink / raw)
  To: Leonid Vasiliev; +Cc: tarantool-patches

Thanks for the patch!

Sorry, some of my comments may contain typos, because it is
very late and I can't continue polishing it today.

See 21 comments below, review fixes in the end of the email,
and on a new branch in a separate commit.

On 10/04/2020 10:10, Leonid Vasiliev wrote:
> A possibility to create an error with a custom subtype was added.
> 
> In accordance with https://github.com/tarantool/tarantool/issues/4398
> a custom error type has been added to the box.error.
> Now, it's possible to create an error with a custom subtype (string value)
> for use it in applications.
> 
> @TarantoolBot document
> Title: error.custom_type

1. custom_type field does not exist in any user visible API. Even
after this patch.

> A custom error type has been added to the box.error.
> Now, it's possible to create an error with a custom subtype (string value)
> for use it in applications.

2. When docbot duplicates commit message, you can omit the latter.

> 
> To API has been added:
> - For create CustomError: box.error.new(type(str), format, ...)
>                           box.error.new({type = str,
>                                          reason = str})
> 
> - error.type - CustomType error return custom type
>     other errors return "base" error tybe
> - base_type - return "base" errror type
> Example:
> 
> err_custom = box.error.new("My Custom Type", "Reason")
> Now:
> err_custom.type == "My Custom Type"
> err_custom.base_type == "CustomType"
> err_custom.message == "Reason"

3. There is no a single word about name limit in 63 bytes.

> 
> Needed for #4398
> ---
>  src/box/errcode.h             |   1 +
>  src/box/error.cc              |  59 +++++++++++++++++
>  src/box/error.h               |  38 ++++++++++-
>  src/box/lua/error.cc          | 147 ++++++++++++++++++++++++++++++------------
>  src/lua/error.lua             |  11 ++++
>  test/app/fiber.result         |   5 +-
>  test/box/error.result         |  10 +--
>  test/engine/func_index.result |  18 +++---
>  8 files changed, 234 insertions(+), 55 deletions(-)
> 
> diff --git a/src/box/error.cc b/src/box/error.cc
> index 233b312..8179e52 100644
> --- a/src/box/error.cc
> +++ b/src/box/error.cc
> @@ -125,6 +125,31 @@ box_error_add(const char *file, unsigned line, uint32_t code,
>  
>  /* }}} */
>  
> +const char *
> +box_custom_error_type(const box_error_t *e)

4. box_error_t is a type for the public API. This API is not public and
therefore should use 'struct error' type name.

5. Naming policy for functions is that there should be a prefix
with the object type name in all functions. Or at least the prefix
should be the same on all methods of one type.

Here the prefix should be box_error_*, so this method is
box_error_custom_type(), and the method below is box_error_custom_new().

> +{
> +	CustomError *custom_error = type_cast(CustomError, e);
> +	if (custom_error)
> +		return custom_error->custom_type();
> +
> +	return NULL;
> +}
> +
> +struct error *
> +box_custom_error_new(const char *file, unsigned line,
> +		     const char *custom, const char *fmt, ...)
> +{
> +	struct error *e = BuildCustomError(file, line, custom);
> +	CustomError *custom_error = type_cast(CustomError, e);
> +	if (custom_error != NULL) {
> +		va_list ap;
> +		va_start(ap, fmt);
> +		error_vformat_msg(e, fmt, ap);
> +		va_end(ap);
> +	}
> +	return e;
> +}
> +
>  struct rmean *rmean_error = NULL;
>  
>  const char *rmean_error_strings[RMEAN_ERROR_LAST] = {
> @@ -290,3 +315,37 @@ 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);

6. Make_type() perfectly fits in one line.

> +
> +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 ?: "");

7. custom_type is never NULL.

> +
> +	if (custom_type) {
> +		strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1);
> +		m_custom_type[sizeof(m_custom_type) - 1] = '\0';
> +	} else {
> +		m_custom_type[0] = '\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..5013488 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,6 +141,14 @@ box_error_set(const char *file, unsigned line, uint32_t code,
>  /** \endcond public */
>  
>  /**
> + * Return the error custom type,
> + * \param error

8. \param or @param takes two arguments - parameter name and its
description. You provided only name, and the name does not match
the actual parameter, which is 'e'.

> + * \return pointer to custom error type. On error, return NULL

9. Please, try to start sentences from a capital letter, and finish
them with a dot.

> + */
> +const char *
> +box_custom_error_type(const box_error_t *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.
> @@ -155,15 +166,24 @@ box_error_add(const char *file, unsigned line, uint32_t code,
>  
>  /**
>   * Construct error object without setting it in the diagnostics
> - * area. On the memory allocation fail returns NULL.
> + * area. On the memory allocation fail returns  OutOfMemory error.

10. Unneccessary diff.

>   */
>  struct error *
>  box_error_new(const char *file, unsigned line, uint32_t code,
>  	      const char *fmt, ...);
>  
> +/**
> + * Construct error object without setting it in the diagnostics
> + * area. On the memory allocation fail returns OutOfMemory error.

11. This is a copy-paste box box_error_new() comment. Better write
here a couple of words how is it different. Or better merge box_error_new()
and box_custom_error_new(). Because in Lua I can specify both code
and type. Don't see why can't I do this in C.

Honestly, I think we should use kind of struct error_args like you
tried to do for lua/error.cc for the C API. Because with more
arguments it will be hard to maintain them all as function paramters.
But not now.

> + */
> +struct error *
> +box_custom_error_new(const char *file, unsigned line,
> +		     const char *custom, 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 +310,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 4432bc8..1bcce1f 100644
> --- a/src/box/lua/error.cc
> +++ b/src/box/lua/error.cc
> @@ -42,64 +42,101 @@ extern "C" {
>  #include "lua/utils.h"
>  #include "box/error.h"
>  
> -/**
> - * 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.
> - * In case one of arguments is missing its corresponding field
> - * in struct error is filled with default value.
> - */
> -static struct error *

12. Most of changes in this file does not really realate to the
feature you are trying to introduce. It is just refactoring. If
you want it, it should be done in a separate commit, because
otherwise it is hard to see which changes are functional and need
more attention.

> -luaT_error_create(lua_State *L, int top_base)
> +struct error_args {
> +	uint32_t code;
> +	const char *reason;
> +	const char *custom;
> +	bool tb_mode;
> +	bool tb_parsed;

13. All structures and their attributes usually have a comment
explaining their purpose. In this structure it is hard to
understand what is 'tb', what is 'tb_mode', what is 'tb_parsed',
if you are not in the context of the patch.

> +};
> +
> +static int
> +luaT_error_parse_args(struct lua_State *L, int top_base,
> +		      struct error_args *args)
>  {
> -	uint32_t code = 0;
> -	const char *reason = NULL;
> -	bool tb_parsed = false;
> -	bool tb_mode = false;
> -	const char *file = "";
> -	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);
> -		reason = tnt_errcode_desc(code);
> +	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) {
> +			args->code = lua_tonumber(L, top_base);
> +		} else if (top_type == LUA_TSTRING) {
> +			args->code = ER_CUSTOM_ERROR;
> +			args->custom = lua_tostring(L, top_base);
> +		} else {
> +			return -1;
> +		}
> +		args->reason = tnt_errcode_desc(args->code);
>  		if (top > top_base) {
>  			/* Call string.format(reason, ...) to format message */
>  			lua_getglobal(L, "string");
>  			if (lua_isnil(L, -1))
> -				goto raise;
> +				return 0;
>  			lua_getfield(L, -1, "format");
>  			if (lua_isnil(L, -1))
> -				goto raise;
> -			lua_pushstring(L, reason);
> +				return 0;
> +			lua_pushstring(L, args->reason);
>  			for (int i = top_base + 1; i <= top; i++)
>  				lua_pushvalue(L, i);
>  			lua_call(L, top - top_base + 1, 1);
> -			reason = lua_tostring(L, -1);
> -		} else if (strchr(reason, '%') != NULL) {
> +			args->reason = lua_tostring(L, -1);
> +		} else if (strchr(args->reason, '%') != NULL) {
>  			/* Missing arguments to format string */
> -			return NULL;
> +			return -1;
>  		}
> -	} 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);
> +		if (lua_isnil(L, -1) == 0)
> +			args->code = lua_tonumber(L, -1);

14. I reverted this change and didn't get any problems
in the tests. Why?

>  		lua_pop(L, 1);
>  		lua_getfield(L, top_base, "reason");
> -		reason = lua_tostring(L, -1);
> -		if (reason == NULL)
> -			reason = "";
> +		if (lua_isnil(L, -1) == 0)
> +			args->reason = lua_tostring(L, -1);

15. Previously it was not checked for nil, and all worked fine.
Why did you change that?

> +		lua_pop(L, 1);
> +		lua_getfield(L, top_base, "type");
> +		if (lua_isnil(L, -1) == 0)
> +			args->custom = lua_tostring(L, -1);
>  		lua_pop(L, 1);
>  		lua_getfield(L, top_base, "traceback");
>  		if (lua_isboolean(L, -1)) {
> -			tb_parsed = true;
> -			tb_mode = lua_toboolean(L, -1);
> +			args->tb_parsed = true;
> +			args->tb_mode = lua_toboolean(L, -1);
>  		}
>  		lua_pop(L, -1);
>  	} else {
> +		return -1;
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * 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.

16. The comment does not describe new syntax with 'type' argument.

> + * In case one of arguments is missing its corresponding field
> + * in struct error is filled with default value.
> + */
> +static struct error *
> +luaT_error_create(lua_State *L, int top_base)
> +{
> +	struct error_args args;
> +	args.code = UINT32_MAX;

17. Before your patch default error code was 0. Why is it
changed to UINT32_MAX?

> +	args.reason = NULL;
> +	args.custom = NULL;
> +	args.tb_mode = false;
> +	args.tb_parsed = false;
> +
> +	if (luaT_error_parse_args(L, top_base, &args) != 0) {
>  		return NULL;
>  	}
>  
> -raise:
> +	if (args.reason == NULL)
> +		args.reason = "";
> +
> +	const char *file = "";
> +	unsigned line = 0;
> +	lua_Debug info;
>  	if (lua_getstack(L, 1, &info) && lua_getinfo(L, "Sl", &info)) {
>  		if (*info.short_src) {
>  			file = info.short_src;
> @@ -111,9 +148,16 @@ raise:
>  		line = info.currentline;
>  	}
>  
> -	struct error *err = box_error_new(file, line, code, "%s", reason);
> -	if (tb_parsed)
> -		err->traceback_mode = tb_mode;
> +	struct error *err = NULL;
> +	if (args.custom) {
> +		err = box_custom_error_new(file, line, args.custom,
> +					   "%s", args.reason);

18. Code argument is ignored here. Although I am not sure it shouldn't
be ignored. From what I remember, we decided that code should stay,
but I may be wrong.

> +	} else {
> +		err = box_error_new(file, line, args.code, "%s", args.reason);
> +	}
> +
> +	if (args.tb_parsed)
> +		err->traceback_mode = args.tb_mode;
>  
>  	return err;
>  }
> @@ -162,17 +206,36 @@ 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)");
> +	if (lua_gettop(L) == 0) {
> +		return luaL_error(L, "Usage: box.error.new(code, args) or "\
> +				     "box.error.new(type, args)");
> +	}
>  	struct error *e = luaT_error_create(L, 1);
> -	if (e == NULL)
> -		return luaL_error(L, "Usage: box.error.new(code, args)");
> +	if (e == 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;
>  }
>  
>  static int
> +luaT_error_custom_type(lua_State *L)

19. This method is not needed. We don't have 'global' getters
like box.error.prev, or box.error.type, and so on. Every
error object provides API to get its members without calling
global functions.

> +{
> +	struct error *e = luaL_checkerror(L, -1);
> +
> +	const char *custom_type = box_custom_error_type(e);
> +	if (custom_type == NULL) {
> +		lua_pushfstring(L, "The error has't custom type");
> +		return 1;
> +	}
> +
> +	lua_pushstring(L, custom_type);
> +	return 1;
> +}
> +
> +static int
>  luaT_error_clear(lua_State *L)
>  {
>  	if (lua_gettop(L) >= 1)
> diff --git a/src/lua/error.lua b/src/lua/error.lua
> index 46d2866..5b32607 100644
> --- a/src/lua/error.lua
> +++ b/src/lua/error.lua
> @@ -78,6 +78,16 @@ local function reflection_get(err, method)
>  end
>  
>  local function error_type(err)
> +    local res
> +    if ffi.string(err._type.name) == 'CustomError' then
> +        res = box.error.custom_type(err)
> +    else
> +        res = ffi.string(err._type.name)

20. ffi.string() is called on one string 2 times. But it
is not free, it allocates a new string each time.

> +    end
> +    return res
> +end

21. There is again not a single test.

Consider my review fixes below and on a new branch in a
separate commit. Note, it is not entirely separated
from the original commit in a couple of places, since I had
rebase conflicts.

Branch is:

    lvasiliev/gh-4398-expose-error-module-4-review

====================
    Review fixes
    
    New commit message proposal:
    
    error: add custom error type
    
    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
    ...
    ```

diff --git a/extra/exports b/extra/exports
index ffd11145d..a5ebe0884 100644
--- a/extra/exports
+++ b/extra/exports
@@ -234,6 +234,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/error.cc b/src/box/error.cc
index 8179e5287..d854b86e9 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);
@@ -126,7 +142,7 @@ box_error_add(const char *file, unsigned line, uint32_t code,
 /* }}} */
 
 const char *
-box_custom_error_type(const box_error_t *e)
+box_error_custom_type(const struct error *e)
 {
 	CustomError *custom_error = type_cast(CustomError, e);
 	if (custom_error)
@@ -135,21 +151,6 @@ box_custom_error_type(const box_error_t *e)
 	return NULL;
 }
 
-struct error *
-box_custom_error_new(const char *file, unsigned line,
-		     const char *custom, const char *fmt, ...)
-{
-	struct error *e = BuildCustomError(file, line, custom);
-	CustomError *custom_error = type_cast(CustomError, e);
-	if (custom_error != NULL) {
-		va_list ap;
-		va_start(ap, fmt);
-		error_vformat_msg(e, fmt, ap);
-		va_end(ap);
-	}
-	return e;
-}
-
 struct rmean *rmean_error = NULL;
 
 const char *rmean_error_strings[RMEAN_ERROR_LAST] = {
@@ -322,22 +323,15 @@ static struct method_info customerror_methods[] = {
 };
 
 const struct type_info type_CustomError =
-	make_type("CustomError", &type_ClientError,
-		  customerror_methods);
+	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 ?: "");
-
-	if (custom_type) {
-		strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1);
-		m_custom_type[sizeof(m_custom_type) - 1] = '\0';
-	} else {
-		m_custom_type[0] = '\0';
-	}
+	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 *
diff --git a/src/box/error.h b/src/box/error.h
index 501348885..540008f35 100644
--- a/src/box/error.h
+++ b/src/box/error.h
@@ -142,11 +142,11 @@ box_error_set(const char *file, unsigned line, uint32_t code,
 
 /**
  * Return the error custom type,
- * \param error
- * \return pointer to custom error type. On error, return NULL
+ * \param e Error object.
+ * \return Pointer to custom error type. On error return NULL.
  */
 const char *
-box_custom_error_type(const box_error_t *e);
+box_error_custom_type(const struct error *e);
 
 /**
  * Add error to the diagnostic area. In contrast to box_error_set()
@@ -154,6 +154,8 @@ box_custom_error_type(const box_error_t *e);
  * 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
@@ -162,23 +164,15 @@ box_custom_error_type(const box_error_t *e);
  */
 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
- * area. On the memory allocation fail returns  OutOfMemory error.
+ * area. On the memory allocation fail returns NULL.
  */
 struct error *
 box_error_new(const char *file, unsigned line, uint32_t code,
-	      const char *fmt, ...);
-
-/**
- * Construct error object without setting it in the diagnostics
- * area. On the memory allocation fail returns OutOfMemory error.
- */
-struct error *
-box_custom_error_new(const char *file, unsigned line,
-		     const char *custom, const char *fmt, ...);
+	      const char *custom_type, const char *fmt, ...);
 
 extern const struct type_info type_ClientError;
 extern const struct type_info type_XlogError;
diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
index dd7068a83..cf771935e 100644
--- a/src/box/lua/error.cc
+++ b/src/box/lua/error.cc
@@ -42,95 +42,83 @@ extern "C" {
 #include "lua/utils.h"
 #include "box/error.h"
 
-struct error_args {
-	uint32_t code;
-	const char *reason;
-	const char *custom;
-	const char *traceback;
-};
-
-static int
-luaT_error_parse_args(struct lua_State *L, int top_base,
-		      struct error_args *args)
+/**
+ * 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.
+ */
+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;
+	const char *file = "";
+	unsigned line = 0;
+	lua_Debug info;
 	int top = lua_gettop(L);
 	int top_type = lua_type(L, top_base);
-	if (top >= top_base &&
-	    (top_type == LUA_TNUMBER || top_type == LUA_TSTRING)) {
+	if (top >= top_base && (top_type == LUA_TNUMBER ||
+				top_type == LUA_TSTRING)) {
 		if (top_type == LUA_TNUMBER) {
-			args->code = lua_tonumber(L, top_base);
-		} else if (top_type == LUA_TSTRING) {
-			args->code = ER_CUSTOM_ERROR;
-			args->custom = lua_tostring(L, top_base);
+			code = lua_tonumber(L, top_base);
 		} else {
-			return -1;
+			code = ER_CUSTOM_ERROR;
+			custom_type = lua_tostring(L, top_base);
 		}
-		args->reason = tnt_errcode_desc(args->code);
+		reason = tnt_errcode_desc(code);
 		if (top > top_base) {
 			/* Call string.format(reason, ...) to format message */
 			lua_getglobal(L, "string");
 			if (lua_isnil(L, -1))
-				return 0;
+				goto raise;
 			lua_getfield(L, -1, "format");
 			if (lua_isnil(L, -1))
-				return 0;
-			lua_pushstring(L, args->reason);
+				goto raise;
+			lua_pushstring(L, reason);
 			for (int i = top_base + 1; i <= top; i++)
 				lua_pushvalue(L, i);
 			lua_call(L, top - top_base + 1, 1);
-			args->reason = lua_tostring(L, -1);
-		} else if (strchr(args->reason, '%') != NULL) {
+			reason = lua_tostring(L, -1);
+		} else if (strchr(reason, '%') != NULL) {
 			/* Missing arguments to format string */
-			return -1;
+			return NULL;
 		}
 	} else if (top == top_base && top_type == LUA_TTABLE) {
 		lua_getfield(L, top_base, "code");
-		if (lua_isnil(L, -1) == 0)
-			args->code = lua_tonumber(L, -1);
+		if (!lua_isnil(L, -1))
+			code = lua_tonumber(L, -1);
+		else
+			code = ER_CUSTOM_ERROR;
 		lua_getfield(L, top_base, "reason");
-		if (lua_isnil(L, -1) == 0)
-			args->reason = lua_tostring(L, -1);
+		reason = lua_tostring(L, -1);
+		if (reason == NULL)
+			reason = "";
 		lua_getfield(L, top_base, "type");
-		if (lua_isnil(L, -1) == 0)
-			args->custom = lua_tostring(L, -1);
+		if (!lua_isnil(L, -1))
+			custom_type = lua_tostring(L, -1);
 		lua_getfield(L, top_base, "traceback");
-		if (lua_isboolean(L, -1) && lua_toboolean(L, -1)) {
-			luaL_traceback(L, L, NULL, 0);
-			if (lua_isstring(L, -1))
-				args->traceback = lua_tostring(L, -1);
+		if (lua_isboolean(L, -1)) {
+			is_traceback_enabled = lua_toboolean(L, -1);
+			is_traceback_specified = true;
 		}
 		lua_pop(L, 1);
 	} else {
-		return -1;
-	}
-	return 0;
-}
-
-/**
- * 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.
- * In case one of arguments is missing its corresponding field
- * in struct error is filled with default value.
- */
-static struct error *
-luaT_error_create(lua_State *L, int top_base)
-{
-	struct error_args args;
-	args.code = UINT32_MAX;
-	args.reason = NULL;
-	args.custom = NULL;
-	args.traceback = NULL;
-
-	if (luaT_error_parse_args(L, top_base, &args) != 0)
 		return NULL;
+	}
 
-	if (args.reason == NULL)
-		args.reason = "";
-
-	const char *file = "";
-	unsigned line = 0;
-	lua_Debug info;
+raise:
 	if (lua_getstack(L, 1, &info) && lua_getinfo(L, "Sl", &info)) {
 		if (*info.short_src) {
 			file = info.short_src;
@@ -142,15 +130,13 @@ luaT_error_create(lua_State *L, int top_base)
 		line = info.currentline;
 	}
 
-	struct error *err;
-	if (args.custom) {
-		err = box_custom_error_new(file, line, args.custom,
-					   "%s", args.reason);
-	} else {
-		err = box_error_new(file, line, args.code, "%s", args.reason);
-	}
-	if (args.traceback != NULL && err != NULL)
-		error_set_traceback(err, args.traceback);
+	struct error *err = box_error_new(file, line, code, custom_type,
+					  "%s", reason);
+	/*
+	 * Explicit traceback option overrides the global setting.
+	 */
+	if (err != NULL && is_traceback_specified)
+		err->is_traceback_enabled = is_traceback_enabled;
 	return err;
 }
 
@@ -198,35 +184,16 @@ 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) or "\
-				     "box.error.new(type, args)");
-	}
-	struct error *e = luaT_error_create(L, 1);
-	if (e == NULL) {
+	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)");
+				  "box.error.new(type, args)");
 	}
 	lua_settop(L, 0);
 	luaT_pusherror(L, e);
 	return 1;
 }
 
-static int
-luaT_error_custom_type(lua_State *L)
-{
-	struct error *e = luaL_checkerror(L, -1);
-
-	const char *custom_type = box_custom_error_type(e);
-	if (custom_type == NULL) {
-		lua_pushfstring(L, "The error has't custom type");
-		return 1;
-	}
-
-	lua_pushstring(L, custom_type);
-	return 1;
-}
-
 static int
 luaT_error_clear(lua_State *L)
 {
@@ -381,10 +348,6 @@ box_lua_error_init(struct lua_State *L) {
 			lua_pushcfunction(L, luaT_error_cfg);
 			lua_setfield(L, -2, "cfg");
 		}
-		{
-			lua_pushcfunction(L, luaT_error_custom_type);
-			lua_setfield(L, -2, "custom_type");
-		}
 		lua_setfield(L, -2, "__index");
 	}
 	lua_setmetatable(L, -2);
diff --git a/src/box/xrow.c b/src/box/xrow.c
index a494d1f46..5494b41cd 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 1f9ae9375..1d3413b19 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,20 +80,18 @@ local function reflection_get(err, method)
     end
 end
 
-local function error_type(err)
-    local res
-    if ffi.string(err._type.name) == 'CustomError' then
-        res = box.error.custom_type(err)
-    else
-        res = ffi.string(err._type.name)
-    end
-    return res
-end
-
 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
@@ -148,7 +149,7 @@ local error_fields = {
     ["errno"]       = error_errno;
     ["prev"]        = error_prev;
     ["traceback"]   = error_traceback;
-    ["base_type"]    = error_base_type
+    ["base_type"]   = error_base_type,
 }
 
 local function error_unpack(err)
diff --git a/test/box/error.result b/test/box/error.result
index 2b837e434..b717a4ff4 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -431,11 +431,8 @@ t;
  |   210: box.error.SQL_PREPARE
  |   211: box.error.WRONG_QUERY_ID
  |   212: box.error.SEQUENCE_NOT_STARTED
-<<<<<<< HEAD
  |   213: box.error.NO_SUCH_SESSION_SETTING
-=======
- |   213: box.error.CUSTOM_ERROR
->>>>>>> error: Add the custom error type
+ |   214: box.error.CUSTOM_ERROR
  | ...
 
 test_run:cmd("setopt delimiter ''");
@@ -895,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 6f1271630..fe4051899 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

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO Leonid Vasiliev
@ 2020-04-14  1:12   ` Vladislav Shpilevoy
  2020-04-15  9:25     ` lvasiliev
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-14  1:12 UTC (permalink / raw)
  To: Leonid Vasiliev; +Cc: tarantool-patches

Thanks for the patch!

Sorry, some of my comments may contain typos, because it is
very late and I can't continue polishing it today.

See 4 comments below, review fixes in the end of the email,
and on a new branch in a separate commit.

On 10/04/2020 10:10, Leonid Vasiliev wrote:
> Has been added IPROTO_ERROR_TRACEBACK and IPROTO_ERROR_CUSTOM_TYPE
> fields to IPROTO_ERROR_STACK for transfering through network
> a backtrace and custom type.

1. The new IProto codes are part of the public API as well, and should
be documented.

> 
> Needed for #4398
> ---
>  src/box/iproto_constants.h |  2 ++
>  src/box/lua/error.cc       | 23 +++++++++++++++++++++++
>  src/box/lua/net_box.lua    | 21 ++++++++++++++++++++-
>  src/box/xrow.c             | 19 ++++++++++++++++++-
>  4 files changed, 63 insertions(+), 2 deletions(-)
> 
> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
> index 1bcce1f..27dee7a 100644
> --- a/src/box/lua/error.cc
> +++ b/src/box/lua/error.cc
> @@ -236,6 +236,25 @@ luaT_error_custom_type(lua_State *L)
>  }
>  
>  static int
> +luaT_error_set_lua_traceback(lua_State *L)

2. This is not necessary as well as box.error.custom_type was not
needed. Can be done via FFI.

> +{
> +	if (lua_gettop(L) < 2)
> +		return luaL_error(L, "Usage: box.error.set_lua_traceback"\
> +				     "(error, traceback)");
> +
> +	struct error *e = luaL_checkerror(L, 1);
> +
> +	if (lua_type(L, 2) == LUA_TSTRING) {
> +		error_set_lua_traceback(e, lua_tostring(L, 2));
> +	} else {
> +		return luaL_error(L, "Usage: box.error.set_lua_traceback"\
> +				     "(error, traceback)");
> +	}
> +
> +	return 0;
> +}
> +
> diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
> index 07fa54c..1e0cd7a 100644
> --- a/src/box/lua/net_box.lua
> +++ b/src/box/lua/net_box.lua
> @@ -287,7 +289,24 @@ local function create_transport(host, port, user, password, callback,
>                      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 custom_type = error[IPROTO_ERROR_CUSTOM_TYPE]
> +                    local traceback = error[IPROTO_ERROR_TRACEBACK]
> +
> +                    local new_err
> +                    if custom_type then
> +                        new_err = box.error.new({type = custom_type,
> +                                                 reason = msg,
> +                                                 traceback = false})
> +                    else
> +                        new_err = box.error.new({code = code,
> +                                                 reason = msg,
> +                                                 traceback = false})

3. You can write all arguments in one table, box.error.new() should
handle that fine.

> +                    end
> +
> +                    if traceback then
> +                        box.error.set_lua_traceback(new_err, traceback)
> +                    end
> +
>                      new_err:set_prev(prev)
>                      prev = new_err
>                  end
> diff --git a/src/box/xrow.c b/src/box/xrow.c
> index be026a4..cd88e49 100644
> --- a/src/box/xrow.c
> +++ b/src/box/xrow.c
> @@ -494,11 +494,28 @@ 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);
> +		/* Code and message are necessary fields */
> +		uint32_t map_size = 2;
> +		const char *custom_type = NULL;
> +		if (it->lua_traceback)
> +			++map_size;
> +		if (strcmp(box_error_type(it), "CustomError") == 0) {
> +			++map_size;
> +			custom_type = box_custom_error_type(it);
> +		}
> +		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 (it->lua_traceback) {
> +			mpstream_encode_uint(stream, IPROTO_ERROR_TRACEBACK);
> +			mpstream_encode_str(stream, it->lua_traceback);
> +		}
> +		if (custom_type) {
> +			mpstream_encode_uint(stream, IPROTO_ERROR_CUSTOM_TYPE);
> +			mpstream_encode_str(stream, custom_type);
> +		}
>  	}
>  }

4. No any test. I wonder how did you check that it actually works?
After I glanced at the last patch, I realized that looks like you
implemented everything in a single huge commit, and then split it
up, correct?

Consider my review fixes below and on a new branch in a
separate commit.

Branch is:

    lvasiliev/gh-4398-expose-error-module-4-review

====================

====================
    Review fixes
    
    New commit message proposal:
    
    error: send traceback and custom type in IProto
    
    Error traceback and custom type features were added to the public
    Lua API in the previous commits. This one makes the new attributes
    being sent in IProto.
    
    @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 there are 2 more:
    
    `IPROTO_ERROR_TRACEBACK = 0x03` and
    `IPROTO_ERROR_CUSTOM_TYPE = 0x04`.
    
    Both are optional, have MP_STR type, and speak for themselves.
    Traceback is whatever the error object managed to collect from
    the caller's stack when was pushed on it. It is visible in Lua via
    `err_object.traceback`. Custom error type is another error object
    attribute. This is what a user specifies in
    `box.error.new({type = <custom_type>})` or
    `box.error.new(<custom_type>)`.

diff --git a/extra/exports b/extra/exports
index a5ebe0884..967e994c9 100644
--- a/extra/exports
+++ b/extra/exports
@@ -241,6 +241,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/lua/net_box.lua b/src/box/lua/net_box.lua
index 1e0cd7aba..5c07bb07c 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
@@ -287,26 +292,16 @@ 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 custom_type = error[IPROTO_ERROR_CUSTOM_TYPE]
                     local traceback = error[IPROTO_ERROR_TRACEBACK]
-
-                    local new_err
-                    if custom_type then
-                        new_err = box.error.new({type = custom_type,
-                                                 reason = msg,
-                                                 traceback = false})
-                    else
-                        new_err = box.error.new({code = code,
-                                                 reason = msg,
-                                                 traceback = false})
+                    local new_err = box.error.new({
+                        type = error[IPROTO_ERROR_CUSTOM_TYPE],
+                        code = error[IPROTO_ERROR_CODE],
+                        reason = error[IPROTO_ERROR_MESSAGE],
+                        traceback = false
+                    })
+                    if traceback ~= nil then
+                        ffi.C.error_set_traceback(new_err, traceback)
                     end
-
-                    if traceback then
-                        box.error.set_lua_traceback(new_err, traceback)
-                    end
-
                     new_err:set_prev(prev)
                     prev = new_err
                 end
diff --git a/src/box/xrow.c b/src/box/xrow.c
index 04775d8ce..a9a6a6c75 100644
--- a/src/box/xrow.c
+++ b/src/box/xrow.c
@@ -494,7 +494,6 @@ 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) {
-		/* Code and message are necessary fields */
 		uint32_t map_size = 2;
 		const char *custom_type = box_error_custom_type(it);
 		map_size += (it->traceback != NULL);
diff --git a/test/box/error.result b/test/box/error.result
index b717a4ff4..6db8813b4 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -949,3 +949,59 @@ e = box.error.new({type = string.rep('a', 128)})
  | ---
  | - 63
  | ...
+
+--
+-- Check how traceback and 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, 't3', {true, true})
+ | ---
+ | ...
+check_trace(e:unpack().traceback)
+ | ---
+ | - true
+ | ...
+
+_, e = pcall(c.call, c, 'box.error', {                                          \
+    {code = 123, type = 'TestType', traceback = true, reason = 'Test reason'}   \
+})
+ | ---
+ | ...
+e = e:unpack()
+ | ---
+ | ...
+e.traceback ~= nil or e.traceback
+ | ---
+ | - true
+ | ...
+e.traceback = nil
+ | ---
+ | ...
+e.trace = nil
+ | ---
+ | ...
+e
+ | ---
+ | - code: 123
+ |   base_type: CustomError
+ |   type: TestType
+ |   custom_type: TestType
+ |   message: Test reason
+ | ...
+
+c:close()
+ | ---
+ | ...
+box.schema.user.revoke('guest', 'super')
+ | ---
+ | ...
diff --git a/test/box/error.test.lua b/test/box/error.test.lua
index fe4051899..0821fa0a8 100644
--- a/test/box/error.test.lua
+++ b/test/box/error.test.lua
@@ -278,3 +278,26 @@ e:unpack()
 -- Try too long type name.
 e = box.error.new({type = string.rep('a', 128)})
 #e.type
+
+--
+-- Check how traceback and 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, 't3', {true, true})
+check_trace(e:unpack().traceback)
+
+_, e = pcall(c.call, c, 'box.error', {                                          \
+    {code = 123, type = 'TestType', traceback = true, reason = 'Test reason'}   \
+})
+e = e:unpack()
+e.traceback ~= nil or e.traceback
+e.traceback = nil
+e.trace = nil
+e
+
+c:close()
+box.schema.user.revoke('guest', 'super')

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO Leonid Vasiliev
@ 2020-04-14  1:12   ` Vladislav Shpilevoy
  2020-04-15  9:26     ` lvasiliev
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-14  1:12 UTC (permalink / raw)
  To: Leonid Vasiliev; +Cc: tarantool-patches

Thanks for the patch!

Sorry, some of my comments may contain typos, because it is
very late and I can't continue polishing it today.

See 5 comments below, review fixes in the end of the email,
and on a new branch in a separate commit.

On 10/04/2020 10:10, Leonid Vasiliev wrote:
> IPROTO session settings for error transmission in various formats
> depending on session settings have been added.
> This is required for backward compatibility.

What backward compatibility? Keep in mind, that the team consists not
only of you, me, and Alexander T. Other people don't know what a
backward compatibility is being tried to be preserved here. Just
'backward compatibility' does not tell anything at all.

> @TarantoolBot document
>     Title: Add session_setting

The doc request is corrupted, docbot won't understand that because
of leading whitespaces.

> iproto_error_format setting has been added to _session_settings

Looks like there is a lack of global setting similar to what we had
for tracebacks. Currently, when the option is false (by default), and
I want to use the new format everywhere, I need to find every single
place where I create a new session, and put there code saying

    box.session.settings.error_format = new/old/whatever

I think there should be a global option when a user's application is
ready to switch to the new format completely. Otherwise it is going
to be hell to find all places where a new session is created, and patch
them.

Just a reminder - every fiber.new(), fiber.create() creates a session,
every iproto connection is a session.

> Used to set the error transmission format in the session.
> Old format: transmits as string at IPROTO_BODY
> New format: transmits as error object at IPROTO_BODY
> 
> Needed for #4398
> ---
>  src/box/iproto.cc          | 97 ++++++++++++++++++++++++++++++++++++++++++++++
>  src/box/lua/net_box.lua    | 12 ++++++
>  src/box/session.cc         |  3 ++
>  src/box/session.h          |  3 ++
>  src/box/session_settings.h |  1 +
>  src/lua/utils.h            | 20 ++++++++++
>  6 files changed, 136 insertions(+)
> 
> diff --git a/src/box/iproto.cc b/src/box/iproto.cc
> index 9dad43b..92be645 100644
> --- a/src/box/iproto.cc
> +++ b/src/box/iproto.cc
> @@ -2183,6 +2184,100 @@ iproto_session_push(struct session *session, uint64_t sync, struct port *port)
>  
>  /** }}} */
>  
> +enum {
> +	IPROTO_SESSION_SETTING_ERR_FORMAT = 0,
> +	iproto_session_setting_MAX,
> +};

1. The new setting is not about iproto only. It is about
error serialization to MessagePack. MessagePack != IProto.
So iproto.cc is not a good place for the new setting.

> diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
> index 1e0cd7a..c8f76b0 100644
> --- a/src/box/lua/net_box.lua
> +++ b/src/box/lua/net_box.lua
> @@ -1047,6 +1047,18 @@ local function new_sm(host, port, opts, connection, greeting)
>      if opts.wait_connected ~= false then
>          remote._transport.wait_state('active', tonumber(opts.wait_connected))
>      end
> +
> +    -- Set extended error format for session.
> +    if opts.error_extended then

2. You are inconsistent in what an option do you want. In _session_setting
you call it 'iproto_error_format' assuming it is a format. In practice it
is a number, which, I assume, means version number. In the public API it
is called 'error_extended' assuming it is a flag - either true or false.

So what is it?

In my review fixes I made it a flag. Because there is no way we will support
multiple 'degrees' of error extension.

> +        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1)

3. This won't work in case it is an instance bootstrapped from the master
branch before 2.4.1 was released. I don't know how to fix it now.

> +        if not ext_err_supported then
> +            box.error(box.error.PROC_LUA,
> +                      "Server doesn't support extended error format")
> +        end
> +        remote.space._session_settings:update('iproto_error_format',
> +                                              {{'=', 2, 1}})

4. This is additional network hop. I don't think it should be done
automatically. I would let users do that.

Besides, you didn't document that option, so how were they supposed
to know about it?

> +    end
> +
>      return remote
>  end
>  
> diff --git a/src/box/session.h b/src/box/session.h
> index 6dfc7cb..a8903aa 100644
> --- a/src/box/session.h
> +++ b/src/box/session.h
> @@ -36,6 +36,7 @@
>  #include "fiber.h"
>  #include "user.h"
>  #include "authentication.h"
> +#include "lua/utils.h"

5. This is clearly broken dependency. lua/utils.h is from src/lua/,
and it obviously depends on Lua language.

Session is from src/box/. So it is from different hierarchy, and
is not related to any language.


Consider my review fixes below and on a new branch in a
separate commit.

Branch is:

    lvasiliev/gh-4398-expose-error-module-4-review

====================
    Review fixes
    
    New commit message proposal:
    
    error: add session setting for error type marshaling
    
    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.

diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index 92be645d2..9dad43b0b 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -55,7 +55,6 @@
 #include "call.h"
 #include "tuple_convert.h"
 #include "session.h"
-#include "session_settings.h"
 #include "xrow.h"
 #include "schema.h" /* schema_version */
 #include "replication.h" /* instance_uuid */
@@ -2184,100 +2183,6 @@ iproto_session_push(struct session *session, uint64_t sync, struct port *port)
 
 /** }}} */
 
-enum {
-	IPROTO_SESSION_SETTING_ERR_FORMAT = 0,
-	iproto_session_setting_MAX,
-};
-
-static const char *iproto_session_setting_strs[iproto_session_setting_MAX] = {
-	"iproto_error_format",
-};
-
-static int iproto_session_field_type[] = {
-	/** IPROTO_SESSION_SETTING_ERR_FORMAT */
-	FIELD_TYPE_UNSIGNED,
-};
-
-static void
-iproto_session_setting_get(int id, const char **mp_pair,
-			   const char **mp_pair_end)
-{
-	if (id < 0 || id >= iproto_session_setting_MAX) {
-		diag_set(ClientError, ER_ILLEGAL_PARAMS,
-			 "unknown session setting");
-		return;
-	}
-	struct session *session = current_session();
-
-	const char *name = iproto_session_setting_strs[id];
-	size_t name_len = strlen(name);
-
-	/* Now we have only one iproto session setting. */
-	size_t size = mp_sizeof_array(2) + mp_sizeof_str(name_len)
-		+ mp_sizeof_uint(session->serializer_ctx.err_format_ver);
-
-	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_uint(pos_end,
-				 session->serializer_ctx.err_format_ver);
-	*mp_pair = pos;
-	*mp_pair_end = pos_end;
-}
-
-static int
-iproto_session_setting_set(int id, const char *mp_value)
-{
-	if (id < 0 || id >= iproto_session_setting_MAX) {
-		diag_set(ClientError, ER_ILLEGAL_PARAMS,
-			 "unknown session setting");
-		return -1;
-	}
-	/*Current IPROTO session settings are used only for BINARY session */
-	if (current_session()->type != SESSION_TYPE_BINARY)
-		return -1;
-
-	enum mp_type mtype = mp_typeof(*mp_value);
-	int stype = iproto_session_field_type[id];
-	switch(stype) {
-	case FIELD_TYPE_UNSIGNED: {
-		if (mtype != MP_UINT)
-			break;
-		int val = mp_decode_uint(&mp_value);
-		switch (id) {
-		case IPROTO_SESSION_SETTING_ERR_FORMAT:
-			if (val >= ERR_FORMAT_UNK)
-				break;
-			current_session()->serializer_ctx.err_format_ver = val;
-			return 0;
-		default:
-			diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE,
-				 iproto_session_setting_strs[id],
-				 field_type_strs[stype]);
-			return -1;
-		}
-		break;
-	}
-	default:
-		unreachable();
-	}
-	diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE,
-		 iproto_session_setting_strs[id], field_type_strs[stype]);
-	return -1;
-}
-
-void
-iproto_session_settings_init()
-{
-	struct session_setting_module *module =
-		&session_setting_modules[SESSION_SETTING_IPROTO];
-	module->settings = iproto_session_setting_strs;
-	module->setting_count = iproto_session_setting_MAX;
-	module->get = iproto_session_setting_get;
-	module->set = iproto_session_setting_set;
-}
-
 /** Initialize the iproto subsystem and start network io thread */
 void
 iproto_init()
@@ -2296,8 +2201,6 @@ iproto_init()
 		/* .sync = */ iproto_session_sync,
 	};
 	session_vtab_registry[SESSION_TYPE_BINARY] = iproto_session_vtab;
-
-	iproto_session_settings_init();
 }
 
 /** Available iproto configuration changes. */
diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index a1e9ee745..5c07bb07c 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -1042,18 +1042,6 @@ local function new_sm(host, port, opts, connection, greeting)
     if opts.wait_connected ~= false then
         remote._transport.wait_state('active', tonumber(opts.wait_connected))
     end
-
-    -- Set extended error format for session.
-    if opts.error_extended then
-        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1)
-        if not ext_err_supported then
-            box.error(box.error.PROC_LUA,
-                      "Server doesn't support extended error format")
-        end
-        remote.space._session_settings:update('iproto_error_format',
-                                              {{'=', 2, 1}})
-    end
-
     return remote
 end
 
diff --git a/src/box/session.cc b/src/box/session.cc
index 89435ee0c..08a10924a 100644
--- a/src/box/session.cc
+++ b/src/box/session.cc
@@ -144,9 +144,6 @@ session_create(enum session_type type)
 	session->sql_default_engine = SQL_STORAGE_ENGINE_MEMTX;
 	session->sql_stmts = NULL;
 
-	/* Set default Lua serializer context */
-	session->serializer_ctx.err_format_ver = ERR_FORMAT_DEF;
-
 	/* For on_connect triggers. */
 	credentials_create(&session->credentials, guest_user);
 	struct mh_i64ptr_node_t node;
@@ -278,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()
 {
@@ -286,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 fdc9f03d7..500a88b22 100644
--- a/src/box/session.h
+++ b/src/box/session.h
@@ -36,14 +36,12 @@
 #include "fiber.h"
 #include "user.h"
 #include "authentication.h"
-#include "lua/utils.h"
+#include "serializer_opts.h"
 
 #if defined(__cplusplus)
 extern "C" {
 #endif /* defined(__cplusplus) */
 
-extern void sql_session_settings_init();
-
 struct port;
 struct session_vtab;
 
@@ -91,6 +89,8 @@ struct session_meta {
 	};
 	/** Console output format. */
 	enum output_format output_format;
+	/** Session-specific serialization options. */
+	struct serializer_opts serializer_opts;
 };
 
 /**
@@ -123,8 +123,6 @@ struct session {
 	struct credentials credentials;
 	/** Trigger for fiber on_stop to cleanup created on-demand session */
 	struct trigger fiber_on_stop;
-	/** Session Lua serializer context */
-	struct luaL_serializer_ctx serializer_ctx;
 };
 
 struct session_vtab {
diff --git a/src/box/session_settings.c b/src/box/session_settings.c
index 79c4b8d3c..dbbbf2461 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/lua/utils.h b/src/lua/utils.h
index cac9c57b8..4bc041796 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -270,26 +270,6 @@ struct luaL_serializer {
 	struct rlist on_update;
 };
 
-/**
- * An error serialization formats
- */
-enum error_formats {
-	/** Default(old) format */
-	ERR_FORMAT_DEF,
-	/** Extended format */
-	ERR_FORMAT_EX,
-	/** The max version of error format */
-	ERR_FORMAT_UNK
-};
-
-/**
- * A serializer context (additional settings for a serializer)
- */
-struct luaL_serializer_ctx {
-	/** Version of a format for an error transmission */
-	uint8_t err_format_ver;
-};
-
 extern int luaL_nil_ref;
 extern int luaL_map_metatable_ref;
 extern int luaL_array_metatable_ref;
diff --git a/src/serializer_opts.h b/src/serializer_opts.h
new file mode 100644
index 000000000..9e2c15eff
--- /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 ea6302dff..149cc4bd5 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]

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding
  2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding Leonid Vasiliev
@ 2020-04-14  1:12   ` Vladislav Shpilevoy
  2020-04-15  9:25     ` lvasiliev
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-14  1:12 UTC (permalink / raw)
  To: Leonid Vasiliev; +Cc: tarantool-patches

Thanks for the patch!

Sorry, some of my comments may contain typos, because it is
very late and I can't continue polishing it today.

I didn't have time to review it properly yet. Just some
first comments below. See 12 of them below.

On 10/04/2020 10:10, Leonid Vasiliev wrote:
> New MsgPack encode format for error (MP_ERROR) has been added.
> If the extended error format is enabled using iproto session settings
> MP_ERROR type will be used for transferring error through network,
> MP_STR was used before.

1. For such a big feature and not a single word of documentation.

> Needed for #4398

2. Shouldn't that be 'Closes'?

> diff --git a/src/box/error.cc b/src/box/error.cc
> index 8179e52..f2e60c1 100644
> --- a/src/box/error.cc
> +++ b/src/box/error.cc
> @@ -253,6 +253,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)
> @@ -264,6 +271,16 @@ BuildXlogGapError(const char *file, unsigned line,
>  	}
>  }
>  
> +struct error *
> +ReBuildXlogGapError(const char *file, unsigned line, const char *msg)

3. Wtf is Rebuild? Why normal Build does not work? If that is
because of vclock, then just change vclock to const char * in
the original Build in a separate preparatory commit.

> +{
> +	try {
> +		return new XlogGapError(file, line, msg);
> +	} catch (OutOfMemory *e) {
> +		return e;
> +	}
> +}
> +
>  struct rlist on_access_denied = RLIST_HEAD_INITIALIZER(on_access_denied);
>  > diff --git a/src/box/lua/call.c b/src/box/lua/call.c
> index 5d3579e..6d1d247 100644
> --- a/src/box/lua/call.c
> +++ b/src/box/lua/call.c
> @@ -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)

4. I think addition of that parameter should be a part of the previous
commit. So as not to pollute this commit with such minor changes.

In the previous commit it can be added and ignored inside luamp_encode_r().
But it will absorb all these not interesting changes.

>  				return luaT_error(L);
>  			struct tuple *tuple;
>  			if (field.type == MP_EXT &&
> @@ -379,6 +380,7 @@ execute_lua_eval(lua_State *L)
>  struct encode_lua_ctx {
>  	struct port_lua *port;
>  	struct mpstream *stream;
> +	struct luaL_serializer_ctx *serializer_ctx;

5. Not. A. Single. Word. Please, start writing descriptive
comments.

>  };
>  
>  static int
> diff --git a/src/box/lua/mp_error.cc b/src/box/lua/mp_error.cc
> new file mode 100644
> index 0000000..f99da0f
> --- /dev/null
> +++ b/src/box/lua/mp_error.cc
> @@ -0,0 +1,454 @@
> +/*
> + * 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.h"
> +#include "msgpuck.h"
> +#include "mp_extension_types.h"
> +
> +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_BACKTRACE,
> +	MP_ERROR_DET_CUSTOM_TYPE,
> +	MP_ERROR_DET_AD_OBJ_TYPE,
> +	MP_ERROR_DET_AD_OBJ_NAME,
> +	MP_ERROR_DET_AD_ACCESS_TYPE
> +};
> +
> +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,

6. I assume you use some kind of smart editor with
autocompletion so as you missed this typo:

    TyPE -> TYPE

7. Why are these values in a enum? I thought we
decided to use string types?

> +	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
> +};
> +
> +struct mp_error {
> +	uint32_t error_code;
> +	uint8_t error_type;

8. If you assign enum to a variable, that variable should
have type of that enum, usually.

> +	uint32_t line;
> +	uint32_t saved_errno;
> +	char *file;
> +	char *backtrace;
> +	char *reason;
> +	char *custom_type;
> +	char *ad_obj_type;
> +	char *ad_obj_name;
> +	char *ad_access_type;
> +};
> diff --git a/src/box/lua/mp_error.h b/src/box/lua/mp_error.h
> new file mode 100644
> index 0000000..9eab213
> --- /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"

9. Standard and other external headers should be in <>, not "".

> +
> +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/net_box.lua b/src/box/lua/net_box.lua
> index c8f76b0..d3997af 100644
> --- a/src/box/lua/net_box.lua
> +++ b/src/box/lua/net_box.lua
> @@ -1050,7 +1050,7 @@ local function new_sm(host, port, opts, connection, greeting)
>  
>      -- Set extended error format for session.
>      if opts.error_extended then
> -        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1)
> +        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 0)

10. Why?

>          if not ext_err_supported then
>              box.error(box.error.PROC_LUA,
>                        "Server doesn't support extended error format")
> diff --git a/src/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h
> index bc9873f..a2a5079 100644
> --- a/src/lib/core/mp_extension_types.h
> +++ b/src/lib/core/mp_extension_types.h
> @@ -40,8 +40,9 @@
>   * You may assign values in range [0, 127]
>   */
>  enum mp_extension_type {
> -    MP_UNKNOWN_EXTENSION = 0,
> -    MP_DECIMAL = 1,
> +	MP_UNKNOWN_EXTENSION = 0,
> +	MP_DECIMAL = 1,
> +	MP_ERROR = 2
>  };

11. Please, rebase before applying any review fixes. This code is
changed in the master. As well as session settings.

>  #endif
> diff --git a/src/lua/error.c b/src/lua/error.c
> index cd6ab54..109f947 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) */
>  
> -

12. Please, remove all unnecessary diff.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding
  2020-04-14  1:12   ` Vladislav Shpilevoy
@ 2020-04-15  9:25     ` lvasiliev
  2020-04-16  0:11       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 37+ messages in thread
From: lvasiliev @ 2020-04-15  9:25 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thanks you for the review.

On 14.04.2020 4:12, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> Sorry, some of my comments may contain typos, because it is
> very late and I can't continue polishing it today.
> 
> I didn't have time to review it properly yet. Just some
> first comments below. See 12 of them below.
> 
> On 10/04/2020 10:10, Leonid Vasiliev wrote:
>> New MsgPack encode format for error (MP_ERROR) has been added.
>> If the extended error format is enabled using iproto session settings
>> MP_ERROR type will be used for transferring error through network,
>> MP_STR was used before.
> 
> 1. For such a big feature and not a single word of documentation.
fixed in new patch
> 
>> Needed for #4398
> 
> 2. Shouldn't that be 'Closes'?
fixed in new patch
> 
>> diff --git a/src/box/error.cc b/src/box/error.cc
>> index 8179e52..f2e60c1 100644
>> --- a/src/box/error.cc
>> +++ b/src/box/error.cc
>> @@ -253,6 +253,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)
>> @@ -264,6 +271,16 @@ BuildXlogGapError(const char *file, unsigned line,
>>   	}
>>   }
>>   
>> +struct error *
>> +ReBuildXlogGapError(const char *file, unsigned line, const char *msg)
> 
> 3. Wtf is Rebuild? Why normal Build does not work? If that is
> because of vclock, then just change vclock to const char * in
> the original Build in a separate preparatory commit.
fixed in new patch
> 
>> +{
>> +	try {
>> +		return new XlogGapError(file, line, msg);
>> +	} catch (OutOfMemory *e) {
>> +		return e;
>> +	}
>> +}
>> +
>>   struct rlist on_access_denied = RLIST_HEAD_INITIALIZER(on_access_denied);
>>   > diff --git a/src/box/lua/call.c b/src/box/lua/call.c
>> index 5d3579e..6d1d247 100644
>> --- a/src/box/lua/call.c
>> +++ b/src/box/lua/call.c
>> @@ -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)
> 
> 4. I think addition of that parameter should be a part of the previous
> commit. So as not to pollute this commit with such minor changes.
> 
> In the previous commit it can be added and ignored inside luamp_encode_r().
> But it will absorb all these not interesting changes.
fixed in new patch
> 
>>   				return luaT_error(L);
>>   			struct tuple *tuple;
>>   			if (field.type == MP_EXT &&
>> @@ -379,6 +380,7 @@ execute_lua_eval(lua_State *L)
>>   struct encode_lua_ctx {
>>   	struct port_lua *port;
>>   	struct mpstream *stream;
>> +	struct luaL_serializer_ctx *serializer_ctx;
> 
> 5. Not. A. Single. Word. Please, start writing descriptive
> comments.
Ok. I will try.
> 
>>   };
>>   
>>   static int
>> diff --git a/src/box/lua/mp_error.cc b/src/box/lua/mp_error.cc
>> new file mode 100644
>> index 0000000..f99da0f
>> --- /dev/null
>> +++ b/src/box/lua/mp_error.cc
>> @@ -0,0 +1,454 @@
>> +/*
>> + * 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.h"
>> +#include "msgpuck.h"
>> +#include "mp_extension_types.h"
>> +
>> +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_BACKTRACE,
>> +	MP_ERROR_DET_CUSTOM_TYPE,
>> +	MP_ERROR_DET_AD_OBJ_TYPE,
>> +	MP_ERROR_DET_AD_OBJ_NAME,
>> +	MP_ERROR_DET_AD_ACCESS_TYPE
>> +};
>> +
>> +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,
> 
> 6. I assume you use some kind of smart editor with
> autocompletion so as you missed this typo:
> 
>      TyPE -> TYPE
Fixed.
> 
> 7. Why are these values in a enum? I thought we
> decided to use string types?
Now we compare a string with error type when encode and use number 
further. If we encode error type as string we still have to compare the 
strings when decoding to create the error of the desired type. But 
number takes up less space versus string  when transmitting over a 
network and makes it possible to use a switch when create an error after 
decoding (which looks much nicer).
> 
>> +	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
>> +};
>> +
>> +struct mp_error {
>> +	uint32_t error_code;
>> +	uint8_t error_type;
> 
> 8. If you assign enum to a variable, that variable should
> have type of that enum, usually.
fixed
> 
>> +	uint32_t line;
>> +	uint32_t saved_errno;
>> +	char *file;
>> +	char *backtrace;
>> +	char *reason;
>> +	char *custom_type;
>> +	char *ad_obj_type;
>> +	char *ad_obj_name;
>> +	char *ad_access_type;
>> +};
>> diff --git a/src/box/lua/mp_error.h b/src/box/lua/mp_error.h
>> new file mode 100644
>> index 0000000..9eab213
>> --- /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"
> 
> 9. Standard and other external headers should be in <>, not "".
fixed
> 
>> +
>> +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/net_box.lua b/src/box/lua/net_box.lua
>> index c8f76b0..d3997af 100644
>> --- a/src/box/lua/net_box.lua
>> +++ b/src/box/lua/net_box.lua
>> @@ -1050,7 +1050,7 @@ local function new_sm(host, port, opts, connection, greeting)
>>   
>>       -- Set extended error format for session.
>>       if opts.error_extended then
>> -        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1)
>> +        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 0)
> 
> 10. Why?
It's mistake. Now deleted.
> 
>>           if not ext_err_supported then
>>               box.error(box.error.PROC_LUA,
>>                         "Server doesn't support extended error format")
>> diff --git a/src/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h
>> index bc9873f..a2a5079 100644
>> --- a/src/lib/core/mp_extension_types.h
>> +++ b/src/lib/core/mp_extension_types.h
>> @@ -40,8 +40,9 @@
>>    * You may assign values in range [0, 127]
>>    */
>>   enum mp_extension_type {
>> -    MP_UNKNOWN_EXTENSION = 0,
>> -    MP_DECIMAL = 1,
>> +	MP_UNKNOWN_EXTENSION = 0,
>> +	MP_DECIMAL = 1,
>> +	MP_ERROR = 2
>>   };
> 
> 11. Please, rebase before applying any review fixes. This code is
> changed in the master. As well as session settings.
> 
>>   #endif
>> diff --git a/src/lua/error.c b/src/lua/error.c
>> index cd6ab54..109f947 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) */
>>   
>> -
> 
> 12. Please, remove all unnecessary diff.
fixed

I have a some additional question:
"Do I understand correctly that MP_ERROR should not be added to 
field_def.h/field_def.c or I am wrong?"
> 

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error
  2020-04-14  1:11   ` Vladislav Shpilevoy
@ 2020-04-15  9:25     ` lvasiliev
  2020-04-16  0:00       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 37+ messages in thread
From: lvasiliev @ 2020-04-15  9:25 UTC (permalink / raw)
  To: Vladislav Shpilevoy, Alexander Turenko; +Cc: tarantool-patches

Hi! Thanks for the review.

On 14.04.2020 4:11, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> Sorry, some of my comments may contain typos, because it is
> very late and I can't continue polishing it today.
> 
> First of all there are some major issues with the
> patch, which IMO should be clarified before it is pushed.
> 
> 1) Why do we need the traceback in scope of 4398? It has
> nothing to do with marshaling through IProto, nor with
> custom error types.
This is part of the errors.lua functional, which was attached as an 
example. After that it was also one of the functionality requested by 
Nazarov.
> 
> 2) What to do with stacked errors? Currently only the first
> error in the stack gets a traceback, because luaT_pusherror() is
> called only on the top error in the stack. Consider this test:
> 
>      box.cfg{}
>      lua_code = [[function(tuple)
>                      local json = require('json')
>                      return json.encode(tuple)
>                   end]]
>      box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
>      s = box.schema.space.create('withdata')
>      pk = s:create_index('pk')
>      idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})
> 
>      function test_func() return pcall(s.insert, s, {1}) end
>      box.error.cfg{traceback_enable = true}
>      ok, err = test_func()
> 
> 
>      tarantool> err:unpack()
>      ---
>      - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string
>          \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382:
>          in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725:
>          in function <builtin/box/console.lua:713>"
>      ... <snipped>
> 
>      tarantool> err.prev:unpack()
>      ---
>      - type: LuajitError
>        message: '[string "return function(tuple)..."]:2: attempt to call global ''require''
>          (a nil value)'
>      ... <snipped>
> 
> The second error does not have a traceback at all.
(I added Turenko to To)
I have two variants:
- Leave as is and to document such behavior
- Add the same traceback to all errors in the stack
Alexander what do you think?
> 
> 
> See 10 comments below. Also I started a new branch with review
> fixes to speed up the process. The branch is called
> 
>      lvasiliev/gh-4398-expose-error-module-4-review
> 
> You can find review fixes to this commit in the end of the
> email and on this branch in a separate commit. Note, my branch
> is rebased on master already. So some changes are related to
> cleaning the mess after it.
> 
> On 10/04/2020 10:10, Leonid Vasiliev wrote:
>> In accordance with https://github.com/tarantool/tarantool/issues/4398
> 
> 1. We don't put links when issue is in the same repository.
> We always use '#<number>' reference. This concerns this and all
> next commits.
> 
>> Lua traceback has been added for box.error.
>> Has been added a per server flag for turn on/off traceback adding
>> and ability to force it at creation time.
>>
>> @TarantoolBot document
>> Title: error.traceback
>> Was added:
>> Per server flag for turn on/off adding a traceback to the errors.
>> box.error.cfg({traceback_supplementation = true/false})
> 
> 2. The option name is utterly overcomplicated. Just use
> 
>      traceback_enable
> 
>> Adding a traceback can be forced on creation.
>> box.error.new({type = "CustomType", reason = "reason", traceback = true/false})
> 
> 3. CustomType errors are not defined here, you can't use it in
> the request description.
> 
>> Needed for #4398
> 
> 4. Please, don't put issue references inside docbot requests. This
> concerns this and all next commits.
> 
>> ---
>>   src/box/lua/error.cc          | 33 ++++++++++++++++++++++++++++++++-
>>   src/lib/core/diag.c           | 32 ++++++++++++++++++++++++++++++++
>>   src/lib/core/diag.h           | 11 +++++++++++
>>   src/lib/core/exception.cc     |  1 +
>>   src/lua/error.c               | 10 ++++++++++
>>   src/lua/error.lua             | 12 +++++++++++-
>>   test/app/fiber.result         |  5 +++--
>>   test/box/error.result         |  5 +++--
>>   test/engine/func_index.result | 10 ++++++----
>>   9 files changed, 109 insertions(+), 10 deletions(-)
>>
>> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
>> index b2625bf..4432bc8 100644
>> --- a/src/box/lua/error.cc
>> +++ b/src/box/lua/error.cc> @@ -180,6 +193,20 @@ 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_supplementation");
>> +	if (lua_isnil(L, -1) == 0)
> 
> 5. If something is not nil, it does not mean it is a boolean.
> Better check lua_isboolean().
> 
>> +		error_set_traceback_supplementation(lua_toboolean(L, -1));
>> +	lua_pop(L, 1);
>> +
>> +	return 0;
>> +}
>> diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c
>> index e143db1..1caa75e 100644
>> --- a/src/lib/core/diag.c
>> +++ b/src/lib/core/diag.c
>> @@ -120,3 +127,28 @@ error_vformat_msg(struct error *e, const char *format, va_list ap)
>>   	vsnprintf(e->errmsg, sizeof(e->errmsg), format, ap);
>>   }
>>   
>> +void
>> +error_set_lua_traceback(struct error *e, const char *lua_traceback)
>> +{
>> +	if (e == NULL)
>> +		return;
> 
> 6. It makes no sense to call this function on a NULL 'e'. This
> should be an assertion. As well as it is not possible to 'reset'
> the trace, so e->lua_traceback should be always NULL here.
> 
> 7. Also I don't think it is a good idea to call traceback attribute
> 'lua_traceback'. Because we won't have separated 'lua_traceback'
> and 'c_traceback'. Anyway there will be always only one. Probably
> with C and Lua mixed (it is possible via space triggers to mix Lua
> and C in layers).
> 
>> +
>> +	if (lua_traceback == NULL) {
>> +		free(e->lua_traceback);
>> +		e->lua_traceback = NULL;
>> +		return;
>> +	}
>> +
>> +	size_t tb_len = strlen(lua_traceback);
>> +	e->lua_traceback = realloc(e->lua_traceback, tb_len + 1);
>> +	if (e->lua_traceback == NULL)
>> +		return;
>> +	strcpy(e->lua_traceback, lua_traceback);
>> +	return;
>> +}
>> +
>> +void
>> +error_set_traceback_supplementation(bool traceback_mode)
>> +{
>> +	global_traceback_mode = traceback_mode;
>> +}
>> diff --git a/src/lua/error.lua b/src/lua/error.lua
>> index bdc9c71..46d2866 100644
>> --- a/src/lua/error.lua
>> +++ b/src/lua/error.lua
>> @@ -92,6 +94,14 @@ local function error_trace(err)
>>       }
>>   end
>>   
>> +local function error_traceback(err)
>> +    local result = "Traceback is absent"
> 
> 8. I don't think it is a good idea to add this to
> every error object when there is no a trace. Just
> leave it nil.
> 
>> +    if err.lua_traceback ~= ffi.nullptr then
>> +        result = ffi.string(err.lua_traceback)
>> +    end
>> +    return result
>> +end
>> +
>>   local function error_errno(err)
>>       local e = err._saved_errno
>>       if e == 0 then
>> @@ -122,7 +132,6 @@ local function error_set_prev(err, prev)
>>       if ok ~= 0 then
>>           error("Cycles are not allowed")
>>       end
>> -
> 
> 9. Please, avoid unnecessary diff.
> 
>>   end
>>   
>>   local error_fields = {
>> diff --git a/test/engine/func_index.result b/test/engine/func_index.result
>> index a827c92..28befca 100644
>> --- a/test/engine/func_index.result
>> +++ b/test/engine/func_index.result
>> @@ -291,12 +292,13 @@ e = e.prev
>>    | ...
>>   e:unpack()
>>    | ---
>> - | - type: LuajitError
>> - |   message: '[string "return function(tuple)                 local ..."]:1: attempt
>> - |     to call global ''require'' (a nil value)'
>> + | - traceback: Traceback is absent
>>    |   trace:
>>    |   - file: <filename>
>>    |     line: <line>
>> + |   type: LuajitError
>> + |   message: '[string "return function(tuple)                 local ..."]:1: attempt
>> + |     to call global ''require'' (a nil value)'
>>    | ...
>>   e = e.prev
>>    | ---
> 
> 10. Usually every commit should add tests on the feature it
> introduces. You didn't add any single test. Why?
That's my mistake. After our discussion for testing I used the test 
added in the last commit and deleted the ones added before in misc.
> 
> Consider my review fixes below and on a new branch in a
> separate commit. It solves all the code-related problems,
> but I still don't know what to do with the major issues
> described in the beginning.
I applied your patch and added you to Co-authored. But I have a couple 
of questions. See 4 comments below.
> 
> Branch is:
> 
>      lvasiliev/gh-4398-expose-error-module-4-review
> 
> ====================
>      Review fixes
>      
>      New commit message proposal:
>      
>      error: add a Lua traceback to error
>      
>      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.
>      
>      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
>          })
>      ```
> 
> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
> index 4432bc89b..a616e87a4 100644
> --- a/src/box/lua/error.cc
> +++ b/src/box/lua/error.cc
> @@ -54,8 +54,8 @@ luaT_error_create(lua_State *L, int top_base)
>   {
>   	uint32_t code = 0;
>   	const char *reason = NULL;
> -	bool tb_parsed = false;
> -	bool tb_mode = false;
> +	bool is_traceback_enabled = false;
> +	bool is_traceback_specified = false;
>   	const char *file = "";
>   	unsigned line = 0;
>   	lua_Debug info;
> @@ -91,10 +91,10 @@ luaT_error_create(lua_State *L, int top_base)
>   		lua_pop(L, 1);
>   		lua_getfield(L, top_base, "traceback");
>   		if (lua_isboolean(L, -1)) {
> -			tb_parsed = true;
> -			tb_mode = lua_toboolean(L, -1);
> +			is_traceback_enabled = lua_toboolean(L, -1);
> +			is_traceback_specified = true;
>   		}
> -		lua_pop(L, -1);
> +		lua_pop(L, 1);
>   	} else {
>   		return NULL;
>   	}
> @@ -112,9 +112,11 @@ raise:
>   	}
>   
>   	struct error *err = box_error_new(file, line, code, "%s", reason);
> -	if (tb_parsed)
> -		err->traceback_mode = tb_mode;
> -
> +	/*
> +	 * Explicit traceback option overrides the global setting.
> +	 */
> +	if (err != NULL && is_traceback_specified)
1) box_error_new don't return NULL
> +		err->is_traceback_enabled = is_traceback_enabled;
>   	return err;
>   }
>   
> @@ -198,11 +200,9 @@ 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_supplementation");
> -	if (lua_isnil(L, -1) == 0)
> -		error_set_traceback_supplementation(lua_toboolean(L, -1));
> -	lua_pop(L, 1);
> -
> +	lua_getfield(L, 1, "traceback_enable");
> +	if (lua_isboolean(L, -1))
> +		error_is_traceback_enabled = lua_toboolean(L, -1);
>   	return 0;
>   }
>   
> diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c
> index 1caa75ee9..833454bac 100644
> --- a/src/lib/core/diag.c
> +++ b/src/lib/core/diag.c
> @@ -31,10 +31,7 @@
>   #include "diag.h"
>   #include "fiber.h"
>   
> -/**
> - * Global flag to add or not backtrace to errors.
> - */
> -static bool global_traceback_mode = false;
> +bool error_is_traceback_enabled = false;
>   
>   int
>   error_set_prev(struct error *e, struct error *prev)
> @@ -102,8 +99,8 @@ error_create(struct error *e,
>   	e->errmsg[0] = '\0';
>   	e->cause = NULL;
>   	e->effect = NULL;
> -	e->lua_traceback = NULL;
> -	e->traceback_mode = global_traceback_mode;
> +	e->traceback = NULL;
> +	e->is_traceback_enabled = error_is_traceback_enabled;
>   }
>   
>   struct diag *
> @@ -128,27 +125,14 @@ error_vformat_msg(struct error *e, const char *format, va_list ap)
>   }
>   
>   void
> -error_set_lua_traceback(struct error *e, const char *lua_traceback)
> -{
> -	if (e == NULL)
> -		return;
> -
> -	if (lua_traceback == NULL) {
> -		free(e->lua_traceback);
> -		e->lua_traceback = NULL;
> -		return;
> -	}
> -
> -	size_t tb_len = strlen(lua_traceback);
> -	e->lua_traceback = realloc(e->lua_traceback, tb_len + 1);
> -	if (e->lua_traceback == NULL)
> -		return;
> -	strcpy(e->lua_traceback, lua_traceback);
> -	return;
> -}
> -
> -void
> -error_set_traceback_supplementation(bool traceback_mode)
> +error_set_traceback(struct error *e, const char *traceback)
>   {
> -	global_traceback_mode = traceback_mode;
> +	assert(e->traceback == NULL);
2) Do I understand correctly that asserts only work on debug? Will this 
approach not be dangerous by memory leaks on release?
> +	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 f38009c54..e918d3089 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;
3) Why a global variable is more preferable than static?
> +
>   enum {
>   	DIAG_ERRMSG_MAX = 512,
>   	DIAG_FILENAME_MAX = 256
> @@ -110,8 +117,19 @@ struct error {
>   	 */
>   	struct error *cause;
>   	struct error *effect;
> -	char *lua_traceback;
> -	bool traceback_mode;
> +	/**
> +	 * 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
> @@ -174,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)
>   {
> @@ -199,15 +225,6 @@ error_format_msg(struct error *e, const char *format, ...);
>   void
>   error_vformat_msg(struct error *e, const char *format, va_list ap);
>   
> -void
> -error_set_lua_traceback(struct error *e, const char *lua_traceback);
> -
> -/**
> -* Sets the global flag to add or not backtrace to errors.
> -*/
> -void
> -error_set_traceback_supplementation(bool traceback_mode);
> -
>   /**
>    * Diagnostics Area - a container for errors
>    */
> diff --git a/src/lib/core/exception.cc b/src/lib/core/exception.cc
> index 0e4b6ca8b..1717b76fb 100644
> --- a/src/lib/core/exception.cc
> +++ b/src/lib/core/exception.cc
> @@ -42,7 +42,7 @@ extern "C" {
>   static void
>   exception_destroy(struct error *e)
>   {
> -	free(e->lua_traceback);
> +	free(e->traceback);
>   	delete (Exception *) e;
>   }
>   
> diff --git a/src/lua/error.c b/src/lua/error.c
> index cd6ab54b1..a21548ce6 100644
> --- a/src/lua/error.c
> +++ b/src/lua/error.c
> @@ -86,12 +86,11 @@ luaT_pusherror(struct lua_State *L, struct error *e)
>   	 */
>   	error_ref(e);
>   
> -	if (e->lua_traceback == NULL && e->traceback_mode) {
> +	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_lua_traceback(e, lua_tostring(L, -1));
> -		}
> +		if (lua_isstring(L, -1))
> +			error_set_traceback(e, lua_tostring(L, -1));
>   		lua_settop(L, top);
>   	}
>   
> diff --git a/src/lua/error.lua b/src/lua/error.lua
> index 46d28667f..535110588 100644
> --- a/src/lua/error.lua
> +++ b/src/lua/error.lua
> @@ -26,8 +26,8 @@ struct error {
>       char _errmsg[DIAG_ERRMSG_MAX];
>       struct error *_cause;
>       struct error *_effect;
> -    char *lua_traceback;
> -    bool traceback_mode;
> +    char *traceback;
4) Replaced to err_traceback. It overlaps the traceback getter.
> +    bool is_traceback_enabled;
>   };
>   
>   char *
> @@ -95,11 +95,7 @@ local function error_trace(err)
>   end
>   
>   local function error_traceback(err)
> -    local result = "Traceback is absent"
> -    if err.lua_traceback ~= ffi.nullptr then
> -        result = ffi.string(err.lua_traceback)
> -    end
> -    return result
> +    return err.traceback ~= ffi.nullptr and ffi.string(err.traceback) or nil
>   end
>   
>   local function error_errno(err)
> @@ -132,6 +128,7 @@ local function error_set_prev(err, prev)
>       if ok ~= 0 then
>           error("Cycles are not allowed")
>       end
> +
>   end
>   
>   local error_fields = {
> diff --git a/test/app/fiber.result b/test/app/fiber.result
> index fc6b3c92d..debfc6718 100644
> --- a/test/app/fiber.result
> +++ b/test/app/fiber.result
> @@ -1038,13 +1038,12 @@ st;
>   ...
>   e:unpack();
>   ---
> -- traceback: Traceback is absent
> +- type: ClientError
>     code: 1
> +  message: Illegal parameters, oh my
>     trace:
>     - file: '[string "function err() box.error(box.error.ILLEGAL_PA..."]'
>       line: 1
> -  type: ClientError
> -  message: Illegal parameters, oh my
>   ...
>   flag = false;
>   ---
> diff --git a/test/box/error.result b/test/box/error.result
> index 574bc481f..2502d88c4 100644
> --- a/test/box/error.result
> +++ b/test/box/error.result
> @@ -34,13 +34,12 @@ e
>    | ...
>   e:unpack()
>    | ---
> - | - traceback: Traceback is absent
> + | - type: ClientError
>    |   code: 1
> + |   message: Illegal parameters, bla bla
>    |   trace:
>    |   - file: '[C]'
>    |     line: 4294967295
> - |   type: ClientError
> - |   message: Illegal parameters, bla bla
>    | ...
>   e.type
>    | ---
> @@ -832,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 41baed52d..6f1271630 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)
> diff --git a/test/engine/func_index.result b/test/engine/func_index.result
> index 28befca39..a827c929f 100644
> --- a/test/engine/func_index.result
> +++ b/test/engine/func_index.result
> @@ -276,8 +276,7 @@ e = box.error.last()
>    | ...
>   e:unpack()
>    | ---
> - | - traceback: Traceback is absent
> - |   code: 198
> + | - code: 198
>    |   trace:
>    |   - file: <filename>
>    |     line: <line>
> @@ -292,13 +291,12 @@ e = e.prev
>    | ...
>   e:unpack()
>    | ---
> - | - traceback: Traceback is absent
> + | - type: LuajitError
> + |   message: '[string "return function(tuple)                 local ..."]:1: attempt
> + |     to call global ''require'' (a nil value)'
>    |   trace:
>    |   - file: <filename>
>    |     line: <line>
> - |   type: LuajitError
> - |   message: '[string "return function(tuple)                 local ..."]:1: attempt
> - |     to call global ''require'' (a nil value)'
>    | ...
>   e = e.prev
>    | ---
> 

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO
  2020-04-14  1:12   ` Vladislav Shpilevoy
@ 2020-04-15  9:25     ` lvasiliev
  2020-04-16  0:02       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 37+ messages in thread
From: lvasiliev @ 2020-04-15  9:25 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thanks for the review.

On 14.04.2020 4:12, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> Sorry, some of my comments may contain typos, because it is
> very late and I can't continue polishing it today.
> 
> See 4 comments below, review fixes in the end of the email,
> and on a new branch in a separate commit.
> 
> On 10/04/2020 10:10, Leonid Vasiliev wrote:
>> Has been added IPROTO_ERROR_TRACEBACK and IPROTO_ERROR_CUSTOM_TYPE
>> fields to IPROTO_ERROR_STACK for transfering through network
>> a backtrace and custom type.
> 
> 1. The new IProto codes are part of the public API as well, and should
> be documented.
Ok
> 
>>
>> Needed for #4398
>> ---
>>   src/box/iproto_constants.h |  2 ++
>>   src/box/lua/error.cc       | 23 +++++++++++++++++++++++
>>   src/box/lua/net_box.lua    | 21 ++++++++++++++++++++-
>>   src/box/xrow.c             | 19 ++++++++++++++++++-
>>   4 files changed, 63 insertions(+), 2 deletions(-)
>>
>> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
>> index 1bcce1f..27dee7a 100644
>> --- a/src/box/lua/error.cc
>> +++ b/src/box/lua/error.cc
>> @@ -236,6 +236,25 @@ luaT_error_custom_type(lua_State *L)
>>   }
>>   
>>   static int
>> +luaT_error_set_lua_traceback(lua_State *L)
> 
> 2. This is not necessary as well as box.error.custom_type was not
> needed. Can be done via FFI.
Ok
> 
>> +{
>> +	if (lua_gettop(L) < 2)
>> +		return luaL_error(L, "Usage: box.error.set_lua_traceback"\
>> +				     "(error, traceback)");
>> +
>> +	struct error *e = luaL_checkerror(L, 1);
>> +
>> +	if (lua_type(L, 2) == LUA_TSTRING) {
>> +		error_set_lua_traceback(e, lua_tostring(L, 2));
>> +	} else {
>> +		return luaL_error(L, "Usage: box.error.set_lua_traceback"\
>> +				     "(error, traceback)");
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
>> index 07fa54c..1e0cd7a 100644
>> --- a/src/box/lua/net_box.lua
>> +++ b/src/box/lua/net_box.lua
>> @@ -287,7 +289,24 @@ local function create_transport(host, port, user, password, callback,
>>                       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 custom_type = error[IPROTO_ERROR_CUSTOM_TYPE]
>> +                    local traceback = error[IPROTO_ERROR_TRACEBACK]
After we decided don't concatenate the traceback, I decided that it 
would be inconsistent to transmit the traceback in the case of "throw" 
over the network, since when creating a new error, the "trace" is set 
new and it always has been. Furthermore, an error can change the type. 
So, I delete IPROTO_ERROR_TRACEBACK.If you consider it wrong decision, I 
can return IPROTO_ERROR_TRACEBACK.
>> +
>> +                    local new_err
>> +                    if custom_type then
>> +                        new_err = box.error.new({type = custom_type,
>> +                                                 reason = msg,
>> +                                                 traceback = false})
>> +                    else
>> +                        new_err = box.error.new({code = code,
>> +                                                 reason = msg,
>> +                                                 traceback = false})
> 
> 3. You can write all arguments in one table, box.error.new() should
> handle that fine.
> 
>> +                    end
>> +
>> +                    if traceback then
>> +                        box.error.set_lua_traceback(new_err, traceback)
>> +                    end
>> +
>>                       new_err:set_prev(prev)
>>                       prev = new_err
>>                   end
>> diff --git a/src/box/xrow.c b/src/box/xrow.c
>> index be026a4..cd88e49 100644
>> --- a/src/box/xrow.c
>> +++ b/src/box/xrow.c
>> @@ -494,11 +494,28 @@ 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);
>> +		/* Code and message are necessary fields */
>> +		uint32_t map_size = 2;
>> +		const char *custom_type = NULL;
>> +		if (it->lua_traceback)
>> +			++map_size;
>> +		if (strcmp(box_error_type(it), "CustomError") == 0) {
>> +			++map_size;
>> +			custom_type = box_custom_error_type(it);
>> +		}
>> +		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 (it->lua_traceback) {
>> +			mpstream_encode_uint(stream, IPROTO_ERROR_TRACEBACK);
>> +			mpstream_encode_str(stream, it->lua_traceback);
>> +		}
>> +		if (custom_type) {
>> +			mpstream_encode_uint(stream, IPROTO_ERROR_CUSTOM_TYPE);
>> +			mpstream_encode_str(stream, custom_type);
>> +		}
>>   	}
>>   }
> 
> 4. No any test. I wonder how did you check that it actually works?
That's my mistake. After our discussion for testing I used the test 
added in the last commit
> After I glanced at the last patch, I realized that looks like you
> implemented everything in a single huge commit, and then split it
> up, correct?
It's kind of true. After our discussion the patchset had to be heavily 
redesigned and this was done in one commit, which has been split after)
> 
> Consider my review fixes below and on a new branch in a
> separate commit.
> 
I applied your patch and added you to Co-authored.
> Branch is:
> 
>      lvasiliev/gh-4398-expose-error-module-4-review
> 
> ====================
> 
> ====================
>      Review fixes
>      
>      New commit message proposal:
>      
>      error: send traceback and custom type in IProto
>      
>      Error traceback and custom type features were added to the public
>      Lua API in the previous commits. This one makes the new attributes
>      being sent in IProto.
>      
>      @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 there are 2 more:
>      
>      `IPROTO_ERROR_TRACEBACK = 0x03` and
>      `IPROTO_ERROR_CUSTOM_TYPE = 0x04`.
>      
>      Both are optional, have MP_STR type, and speak for themselves.
>      Traceback is whatever the error object managed to collect from
>      the caller's stack when was pushed on it. It is visible in Lua via
>      `err_object.traceback`. Custom error type is another error object
>      attribute. This is what a user specifies in
>      `box.error.new({type = <custom_type>})` or
>      `box.error.new(<custom_type>)`.
> 
> diff --git a/extra/exports b/extra/exports
> index a5ebe0884..967e994c9 100644
> --- a/extra/exports
> +++ b/extra/exports
> @@ -241,6 +241,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/lua/net_box.lua b/src/box/lua/net_box.lua
> index 1e0cd7aba..5c07bb07c 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
> @@ -287,26 +292,16 @@ 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 custom_type = error[IPROTO_ERROR_CUSTOM_TYPE]
>                       local traceback = error[IPROTO_ERROR_TRACEBACK]
> -
> -                    local new_err
> -                    if custom_type then
> -                        new_err = box.error.new({type = custom_type,
> -                                                 reason = msg,
> -                                                 traceback = false})
> -                    else
> -                        new_err = box.error.new({code = code,
> -                                                 reason = msg,
> -                                                 traceback = false})
> +                    local new_err = box.error.new({
> +                        type = error[IPROTO_ERROR_CUSTOM_TYPE],
> +                        code = error[IPROTO_ERROR_CODE],
> +                        reason = error[IPROTO_ERROR_MESSAGE],
> +                        traceback = false
> +                    })
> +                    if traceback ~= nil then
> +                        ffi.C.error_set_traceback(new_err, traceback)
>                       end
> -
> -                    if traceback then
> -                        box.error.set_lua_traceback(new_err, traceback)
> -                    end
> -
>                       new_err:set_prev(prev)
>                       prev = new_err
>                   end
> diff --git a/src/box/xrow.c b/src/box/xrow.c
> index 04775d8ce..a9a6a6c75 100644
> --- a/src/box/xrow.c
> +++ b/src/box/xrow.c
> @@ -494,7 +494,6 @@ 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) {
> -		/* Code and message are necessary fields */
>   		uint32_t map_size = 2;
>   		const char *custom_type = box_error_custom_type(it);
>   		map_size += (it->traceback != NULL);
> diff --git a/test/box/error.result b/test/box/error.result
> index b717a4ff4..6db8813b4 100644
> --- a/test/box/error.result
> +++ b/test/box/error.result
> @@ -949,3 +949,59 @@ e = box.error.new({type = string.rep('a', 128)})
>    | ---
>    | - 63
>    | ...
> +
> +--
> +-- Check how traceback and 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, 't3', {true, true})
> + | ---
> + | ...
> +check_trace(e:unpack().traceback)
> + | ---
> + | - true
> + | ...
> +
> +_, e = pcall(c.call, c, 'box.error', {                                          \
> +    {code = 123, type = 'TestType', traceback = true, reason = 'Test reason'}   \
> +})
> + | ---
> + | ...
> +e = e:unpack()
> + | ---
> + | ...
> +e.traceback ~= nil or e.traceback
> + | ---
> + | - true
> + | ...
> +e.traceback = nil
> + | ---
> + | ...
> +e.trace = nil
> + | ---
> + | ...
> +e
> + | ---
> + | - code: 123
> + |   base_type: CustomError
> + |   type: TestType
> + |   custom_type: TestType
> + |   message: Test reason
> + | ...
> +
> +c:close()
> + | ---
> + | ...
> +box.schema.user.revoke('guest', 'super')
> + | ---
> + | ...
> diff --git a/test/box/error.test.lua b/test/box/error.test.lua
> index fe4051899..0821fa0a8 100644
> --- a/test/box/error.test.lua
> +++ b/test/box/error.test.lua
> @@ -278,3 +278,26 @@ e:unpack()
>   -- Try too long type name.
>   e = box.error.new({type = string.rep('a', 128)})
>   #e.type
> +
> +--
> +-- Check how traceback and 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, 't3', {true, true})
> +check_trace(e:unpack().traceback)
> +
> +_, e = pcall(c.call, c, 'box.error', {                                          \
> +    {code = 123, type = 'TestType', traceback = true, reason = 'Test reason'}   \
> +})
> +e = e:unpack()
> +e.traceback ~= nil or e.traceback
> +e.traceback = nil
> +e.trace = nil
> +e
> +
> +c:close()
> +box.schema.user.revoke('guest', 'super')
> 

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type
  2020-04-14  1:11   ` Vladislav Shpilevoy
@ 2020-04-15  9:25     ` lvasiliev
  2020-04-16  0:02       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 37+ messages in thread
From: lvasiliev @ 2020-04-15  9:25 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thanks for the review.

I applied your patch. Since I applied your patch, I did not write fixed 
on every comment. But I wrote a few questions where I did not understand 
your approach.

On 14.04.2020 4:11, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> Sorry, some of my comments may contain typos, because it is
> very late and I can't continue polishing it today.
> 
> See 21 comments below, review fixes in the end of the email,
> and on a new branch in a separate commit.
> 
> On 10/04/2020 10:10, Leonid Vasiliev wrote:
>> A possibility to create an error with a custom subtype was added.
>>
>> In accordance with https://github.com/tarantool/tarantool/issues/4398
>> a custom error type has been added to the box.error.
>> Now, it's possible to create an error with a custom subtype (string value)
>> for use it in applications.
>>
>> @TarantoolBot document
>> Title: error.custom_type
> 
> 1. custom_type field does not exist in any user visible API. Even
> after this patch.
> 
>> A custom error type has been added to the box.error.
>> Now, it's possible to create an error with a custom subtype (string value)
>> for use it in applications.
> 
> 2. When docbot duplicates commit message, you can omit the latter.
> 
>>
>> To API has been added:
>> - For create CustomError: box.error.new(type(str), format, ...)
>>                            box.error.new({type = str,
>>                                           reason = str})
>>
>> - error.type - CustomType error return custom type
>>      other errors return "base" error tybe
>> - base_type - return "base" errror type
>> Example:
>>
>> err_custom = box.error.new("My Custom Type", "Reason")
>> Now:
>> err_custom.type == "My Custom Type"
>> err_custom.base_type == "CustomType"
>> err_custom.message == "Reason"
> 
> 3. There is no a single word about name limit in 63 bytes.
> 
>>
>> Needed for #4398
>> ---
>>   src/box/errcode.h             |   1 +
>>   src/box/error.cc              |  59 +++++++++++++++++
>>   src/box/error.h               |  38 ++++++++++-
>>   src/box/lua/error.cc          | 147 ++++++++++++++++++++++++++++++------------
>>   src/lua/error.lua             |  11 ++++
>>   test/app/fiber.result         |   5 +-
>>   test/box/error.result         |  10 +--
>>   test/engine/func_index.result |  18 +++---
>>   8 files changed, 234 insertions(+), 55 deletions(-)
>>
>> diff --git a/src/box/error.cc b/src/box/error.cc
>> index 233b312..8179e52 100644
>> --- a/src/box/error.cc
>> +++ b/src/box/error.cc
>> @@ -125,6 +125,31 @@ box_error_add(const char *file, unsigned line, uint32_t code,
>>   
>>   /* }}} */
>>   
>> +const char *
>> +box_custom_error_type(const box_error_t *e)
> 
> 4. box_error_t is a type for the public API. This API is not public and
> therefore should use 'struct error' type name.
> 
> 5. Naming policy for functions is that there should be a prefix
> with the object type name in all functions. Or at least the prefix
> should be the same on all methods of one type.
> 
> Here the prefix should be box_error_*, so this method is
> box_error_custom_type(), and the method below is box_error_custom_new().
> 
>> +{
>> +	CustomError *custom_error = type_cast(CustomError, e);
>> +	if (custom_error)
>> +		return custom_error->custom_type();
>> +
>> +	return NULL;
>> +}
>> +
>> +struct error *
>> +box_custom_error_new(const char *file, unsigned line,
>> +		     const char *custom, const char *fmt, ...)
>> +{
>> +	struct error *e = BuildCustomError(file, line, custom);
>> +	CustomError *custom_error = type_cast(CustomError, e);
>> +	if (custom_error != NULL) {
>> +		va_list ap;
>> +		va_start(ap, fmt);
>> +		error_vformat_msg(e, fmt, ap);
>> +		va_end(ap);
>> +	}
>> +	return e;
>> +}
>> +
>>   struct rmean *rmean_error = NULL;
>>   
>>   const char *rmean_error_strings[RMEAN_ERROR_LAST] = {
>> @@ -290,3 +315,37 @@ 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);
> 
> 6. Make_type() perfectly fits in one line.
> 
>> +
>> +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 ?: "");
> 
> 7. custom_type is never NULL.
Why? It's a constructor argument.
> 
>> +
>> +	if (custom_type) {
>> +		strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1);
>> +		m_custom_type[sizeof(m_custom_type) - 1] = '\0';
>> +	} else {
>> +		m_custom_type[0] = '\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..5013488 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,6 +141,14 @@ box_error_set(const char *file, unsigned line, uint32_t code,
>>   /** \endcond public */
>>   
>>   /**
>> + * Return the error custom type,
>> + * \param error
> 
> 8. \param or @param takes two arguments - parameter name and its
> description. You provided only name, and the name does not match
> the actual parameter, which is 'e'.
> 
>> + * \return pointer to custom error type. On error, return NULL
> 
> 9. Please, try to start sentences from a capital letter, and finish
> them with a dot.
> 
>> + */
>> +const char *
>> +box_custom_error_type(const box_error_t *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.
>> @@ -155,15 +166,24 @@ box_error_add(const char *file, unsigned line, uint32_t code,
>>   
>>   /**
>>    * Construct error object without setting it in the diagnostics
>> - * area. On the memory allocation fail returns NULL.
>> + * area. On the memory allocation fail returns  OutOfMemory error.
> 
> 10. Unneccessary diff.
The comment is wrong. How I can fix it?
> 
>>    */
>>   struct error *
>>   box_error_new(const char *file, unsigned line, uint32_t code,
>>   	      const char *fmt, ...);
>>   
>> +/**
>> + * Construct error object without setting it in the diagnostics
>> + * area. On the memory allocation fail returns OutOfMemory error.
> 
> 11. This is a copy-paste box box_error_new() comment. Better write
> here a couple of words how is it different. Or better merge box_error_new()
> and box_custom_error_new(). Because in Lua I can specify both code
> and type. Don't see why can't I do this in C.
I thought we can’t change public API.
> 
> Honestly, I think we should use kind of struct error_args like you
> tried to do for lua/error.cc for the C API. Because with more
> arguments it will be hard to maintain them all as function paramters.
> But not now.
> 
>> + */
>> +struct error *
>> +box_custom_error_new(const char *file, unsigned line,
>> +		     const char *custom, 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 +310,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 4432bc8..1bcce1f 100644
>> --- a/src/box/lua/error.cc
>> +++ b/src/box/lua/error.cc
>> @@ -42,64 +42,101 @@ extern "C" {
>>   #include "lua/utils.h"
>>   #include "box/error.h"
>>   
>> -/**
>> - * 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.
>> - * In case one of arguments is missing its corresponding field
>> - * in struct error is filled with default value.
>> - */
>> -static struct error *
> 
> 12. Most of changes in this file does not really realate to the
> feature you are trying to introduce. It is just refactoring. If
> you want it, it should be done in a separate commit, because
> otherwise it is hard to see which changes are functional and need
> more attention.
Discarded
> 
>> -luaT_error_create(lua_State *L, int top_base)
>> +struct error_args {
>> +	uint32_t code;
>> +	const char *reason;
>> +	const char *custom;
>> +	bool tb_mode;
>> +	bool tb_parsed;
> 
> 13. All structures and their attributes usually have a comment
> explaining their purpose. In this structure it is hard to
> understand what is 'tb', what is 'tb_mode', what is 'tb_parsed',
> if you are not in the context of the patch.
> 
>> +};
>> +
>> +static int
>> +luaT_error_parse_args(struct lua_State *L, int top_base,
>> +		      struct error_args *args)
>>   {
>> -	uint32_t code = 0;
>> -	const char *reason = NULL;
>> -	bool tb_parsed = false;
>> -	bool tb_mode = false;
>> -	const char *file = "";
>> -	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);
>> -		reason = tnt_errcode_desc(code);
>> +	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) {
>> +			args->code = lua_tonumber(L, top_base);
>> +		} else if (top_type == LUA_TSTRING) {
>> +			args->code = ER_CUSTOM_ERROR;
>> +			args->custom = lua_tostring(L, top_base);
>> +		} else {
>> +			return -1;
>> +		}
>> +		args->reason = tnt_errcode_desc(args->code);
>>   		if (top > top_base) {
>>   			/* Call string.format(reason, ...) to format message */
>>   			lua_getglobal(L, "string");
>>   			if (lua_isnil(L, -1))
>> -				goto raise;
>> +				return 0;
>>   			lua_getfield(L, -1, "format");
>>   			if (lua_isnil(L, -1))
>> -				goto raise;
>> -			lua_pushstring(L, reason);
>> +				return 0;
>> +			lua_pushstring(L, args->reason);
>>   			for (int i = top_base + 1; i <= top; i++)
>>   				lua_pushvalue(L, i);
>>   			lua_call(L, top - top_base + 1, 1);
>> -			reason = lua_tostring(L, -1);
>> -		} else if (strchr(reason, '%') != NULL) {
>> +			args->reason = lua_tostring(L, -1);
>> +		} else if (strchr(args->reason, '%') != NULL) {
>>   			/* Missing arguments to format string */
>> -			return NULL;
>> +			return -1;
>>   		}
>> -	} 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);
>> +		if (lua_isnil(L, -1) == 0)
>> +			args->code = lua_tonumber(L, -1);
> 
> 14. I reverted this change and didn't get any problems
> in the tests. Why?
> 
>>   		lua_pop(L, 1);
>>   		lua_getfield(L, top_base, "reason");
>> -		reason = lua_tostring(L, -1);
>> -		if (reason == NULL)
>> -			reason = "";
>> +		if (lua_isnil(L, -1) == 0)
>> +			args->reason = lua_tostring(L, -1);
> 
> 15. Previously it was not checked for nil, and all worked fine.
> Why did you change that?
> 
>> +		lua_pop(L, 1);
>> +		lua_getfield(L, top_base, "type");
>> +		if (lua_isnil(L, -1) == 0)
>> +			args->custom = lua_tostring(L, -1);
>>   		lua_pop(L, 1);
>>   		lua_getfield(L, top_base, "traceback");
>>   		if (lua_isboolean(L, -1)) {
>> -			tb_parsed = true;
>> -			tb_mode = lua_toboolean(L, -1);
>> +			args->tb_parsed = true;
>> +			args->tb_mode = lua_toboolean(L, -1);
>>   		}
>>   		lua_pop(L, -1);
>>   	} else {
>> +		return -1;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +/**
>> + * 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.
> 
> 16. The comment does not describe new syntax with 'type' argument.
> 
>> + * In case one of arguments is missing its corresponding field
>> + * in struct error is filled with default value.
>> + */
>> +static struct error *
>> +luaT_error_create(lua_State *L, int top_base)
>> +{
>> +	struct error_args args;
>> +	args.code = UINT32_MAX;
> 
> 17. Before your patch default error code was 0. Why is it
> changed to UINT32_MAX?
> 
>> +	args.reason = NULL;
>> +	args.custom = NULL;
>> +	args.tb_mode = false;
>> +	args.tb_parsed = false;
>> +
>> +	if (luaT_error_parse_args(L, top_base, &args) != 0) {
>>   		return NULL;
>>   	}
>>   
>> -raise:
>> +	if (args.reason == NULL)
>> +		args.reason = "";
>> +
>> +	const char *file = "";
>> +	unsigned line = 0;
>> +	lua_Debug info;
>>   	if (lua_getstack(L, 1, &info) && lua_getinfo(L, "Sl", &info)) {
>>   		if (*info.short_src) {
>>   			file = info.short_src;
>> @@ -111,9 +148,16 @@ raise:
>>   		line = info.currentline;
>>   	}
>>   
>> -	struct error *err = box_error_new(file, line, code, "%s", reason);
>> -	if (tb_parsed)
>> -		err->traceback_mode = tb_mode;
>> +	struct error *err = NULL;
>> +	if (args.custom) {
>> +		err = box_custom_error_new(file, line, args.custom,
>> +					   "%s", args.reason);
> 
> 18. Code argument is ignored here. Although I am not sure it shouldn't
> be ignored. From what I remember, we decided that code should stay,
> but I may be wrong.
CustomError type has a predefinded code. Why it shouldn't be ignored?
> 
>> +	} else {
>> +		err = box_error_new(file, line, args.code, "%s", args.reason);
>> +	}
>> +
>> +	if (args.tb_parsed)
>> +		err->traceback_mode = args.tb_mode;
>>   
>>   	return err;
>>   }
>> @@ -162,17 +206,36 @@ 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)");
>> +	if (lua_gettop(L) == 0) {
>> +		return luaL_error(L, "Usage: box.error.new(code, args) or "\
>> +				     "box.error.new(type, args)");
>> +	}
>>   	struct error *e = luaT_error_create(L, 1);
>> -	if (e == NULL)
>> -		return luaL_error(L, "Usage: box.error.new(code, args)");
>> +	if (e == 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;
>>   }
>>   
>>   static int
>> +luaT_error_custom_type(lua_State *L)
> 
> 19. This method is not needed. We don't have 'global' getters
> like box.error.prev, or box.error.type, and so on. Every
> error object provides API to get its members without calling
> global functions.
> 
>> +{
>> +	struct error *e = luaL_checkerror(L, -1);
>> +
>> +	const char *custom_type = box_custom_error_type(e);
>> +	if (custom_type == NULL) {
>> +		lua_pushfstring(L, "The error has't custom type");
>> +		return 1;
>> +	}
>> +
>> +	lua_pushstring(L, custom_type);
>> +	return 1;
>> +}
>> +
>> +static int
>>   luaT_error_clear(lua_State *L)
>>   {
>>   	if (lua_gettop(L) >= 1)
>> diff --git a/src/lua/error.lua b/src/lua/error.lua
>> index 46d2866..5b32607 100644
>> --- a/src/lua/error.lua
>> +++ b/src/lua/error.lua
>> @@ -78,6 +78,16 @@ local function reflection_get(err, method)
>>   end
>>   
>>   local function error_type(err)
>> +    local res
>> +    if ffi.string(err._type.name) == 'CustomError' then
>> +        res = box.error.custom_type(err)
>> +    else
>> +        res = ffi.string(err._type.name)
> 
> 20. ffi.string() is called on one string 2 times. But it
> is not free, it allocates a new string each time.
> 
>> +    end
>> +    return res
>> +end
> 
> 21. There is again not a single test.
That's my mistake. After our discussion for testing I used the test 
added in the last commit and deleted the ones added before in misc.
> 
> Consider my review fixes below and on a new branch in a
> separate commit. Note, it is not entirely separated
> from the original commit in a couple of places, since I had
> rebase conflicts.
I applied your patch and added you to Co-authored.
> 
> Branch is:
> 
>      lvasiliev/gh-4398-expose-error-module-4-review
> 
> ====================
>      Review fixes
>      
>      New commit message proposal:
>      
>      error: add custom error type
>      
>      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
>      ...
>      ```
> 
> diff --git a/extra/exports b/extra/exports
> index ffd11145d..a5ebe0884 100644
> --- a/extra/exports
> +++ b/extra/exports
> @@ -234,6 +234,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/error.cc b/src/box/error.cc
> index 8179e5287..d854b86e9 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);
> @@ -126,7 +142,7 @@ box_error_add(const char *file, unsigned line, uint32_t code,
>   /* }}} */
>   
>   const char *
> -box_custom_error_type(const box_error_t *e)
> +box_error_custom_type(const struct error *e)
>   {
>   	CustomError *custom_error = type_cast(CustomError, e);
>   	if (custom_error)
> @@ -135,21 +151,6 @@ box_custom_error_type(const box_error_t *e)
>   	return NULL;
>   }
>   
> -struct error *
> -box_custom_error_new(const char *file, unsigned line,
> -		     const char *custom, const char *fmt, ...)
> -{
> -	struct error *e = BuildCustomError(file, line, custom);
> -	CustomError *custom_error = type_cast(CustomError, e);
> -	if (custom_error != NULL) {
> -		va_list ap;
> -		va_start(ap, fmt);
> -		error_vformat_msg(e, fmt, ap);
> -		va_end(ap);
> -	}
> -	return e;
> -}
> -
>   struct rmean *rmean_error = NULL;
>   
>   const char *rmean_error_strings[RMEAN_ERROR_LAST] = {
> @@ -322,22 +323,15 @@ static struct method_info customerror_methods[] = {
>   };
>   
>   const struct type_info type_CustomError =
> -	make_type("CustomError", &type_ClientError,
> -		  customerror_methods);
> +	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 ?: "");
> -
> -	if (custom_type) {
> -		strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1);
> -		m_custom_type[sizeof(m_custom_type) - 1] = '\0';
> -	} else {
> -		m_custom_type[0] = '\0';
> -	}
> +	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 *
> diff --git a/src/box/error.h b/src/box/error.h
> index 501348885..540008f35 100644
> --- a/src/box/error.h
> +++ b/src/box/error.h
> @@ -142,11 +142,11 @@ box_error_set(const char *file, unsigned line, uint32_t code,
>   
>   /**
>    * Return the error custom type,
> - * \param error
> - * \return pointer to custom error type. On error, return NULL
> + * \param e Error object.
> + * \return Pointer to custom error type. On error return NULL.
>    */
>   const char *
> -box_custom_error_type(const box_error_t *e);
> +box_error_custom_type(const struct error *e);
>   
>   /**
>    * Add error to the diagnostic area. In contrast to box_error_set()
> @@ -154,6 +154,8 @@ box_custom_error_type(const box_error_t *e);
>    * 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
> @@ -162,23 +164,15 @@ box_custom_error_type(const box_error_t *e);
>    */
>   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
> - * area. On the memory allocation fail returns  OutOfMemory error.
> + * area. On the memory allocation fail returns NULL.
>    */
>   struct error *
>   box_error_new(const char *file, unsigned line, uint32_t code,
> -	      const char *fmt, ...);
> -
> -/**
> - * Construct error object without setting it in the diagnostics
> - * area. On the memory allocation fail returns OutOfMemory error.
> - */
> -struct error *
> -box_custom_error_new(const char *file, unsigned line,
> -		     const char *custom, const char *fmt, ...);
> +	      const char *custom_type, const char *fmt, ...);
>   
>   extern const struct type_info type_ClientError;
>   extern const struct type_info type_XlogError;
> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
> index dd7068a83..cf771935e 100644
> --- a/src/box/lua/error.cc
> +++ b/src/box/lua/error.cc
> @@ -42,95 +42,83 @@ extern "C" {
>   #include "lua/utils.h"
>   #include "box/error.h"
>   
> -struct error_args {
> -	uint32_t code;
> -	const char *reason;
> -	const char *custom;
> -	const char *traceback;
> -};
> -
> -static int
> -luaT_error_parse_args(struct lua_State *L, int top_base,
> -		      struct error_args *args)
> +/**
> + * 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.
> + */
> +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;
> +	const char *file = "";
> +	unsigned line = 0;
> +	lua_Debug info;
>   	int top = lua_gettop(L);
>   	int top_type = lua_type(L, top_base);
> -	if (top >= top_base &&
> -	    (top_type == LUA_TNUMBER || top_type == LUA_TSTRING)) {
> +	if (top >= top_base && (top_type == LUA_TNUMBER ||
> +				top_type == LUA_TSTRING)) {
>   		if (top_type == LUA_TNUMBER) {
> -			args->code = lua_tonumber(L, top_base);
> -		} else if (top_type == LUA_TSTRING) {
> -			args->code = ER_CUSTOM_ERROR;
> -			args->custom = lua_tostring(L, top_base);
> +			code = lua_tonumber(L, top_base);
>   		} else {
> -			return -1;
> +			code = ER_CUSTOM_ERROR;
> +			custom_type = lua_tostring(L, top_base);
>   		}
> -		args->reason = tnt_errcode_desc(args->code);
> +		reason = tnt_errcode_desc(code);
>   		if (top > top_base) {
>   			/* Call string.format(reason, ...) to format message */
>   			lua_getglobal(L, "string");
>   			if (lua_isnil(L, -1))
> -				return 0;
> +				goto raise;
>   			lua_getfield(L, -1, "format");
>   			if (lua_isnil(L, -1))
> -				return 0;
> -			lua_pushstring(L, args->reason);
> +				goto raise;
> +			lua_pushstring(L, reason);
>   			for (int i = top_base + 1; i <= top; i++)
>   				lua_pushvalue(L, i);
>   			lua_call(L, top - top_base + 1, 1);
> -			args->reason = lua_tostring(L, -1);
> -		} else if (strchr(args->reason, '%') != NULL) {
> +			reason = lua_tostring(L, -1);
> +		} else if (strchr(reason, '%') != NULL) {
>   			/* Missing arguments to format string */
> -			return -1;
> +			return NULL;
>   		}
>   	} else if (top == top_base && top_type == LUA_TTABLE) {
>   		lua_getfield(L, top_base, "code");
> -		if (lua_isnil(L, -1) == 0)
> -			args->code = lua_tonumber(L, -1);
> +		if (!lua_isnil(L, -1))
> +			code = lua_tonumber(L, -1);
> +		else
> +			code = ER_CUSTOM_ERROR;
>   		lua_getfield(L, top_base, "reason");
> -		if (lua_isnil(L, -1) == 0)
> -			args->reason = lua_tostring(L, -1);
> +		reason = lua_tostring(L, -1);
> +		if (reason == NULL)
> +			reason = "";
>   		lua_getfield(L, top_base, "type");
> -		if (lua_isnil(L, -1) == 0)
> -			args->custom = lua_tostring(L, -1);
> +		if (!lua_isnil(L, -1))
> +			custom_type = lua_tostring(L, -1);
>   		lua_getfield(L, top_base, "traceback");
> -		if (lua_isboolean(L, -1) && lua_toboolean(L, -1)) {
> -			luaL_traceback(L, L, NULL, 0);
> -			if (lua_isstring(L, -1))
> -				args->traceback = lua_tostring(L, -1);
> +		if (lua_isboolean(L, -1)) {
> +			is_traceback_enabled = lua_toboolean(L, -1);
> +			is_traceback_specified = true;
>   		}
>   		lua_pop(L, 1);
>   	} else {
> -		return -1;
> -	}
> -	return 0;
> -}
> -
> -/**
> - * 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.
> - * In case one of arguments is missing its corresponding field
> - * in struct error is filled with default value.
> - */
> -static struct error *
> -luaT_error_create(lua_State *L, int top_base)
> -{
> -	struct error_args args;
> -	args.code = UINT32_MAX;
> -	args.reason = NULL;
> -	args.custom = NULL;
> -	args.traceback = NULL;
> -
> -	if (luaT_error_parse_args(L, top_base, &args) != 0)
>   		return NULL;
> +	}
>   
> -	if (args.reason == NULL)
> -		args.reason = "";
> -
> -	const char *file = "";
> -	unsigned line = 0;
> -	lua_Debug info;
> +raise:
>   	if (lua_getstack(L, 1, &info) && lua_getinfo(L, "Sl", &info)) {
>   		if (*info.short_src) {
>   			file = info.short_src;
> @@ -142,15 +130,13 @@ luaT_error_create(lua_State *L, int top_base)
>   		line = info.currentline;
>   	}
>   
> -	struct error *err;
> -	if (args.custom) {
> -		err = box_custom_error_new(file, line, args.custom,
> -					   "%s", args.reason);
> -	} else {
> -		err = box_error_new(file, line, args.code, "%s", args.reason);
> -	}
> -	if (args.traceback != NULL && err != NULL)
> -		error_set_traceback(err, args.traceback);
> +	struct error *err = box_error_new(file, line, code, custom_type,
> +					  "%s", reason);
> +	/*
> +	 * Explicit traceback option overrides the global setting.
> +	 */
> +	if (err != NULL && is_traceback_specified)
> +		err->is_traceback_enabled = is_traceback_enabled;
>   	return err;
>   }
>   
> @@ -198,35 +184,16 @@ 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) or "\
> -				     "box.error.new(type, args)");
> -	}
> -	struct error *e = luaT_error_create(L, 1);
> -	if (e == NULL) {
> +	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)");
> +				  "box.error.new(type, args)");
>   	}
>   	lua_settop(L, 0);
>   	luaT_pusherror(L, e);
>   	return 1;
>   }
>   
> -static int
> -luaT_error_custom_type(lua_State *L)
> -{
> -	struct error *e = luaL_checkerror(L, -1);
> -
> -	const char *custom_type = box_custom_error_type(e);
> -	if (custom_type == NULL) {
> -		lua_pushfstring(L, "The error has't custom type");
> -		return 1;
> -	}
> -
> -	lua_pushstring(L, custom_type);
> -	return 1;
> -}
> -
>   static int
>   luaT_error_clear(lua_State *L)
>   {
> @@ -381,10 +348,6 @@ box_lua_error_init(struct lua_State *L) {
>   			lua_pushcfunction(L, luaT_error_cfg);
>   			lua_setfield(L, -2, "cfg");
>   		}
> -		{
> -			lua_pushcfunction(L, luaT_error_custom_type);
> -			lua_setfield(L, -2, "custom_type");
> -		}
>   		lua_setfield(L, -2, "__index");
>   	}
>   	lua_setmetatable(L, -2);
> diff --git a/src/box/xrow.c b/src/box/xrow.c
> index a494d1f46..5494b41cd 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 1f9ae9375..1d3413b19 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,20 +80,18 @@ local function reflection_get(err, method)
>       end
>   end
>   
> -local function error_type(err)
> -    local res
> -    if ffi.string(err._type.name) == 'CustomError' then
> -        res = box.error.custom_type(err)
> -    else
> -        res = ffi.string(err._type.name)
> -    end
> -    return res
> -end
> -
>   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
> @@ -148,7 +149,7 @@ local error_fields = {
>       ["errno"]       = error_errno;
>       ["prev"]        = error_prev;
>       ["traceback"]   = error_traceback;
> -    ["base_type"]    = error_base_type
> +    ["base_type"]   = error_base_type,
>   }
>   
>   local function error_unpack(err)
> diff --git a/test/box/error.result b/test/box/error.result
> index 2b837e434..b717a4ff4 100644
> --- a/test/box/error.result
> +++ b/test/box/error.result
> @@ -431,11 +431,8 @@ t;
>    |   210: box.error.SQL_PREPARE
>    |   211: box.error.WRONG_QUERY_ID
>    |   212: box.error.SEQUENCE_NOT_STARTED
> -<<<<<<< HEAD
>    |   213: box.error.NO_SUCH_SESSION_SETTING
> -=======
> - |   213: box.error.CUSTOM_ERROR
> ->>>>>>> error: Add the custom error type
> + |   214: box.error.CUSTOM_ERROR
>    | ...
>   
>   test_run:cmd("setopt delimiter ''");
> @@ -895,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 6f1271630..fe4051899 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
> 

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO
  2020-04-14  1:12   ` Vladislav Shpilevoy
@ 2020-04-15  9:26     ` lvasiliev
  2020-04-16  0:06       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 37+ messages in thread
From: lvasiliev @ 2020-04-15  9:26 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thanks for the review.

On 14.04.2020 4:12, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> Sorry, some of my comments may contain typos, because it is
> very late and I can't continue polishing it today.
> 
> See 5 comments below, review fixes in the end of the email,
> and on a new branch in a separate commit.
> 
> On 10/04/2020 10:10, Leonid Vasiliev wrote:
>> IPROTO session settings for error transmission in various formats
>> depending on session settings have been added.
>> This is required for backward compatibility.
> 
> What backward compatibility? Keep in mind, that the team consists not
> only of you, me, and Alexander T. Other people don't know what a
> backward compatibility is being tried to be preserved here. Just
> 'backward compatibility' does not tell anything at all.
> 
>> @TarantoolBot document
>>      Title: Add session_setting
> 
> The doc request is corrupted, docbot won't understand that because
> of leading whitespaces.
> 
>> iproto_error_format setting has been added to _session_settings
> 
> Looks like there is a lack of global setting similar to what we had
> for tracebacks. Currently, when the option is false (by default), and
> I want to use the new format everywhere, I need to find every single
> place where I create a new session, and put there code saying
> 
>      box.session.settings.error_format = new/old/whatever
> 
> I think there should be a global option when a user's application is
> ready to switch to the new format completely. Otherwise it is going
> to be hell to find all places where a new session is created, and patch
> them.
> 
> Just a reminder - every fiber.new(), fiber.create() creates a session,
> every iproto connection is a session.
This was discussed in TPT chat with Mons and Nazarov, and after that 
with you, Turenko, Mons ... in zoom. I was orienteted by the net.box 
session and I might not know something. Where do you suggest storing the 
setting?
> 
>> Used to set the error transmission format in the session.
>> Old format: transmits as string at IPROTO_BODY
>> New format: transmits as error object at IPROTO_BODY
>>
>> Needed for #4398
>> ---
>>   src/box/iproto.cc          | 97 ++++++++++++++++++++++++++++++++++++++++++++++
>>   src/box/lua/net_box.lua    | 12 ++++++
>>   src/box/session.cc         |  3 ++
>>   src/box/session.h          |  3 ++
>>   src/box/session_settings.h |  1 +
>>   src/lua/utils.h            | 20 ++++++++++
>>   6 files changed, 136 insertions(+)
>>
>> diff --git a/src/box/iproto.cc b/src/box/iproto.cc
>> index 9dad43b..92be645 100644
>> --- a/src/box/iproto.cc
>> +++ b/src/box/iproto.cc
>> @@ -2183,6 +2184,100 @@ iproto_session_push(struct session *session, uint64_t sync, struct port *port)
>>   
>>   /** }}} */
>>   
>> +enum {
>> +	IPROTO_SESSION_SETTING_ERR_FORMAT = 0,
>> +	iproto_session_setting_MAX,
>> +};
> 
> 1. The new setting is not about iproto only. It is about
> error serialization to MessagePack. MessagePack != IProto.
> So iproto.cc is not a good place for the new setting.
ok
> 
>> diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
>> index 1e0cd7a..c8f76b0 100644
>> --- a/src/box/lua/net_box.lua
>> +++ b/src/box/lua/net_box.lua
>> @@ -1047,6 +1047,18 @@ local function new_sm(host, port, opts, connection, greeting)
>>       if opts.wait_connected ~= false then
>>           remote._transport.wait_state('active', tonumber(opts.wait_connected))
>>       end
>> +
>> +    -- Set extended error format for session.
>> +    if opts.error_extended then
> 
> 2. You are inconsistent in what an option do you want. In _session_setting
> you call it 'iproto_error_format' assuming it is a format. In practice it
> is a number, which, I assume, means version number. In the public API it
> is called 'error_extended' assuming it is a flag - either true or false.
> 
> So what is it?
> 
> In my review fixes I made it a flag. Because there is no way we will support
> multiple 'degrees' of error extension.
I don't mind.
> 
>> +        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1)
> 
> 3. This won't work in case it is an instance bootstrapped from the master
> branch before 2.4.1 was released. I don't know how to fix it now.
Sorry, I think this should not work until the release.
> 
>> +        if not ext_err_supported then
>> +            box.error(box.error.PROC_LUA,
>> +                      "Server doesn't support extended error format")
>> +        end
>> +        remote.space._session_settings:update('iproto_error_format',
>> +                                              {{'=', 2, 1}})
> 
> 4. This is additional network hop. I don't think it should be done
> automatically. I would let users do that.
It's network hop only if option will be set.As optimization it can be 
included to auth packet in future.

> 
> Besides, you didn't document that option, so how were they supposed
> to know about it?
> 
>> +    end
>> +
>>       return remote
>>   end
>>   
>> diff --git a/src/box/session.h b/src/box/session.h
>> index 6dfc7cb..a8903aa 100644
>> --- a/src/box/session.h
>> +++ b/src/box/session.h
>> @@ -36,6 +36,7 @@
>>   #include "fiber.h"
>>   #include "user.h"
>>   #include "authentication.h"
>> +#include "lua/utils.h"
> 
> 5. This is clearly broken dependency. lua/utils.h is from src/lua/,
> and it obviously depends on Lua language.
> 
> Session is from src/box/. So it is from different hierarchy, and
> is not related to any language.
> 
> 
> Consider my review fixes below and on a new branch in a
> separate commit.
> 
I applied your patch and added you to Co-authored.
> Branch is:
> 
>      lvasiliev/gh-4398-expose-error-module-4-review
> 
> ====================
>      Review fixes
>      
>      New commit message proposal:
>      
>      error: add session setting for error type marshaling
>      
>      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.
> 
> diff --git a/src/box/iproto.cc b/src/box/iproto.cc
> index 92be645d2..9dad43b0b 100644
> --- a/src/box/iproto.cc
> +++ b/src/box/iproto.cc
> @@ -55,7 +55,6 @@
>   #include "call.h"
>   #include "tuple_convert.h"
>   #include "session.h"
> -#include "session_settings.h"
>   #include "xrow.h"
>   #include "schema.h" /* schema_version */
>   #include "replication.h" /* instance_uuid */
> @@ -2184,100 +2183,6 @@ iproto_session_push(struct session *session, uint64_t sync, struct port *port)
>   
>   /** }}} */
>   
> -enum {
> -	IPROTO_SESSION_SETTING_ERR_FORMAT = 0,
> -	iproto_session_setting_MAX,
> -};
> -
> -static const char *iproto_session_setting_strs[iproto_session_setting_MAX] = {
> -	"iproto_error_format",
> -};
> -
> -static int iproto_session_field_type[] = {
> -	/** IPROTO_SESSION_SETTING_ERR_FORMAT */
> -	FIELD_TYPE_UNSIGNED,
> -};
> -
> -static void
> -iproto_session_setting_get(int id, const char **mp_pair,
> -			   const char **mp_pair_end)
> -{
> -	if (id < 0 || id >= iproto_session_setting_MAX) {
> -		diag_set(ClientError, ER_ILLEGAL_PARAMS,
> -			 "unknown session setting");
> -		return;
> -	}
> -	struct session *session = current_session();
> -
> -	const char *name = iproto_session_setting_strs[id];
> -	size_t name_len = strlen(name);
> -
> -	/* Now we have only one iproto session setting. */
> -	size_t size = mp_sizeof_array(2) + mp_sizeof_str(name_len)
> -		+ mp_sizeof_uint(session->serializer_ctx.err_format_ver);
> -
> -	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_uint(pos_end,
> -				 session->serializer_ctx.err_format_ver);
> -	*mp_pair = pos;
> -	*mp_pair_end = pos_end;
> -}
> -
> -static int
> -iproto_session_setting_set(int id, const char *mp_value)
> -{
> -	if (id < 0 || id >= iproto_session_setting_MAX) {
> -		diag_set(ClientError, ER_ILLEGAL_PARAMS,
> -			 "unknown session setting");
> -		return -1;
> -	}
> -	/*Current IPROTO session settings are used only for BINARY session */
> -	if (current_session()->type != SESSION_TYPE_BINARY)
> -		return -1;
> -
> -	enum mp_type mtype = mp_typeof(*mp_value);
> -	int stype = iproto_session_field_type[id];
> -	switch(stype) {
> -	case FIELD_TYPE_UNSIGNED: {
> -		if (mtype != MP_UINT)
> -			break;
> -		int val = mp_decode_uint(&mp_value);
> -		switch (id) {
> -		case IPROTO_SESSION_SETTING_ERR_FORMAT:
> -			if (val >= ERR_FORMAT_UNK)
> -				break;
> -			current_session()->serializer_ctx.err_format_ver = val;
> -			return 0;
> -		default:
> -			diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE,
> -				 iproto_session_setting_strs[id],
> -				 field_type_strs[stype]);
> -			return -1;
> -		}
> -		break;
> -	}
> -	default:
> -		unreachable();
> -	}
> -	diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE,
> -		 iproto_session_setting_strs[id], field_type_strs[stype]);
> -	return -1;
> -}
> -
> -void
> -iproto_session_settings_init()
> -{
> -	struct session_setting_module *module =
> -		&session_setting_modules[SESSION_SETTING_IPROTO];
> -	module->settings = iproto_session_setting_strs;
> -	module->setting_count = iproto_session_setting_MAX;
> -	module->get = iproto_session_setting_get;
> -	module->set = iproto_session_setting_set;
> -}
> -
>   /** Initialize the iproto subsystem and start network io thread */
>   void
>   iproto_init()
> @@ -2296,8 +2201,6 @@ iproto_init()
>   		/* .sync = */ iproto_session_sync,
>   	};
>   	session_vtab_registry[SESSION_TYPE_BINARY] = iproto_session_vtab;
> -
> -	iproto_session_settings_init();
>   }
>   
>   /** Available iproto configuration changes. */
> diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
> index a1e9ee745..5c07bb07c 100644
> --- a/src/box/lua/net_box.lua
> +++ b/src/box/lua/net_box.lua
> @@ -1042,18 +1042,6 @@ local function new_sm(host, port, opts, connection, greeting)
>       if opts.wait_connected ~= false then
>           remote._transport.wait_state('active', tonumber(opts.wait_connected))
>       end
> -
> -    -- Set extended error format for session.
> -    if opts.error_extended then
> -        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1)
> -        if not ext_err_supported then
> -            box.error(box.error.PROC_LUA,
> -                      "Server doesn't support extended error format")
> -        end
> -        remote.space._session_settings:update('iproto_error_format',
> -                                              {{'=', 2, 1}})
> -    end
> -
>       return remote
>   end
>   
> diff --git a/src/box/session.cc b/src/box/session.cc
> index 89435ee0c..08a10924a 100644
> --- a/src/box/session.cc
> +++ b/src/box/session.cc
> @@ -144,9 +144,6 @@ session_create(enum session_type type)
>   	session->sql_default_engine = SQL_STORAGE_ENGINE_MEMTX;
>   	session->sql_stmts = NULL;
>   
> -	/* Set default Lua serializer context */
> -	session->serializer_ctx.err_format_ver = ERR_FORMAT_DEF;
> -
>   	/* For on_connect triggers. */
>   	credentials_create(&session->credentials, guest_user);
>   	struct mh_i64ptr_node_t node;
> @@ -278,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()
>   {
> @@ -286,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 fdc9f03d7..500a88b22 100644
> --- a/src/box/session.h
> +++ b/src/box/session.h
> @@ -36,14 +36,12 @@
>   #include "fiber.h"
>   #include "user.h"
>   #include "authentication.h"
> -#include "lua/utils.h"
> +#include "serializer_opts.h"
>   
>   #if defined(__cplusplus)
>   extern "C" {
>   #endif /* defined(__cplusplus) */
>   
> -extern void sql_session_settings_init();
> -
>   struct port;
>   struct session_vtab;
>   
> @@ -91,6 +89,8 @@ struct session_meta {
>   	};
>   	/** Console output format. */
>   	enum output_format output_format;
> +	/** Session-specific serialization options. */
> +	struct serializer_opts serializer_opts;
>   };
>   
>   /**
> @@ -123,8 +123,6 @@ struct session {
>   	struct credentials credentials;
>   	/** Trigger for fiber on_stop to cleanup created on-demand session */
>   	struct trigger fiber_on_stop;
> -	/** Session Lua serializer context */
> -	struct luaL_serializer_ctx serializer_ctx;
>   };
>   
>   struct session_vtab {
> diff --git a/src/box/session_settings.c b/src/box/session_settings.c
> index 79c4b8d3c..dbbbf2461 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/lua/utils.h b/src/lua/utils.h
> index cac9c57b8..4bc041796 100644
> --- a/src/lua/utils.h
> +++ b/src/lua/utils.h
> @@ -270,26 +270,6 @@ struct luaL_serializer {
>   	struct rlist on_update;
>   };
>   
> -/**
> - * An error serialization formats
> - */
> -enum error_formats {
> -	/** Default(old) format */
> -	ERR_FORMAT_DEF,
> -	/** Extended format */
> -	ERR_FORMAT_EX,
> -	/** The max version of error format */
> -	ERR_FORMAT_UNK
> -};
> -
> -/**
> - * A serializer context (additional settings for a serializer)
> - */
> -struct luaL_serializer_ctx {
> -	/** Version of a format for an error transmission */
> -	uint8_t err_format_ver;
> -};
> -
>   extern int luaL_nil_ref;
>   extern int luaL_map_metatable_ref;
>   extern int luaL_array_metatable_ref;
> diff --git a/src/serializer_opts.h b/src/serializer_opts.h
> new file mode 100644
> index 000000000..9e2c15eff
> --- /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 ea6302dff..149cc4bd5 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]
> 

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 0/5] Extending error functionality
  2020-04-14  1:10 ` [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Vladislav Shpilevoy
@ 2020-04-15  9:48   ` lvasiliev
  0 siblings, 0 replies; 37+ messages in thread
From: lvasiliev @ 2020-04-15  9:48 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches



On 14.04.2020 4:10, Vladislav Shpilevoy wrote:
> Hi! Thanks for the patchset!
> 
> On 10/04/2020 10:10, Leonid Vasiliev wrote:
>> https://github.com/tarantool/tarantool/issues/4398
>> https://github.com/tarantool/tarantool/tree/lvasiliev/gh-4398-expose-error-module-4
> 
> The branch build fails on Travis.
Thanks, a bug in session setting module was a reason.
> 

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error
  2020-04-15  9:25     ` lvasiliev
@ 2020-04-16  0:00       ` Vladislav Shpilevoy
  2020-04-16  1:11         ` Alexander Turenko
  2020-04-16  8:40         ` lvasiliev
  0 siblings, 2 replies; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-16  0:00 UTC (permalink / raw)
  To: lvasiliev, Alexander Turenko; +Cc: tarantool-patches

Hi! Thanks for the fixes!

> On 14.04.2020 4:11, Vladislav Shpilevoy wrote:
>> Thanks for the patch!
>>
>> Sorry, some of my comments may contain typos, because it is
>> very late and I can't continue polishing it today.
>>
>> First of all there are some major issues with the
>> patch, which IMO should be clarified before it is pushed.
>>
>> 1) Why do we need the traceback in scope of 4398? It has
>> nothing to do with marshaling through IProto, nor with
>> custom error types.
> This is part of the errors.lua functional, which was attached as an example. After that it was also one of the functionality requested by Nazarov.

The original ticket does not contain any mentioning of a traceback:
https://github.com/tarantool/tarantool/issues/4398#issue-475632900

I agree that it was requested later, in comments, but still it
looks pretty separate from this, don't you agree?

>> 2) What to do with stacked errors? Currently only the first
>> error in the stack gets a traceback, because luaT_pusherror() is
>> called only on the top error in the stack. Consider this test:
>>
>>      box.cfg{}
>>      lua_code = [[function(tuple)
>>                      local json = require('json')
>>                      return json.encode(tuple)
>>                   end]]
>>      box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
>>      s = box.schema.space.create('withdata')
>>      pk = s:create_index('pk')
>>      idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})
>>
>>      function test_func() return pcall(s.insert, s, {1}) end
>>      box.error.cfg{traceback_enable = true}
>>      ok, err = test_func()
>>
>>
>>      tarantool> err:unpack()
>>      ---
>>      - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string
>>          \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382:
>>          in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725:
>>          in function <builtin/box/console.lua:713>"
>>      ... <snipped>
>>
>>      tarantool> err.prev:unpack()
>>      ---
>>      - type: LuajitError
>>        message: '[string "return function(tuple)..."]:2: attempt to call global ''require''
>>          (a nil value)'
>>      ... <snipped>
>>
>> The second error does not have a traceback at all.
> (I added Turenko to To)
> I have two variants:
> - Leave as is and to document such behavior
> - Add the same traceback to all errors in the stack
> Alexander what do you think?

There is a third option - leave traceback out of this patchset for
a next release. Because they are clearly underdesigned. But on the
other hand it is not something critical. After all, we just can say,
that a traceback can be present, or can be not, and its content is
just a string, which can't be assumed to have any special format.

That would allow us to add/remove them and change their format anytime.

>> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
>> index 4432bc89b..a616e87a4 100644
>> --- a/src/box/lua/error.cc
>> +++ b/src/box/lua/error.cc
>> @@ -112,9 +112,11 @@ raise:
>>       }
>>         struct error *err = box_error_new(file, line, code, "%s", reason);
>> -    if (tb_parsed)
>> -        err->traceback_mode = tb_mode;
>> -
>> +    /*
>> +     * Explicit traceback option overrides the global setting.
>> +     */
>> +    if (err != NULL && is_traceback_specified)
> 1) box_error_new don't return NULL

True, then 'err != NULL' check can be dropped.

>>   diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c
>> index 1caa75ee9..833454bac 100644
>> --- a/src/lib/core/diag.c
>> +++ b/src/lib/core/diag.c
>> @@ -128,27 +125,14 @@ error_vformat_msg(struct error *e, const char *format, va_list ap)
>> -void
>> -error_set_traceback_supplementation(bool traceback_mode)
>> +error_set_traceback(struct error *e, const char *traceback)
>>   {
>> -    global_traceback_mode = traceback_mode;
>> +    assert(e->traceback == NULL);
> 2) Do I understand correctly that asserts only work on debug? Will this approach not be dangerous by memory leaks on release?

It should not be. Because the method is not public, and
in private methods it is never called with non-zero e->traceback.

>> +    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 f38009c54..e918d3089 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;
> 3) Why a global variable is more preferable than static?

Static variables are also global. I just removed the setter, since
it is trivial.

>>   diff --git a/src/lua/error.lua b/src/lua/error.lua
>> index 46d28667f..535110588 100644
>> --- a/src/lua/error.lua
>> +++ b/src/lua/error.lua
>> @@ -26,8 +26,8 @@ struct error {
>>       char _errmsg[DIAG_ERRMSG_MAX];
>>       struct error *_cause;
>>       struct error *_effect;
>> -    char *lua_traceback;
>> -    bool traceback_mode;
>> +    char *traceback;
> 4) Replaced to err_traceback. It overlaps the traceback getter.
>> +    bool is_traceback_enabled;

Better replace it with _traceback. Similar to other members.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type
  2020-04-15  9:25     ` lvasiliev
@ 2020-04-16  0:02       ` Vladislav Shpilevoy
  2020-04-16  9:18         ` lvasiliev
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-16  0:02 UTC (permalink / raw)
  To: lvasiliev; +Cc: tarantool-patches

Thanks for the fixes!

>> 6. Make_type() perfectly fits in one line.
>>
>>> +
>>> +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 ?: "");
>>
>> 7. custom_type is never NULL.
> Why? It's a constructor argument.

And still it is never NULL. I couldn't find any place, where
you passed NULL here, and anyway that would be incorrect. Type
can't be just NULL. How to present it in Lua then? nil? Doubt
it.

>>> + */
>>> +const char *
>>> +box_custom_error_type(const box_error_t *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.
>>> @@ -155,15 +166,24 @@ box_error_add(const char *file, unsigned line, uint32_t code,
>>>     /**
>>>    * Construct error object without setting it in the diagnostics
>>> - * area. On the memory allocation fail returns NULL.
>>> + * area. On the memory allocation fail returns  OutOfMemory error.
>>
>> 10. Unneccessary diff.
> The comment is wrong. How I can fix it?

Yeah, I just realized that after you said box_error_new() never
returns NULL. Then probably it is ok to change it.

>>
>>>    */
>>>   struct error *
>>>   box_error_new(const char *file, unsigned line, uint32_t code,
>>>             const char *fmt, ...);
>>>   +/**
>>> + * Construct error object without setting it in the diagnostics
>>> + * area. On the memory allocation fail returns OutOfMemory error.
>>
>> 11. This is a copy-paste box box_error_new() comment. Better write
>> here a couple of words how is it different. Or better merge box_error_new()
>> and box_custom_error_new(). Because in Lua I can specify both code
>> and type. Don't see why can't I do this in C.
> I thought we can’t change public API.

This is not public API. If something is not compiled into module.h,
it is not public (in C. For Lua rules are different).

>>> @@ -111,9 +148,16 @@ raise:
>>>           line = info.currentline;
>>>       }
>>>   -    struct error *err = box_error_new(file, line, code, "%s", reason);
>>> -    if (tb_parsed)
>>> -        err->traceback_mode = tb_mode;
>>> +    struct error *err = NULL;
>>> +    if (args.custom) {
>>> +        err = box_custom_error_new(file, line, args.custom,
>>> +                       "%s", args.reason);
>>
>> 18. Code argument is ignored here. Although I am not sure it shouldn't
>> be ignored. From what I remember, we decided that code should stay,
>> but I may be wrong.
> CustomError type has a predefinded code. Why it shouldn't be ignored?

Because it is not a 'ClientError' object from user's point of view. The
fact that we implemented it as a descendant of 'ClientError' class should
not leak into the API. User can't assume anything about error hierarchies.
He does not even see them.

From user's point of view there are separate built-in 'ClientError'. And
separate 'CustomError'. Both accept error code now. But 'CustomError'
ignores it somewhy. Does not look right.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO
  2020-04-15  9:25     ` lvasiliev
@ 2020-04-16  0:02       ` Vladislav Shpilevoy
  0 siblings, 0 replies; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-16  0:02 UTC (permalink / raw)
  To: lvasiliev; +Cc: tarantool-patches

Thanks for the fixes!

>>> diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
>>> index 07fa54c..1e0cd7a 100644
>>> --- a/src/box/lua/net_box.lua
>>> +++ b/src/box/lua/net_box.lua
>>> @@ -287,7 +289,24 @@ local function create_transport(host, port, user, password, callback,
>>>                       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 custom_type = error[IPROTO_ERROR_CUSTOM_TYPE]
>>> +                    local traceback = error[IPROTO_ERROR_TRACEBACK]
> After we decided don't concatenate the traceback, I decided that it would be inconsistent to transmit the traceback in the case of "throw" over the network, since when creating a new error, the "trace" is set new and it always has been. Furthermore, an error can change the type. So, I delete IPROTO_ERROR_TRACEBACK.If you consider it wrong decision, I can return IPROTO_ERROR_TRACEBACK.

I am ok with that. As I said, traceback is not really a kind of
critical and stable feature anyway.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO
  2020-04-15  9:26     ` lvasiliev
@ 2020-04-16  0:06       ` Vladislav Shpilevoy
  2020-04-16  9:36         ` lvasiliev
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-16  0:06 UTC (permalink / raw)
  To: lvasiliev; +Cc: tarantool-patches

Hi! Thanks for the fixes!

>>> @TarantoolBot document
>>>      Title: Add session_setting
>>
>> The doc request is corrupted, docbot won't understand that because
>> of leading whitespaces.
>>
>>> iproto_error_format setting has been added to _session_settings
>>
>> Looks like there is a lack of global setting similar to what we had
>> for tracebacks. Currently, when the option is false (by default), and
>> I want to use the new format everywhere, I need to find every single
>> place where I create a new session, and put there code saying
>>
>>      box.session.settings.error_format = new/old/whatever
>>
>> I think there should be a global option when a user's application is
>> ready to switch to the new format completely. Otherwise it is going
>> to be hell to find all places where a new session is created, and patch
>> them.
>>
>> Just a reminder - every fiber.new(), fiber.create() creates a session,
>> every iproto connection is a session.
> This was discussed in TPT chat with Mons and Nazarov, and after that with you, Turenko, Mons ... in zoom. I was orienteted by the net.box session and I might not know something. Where do you suggest storing the setting?

Looks like you didn't understood me. I don't propose to remove the
session local option. I propose to provide an ability to override it
with a global option if necessary. So we have session option by
default with the old format, and global option unset.

If someone is ready to start using new errors everywhere, he just turns
the global option on.

If someone is not ready, he continues using session local option.

Why that may be needed - I said above. Turning that option on for every
session is going to be very tricky. But maybe it can be moved to the next
release (the global option). Don't know. The issue is already a huge
blackhole which absorbed lots of independent initially unplanned
features. The more we move for later, the better.

You should try to ask users in the red chat. It helps sometimes, they
can share their opinions and experience of upgrades. Just formulate your
question in a short but detailed manner, provide a couple of
ready-to-choose answers, probably a poll.

>>> +        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1)
>>
>> 3. This won't work in case it is an instance bootstrapped from the master
>> branch before 2.4.1 was released. I don't know how to fix it now.
> Sorry, I think this should not work until the release.

Exactly for 'between-releases' users we introduced the new versioning schema
with 4 numbers. I don't think we can just say fuck off to them now. I
would better drop this from the patchset and added when somebody explicitly
asks for that, with good design and planning.

>>
>>> +        if not ext_err_supported then
>>> +            box.error(box.error.PROC_LUA,
>>> +                      "Server doesn't support extended error format")
>>> +        end
>>> +        remote.space._session_settings:update('iproto_error_format',
>>> +                                              {{'=', 2, 1}})
>>
>> 4. This is additional network hop. I don't think it should be done
>> automatically. I would let users do that.
> It's network hop only if option will be set.As optimization it can be included to auth packet in future.

Auth packets have nothing to do with session settings. It would be
encapsulation violation. Moreover, in addition to settings we may
want to configure more things in future. What if an encryption will
be added to Tarantool? What if we will add key exchange? I think we
should not add a new option + 1 network hop for every 'negotiation'
thing. Better omit this for now and design this properly when we have
more time.

I don't propose to remove your code. I just propose to finish some
non-critical parts of it later.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding
  2020-04-15  9:25     ` lvasiliev
@ 2020-04-16  0:11       ` Vladislav Shpilevoy
  2020-04-16 10:04         ` lvasiliev
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-16  0:11 UTC (permalink / raw)
  To: lvasiliev; +Cc: tarantool-patches

Hi! Thanks for the fixes!

>> 7. Why are these values in a enum? I thought we
>> decided to use string types?
> Now we compare a string with error type when encode and use number further. If we encode error type as string we still have to compare the strings when decoding to create the error of the desired type. But number takes up less space versus string  when transmitting over a network and makes it possible to use a switch when create an error after decoding (which looks much nicer).

Wait, so this is inside the MessagePack too? This is not a good
idea really. I thought you just converted strings to enums and
then enums to classes.

First about 'less space'. These are error objects. It does not
really matter if every one of them becomes +10 or even +20 bytes
if we use string types instead of numbers. They are not critical
in any aspect, not in perf nor in space.

Second about switch, 'which looks nicer'. Nicely formatted if-else
sequence also look fine. This is subjective though. I won't argue.

Thirdly, about why I think string types are better. The problem is
that I don't want us to document all the error types as numbers
and support them forever. When we use strings, we easily can remove
old errors, add new errors. When we use numbers, we

    1) will need to document what every number means. Strings are
    self-documenting. OutOfMemory means out of memory, obviously.
    And so on.

    2) will one day have holes in these numbers left from removed
    errors, this won't look nice, trust me.

    3) that complicates compatibility. What if some error type was
    added to a newer tarantool version, and an old connector connected
    to the instance? How will it handle the new error types? With
    string types the problem does not exist.

Numbers are fine for error codes. For example, SQL drivers define
certain error codes as kind of a standard, and that simplifies
support. Also an error code can describe actually a pretty big
problem, which would be impossible to say in a short string. This
is not so for types.

I hope you follow my idea, I don't want you to just blindly agree,
if you actually don't agree and don't want to argue either.

>>> 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) */
>>>   -
>>
>> 12. Please, remove all unnecessary diff.
> fixed
> 
> I have a some additional question:
> "Do I understand correctly that MP_ERROR should not be added to field_def.h/field_def.c or I am wrong?"

I didn't read to that place, but sounds strange. We can't create a field
of type 'error' in a space. We can't store errors anywhere. So it looks
incorrect for field_def to depend on MP_ERROR.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error
  2020-04-16  0:00       ` Vladislav Shpilevoy
@ 2020-04-16  1:11         ` Alexander Turenko
  2020-04-16  8:58           ` lvasiliev
  2020-04-16  8:40         ` lvasiliev
  1 sibling, 1 reply; 37+ messages in thread
From: Alexander Turenko @ 2020-04-16  1:11 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

> >> 2) What to do with stacked errors? Currently only the first
> >> error in the stack gets a traceback, because luaT_pusherror() is
> >> called only on the top error in the stack. Consider this test:
> >>
> >>      box.cfg{}
> >>      lua_code = [[function(tuple)
> >>                      local json = require('json')
> >>                      return json.encode(tuple)
> >>                   end]]
> >>      box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
> >>      s = box.schema.space.create('withdata')
> >>      pk = s:create_index('pk')
> >>      idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})
> >>
> >>      function test_func() return pcall(s.insert, s, {1}) end
> >>      box.error.cfg{traceback_enable = true}
> >>      ok, err = test_func()
> >>
> >>
> >>      tarantool> err:unpack()
> >>      ---
> >>      - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string
> >>          \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382:
> >>          in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725:
> >>          in function <builtin/box/console.lua:713>"
> >>      ... <snipped>

BTW, can we call :split('\n') for .traceback field in at least
__serialize? The cited output is hard to read. Alternative: place two
newlines in row somewhere to force yaml serializer to use multiline
string format.

> >>
> >>      tarantool> err.prev:unpack()
> >>      ---
> >>      - type: LuajitError
> >>        message: '[string "return function(tuple)..."]:2: attempt to call global ''require''
> >>          (a nil value)'
> >>      ... <snipped>
> >>
> >> The second error does not have a traceback at all.
> > (I added Turenko to To)
> > I have two variants:
> > - Leave as is and to document such behavior
> > - Add the same traceback to all errors in the stack
> > Alexander what do you think?

The first approach look inconsistent. A user may want to get a cause of
a topmost error and pass it somewhere. The function, where the error
will be processed (say, serialized), don't know whether a traceback
should be grabbed from some other error object (and how to find it).

> 
> There is a third option - leave traceback out of this patchset for
> a next release. Because they are clearly underdesigned. But on the
> other hand it is not something critical. After all, we just can say,
> that a traceback can be present, or can be not, and its content is
> just a string, which can't be assumed to have any special format.
> 
> That would allow us to add/remove them and change their format anytime.

Are there other problems with traceback?

BTW, how traceback is linked with .trace?

Generally I'm for decomposing problems. Leonid, you may just keep the
feature at top patches of your series. If it'll look ready, it will
land. Otherwise, only first part will land.

I think that it is okay to implement marshalling over net.box first.

WBR, Alexander Turenko.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error
  2020-04-16  0:00       ` Vladislav Shpilevoy
  2020-04-16  1:11         ` Alexander Turenko
@ 2020-04-16  8:40         ` lvasiliev
  2020-04-16  9:04           ` lvasiliev
  1 sibling, 1 reply; 37+ messages in thread
From: lvasiliev @ 2020-04-16  8:40 UTC (permalink / raw)
  To: Vladislav Shpilevoy, Alexander Turenko; +Cc: tarantool-patches

Hi! Thanks you for the feedback.

On 16.04.2020 3:00, Vladislav Shpilevoy wrote:
> Hi! Thanks for the fixes!
> 
>> On 14.04.2020 4:11, Vladislav Shpilevoy wrote:
>>> Thanks for the patch!
>>>
>>> Sorry, some of my comments may contain typos, because it is
>>> very late and I can't continue polishing it today.
>>>
>>> First of all there are some major issues with the
>>> patch, which IMO should be clarified before it is pushed.
>>>
>>> 1) Why do we need the traceback in scope of 4398? It has
>>> nothing to do with marshaling through IProto, nor with
>>> custom error types.
>> This is part of the errors.lua functional, which was attached as an example. After that it was also one of the functionality requested by Nazarov.
> 
> The original ticket does not contain any mentioning of a traceback:
> https://github.com/tarantool/tarantool/issues/4398#issue-475632900
> 
> I agree that it was requested later, in comments, but still it
> looks pretty separate from this, don't you agree?
At the beginning of my work on the task I said that it has three 
separate tasks:
1) Add Custom type.
2) Add traceback.
3) Marshalling through net.box.
But when I tried to send it for review separated it has not of any 
interest to reviewers, because the task didn't seem to be completed (as 
I understand). Or I could not correctly explain my point of view.
So, I agree that this looks like one of the separate parts of the task.
> 
>>> 2) What to do with stacked errors? Currently only the first
>>> error in the stack gets a traceback, because luaT_pusherror() is
>>> called only on the top error in the stack. Consider this test:
>>>
>>>       box.cfg{}
>>>       lua_code = [[function(tuple)
>>>                       local json = require('json')
>>>                       return json.encode(tuple)
>>>                    end]]
>>>       box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
>>>       s = box.schema.space.create('withdata')
>>>       pk = s:create_index('pk')
>>>       idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})
>>>
>>>       function test_func() return pcall(s.insert, s, {1}) end
>>>       box.error.cfg{traceback_enable = true}
>>>       ok, err = test_func()
>>>
>>>
>>>       tarantool> err:unpack()
>>>       ---
>>>       - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string
>>>           \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382:
>>>           in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725:
>>>           in function <builtin/box/console.lua:713>"
>>>       ... <snipped>
>>>
>>>       tarantool> err.prev:unpack()
>>>       ---
>>>       - type: LuajitError
>>>         message: '[string "return function(tuple)..."]:2: attempt to call global ''require''
>>>           (a nil value)'
>>>       ... <snipped>
>>>
>>> The second error does not have a traceback at all.
>> (I added Turenko to To)
>> I have two variants:
>> - Leave as is and to document such behavior
>> - Add the same traceback to all errors in the stack
>> Alexander what do you think?
> 
> There is a third option - leave traceback out of this patchset for
> a next release. Because they are clearly underdesigned. But on the
> other hand it is not something critical. After all, we just can say,
> that a traceback can be present, or can be not, and its content is
> just a string, which can't be assumed to have any special format.
> 
> That would allow us to add/remove them and change their format anytime.
If I understand the problem correctly, now we will not have an error 
traceback for some errors (in the case of the stack diagnostic), if we 
delete the traceback patch, it will not be at all. Perhaps, it is more 
user-friendly to leave a traceback for at least part of the errors. If 
string is the normal format for a traceback (and I think it is), then it 
seems to me good to leave it as is and finalize in the future with some 
users feedbacks. But, if string is a bad data format for traceback then 
patch must be deleted from the patchset.
> 
>>> diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
>>> index 4432bc89b..a616e87a4 100644
>>> --- a/src/box/lua/error.cc
>>> +++ b/src/box/lua/error.cc
>>> @@ -112,9 +112,11 @@ raise:
>>>        }
>>>          struct error *err = box_error_new(file, line, code, "%s", reason);
>>> -    if (tb_parsed)
>>> -        err->traceback_mode = tb_mode;
>>> -
>>> +    /*
>>> +     * Explicit traceback option overrides the global setting.
>>> +     */
>>> +    if (err != NULL && is_traceback_specified)
>> 1) box_error_new don't return NULL
> 
> True, then 'err != NULL' check can be dropped.
> 
>>>    diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c
>>> index 1caa75ee9..833454bac 100644
>>> --- a/src/lib/core/diag.c
>>> +++ b/src/lib/core/diag.c
>>> @@ -128,27 +125,14 @@ error_vformat_msg(struct error *e, const char *format, va_list ap)
>>> -void
>>> -error_set_traceback_supplementation(bool traceback_mode)
>>> +error_set_traceback(struct error *e, const char *traceback)
>>>    {
>>> -    global_traceback_mode = traceback_mode;
>>> +    assert(e->traceback == NULL);
>> 2) Do I understand correctly that asserts only work on debug? Will this approach not be dangerous by memory leaks on release?
> 
> It should not be. Because the method is not public, and
> in private methods it is never called with non-zero e->traceback.
Ok, I understand you point of view.
> 
>>> +    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 f38009c54..e918d3089 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;
>> 3) Why a global variable is more preferable than static?
> 
> Static variables are also global. I just removed the setter, since
> it is trivial.
Hmm, ok.
> 
>>>    diff --git a/src/lua/error.lua b/src/lua/error.lua
>>> index 46d28667f..535110588 100644
>>> --- a/src/lua/error.lua
>>> +++ b/src/lua/error.lua
>>> @@ -26,8 +26,8 @@ struct error {
>>>        char _errmsg[DIAG_ERRMSG_MAX];
>>>        struct error *_cause;
>>>        struct error *_effect;
>>> -    char *lua_traceback;
>>> -    bool traceback_mode;
>>> +    char *traceback;
>> 4) Replaced to err_traceback. It overlaps the traceback getter.
>>> +    bool is_traceback_enabled;
> 
> Better replace it with _traceback. Similar to other members.
Ok, I will replace it in the new version of the patchset if the 
traceback patch will be saved. But I can replace only traceback and 
traceback_mode(is_traceback_enabled), because other is "Unneccessary diff".
> 

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error
  2020-04-16  1:11         ` Alexander Turenko
@ 2020-04-16  8:58           ` lvasiliev
  2020-04-16  9:30             ` Alexander Turenko
  0 siblings, 1 reply; 37+ messages in thread
From: lvasiliev @ 2020-04-16  8:58 UTC (permalink / raw)
  To: Alexander Turenko, Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thanks for the feedback.

On 16.04.2020 4:11, Alexander Turenko wrote:
>>>> 2) What to do with stacked errors? Currently only the first
>>>> error in the stack gets a traceback, because luaT_pusherror() is
>>>> called only on the top error in the stack. Consider this test:
>>>>
>>>>       box.cfg{}
>>>>       lua_code = [[function(tuple)
>>>>                       local json = require('json')
>>>>                       return json.encode(tuple)
>>>>                    end]]
>>>>       box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
>>>>       s = box.schema.space.create('withdata')
>>>>       pk = s:create_index('pk')
>>>>       idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})
>>>>
>>>>       function test_func() return pcall(s.insert, s, {1}) end
>>>>       box.error.cfg{traceback_enable = true}
>>>>       ok, err = test_func()
>>>>
>>>>
>>>>       tarantool> err:unpack()
>>>>       ---
>>>>       - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string
>>>>           \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382:
>>>>           in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725:
>>>>           in function <builtin/box/console.lua:713>"
>>>>       ... <snipped>
> 
> BTW, can we call :split('\n') for .traceback field in at least
> __serialize? The cited output is hard to read. Alternative: place two
> newlines in row somewhere to force yaml serializer to use multiline
> string format.

Traceback is absent in __serialize, because it will change the error 
view for old client. If the client matches result with some pattern it, 
will be broken.

>>>>
>>>>       tarantool> err.prev:unpack()
>>>>       ---
>>>>       - type: LuajitError
>>>>         message: '[string "return function(tuple)..."]:2: attempt to call global ''require''
>>>>           (a nil value)'
>>>>       ... <snipped>
>>>>
>>>> The second error does not have a traceback at all.
>>> (I added Turenko to To)
>>> I have two variants:
>>> - Leave as is and to document such behavior
>>> - Add the same traceback to all errors in the stack
>>> Alexander what do you think?
> 
> The first approach look inconsistent. A user may want to get a cause of
> a topmost error and pass it somewhere. The function, where the error
> will be processed (say, serialized), don't know whether a traceback
> should be grabbed from some other error object (and how to find it).
> 
Not quite, you either have a traceback or not. Don't try to get it from 
another error.
>>
>> There is a third option - leave traceback out of this patchset for
>> a next release. Because they are clearly underdesigned. But on the
>> other hand it is not something critical. After all, we just can say,
>> that a traceback can be present, or can be not, and its content is
>> just a string, which can't be assumed to have any special format.
>>
>> That would allow us to add/remove them and change their format anytime.
> 
> Are there other problems with traceback?
> 
> BTW, how traceback is linked with .trace?
> 
> Generally I'm for decomposing problems. Leonid, you may just keep the
> feature at top patches of your series. If it'll look ready, it will
> land. Otherwise, only first part will land.
> 
> I think that it is okay to implement marshalling over net.box first.
> 
> WBR, Alexander Turenko.
> 
As I said here 3 separate parts (Custom type, traceback and marshalling) 
but I don’t want to shuffle the patchset (patches have some dependencies 
from previous). I suggest just making a decision whether we will add a 
traceback or not. If not, I will simply remove this patch.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error
  2020-04-16  8:40         ` lvasiliev
@ 2020-04-16  9:04           ` lvasiliev
  0 siblings, 0 replies; 37+ messages in thread
From: lvasiliev @ 2020-04-16  9:04 UTC (permalink / raw)
  To: Vladislav Shpilevoy, Alexander Turenko; +Cc: tarantool-patches

Hi!

On 16.04.2020 11:40, lvasiliev wrote:
>> Better replace it with _traceback. Similar to other members.
> Ok, I will replace it in the new version of the patchset if the 
> traceback patch will be saved. But I can replace only traceback and 
> traceback_mode(is_traceback_enabled), because other is "Unneccessary diff".
Sorry, I was wrong. I will replace it in the new version of the patchset.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type
  2020-04-16  0:02       ` Vladislav Shpilevoy
@ 2020-04-16  9:18         ` lvasiliev
  2020-04-16 21:03           ` Vladislav Shpilevoy
  0 siblings, 1 reply; 37+ messages in thread
From: lvasiliev @ 2020-04-16  9:18 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thanks you for the feedback.

On 16.04.2020 3:02, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
>>> 6. Make_type() perfectly fits in one line.
>>>
>>>> +
>>>> +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 ?: "");
>>>
>>> 7. custom_type is never NULL.
>> Why? It's a constructor argument.
> 
> And still it is never NULL. I couldn't find any place, where
> you passed NULL here, and anyway that would be incorrect. Type
> can't be just NULL. How to present it in Lua then? nil? Doubt
> it.
Hmm, ok.
> 
>>>> + */
>>>> +const char *
>>>> +box_custom_error_type(const box_error_t *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.
>>>> @@ -155,15 +166,24 @@ box_error_add(const char *file, unsigned line, uint32_t code,
>>>>      /**
>>>>     * Construct error object without setting it in the diagnostics
>>>> - * area. On the memory allocation fail returns NULL.
>>>> + * area. On the memory allocation fail returns  OutOfMemory error.
>>>
>>> 10. Unneccessary diff.
>> The comment is wrong. How I can fix it?
> 
> Yeah, I just realized that after you said box_error_new() never
> returns NULL. Then probably it is ok to change it.
Ok. I will replace it in the new version of the patchset.
> 
>>>
>>>>     */
>>>>    struct error *
>>>>    box_error_new(const char *file, unsigned line, uint32_t code,
>>>>              const char *fmt, ...);
>>>>    +/**
>>>> + * Construct error object without setting it in the diagnostics
>>>> + * area. On the memory allocation fail returns OutOfMemory error.
>>>
>>> 11. This is a copy-paste box box_error_new() comment. Better write
>>> here a couple of words how is it different. Or better merge box_error_new()
>>> and box_custom_error_new(). Because in Lua I can specify both code
>>> and type. Don't see why can't I do this in C.
>> I thought we can’t change public API.
> 
> This is not public API. If something is not compiled into module.h,
> it is not public (in C. For Lua rules are different).
> 
>>>> @@ -111,9 +148,16 @@ raise:
>>>>            line = info.currentline;
>>>>        }
>>>>    -    struct error *err = box_error_new(file, line, code, "%s", reason);
>>>> -    if (tb_parsed)
>>>> -        err->traceback_mode = tb_mode;
>>>> +    struct error *err = NULL;
>>>> +    if (args.custom) {
>>>> +        err = box_custom_error_new(file, line, args.custom,
>>>> +                       "%s", args.reason);
>>>
>>> 18. Code argument is ignored here. Although I am not sure it shouldn't
>>> be ignored. From what I remember, we decided that code should stay,
>>> but I may be wrong.
>> CustomError type has a predefinded code. Why it shouldn't be ignored?
> 
> Because it is not a 'ClientError' object from user's point of view. The
> fact that we implemented it as a descendant of 'ClientError' class should
> not leak into the API. User can't assume anything about error hierarchies.
> He does not even see them.
> 
>  From user's point of view there are separate built-in 'ClientError'. And
> separate 'CustomError'. Both accept error code now. But 'CustomError'
> ignores it somewhy. Does not look right.
> 
No, from user's point of view it have only type without any codes (In 
accordance with the task). Besides, I think it's strange to have an 
AccessDeniedError with code "ER_SQL_PREPARE" (other branch of 
hierarchy). And user doesn't have an access to "code" of error.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error
  2020-04-16  8:58           ` lvasiliev
@ 2020-04-16  9:30             ` Alexander Turenko
  2020-04-16 12:27               ` lvasiliev
  0 siblings, 1 reply; 37+ messages in thread
From: Alexander Turenko @ 2020-04-16  9:30 UTC (permalink / raw)
  To: lvasiliev; +Cc: tarantool-patches, Vladislav Shpilevoy

On Thu, Apr 16, 2020 at 11:58:03AM +0300, lvasiliev wrote:
> Hi! Thanks for the feedback.
> 
> On 16.04.2020 4:11, Alexander Turenko wrote:
> > > > > 2) What to do with stacked errors? Currently only the first
> > > > > error in the stack gets a traceback, because luaT_pusherror() is
> > > > > called only on the top error in the stack. Consider this test:
> > > > > 
> > > > >       box.cfg{}
> > > > >       lua_code = [[function(tuple)
> > > > >                       local json = require('json')
> > > > >                       return json.encode(tuple)
> > > > >                    end]]
> > > > >       box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
> > > > >       s = box.schema.space.create('withdata')
> > > > >       pk = s:create_index('pk')
> > > > >       idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})
> > > > > 
> > > > >       function test_func() return pcall(s.insert, s, {1}) end
> > > > >       box.error.cfg{traceback_enable = true}
> > > > >       ok, err = test_func()
> > > > > 
> > > > > 
> > > > >       tarantool> err:unpack()
> > > > >       ---
> > > > >       - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string
> > > > >           \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382:
> > > > >           in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725:
> > > > >           in function <builtin/box/console.lua:713>"
> > > > >       ... <snipped>
> > 
> > BTW, can we call :split('\n') for .traceback field in at least
> > __serialize? The cited output is hard to read. Alternative: place two
> > newlines in row somewhere to force yaml serializer to use multiline
> > string format.
> 
> Traceback is absent in __serialize, because it will change the error view
> for old client. If the client matches result with some pattern it, will be
> broken.

It is better to keep __serialize on track with newly added fields. I
would not bother with possibility that someone may call __serialize
manually or capture console output to compare against a sample.
Extending a map should be okay from backward-compatibility point of
view.

> 
> > > > > 
> > > > >       tarantool> err.prev:unpack()
> > > > >       ---
> > > > >       - type: LuajitError
> > > > >         message: '[string "return function(tuple)..."]:2: attempt to call global ''require''
> > > > >           (a nil value)'
> > > > >       ... <snipped>
> > > > > 
> > > > > The second error does not have a traceback at all.
> > > > (I added Turenko to To)
> > > > I have two variants:
> > > > - Leave as is and to document such behavior
> > > > - Add the same traceback to all errors in the stack
> > > > Alexander what do you think?
> > 
> > The first approach look inconsistent. A user may want to get a cause of
> > a topmost error and pass it somewhere. The function, where the error
> > will be processed (say, serialized), don't know whether a traceback
> > should be grabbed from some other error object (and how to find it).
> > 
> Not quite, you either have a traceback or not. Don't try to get it from
> another error.

You propose to introduce some kind of 'full' and 'partial' errors. It is
hard to document, because there is no rationale for this. When something
is introduced, it should be for the sake of something.

A kind of 'the API is complex, but, I see, it is highly flexible' or
'here I should take care of this peculiar, but OTOH some cases may be
processed much faster due to this'.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO
  2020-04-16  0:06       ` Vladislav Shpilevoy
@ 2020-04-16  9:36         ` lvasiliev
  2020-04-16 21:04           ` Vladislav Shpilevoy
  0 siblings, 1 reply; 37+ messages in thread
From: lvasiliev @ 2020-04-16  9:36 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thanks you for the feedback.

On 16.04.2020 3:06, Vladislav Shpilevoy wrote:
> Hi! Thanks for the fixes!
> 
>>>> @TarantoolBot document
>>>>       Title: Add session_setting
>>>
>>> The doc request is corrupted, docbot won't understand that because
>>> of leading whitespaces.
>>>
>>>> iproto_error_format setting has been added to _session_settings
>>>
>>> Looks like there is a lack of global setting similar to what we had
>>> for tracebacks. Currently, when the option is false (by default), and
>>> I want to use the new format everywhere, I need to find every single
>>> place where I create a new session, and put there code saying
>>>
>>>       box.session.settings.error_format = new/old/whatever
>>>
>>> I think there should be a global option when a user's application is
>>> ready to switch to the new format completely. Otherwise it is going
>>> to be hell to find all places where a new session is created, and patch
>>> them.
>>>
>>> Just a reminder - every fiber.new(), fiber.create() creates a session,
>>> every iproto connection is a session.
>> This was discussed in TPT chat with Mons and Nazarov, and after that with you, Turenko, Mons ... in zoom. I was orienteted by the net.box session and I might not know something. Where do you suggest storing the setting?
> 
> Looks like you didn't understood me. I don't propose to remove the
> session local option. I propose to provide an ability to override it
> with a global option if necessary. So we have session option by
> default with the old format, and global option unset.
> 
> If someone is ready to start using new errors everywhere, he just turns
> the global option on.
> 
> If someone is not ready, he continues using session local option.
> 
> Why that may be needed - I said above. Turning that option on for every
> session is going to be very tricky. But maybe it can be moved to the next
> release (the global option). Don't know. The issue is already a huge
> blackhole which absorbed lots of independent initially unplanned
> features. The more we move for later, the better.
> 
> You should try to ask users in the red chat. It helps sometimes, they
> can share their opinions and experience of upgrades. Just formulate your
> question in a short but detailed manner, provide a couple of
> ready-to-choose answers, probably a poll.
> 
Ok. But for beginning, I need to understand where this flag can be
located. I will think about that but if you have a proposal, please,
write it.
>>>> +        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1)
>>>
>>> 3. This won't work in case it is an instance bootstrapped from the master
>>> branch before 2.4.1 was released. I don't know how to fix it now.
>> Sorry, I think this should not work until the release.
> 
> Exactly for 'between-releases' users we introduced the new versioning schema
> with 4 numbers. I don't think we can just say fuck off to them now. I
> would better drop this from the patchset and added when somebody explicitly
> asks for that, with good design and planning.
> 
Ok.I have remove it, but:"Don't have 'between-releases' users a version 
more when previous release?"
>>>
>>>> +        if not ext_err_supported then
>>>> +            box.error(box.error.PROC_LUA,
>>>> +                      "Server doesn't support extended error format")
>>>> +        end
>>>> +        remote.space._session_settings:update('iproto_error_format',
>>>> +                                              {{'=', 2, 1}})
>>>
>>> 4. This is additional network hop. I don't think it should be done
>>> automatically. I would let users do that.
>> It's network hop only if option will be set.As optimization it can be included to auth packet in future.
> 
> Auth packets have nothing to do with session settings. It would be
> encapsulation violation. Moreover, in addition to settings we may
> want to configure more things in future. What if an encryption will
> be added to Tarantool? What if we will add key exchange? I think we
> should not add a new option + 1 network hop for every 'negotiation'
> thing. Better omit this for now and design this properly when we have
> more time.
> 
> I don't propose to remove your code. I just propose to finish some
> non-critical parts of it later.
> 
ok

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding
  2020-04-16  0:11       ` Vladislav Shpilevoy
@ 2020-04-16 10:04         ` lvasiliev
  2020-04-16 21:06           ` Vladislav Shpilevoy
  0 siblings, 1 reply; 37+ messages in thread
From: lvasiliev @ 2020-04-16 10:04 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches



On 16.04.2020 3:11, Vladislav Shpilevoy wrote:
> Hi! Thanks for the fixes!
> 
>>> 7. Why are these values in a enum? I thought we
>>> decided to use string types?
>> Now we compare a string with error type when encode and use number further. If we encode error type as string we still have to compare the strings when decoding to create the error of the desired type. But number takes up less space versus string  when transmitting over a network and makes it possible to use a switch when create an error after decoding (which looks much nicer).
> 
> Wait, so this is inside the MessagePack too? This is not a good
> idea really. I thought you just converted strings to enums and
> then enums to classes.
Yes. It's not inside the MessagePack. MP_ERROR decodes by
external handler as we discussed. It registered by
luamp_set_decode_extension().

> 
> First about 'less space'. These are error objects. It does not
> really matter if every one of them becomes +10 or even +20 bytes
> if we use string types instead of numbers. They are not critical
> in any aspect, not in perf nor in space.
> 
> Second about switch, 'which looks nicer'. Nicely formatted if-else
> sequence also look fine. This is subjective though. I won't argue.
> 
> Thirdly, about why I think string types are better. The problem is
> that I don't want us to document all the error types as numbers
> and support them forever. When we use strings, we easily can remove
> old errors, add new errors. When we use numbers, we
> 
>      1) will need to document what every number means. Strings are
>      self-documenting. OutOfMemory means out of memory, obviously.
>      And so on.
Ok.
> 
>      2) will one day have holes in these numbers left from removed
>      errors, this won't look nice, trust me.
Ok.
> 
>      3) that complicates compatibility. What if some error type was
>      added to a newer tarantool version, and an old connector connected
>      to the instance? How will it handle the new error types? With
>      string types the problem does not exist.
If I understand you correctly, you will have the same problem
with string, because you can't create an error of unknown type.
> 
> Numbers are fine for error codes. For example, SQL drivers define
> certain error codes as kind of a standard, and that simplifies
> support. Also an error code can describe actually a pretty big
> problem, which would be impossible to say in a short string. This
> is not so for types.
> 
> I hope you follow my idea, I don't want you to just blindly agree,
> if you actually don't agree and don't want to argue either.
I will update this in the future version of the patchset.
> 
>>>> 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) */
>>>>    -
>>>
>>> 12. Please, remove all unnecessary diff.
>> fixed
>>
>> I have a some additional question:
>> "Do I understand correctly that MP_ERROR should not be added to field_def.h/field_def.c or I am wrong?"
> 
> I didn't read to that place, but sounds strange. We can't create a field
> of type 'error' in a space. We can't store errors anywhere. So it looks
> incorrect for field_def to depend on MP_ERROR.
> 
As I understand, you haven't reviewed this patch. Please see version
from patchset V3. I will apply all updates after the review, I assume
there will be questions and objections.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error
  2020-04-16  9:30             ` Alexander Turenko
@ 2020-04-16 12:27               ` lvasiliev
  2020-04-16 12:45                 ` Alexander Turenko
  0 siblings, 1 reply; 37+ messages in thread
From: lvasiliev @ 2020-04-16 12:27 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches, Vladislav Shpilevoy



On 16.04.2020 12:30, Alexander Turenko wrote:
> On Thu, Apr 16, 2020 at 11:58:03AM +0300, lvasiliev wrote:
>> Hi! Thanks for the feedback.
>>
>> On 16.04.2020 4:11, Alexander Turenko wrote:
>>>>>> 2) What to do with stacked errors? Currently only the first
>>>>>> error in the stack gets a traceback, because luaT_pusherror() is
>>>>>> called only on the top error in the stack. Consider this test:
>>>>>>
>>>>>>        box.cfg{}
>>>>>>        lua_code = [[function(tuple)
>>>>>>                        local json = require('json')
>>>>>>                        return json.encode(tuple)
>>>>>>                     end]]
>>>>>>        box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
>>>>>>        s = box.schema.space.create('withdata')
>>>>>>        pk = s:create_index('pk')
>>>>>>        idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})
>>>>>>
>>>>>>        function test_func() return pcall(s.insert, s, {1}) end
>>>>>>        box.error.cfg{traceback_enable = true}
>>>>>>        ok, err = test_func()
>>>>>>
>>>>>>
>>>>>>        tarantool> err:unpack()
>>>>>>        ---
>>>>>>        - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string
>>>>>>            \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382:
>>>>>>            in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725:
>>>>>>            in function <builtin/box/console.lua:713>"
>>>>>>        ... <snipped>
>>>
>>> BTW, can we call :split('\n') for .traceback field in at least
>>> __serialize? The cited output is hard to read. Alternative: place two
>>> newlines in row somewhere to force yaml serializer to use multiline
>>> string format.
>>
>> Traceback is absent in __serialize, because it will change the error view
>> for old client. If the client matches result with some pattern it, will be
>> broken.
> 
> It is better to keep __serialize on track with newly added fields. I
> would not bother with possibility that someone may call __serialize
> manually or capture console output to compare against a sample.
> Extending a map should be okay from backward-compatibility point of
> view.
> 
Now __serialize is used for transfer old style error too.
>>
>>>>>>
>>>>>>        tarantool> err.prev:unpack()
>>>>>>        ---
>>>>>>        - type: LuajitError
>>>>>>          message: '[string "return function(tuple)..."]:2: attempt to call global ''require''
>>>>>>            (a nil value)'
>>>>>>        ... <snipped>
>>>>>>
>>>>>> The second error does not have a traceback at all.
>>>>> (I added Turenko to To)
>>>>> I have two variants:
>>>>> - Leave as is and to document such behavior
>>>>> - Add the same traceback to all errors in the stack
>>>>> Alexander what do you think?
>>>
>>> The first approach look inconsistent. A user may want to get a cause of
>>> a topmost error and pass it somewhere. The function, where the error
>>> will be processed (say, serialized), don't know whether a traceback
>>> should be grabbed from some other error object (and how to find it).
>>>
>> Not quite, you either have a traceback or not. Don't try to get it from
>> another error.
> 
> You propose to introduce some kind of 'full' and 'partial' errors. It is
> hard to document, because there is no rationale for this. When something
> is introduced, it should be for the sake of something.
No, the error without traceback is not 'partial'. If
global error_is_traceback_enabled is false - all errors haven't a
traceback. If error creates with traceback=false, it hasn't a traceback.
> 
> A kind of 'the API is complex, but, I see, it is highly flexible' or
> 'here I should take care of this peculiar, but OTOH some cases may be
> processed much faster due to this'.
> 
You are hyperbolizing. It can be regarded as "Technical debt".
If you insist, I can remove this patch.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error
  2020-04-16 12:27               ` lvasiliev
@ 2020-04-16 12:45                 ` Alexander Turenko
  2020-04-16 17:48                   ` lvasiliev
  0 siblings, 1 reply; 37+ messages in thread
From: Alexander Turenko @ 2020-04-16 12:45 UTC (permalink / raw)
  To: lvasiliev; +Cc: tarantool-patches, Vladislav Shpilevoy

On Thu, Apr 16, 2020 at 03:27:35PM +0300, lvasiliev wrote:
> 
> 
> On 16.04.2020 12:30, Alexander Turenko wrote:
> > On Thu, Apr 16, 2020 at 11:58:03AM +0300, lvasiliev wrote:
> > > Hi! Thanks for the feedback.
> > > 
> > > On 16.04.2020 4:11, Alexander Turenko wrote:
> > > > > > > 2) What to do with stacked errors? Currently only the first
> > > > > > > error in the stack gets a traceback, because luaT_pusherror() is
> > > > > > > called only on the top error in the stack. Consider this test:
> > > > > > > 
> > > > > > >        box.cfg{}
> > > > > > >        lua_code = [[function(tuple)
> > > > > > >                        local json = require('json')
> > > > > > >                        return json.encode(tuple)
> > > > > > >                     end]]
> > > > > > >        box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
> > > > > > >        s = box.schema.space.create('withdata')
> > > > > > >        pk = s:create_index('pk')
> > > > > > >        idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})
> > > > > > > 
> > > > > > >        function test_func() return pcall(s.insert, s, {1}) end
> > > > > > >        box.error.cfg{traceback_enable = true}
> > > > > > >        ok, err = test_func()
> > > > > > > 
> > > > > > > 
> > > > > > >        tarantool> err:unpack()
> > > > > > >        ---
> > > > > > >        - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string
> > > > > > >            \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382:
> > > > > > >            in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725:
> > > > > > >            in function <builtin/box/console.lua:713>"
> > > > > > >        ... <snipped>
> > > > 
> > > > BTW, can we call :split('\n') for .traceback field in at least
> > > > __serialize? The cited output is hard to read. Alternative: place two
> > > > newlines in row somewhere to force yaml serializer to use multiline
> > > > string format.
> > > 
> > > Traceback is absent in __serialize, because it will change the error view
> > > for old client. If the client matches result with some pattern it, will be
> > > broken.
> > 
> > It is better to keep __serialize on track with newly added fields. I
> > would not bother with possibility that someone may call __serialize
> > manually or capture console output to compare against a sample.
> > Extending a map should be okay from backward-compatibility point of
> > view.
> > 
> Now __serialize is used for transfer old style error too.

As we discussed, a serializer should not see at __serialize of a type it
knows natively. Let's keep __serialize for REPL only.

> > > 
> > > > > > > 
> > > > > > >        tarantool> err.prev:unpack()
> > > > > > >        ---
> > > > > > >        - type: LuajitError
> > > > > > >          message: '[string "return function(tuple)..."]:2: attempt to call global ''require''
> > > > > > >            (a nil value)'
> > > > > > >        ... <snipped>
> > > > > > > 
> > > > > > > The second error does not have a traceback at all.
> > > > > > (I added Turenko to To)
> > > > > > I have two variants:
> > > > > > - Leave as is and to document such behavior
> > > > > > - Add the same traceback to all errors in the stack
> > > > > > Alexander what do you think?
> > > > 
> > > > The first approach look inconsistent. A user may want to get a cause of
> > > > a topmost error and pass it somewhere. The function, where the error
> > > > will be processed (say, serialized), don't know whether a traceback
> > > > should be grabbed from some other error object (and how to find it).
> > > > 
> > > Not quite, you either have a traceback or not. Don't try to get it from
> > > another error.
> > 
> > You propose to introduce some kind of 'full' and 'partial' errors. It is
> > hard to document, because there is no rationale for this. When something
> > is introduced, it should be for the sake of something.
> No, the error without traceback is not 'partial'. If
> global error_is_traceback_enabled is false - all errors haven't a
> traceback. If error creates with traceback=false, it hasn't a traceback.

So we anyway need to docuemnt a rule when an error will contain the
traceback and the way you implement it does not add much complexity.

I can buy this point.

> > 
> > A kind of 'the API is complex, but, I see, it is highly flexible' or
> > 'here I should take care of this peculiar, but OTOH some cases may be
> > processed much faster due to this'.
> > 
> You are hyperbolizing. It can be regarded as "Technical debt".
> If you insist, I can remove this patch.

I didn't review the patchset, just looked at the question, so I cannot
insist here.

Personally I have questions around traceback (see [1]), so I would
prefer safe way: concentrate on marshaling, postpone traceback.

However I don't feel myself in power to decide.

[1]: https://lists.tarantool.org/pipermail/tarantool-patches/2020-April/016043.html

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error
  2020-04-16 12:45                 ` Alexander Turenko
@ 2020-04-16 17:48                   ` lvasiliev
  0 siblings, 0 replies; 37+ messages in thread
From: lvasiliev @ 2020-04-16 17:48 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches, Vladislav Shpilevoy

Hi! Thanks for the feedback.

On 16.04.2020 15:45, Alexander Turenko wrote:
> On Thu, Apr 16, 2020 at 03:27:35PM +0300, lvasiliev wrote:
>>
>>
>> On 16.04.2020 12:30, Alexander Turenko wrote:
>>> On Thu, Apr 16, 2020 at 11:58:03AM +0300, lvasiliev wrote:
>>>> Hi! Thanks for the feedback.
>>>>
>>>> On 16.04.2020 4:11, Alexander Turenko wrote:
>>>>>>>> 2) What to do with stacked errors? Currently only the first
>>>>>>>> error in the stack gets a traceback, because luaT_pusherror() is
>>>>>>>> called only on the top error in the stack. Consider this test:
>>>>>>>>
>>>>>>>>         box.cfg{}
>>>>>>>>         lua_code = [[function(tuple)
>>>>>>>>                         local json = require('json')
>>>>>>>>                         return json.encode(tuple)
>>>>>>>>                      end]]
>>>>>>>>         box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
>>>>>>>>         s = box.schema.space.create('withdata')
>>>>>>>>         pk = s:create_index('pk')
>>>>>>>>         idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})
>>>>>>>>
>>>>>>>>         function test_func() return pcall(s.insert, s, {1}) end
>>>>>>>>         box.error.cfg{traceback_enable = true}
>>>>>>>>         ok, err = test_func()
>>>>>>>>
>>>>>>>>
>>>>>>>>         tarantool> err:unpack()
>>>>>>>>         ---
>>>>>>>>         - traceback: "stack traceback:\n\t[C]: at 0x010014d1b0\n\t[C]: in function 'test_func'\n\t[string
>>>>>>>>             \"ok, err = test_func()\"]:1: in main chunk\n\t[C]: in function 'pcall'\n\tbuiltin/box/console.lua:382:
>>>>>>>>             in function 'eval'\n\tbuiltin/box/console.lua:676: in function 'repl'\n\tbuiltin/box/console.lua:725:
>>>>>>>>             in function <builtin/box/console.lua:713>"
>>>>>>>>         ... <snipped>
>>>>>
>>>>> BTW, can we call :split('\n') for .traceback field in at least
>>>>> __serialize? The cited output is hard to read. Alternative: place two
>>>>> newlines in row somewhere to force yaml serializer to use multiline
>>>>> string format.
>>>>
>>>> Traceback is absent in __serialize, because it will change the error view
>>>> for old client. If the client matches result with some pattern it, will be
>>>> broken.
>>>
>>> It is better to keep __serialize on track with newly added fields. I
>>> would not bother with possibility that someone may call __serialize
>>> manually or capture console output to compare against a sample.
>>> Extending a map should be okay from backward-compatibility point of
>>> view.
>>>
>> Now __serialize is used for transfer old style error too.
> 
> As we discussed, a serializer should not see at __serialize of a type it
> knows natively. Let's keep __serialize for REPL only.
> 
Discussed with Alexander. Will be moved to a separate task. Now we
continue to use __serialize to transfer the error through net.box in the 
old style.
>>>>
>>>>>>>>
>>>>>>>>         tarantool> err.prev:unpack()
>>>>>>>>         ---
>>>>>>>>         - type: LuajitError
>>>>>>>>           message: '[string "return function(tuple)..."]:2: attempt to call global ''require''
>>>>>>>>             (a nil value)'
>>>>>>>>         ... <snipped>
>>>>>>>>
>>>>>>>> The second error does not have a traceback at all.
>>>>>>> (I added Turenko to To)
>>>>>>> I have two variants:
>>>>>>> - Leave as is and to document such behavior
>>>>>>> - Add the same traceback to all errors in the stack
>>>>>>> Alexander what do you think?
>>>>>
>>>>> The first approach look inconsistent. A user may want to get a cause of
>>>>> a topmost error and pass it somewhere. The function, where the error
>>>>> will be processed (say, serialized), don't know whether a traceback
>>>>> should be grabbed from some other error object (and how to find it).
>>>>>
>>>> Not quite, you either have a traceback or not. Don't try to get it from
>>>> another error.
>>>
>>> You propose to introduce some kind of 'full' and 'partial' errors. It is
>>> hard to document, because there is no rationale for this. When something
>>> is introduced, it should be for the sake of something.
>> No, the error without traceback is not 'partial'. If
>> global error_is_traceback_enabled is false - all errors haven't a
>> traceback. If error creates with traceback=false, it hasn't a traceback.
> 
> So we anyway need to docuemnt a rule when an error will contain the
> traceback and the way you implement it does not add much complexity.
> 
> I can buy this point.
>  >>>
>>> A kind of 'the API is complex, but, I see, it is highly flexible' or
>>> 'here I should take care of this peculiar, but OTOH some cases may be
>>> processed much faster due to this'.
>>>
>> You are hyperbolizing. It can be regarded as "Technical debt".
>> If you insist, I can remove this patch.
> 
> I didn't review the patchset, just looked at the question, so I cannot
> insist here.
> 
> Personally I have questions around traceback (see [1]), so I would
> prefer safe way: concentrate on marshaling, postpone traceback.
> 
> However I don't feel myself in power to decide.
> 
> [1]: https://lists.tarantool.org/pipermail/tarantool-patches/2020-April/016043.html
>
We omit traceback and implement it later (after discussion with
Alexander Turenko).

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type
  2020-04-16  9:18         ` lvasiliev
@ 2020-04-16 21:03           ` Vladislav Shpilevoy
  0 siblings, 0 replies; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-16 21:03 UTC (permalink / raw)
  To: lvasiliev; +Cc: tarantool-patches

Hi! Thanks for the answers!

>>>>> @@ -111,9 +148,16 @@ raise:
>>>>>            line = info.currentline;
>>>>>        }
>>>>>    -    struct error *err = box_error_new(file, line, code, "%s", reason);
>>>>> -    if (tb_parsed)
>>>>> -        err->traceback_mode = tb_mode;
>>>>> +    struct error *err = NULL;
>>>>> +    if (args.custom) {
>>>>> +        err = box_custom_error_new(file, line, args.custom,
>>>>> +                       "%s", args.reason);
>>>>
>>>> 18. Code argument is ignored here. Although I am not sure it shouldn't
>>>> be ignored. From what I remember, we decided that code should stay,
>>>> but I may be wrong.
>>> CustomError type has a predefinded code. Why it shouldn't be ignored?
>>
>> Because it is not a 'ClientError' object from user's point of view. The
>> fact that we implemented it as a descendant of 'ClientError' class should
>> not leak into the API. User can't assume anything about error hierarchies.
>> He does not even see them.
>>
>>  From user's point of view there are separate built-in 'ClientError'. And
>> separate 'CustomError'. Both accept error code now. But 'CustomError'
>> ignores it somewhy. Does not look right.
>>
> No, from user's point of view it have only type without any codes (In accordance with the task).

The task says, I cite: "Ability to skip error codes if we don't need them".
Not drop them. But skip when don't need. So essentially - make the code an
optional parameter.

> Besides, I think it's strange to have an AccessDeniedError with code "ER_SQL_PREPARE" (other branch of hierarchy).

How AccessDeniedError is related to that? It is a different error type. Not
ClientError, not CustomError. It does not have a code at all.

> And user doesn't have an access to "code" of error.

Well, they actually have. This is master branch:

    tarantool> e = box.error.new(1000, 'Message')
    ---
    ...

    tarantool> e.code
    ---
    - 1000
    ...

Talking of "user's point of view" regarding codes: ClientError has one
meaning for error code. CustomError can have another. When you will add
payload, users will add their codes for sure. Error code is something
very basic for any more or less general error type. And if some of them
will match our codes, it does not mean they have the same meaning.

Some error types don't have a code - they type is enough. Such as
AccessDeniedError.

box.error.* codes only valid for ClientError objects.

CustomError does not have any mapping of error codes. A user can define
it.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO
  2020-04-16  9:36         ` lvasiliev
@ 2020-04-16 21:04           ` Vladislav Shpilevoy
  0 siblings, 0 replies; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-16 21:04 UTC (permalink / raw)
  To: lvasiliev; +Cc: tarantool-patches

Thanks for the answers!

>>>>> @TarantoolBot document
>>>>>       Title: Add session_setting
>>>>
>>>> The doc request is corrupted, docbot won't understand that because
>>>> of leading whitespaces.
>>>>
>>>>> iproto_error_format setting has been added to _session_settings
>>>>
>>>> Looks like there is a lack of global setting similar to what we had
>>>> for tracebacks. Currently, when the option is false (by default), and
>>>> I want to use the new format everywhere, I need to find every single
>>>> place where I create a new session, and put there code saying
>>>>
>>>>       box.session.settings.error_format = new/old/whatever
>>>>
>>>> I think there should be a global option when a user's application is
>>>> ready to switch to the new format completely. Otherwise it is going
>>>> to be hell to find all places where a new session is created, and patch
>>>> them.
>>>>
>>>> Just a reminder - every fiber.new(), fiber.create() creates a session,
>>>> every iproto connection is a session.
>>> This was discussed in TPT chat with Mons and Nazarov, and after that with you, Turenko, Mons ... in zoom. I was orienteted by the net.box session and I might not know something. Where do you suggest storing the setting?
>>
>> Looks like you didn't understood me. I don't propose to remove the
>> session local option. I propose to provide an ability to override it
>> with a global option if necessary. So we have session option by
>> default with the old format, and global option unset.
>>
>> If someone is ready to start using new errors everywhere, he just turns
>> the global option on.
>>
>> If someone is not ready, he continues using session local option.
>>
>> Why that may be needed - I said above. Turning that option on for every
>> session is going to be very tricky. But maybe it can be moved to the next
>> release (the global option). Don't know. The issue is already a huge
>> blackhole which absorbed lots of independent initially unplanned
>> features. The more we move for later, the better.
>>
>> You should try to ask users in the red chat. It helps sometimes, they
>> can share their opinions and experience of upgrades. Just formulate your
>> question in a short but detailed manner, provide a couple of
>> ready-to-choose answers, probably a poll.
>>
> Ok. But for beginning, I need to understand where this flag can be
> located. I will think about that but if you have a proposal, please,
> write it.

box.cfg is a first option, but naive - error objects exist before box.cfg
is called.

I would go for box.error.cfg(). It would look fine - one place for configuring
traceback and serialization format.

>>>>> +        local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1)
>>>>
>>>> 3. This won't work in case it is an instance bootstrapped from the master
>>>> branch before 2.4.1 was released. I don't know how to fix it now.
>>> Sorry, I think this should not work until the release.
>>
>> Exactly for 'between-releases' users we introduced the new versioning schema
>> with 4 numbers. I don't think we can just say fuck off to them now. I
>> would better drop this from the patchset and added when somebody explicitly
>> asks for that, with good design and planning.
>>
> Ok.I have remove it, but:"Don't have 'between-releases' users a version more when previous release?"

First 3 numbers stay the same, and 4th is not transmitted to clients
(from what I remember). If correctly parsed your question, because
I am not sure I did.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding
  2020-04-16 10:04         ` lvasiliev
@ 2020-04-16 21:06           ` Vladislav Shpilevoy
  0 siblings, 0 replies; 37+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-16 21:06 UTC (permalink / raw)
  To: lvasiliev; +Cc: tarantool-patches

Thanks for the answers!

>>>> 7. Why are these values in a enum? I thought we
>>>> decided to use string types?
>>> Now we compare a string with error type when encode and use number further. If we encode error type as string we still have to compare the strings when decoding to create the error of the desired type. But number takes up less space versus string  when transmitting over a network and makes it possible to use a switch when create an error after decoding (which looks much nicer).
>>
>> Wait, so this is inside the MessagePack too? This is not a good
>> idea really. I thought you just converted strings to enums and
>> then enums to classes.
> Yes. It's not inside the MessagePack. MP_ERROR decodes by
> external handler as we discussed. It registered by
> luamp_set_decode_extension().

I meant it is inside MessagePack protocol, not library.

>> First about 'less space'. These are error objects. It does not
>> really matter if every one of them becomes +10 or even +20 bytes
>> if we use string types instead of numbers. They are not critical
>> in any aspect, not in perf nor in space.
>>
>> Second about switch, 'which looks nicer'. Nicely formatted if-else
>> sequence also look fine. This is subjective though. I won't argue.
>>
>> Thirdly, about why I think string types are better. The problem is
>> that I don't want us to document all the error types as numbers
>> and support them forever. When we use strings, we easily can remove
>> old errors, add new errors. When we use numbers, we
>>
>>      1) will need to document what every number means. Strings are
>>      self-documenting. OutOfMemory means out of memory, obviously.
>>      And so on.
> Ok.
>>
>>      2) will one day have holes in these numbers left from removed
>>      errors, this won't look nice, trust me.
> Ok.
>>
>>      3) that complicates compatibility. What if some error type was
>>      added to a newer tarantool version, and an old connector connected
>>      to the instance? How will it handle the new error types? With
>>      string types the problem does not exist.
> If I understand you correctly, you will have the same problem
> with string, because you can't create an error of unknown type.

String is the type. Client's connector don't need to keep a track of
all existing error types, and validate every incoming string. The
connector just need to be able to decode MP_STR, and that is all.

When you have numbers, client can't tell error type, if a new unknown
number is received. Instead of a nice human readable error type it
will be just an abstract number like '252'.

With string types whatever new errors are added in newer versions,
all client connectors will be able to decode them without necessity
to validate.

^ permalink raw reply	[flat|nested] 37+ messages in thread

end of thread, other threads:[~2020-04-16 21:06 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-04-10  8:10 [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Leonid Vasiliev
2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 1/5] error: Add a Lua backtrace to error Leonid Vasiliev
2020-04-14  1:11   ` Vladislav Shpilevoy
2020-04-15  9:25     ` lvasiliev
2020-04-16  0:00       ` Vladislav Shpilevoy
2020-04-16  1:11         ` Alexander Turenko
2020-04-16  8:58           ` lvasiliev
2020-04-16  9:30             ` Alexander Turenko
2020-04-16 12:27               ` lvasiliev
2020-04-16 12:45                 ` Alexander Turenko
2020-04-16 17:48                   ` lvasiliev
2020-04-16  8:40         ` lvasiliev
2020-04-16  9:04           ` lvasiliev
2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type Leonid Vasiliev
2020-04-14  1:11   ` Vladislav Shpilevoy
2020-04-15  9:25     ` lvasiliev
2020-04-16  0:02       ` Vladislav Shpilevoy
2020-04-16  9:18         ` lvasiliev
2020-04-16 21:03           ` Vladislav Shpilevoy
2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 3/5] error: Increase the number of fields transmitted through IPROTO Leonid Vasiliev
2020-04-14  1:12   ` Vladislav Shpilevoy
2020-04-15  9:25     ` lvasiliev
2020-04-16  0:02       ` Vladislav Shpilevoy
2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 4/5] iproto: Add session settings for IPROTO Leonid Vasiliev
2020-04-14  1:12   ` Vladislav Shpilevoy
2020-04-15  9:26     ` lvasiliev
2020-04-16  0:06       ` Vladislav Shpilevoy
2020-04-16  9:36         ` lvasiliev
2020-04-16 21:04           ` Vladislav Shpilevoy
2020-04-10  8:10 ` [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding Leonid Vasiliev
2020-04-14  1:12   ` Vladislav Shpilevoy
2020-04-15  9:25     ` lvasiliev
2020-04-16  0:11       ` Vladislav Shpilevoy
2020-04-16 10:04         ` lvasiliev
2020-04-16 21:06           ` Vladislav Shpilevoy
2020-04-14  1:10 ` [Tarantool-patches] [PATCH v2 0/5] Extending error functionality Vladislav Shpilevoy
2020-04-15  9:48   ` lvasiliev

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