[tarantool-patches] [PATCH v3 3/6] box: introduce stacked diagnostic area

Kirill Shcherbatov kshcherbatov at tarantool.org
Mon Sep 2 17:51:11 MSK 2019


Part of #1148

@TarantoolBot document
Title: Stacked Diagnostics for Tarantool

Tarantool statements must produce diagnostic information that
populates the diagnostics area. Diagnostics area stack must
contain a diagnostics area for each nested execution context.

Tarantool used to have the only diag_set() mechanism to set a
diagnostic error. In some cases a diagnostic information must be
more complicated. A new method diag_add() allows to extend
global diagnostics with a new error: the previous set becomes
a reason for a recently-constructed error object. This commit
also introduce a savepoint-semantics mechanism to remove errors
when they are redundant. Thus,
diag_set(diag, FIRST)
struct error *SVP = diag_svp(diag)
diag_add(diag, SECOND)
diag_add(diag, THIRD)
diag_rollback_to_svp(diag, svp) // only the FIRST error is set

To work with errors having a reason efficiently, box.error
endpoint was extended with a new method prev that returns a
reason error object for a given error, when exists or nil
otherwise:
err = box.error.last()
err:unpack()
reason = box.error.prev(err)
assert(err.prev == reason)
reason:unpack()
---
 src/lib/core/diag.h             |  80 +++++++++++++++-
 src/lua/error.h                 |   3 +
 src/box/key_list.c              |  16 ++--
 src/box/lua/call.c              |   6 +-
 src/lib/core/diag.c             |   1 +
 src/lua/error.c                 |   2 +-
 src/box/lua/error.cc            |  20 ++++
 src/lua/error.lua               |  12 +++
 test/app/fiber.result           |   7 +-
 test/box/misc.result            |   7 +-
 test/engine/func_index.result   |  52 +++++++++--
 test/engine/func_index.test.lua |   7 ++
 test/unit/CMakeLists.txt        |   2 +-
 test/unit/fiber.cc              | 157 ++++++++++++++++++++++++++++++++
 test/unit/fiber.result          |  60 ++++++++++++
 15 files changed, 403 insertions(+), 29 deletions(-)

diff --git a/src/lib/core/diag.h b/src/lib/core/diag.h
index 02a67269f..91596b2e6 100644
--- a/src/lib/core/diag.h
+++ b/src/lib/core/diag.h
@@ -78,6 +78,8 @@ struct error {
 	char file[DIAG_FILENAME_MAX];
 	/* Error description. */
 	char errmsg[DIAG_ERRMSG_MAX];
+	/* A pointer to the reason error. */
+	struct error *prev;
 };
 
 static inline void
@@ -90,9 +92,13 @@ static inline void
 error_unref(struct error *e)
 {
 	assert(e->refs > 0);
-	--e->refs;
-	if (e->refs == 0)
+	while (--e->refs == 0) {
+		struct error *prev = e->prev;
 		e->destroy(e);
+		if (prev == NULL)
+			break;
+		e = prev;
+	}
 }
 
 NORETURN static inline void
@@ -175,6 +181,26 @@ diag_set_error(struct diag *diag, struct error *e)
 	diag->last = e;
 }
 
+/**
+ * Add a new error to the diagnostics area: the previous error
+ * becomes a reason of a current.
+ * \param diag diagnostics area
+ * \param e error to add
+ */
+static inline void
+diag_add_error(struct diag *diag, struct error *e)
+{
+	assert(e != NULL);
+	error_ref(e);
+	/*
+	 * Nominally e takes a reason's reference while diag
+	 * releases it's reference because it holds e now
+	 * instead. I.e. reason->refs kept unchanged.
+	 */
+	e->prev = diag->last;
+	diag->last = e;
+}
+
 /**
  * Move all errors from \a from to \a to.
  * \param from source
@@ -212,6 +238,45 @@ diag_last_error(struct diag *diag)
 	return diag->last;
 }
 
+/**
+ * Get a diagnostic savepoint: a marker that allows to reset all
+ * errors set after that moment.
+ */
+static inline struct error *
+diag_svp(struct diag *diag)
+{
+	return diag_last_error(diag);
+}
+
+/**
+ * Remove all errors set in a given diagnostics area after a
+ * given savepoint.
+ *
+ * Operation removes reason for the error
+ * preceding the savepoint and releases a diagnostic area's
+ * reference on the most recent error (diag::last for the
+ * rollback beginning). This means that if user code have a
+ * pointer and have a reference to an error object from the
+ * rollback zone, this pointer and the following "reason" error
+ * objects are a valid error list.
+ */
+static inline void
+diag_rollback_to_svp(struct diag *diag, struct error *svp)
+{
+	struct error *begin = diag->last, *err = NULL;
+	while (diag->last != svp) {
+		err = diag->last;
+		diag->last = diag->last->prev;
+	}
+	if (diag->last != begin) {
+		assert(err != NULL && err->prev == svp);
+		err->prev = NULL;
+		error_unref(begin);
+		err->prev = svp;
+		error_ref(svp);
+	}
+}
+
 struct diag *
 diag_get();
 
@@ -274,6 +339,17 @@ BuildSocketError(const char *file, unsigned line, const char *socketname,
 	errno = save_errno;                                             \
 } while (0)
 
+#define diag_add(class, ...) do {					\
+	/* Preserve the original errno. */                              \
+	int save_errno = errno;                                         \
+	say_debug("%s at %s:%i", #class, __FILE__, __LINE__);		\
+	struct error *e;						\
+	e = Build##class(__FILE__, __LINE__, ##__VA_ARGS__);		\
+	diag_add_error(diag_get(), e);					\
+	/* Restore the errno which might have been reset.  */           \
+	errno = save_errno;                                             \
+} while (0)
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/src/lua/error.h b/src/lua/error.h
index 64fa5eba3..16cdaf7fe 100644
--- a/src/lua/error.h
+++ b/src/lua/error.h
@@ -65,6 +65,9 @@ luaT_pusherror(struct lua_State *L, struct error *e);
 struct error *
 luaL_iserror(struct lua_State *L, int narg);
 
+struct error *
+luaL_checkerror(struct lua_State *L, int narg);
+
 void
 tarantool_lua_error_init(struct lua_State *L);
 
diff --git a/src/box/key_list.c b/src/box/key_list.c
index e130d1c8c..c3de262cc 100644
--- a/src/box/key_list.c
+++ b/src/box/key_list.c
@@ -63,9 +63,9 @@ key_list_iterator_create(struct key_list_iterator *it, struct tuple *tuple,
 	if (rc != 0) {
 		/* Can't evaluate function. */
 		struct space *space = space_by_id(index_def->space_id);
-		diag_set(ClientError, ER_FUNC_INDEX_FUNC, index_def->name,
-			 space ? space_name(space) : "",
-			 diag_last_error(diag_get())->errmsg);
+		diag_add(ClientError, ER_FUNC_INDEX_FUNC, index_def->name,
+			 space != NULL ? space_name(space) : "",
+			 "can't evaluate function");
 		return -1;
 	}
 	uint32_t key_data_sz;
@@ -74,9 +74,9 @@ key_list_iterator_create(struct key_list_iterator *it, struct tuple *tuple,
 	if (key_data == NULL) {
 		struct space *space = space_by_id(index_def->space_id);
 		/* Can't get a result returned by function . */
-		diag_set(ClientError, ER_FUNC_INDEX_FUNC, index_def->name,
-			 space ? space_name(space) : "",
-			 diag_last_error(diag_get())->errmsg);
+		diag_add(ClientError, ER_FUNC_INDEX_FUNC, index_def->name,
+			 space != NULL ? space_name(space) : "",
+			 "can't get a value returned by function");
 		return -1;
 	}
 
@@ -170,9 +170,9 @@ key_list_iterator_next(struct key_list_iterator *it, const char **value)
 		 * The key doesn't follow functional index key
 		 * definition.
 		 */
-		diag_set(ClientError, ER_FUNC_INDEX_FORMAT, it->index_def->name,
+		diag_add(ClientError, ER_FUNC_INDEX_FORMAT, it->index_def->name,
 			 space ? space_name(space) : "",
-			 diag_last_error(diag_get())->errmsg);
+			 "key does not follow functional index definition");
 		return -1;
 	}
 
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index 631003c84..0d510c4e1 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -683,9 +683,9 @@ func_persistent_lua_load(struct func_lua *func)
 	if (func->base.def->is_sandboxed) {
 		if (prepare_lua_sandbox(tarantool_L, default_sandbox_exports,
 					nelem(default_sandbox_exports)) != 0) {
-			diag_set(ClientError, ER_LOAD_FUNCTION,
-				func->base.def->name,
-				diag_last_error(diag_get())->errmsg);
+			diag_add(ClientError, ER_LOAD_FUNCTION,
+				 func->base.def->name,
+				 "can't prepare a Lua sandbox");
 			goto end;
 		}
 	} else {
diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c
index 248277e74..6ad3378a5 100644
--- a/src/lib/core/diag.c
+++ b/src/lib/core/diag.c
@@ -52,6 +52,7 @@ error_create(struct error *e,
 		e->line = 0;
 	}
 	e->errmsg[0] = '\0';
+	e->prev = NULL;
 }
 
 struct diag *
diff --git a/src/lua/error.c b/src/lua/error.c
index d82e78dc4..18a990a88 100644
--- a/src/lua/error.c
+++ b/src/lua/error.c
@@ -53,7 +53,7 @@ luaL_iserror(struct lua_State *L, int narg)
 	return e;
 }
 
-static struct error *
+struct error *
 luaL_checkerror(struct lua_State *L, int narg)
 {
 	struct error *error = luaL_iserror(L, narg);
diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
index 230d51dec..5a2f5fc7d 100644
--- a/src/box/lua/error.cc
+++ b/src/box/lua/error.cc
@@ -144,6 +144,22 @@ luaT_error_new(lua_State *L)
 	return luaT_error_last(L);
 }
 
+static int
+luaT_error_prev(lua_State *L)
+{
+	if (lua_gettop(L) == 0)
+		return luaL_error(L, "Usage: box.error.prev(error)");
+	struct error *e = luaL_checkerror(L, 1);
+	if (e == NULL)
+		return luaT_error(L);
+
+	if (e->prev != NULL)
+		luaT_pusherror(L, e->prev);
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
 static int
 luaT_error_clear(lua_State *L)
 {
@@ -250,6 +266,10 @@ box_lua_error_init(struct lua_State *L) {
 			lua_pushcfunction(L, luaT_error_new);
 			lua_setfield(L, -2, "new");
 		}
+		{
+			lua_pushcfunction(L, luaT_error_prev);
+			lua_setfield(L, -2, "prev");
+		}
 		lua_setfield(L, -2, "__index");
 	}
 	lua_setmetatable(L, -2);
diff --git a/src/lua/error.lua b/src/lua/error.lua
index 28fc0377d..0f76f6363 100644
--- a/src/lua/error.lua
+++ b/src/lua/error.lua
@@ -23,6 +23,8 @@ struct error {
     char _file[DIAG_FILENAME_MAX];
     /* Error description. */
     char _errmsg[DIAG_ERRMSG_MAX];
+    /* A pointer to the reason error. */
+    struct error *_prev;
 };
 
 char *
@@ -86,10 +88,20 @@ local function error_trace(err)
     }
 end
 
+local function error_refs(err)
+    return err._refs
+end
+
+local function error_prev(err)
+    return box.error.prev(err)
+end
+
 local error_fields = {
     ["type"]        = error_type;
     ["message"]     = error_message;
     ["trace"]       = error_trace;
+    ["refs"]        = error_refs;
+    ["prev"]        = error_prev;
 }
 
 local function error_unpack(err)
diff --git a/test/app/fiber.result b/test/app/fiber.result
index 94e690f6c..a10d43ba9 100644
--- a/test/app/fiber.result
+++ b/test/app/fiber.result
@@ -1038,12 +1038,13 @@ st;
 ...
 e:unpack();
 ---
-- type: ClientError
-  code: 1
-  message: Illegal parameters, oh my
+- code: 1
   trace:
   - file: '[string "function err() box.error(box.error.ILLEGAL_PA..."]'
     line: 1
+  type: ClientError
+  message: Illegal parameters, oh my
+  refs: 2
 ...
 flag = false;
 ---
diff --git a/test/box/misc.result b/test/box/misc.result
index c46c5a9d6..555075a6c 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -125,12 +125,13 @@ e
 ...
 e:unpack()
 ---
-- type: ClientError
-  code: 1
-  message: Illegal parameters, bla bla
+- code: 1
   trace:
   - file: '[C]'
     line: 4294967295
+  type: ClientError
+  message: Illegal parameters, bla bla
+  refs: 4
 ...
 e.type
 ---
diff --git a/test/engine/func_index.result b/test/engine/func_index.result
index bb4200f7a..e4b3db7a6 100644
--- a/test/engine/func_index.result
+++ b/test/engine/func_index.result
@@ -5,6 +5,10 @@ test_run = require('test_run').new()
 engine = test_run:get_cfg('engine')
  | ---
  | ...
+test_run:cmd("push filter \"file: .*\" to \"file: <filename>\"")
+ | ---
+ | - true
+ | ...
 
 --
 -- gh-1260: Func index.
@@ -158,8 +162,7 @@ idx = s:create_index('idx', {func = box.func.invalidreturn1.id, parts = {{1, 'un
 s:insert({1})
  | ---
  | - error: 'Key format doesn''t match one defined in functional index ''idx'' of space
- |     ''withdata'': Supplied key type of part 0 does not match index part type: expected
- |     unsigned'
+ |     ''withdata'': key does not follow functional index definition'
  | ...
 idx:drop()
  | ---
@@ -197,8 +200,7 @@ idx = s:create_index('idx', {func = box.func.invalidreturn3.id, parts = {{1, 'un
 s:insert({1})
  | ---
  | - error: 'Key format doesn''t match one defined in functional index ''idx'' of space
- |     ''withdata'': Supplied key type of part 0 does not match index part type: expected
- |     unsigned'
+ |     ''withdata'': key does not follow functional index definition'
  | ...
 idx:drop()
  | ---
@@ -217,8 +219,7 @@ idx = s:create_index('idx', {func = box.func.invalidreturn4.id, parts = {{1, 'un
 s:insert({1})
  | ---
  | - error: 'Key format doesn''t match one defined in functional index ''idx'' of space
- |     ''withdata'': Supplied key type of part 0 does not match index part type: expected
- |     unsigned'
+ |     ''withdata'': key does not follow functional index definition'
  | ...
 idx:drop()
  | ---
@@ -264,8 +265,43 @@ idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'stri
 s:insert({1})
  | ---
  | - error: 'Failed to build a key for functional index ''idx'' of space ''withdata'':
- |     [string "return function(tuple)                 local ..."]:1: attempt to call
- |     global ''require'' (a nil value)'
+ |     can''t evaluate function'
+ | ...
+e = box.error.last()
+ | ---
+ | ...
+e:unpack()
+ | ---
+ | - code: 198
+ |   trace:
+ |   - file: <filename>
+ |     line: 68
+ |   type: ClientError
+ |   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'
+ |   refs: 3
+ | ...
+e = box.error.prev(e)
+ | ---
+ | ...
+e:unpack()
+ | ---
+ | - type: LuajitError
+ |   refs: 3
+ |   message: '[string "return function(tuple)                 local ..."]:1: attempt
+ |     to call global ''require'' (a nil value)'
+ |   trace:
+ |   - file: <filename>
+ |     line: 1010
+ | ...
+e = box.error.prev(e)
+ | ---
+ | ...
+e == nil
+ | ---
+ | - true
  | ...
 idx:drop()
  | ---
diff --git a/test/engine/func_index.test.lua b/test/engine/func_index.test.lua
index f31162c97..ccbc9822d 100644
--- a/test/engine/func_index.test.lua
+++ b/test/engine/func_index.test.lua
@@ -1,5 +1,6 @@
 test_run = require('test_run').new()
 engine = test_run:get_cfg('engine')
+test_run:cmd("push filter \"file: .*\" to \"file: <filename>\"")
 
 --
 -- gh-1260: Func index.
@@ -99,6 +100,12 @@ test_run:cmd("setopt delimiter ''");
 box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
 idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})
 s:insert({1})
+e = box.error.last()
+e:unpack()
+e = box.error.prev(e)
+e:unpack()
+e = box.error.prev(e)
+e == nil
 idx:drop()
 
 -- Remove old persistent functions
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 4a57597e9..6dfe1f6d9 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -72,7 +72,7 @@ target_link_libraries(decimal.test core unit)
 
 add_executable(fiber.test fiber.cc)
 set_source_files_properties(fiber.cc PROPERTIES COMPILE_FLAGS -O0)
-target_link_libraries(fiber.test core unit)
+target_link_libraries(fiber.test core box unit)
 
 if (NOT ENABLE_GCOV)
     # This test is known to be broken with GCOV
diff --git a/test/unit/fiber.cc b/test/unit/fiber.cc
index 91f7d43f9..2bec8bdf8 100644
--- a/test/unit/fiber.cc
+++ b/test/unit/fiber.cc
@@ -1,3 +1,6 @@
+#include "box/error.h"
+#include "diag.h"
+#include "errcode.h"
 #include "memory.h"
 #include "fiber.h"
 #include "unit.h"
@@ -181,12 +184,166 @@ fiber_name_test()
 	footer();
 }
 
+void
+diag_test()
+{
+	header();
+	plan(28);
+
+	note("constract e1 in global diag, share with local diag");
+	diag_set(ClientError, ER_PROC_LUA, "runtime error");
+	struct diag local_diag;
+	diag_create(&local_diag);
+	/* Copy a last error to the local diagnostics area. */
+	diag_add_error(&local_diag, diag_last_error(diag_get()));
+
+	struct error *e1 = diag_last_error(&local_diag);
+	is(e1, diag_last_error(diag_get()),
+	   "e1 is an error shared between local and global diag");
+	is(e1->refs, 2, "e1::refs: global+local diag");
+
+	note("append e2 to global diag, usr error_ref(e2)");
+	diag_add(ClientError, ER_LOAD_FUNCTION, "test", "internal error");
+	struct error *e2 = diag_last_error(diag_get());
+	error_ref(e2);
+
+	is(e2->prev, e1, "e2::prev == e1");
+	is(e1->prev, NULL, "e1::prev == NULL");
+	is(e1->refs, 2, "e1::refs: e2 + global diag");
+	is(e2->refs, 2, "e2::refs: usr + global diag");
+
+	note("diag_clean global diag");
+	diag_clear(diag_get());
+	is(e1->refs, 2, "e1::refs: e2 + local");
+	is(e2->refs, 1, "e2::refs: usr");
+	note("error_unref(e2) -> e2 destruction");
+	error_unref(e2);
+	e2 = NULL;
+	is(e1->refs, 1, "e1::refs: local diag");
+
+	/* Test rollback to SVP. */
+	note("diag_move(from = local, to = global): move e1 to global");
+	diag_move(&local_diag, diag_get());
+	is(diag_is_empty(&local_diag), true, "local diag is empty");
+	is(diag_is_empty(diag_get()), false, "global diag is not empty");
+	is(diag_last_error(diag_get()), e1, "global diag::last == e1");
+
+	note("svp = diag_svp(global), i.e. 'diag_last(global) = e1' state");
+	struct error *svp = diag_svp(diag_get());
+	fail_if(svp != e1);
+	fail_if(diag_last_error(diag_get()) != e1);
+	fail_if(e1->prev != NULL);
+	note("append e3, e4 to global diag");
+	note("usr error_ref(e1), error_ref(e3), error_ref(e4)");
+	diag_add(ClientError, ER_LOAD_FUNCTION, "test", "internal error");
+	struct error *e3 = diag_last_error(diag_get());
+	error_ref(e3);
+	diag_add(ClientError, ER_FUNC_INDEX_FUNC, "func_idx", "space",
+		 "everything is bad");
+	struct error *e4 = diag_last_error(diag_get());
+	error_ref(e4);
+	is(e1->refs, 1, "e1::refs: e3");
+	is(e3->refs, 2, "e3::refs: usr + e4");
+	is(e4->refs, 2, "e4::refs: usr + global diag");
+	is(e4->prev, e3, "e4::prev == e3");
+	is(e3->prev, e1, "e3::prev == e1");
+	note("diag_rollback_to_svp(global, svp)");
+	/*
+	 * Before rollback there is a sequence
+	 * DIAG[e4]->e3->e1->NULL;
+	 * After rollback there would be DIAG[e1]->NULL and
+	 * a sequence e4->e3->e1->NULL.
+	 */
+	diag_rollback_to_svp(diag_get(), svp);
+	is(e1->refs, 2, "e1::refs: e3 + global diag %d/%d", e1->refs, 2);
+	is(e3->refs, 2, "e3::refs: usr + e4");
+	is(e4->refs, 1, "e4::refs: usr");
+	is(diag_last_error(diag_get()), e1, "diag_last(global) = e1");
+	/* Rollback doesn't change error objects itself. */
+	is(e4->prev, e3, "e4::prev == e3");
+	is(e3->prev, e1, "e3::prev == e1");
+	error_unref(e4);
+	e4 = NULL;
+	is(e3->refs, 1, "e3::refs: usr");
+	error_unref(e3);
+	e3 = NULL;
+
+	note("ensure that sequential rollback is no-op");
+	diag_rollback_to_svp(diag_get(), svp);
+	is(e1->refs, 1, "e1::refs: global diag");
+
+	diag_clear(diag_get());
+	/*
+	 *           usr ref    SVP
+	 *   DEL!       |        |
+	 *  DIAG[#5] -> #4 -> DIAG'[#3] -> #2 -> #1
+	 *
+	 * 1) diag_rollback_to_svp
+	 * del   <-----------<------>
+	 */
+	note("test partial list destruction on rollback");
+	diag_add(ClientError, ER_PROC_LUA, "#1");
+	struct error *er1 = diag_last_error(diag_get());
+	diag_add(ClientError, ER_PROC_LUA, "#2");
+	svp = diag_svp(diag_get());
+	diag_add(ClientError, ER_PROC_LUA, "#3");
+	struct error *er3 = diag_last_error(diag_get());
+	diag_add(ClientError, ER_PROC_LUA, "#4");
+	struct error *er4 = diag_last_error(diag_get());
+	error_ref(er4);
+	diag_add(ClientError, ER_PROC_LUA, "#5");
+	is(er4->refs, 2, "er4:refs: usr + er5 %d/%d", er4->refs, 2);
+
+	diag_rollback_to_svp(diag_get(), svp);
+	note("rollback to svp(er2) -> e5:refs == 0, destruction");
+	is(er4->prev, er3, "er4->prev == er3");
+	is(er3->refs, 1, "er3:refs: er4");
+	is(er3->prev, svp, "er3->prev == svp");
+	is(svp->refs, 2, "svp->refs: global diag + er3");
+	is(svp->prev, er1, "svp->prev == er1");
+	is(er1->refs, 1, "er1->refs: err2");
+
+	/*
+	 * usr ref       SVP
+	 *  |             |
+	 * #4 -> #3 -> DIAG'[#2] -> #1
+	 *                 |
+	 * DIAG[#7] -> #6 -/
+	 *   |
+	 * usr ref
+	 */
+	note("multiple error sequences after rollback");
+	diag_add(ClientError, ER_PROC_LUA, "#6");
+	diag_add(ClientError, ER_PROC_LUA, "#7");
+	struct error *er7 = diag_last_error(diag_get());
+	error_ref(er7);
+	is(er4->refs, 1, "er4->refs: usr");
+	is(er7->refs, 2, "er7->refs: global diag + usr");
+	is(svp->refs, 2, "svp->refs: er3 + er6");
+	is(svp->prev->refs, 1, "svp->prev->refs: svp");
+	diag_rollback_to_svp(diag_get(), svp);
+	is(er4->refs, 1, "er4->refs: usr");
+	is(er7->refs, 1, "er7->refs: usr");
+	is(svp->refs, 3, "svp->refs: global diag + er3 + er6");
+	is(svp->prev->refs, 1, "svp->prev->refs: svp");
+	diag_clear(diag_get());
+	is(svp->refs, 2, "svp->refs: er3 + er6");
+	is(svp->prev->refs, 1, "svp->prev->refs: svp");
+	error_unref(er4);
+	is(svp->refs, 1, "svp->refs: er6");
+	is(svp->prev->refs, 1, "svp->prev->refs: svp");
+	error_unref(er7);
+
+	footer();
+}
+
 static int
 main_f(va_list ap)
 {
 	fiber_name_test();
 	fiber_join_test();
 	fiber_stack_test();
+	diag_test();
 	ev_break(loop(), EVBREAK_ALL);
 	return 0;
 }
diff --git a/test/unit/fiber.result b/test/unit/fiber.result
index 7c9f85dcd..58e67b4b9 100644
--- a/test/unit/fiber.result
+++ b/test/unit/fiber.result
@@ -17,3 +17,63 @@ SystemError Failed to allocate 42 bytes in allocator for exception: Cannot alloc
 # normal-stack fiber not crashed
 # big-stack fiber not crashed
 	*** fiber_stack_test: done ***
+	*** diag_test ***
+1..28
+# constract e1 in global diag, share with local diag
+ok 1 - e1 is an error shared between local and global diag
+ok 2 - e1::refs: global+local diag
+# append e2 to global diag, usr error_ref(e2)
+ok 3 - e2::prev == e1
+ok 4 - e1::prev == NULL
+ok 5 - e1::refs: e2 + global diag
+ok 6 - e2::refs: usr + global diag
+# diag_clean global diag
+ok 7 - e1::refs: e2 + local
+ok 8 - e2::refs: usr
+# error_unref(e2) -> e2 destruction
+ok 9 - e1::refs: local diag
+# diag_move(from = local, to = global): move e1 to global
+ok 10 - local diag is empty
+ok 11 - global diag is not empty
+ok 12 - global diag::last == e1
+# svp = diag_svp(global), i.e. 'diag_last(global) = e1' state
+# append e3, e4 to global diag
+# usr error_ref(e1), error_ref(e3), error_ref(e4)
+ok 13 - e1::refs: e3
+ok 14 - e3::refs: usr + e4
+ok 15 - e4::refs: usr + global diag
+ok 16 - e4::prev == e3
+ok 17 - e3::prev == e1
+# diag_rollback_to_svp(global, svp)
+ok 18 - e1::refs: e3 + global diag 2/2
+ok 19 - e3::refs: usr + e4
+ok 20 - e4::refs: usr
+ok 21 - diag_last(global) = e1
+ok 22 - e4::prev == e3
+ok 23 - e3::prev == e1
+ok 24 - e3::refs: usr
+# ensure that sequential rollback is no-op
+ok 25 - e1::refs: global diag
+# test partial list destruction on rollback
+ok 26 - er4:refs: usr + er5 2/2
+# rollback to svp(er2) -> e5:refs == 0, destruction
+ok 27 - er4->prev == er3
+ok 28 - er3:refs: er4
+ok 29 - er3->prev == svp
+ok 30 - svp->refs: global diag + er3
+ok 31 - svp->prev == er1
+ok 32 - er1->refs: err2
+# multiple error sequences after rollback
+ok 33 - er4->refs: usr
+ok 34 - er7->refs: global diag + usr
+ok 35 - svp->refs: er3 + er6
+ok 36 - svp->prev->refs: svp
+ok 37 - er4->refs: usr
+ok 38 - er7->refs: usr
+ok 39 - svp->refs: global diag + er3 + er6
+ok 40 - svp->prev->refs: svp
+ok 41 - svp->refs: er3 + er6
+ok 42 - svp->prev->refs: svp
+ok 43 - svp->refs: er6
+ok 44 - svp->prev->refs: svp
+	*** diag_test: done ***
-- 
2.22.1





More information about the Tarantool-patches mailing list