[Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding

Leonid Vasiliev lvasiliev at tarantool.org
Fri Apr 10 11:10:43 MSK 2020


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



More information about the Tarantool-patches mailing list