diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
index 95f30013333f0eb2ffe5901855265db69e8bb873..9e86f19cc96c39e6a86906b794bc90779d36175d 100644
--- a/src/box/lua/error.cc
+++ b/src/box/lua/error.cc
@@ -53,8 +53,9 @@ lbox_error_raise(lua_State *L)
 	int top = lua_gettop(L);
 	if (top <= 1) {
 		/* re-throw saved exceptions (if any) */
-		if (fiber()->exception)
-			fiber()->exception->raise();
+		Exception *e = diag_last_error(&fiber()->diag);
+		if (e != NULL)
+			e->raise();
 		return 0;
 	} else if (top >= 2 && lua_type(L, 2) == LUA_TNUMBER) {
 		code = lua_tointeger(L, 2);
@@ -101,7 +102,9 @@ lbox_error_raise(lua_State *L)
 
 	/* see tnt_raise() */
 	say_debug("ClientError at %s:%i", file, line);
-	throw new ClientError(file, line, reason, code);
+	ClientError *e = new ClientError(file, line, reason, code);
+	diag_add_error(&fiber()->diag, e);
+	throw e;
 	return 0;
 }
 
@@ -111,7 +114,7 @@ lbox_error_last(lua_State *L)
 	if (lua_gettop(L) >= 1)
 		luaL_error(L, "box.error.last(): bad arguments");
 
-	Exception *e = fiber()->exception;
+	Exception *e = diag_last_error(&fiber()->diag);
 
 	if (e == NULL) {
 		lua_pushnil(L);
@@ -145,7 +148,7 @@ lbox_error_clear(lua_State *L)
 	if (lua_gettop(L) >= 1)
 		luaL_error(L, "box.error.clear(): bad arguments");
 
-	Exception::clear(&fiber()->exception);
+	diag_clear(&fiber()->diag);
 	return 0;
 }
 
diff --git a/src/box/lua/info.cc b/src/box/lua/info.cc
index 484b76e15efa6996610d38f711a255bfc212c3f5..76fd12c027d40c6cdc326e7e86933a4a7a912ea5 100644
--- a/src/box/lua/info.cc
+++ b/src/box/lua/info.cc
@@ -63,10 +63,10 @@ lbox_info_replication(struct lua_State *L)
 		lua_pushnumber(L, ev_now(loop()) - r->remote.last_row_time);
 		lua_settable(L, -3);
 
-		if (r->remote.reader->exception) {
+		Exception *e = diag_last_error(&r->remote.reader->diag);
+		if (e != NULL) {
 			lua_pushstring(L, "message");
-			lua_pushstring(L,
-				       r->remote.reader->exception->errmsg());
+			lua_pushstring(L, e->errmsg());
 			lua_settable(L, -3);
 		}
 	}
diff --git a/src/box/replica.cc b/src/box/replica.cc
index cfd39d88045d5748c617198af9028c08fb93cfda..ea0ab28c026c1466d45af8474e1dcbd235c2d159 100644
--- a/src/box/replica.cc
+++ b/src/box/replica.cc
@@ -325,7 +325,7 @@ recovery_stop_remote(struct recovery_state *r)
 	 * If the remote died from an exception, don't throw it
 	 * up.
 	 */
-	Exception::clear(&f->exception);
+	diag_clear(&f->diag);
 	fiber_join(f);
 	r->remote.status = "off";
 }
diff --git a/src/coeio.cc b/src/coeio.cc
index e2cbc2f12c8f3ca7909f0cb7a04351936dd27020..fc82be9e2bae4a21d66174987a20a5869d2d23d4 100644
--- a/src/coeio.cc
+++ b/src/coeio.cc
@@ -363,11 +363,12 @@ cord_cojoin(struct cord *cord)
 {
 	assert(cord() != cord); /* Can't join self. */
 	int rc = coio_call(cord_cojoin_cb, cord);
-	if (rc == 0 && cord->fiber->exception) {
-		Exception::move(&cord->fiber->exception, &fiber()->exception);
+	diag_move(&cord->fiber->diag, &fiber()->diag);
+	Exception *e = diag_last_error(&fiber()->diag);
+	if (rc == 0 && e != NULL) {
 		cord_destroy(cord);
 		 /* re-throw exception in this fiber */
-		fiber()->exception->raise();
+		e->raise();
 	}
 	cord_destroy(cord);
 	return rc;
diff --git a/src/exception.cc b/src/exception.cc
index 7c7af79865d6729fe1a5e9656945188d2253b8b5..3d006cb96138cba357d7fcfab4af40fb46208ce8 100644
--- a/src/exception.cc
+++ b/src/exception.cc
@@ -39,52 +39,45 @@
 static OutOfMemory out_of_memory(__FILE__, __LINE__,
 				 sizeof(OutOfMemory), "malloc", "exception");
 
+struct diag *
+diag_get()
+{
+	return &fiber()->diag;
+}
+
 void *
 Exception::operator new(size_t size)
 {
-	struct fiber *fiber = fiber();
-
-	if (fiber->exception && fiber->exception->size == 0)
-		fiber->exception = NULL;
-
-	if (fiber->exception) {
-		/* Explicitly call destructor for previous exception */
-		fiber->exception->~Exception();
-		if (fiber->exception->size >= size) {
-			/* Reuse memory allocated for exception */
-			return fiber->exception;
-		}
-		free(fiber->exception);
-	}
-	fiber->exception = (Exception *) malloc(size);
-	if (fiber->exception) {
-		fiber->exception->size = size;
-		return fiber->exception;
-	}
-	fiber->exception = &out_of_memory;
-	throw fiber->exception;
+	void *buf = malloc(size);
+	if (buf != NULL)
+		return buf;
+	diag_add_error(&fiber()->diag, &out_of_memory);
+	throw &out_of_memory;
 }
 
 void
-Exception::operator delete(void * /* ptr */)
+Exception::operator delete(void *ptr)
 {
-	/* Unsupported */
-	assert(false);
+	free(ptr);
 }
 
-Exception::Exception(const char *file, unsigned line)
-	: m_file(file), m_line(line)
+Exception::~Exception()
 {
-	m_errmsg[0] = 0;
+	if (this != &out_of_memory) {
+		assert(m_ref == 0);
+	}
 }
 
-Exception::Exception(const Exception& e)
-	: Object(), m_file(e.m_file), m_line(e.m_line)
-{
-	memcpy(m_errmsg, e.m_errmsg, sizeof(m_errmsg));
+Exception::Exception(const char *file, unsigned line)
+	: m_ref(0), m_file(file), m_line(line){
+	m_errmsg[0] = 0;
+	if (this == &out_of_memory) {
+		/* A special workaround for out_of_memory static init */
+		out_of_memory.m_ref = 1;
+		return;
+	}
 }
 
-
 const char *
 Exception::type() const
 {
diff --git a/src/exception.h b/src/exception.h
index c7eec1c14ec925c9ed846cf48b5dc92fabe9524b..6b7c05909a76e30ed4e45d51fa131615fc358380 100644
--- a/src/exception.h
+++ b/src/exception.h
@@ -30,6 +30,7 @@
  */
 #include "object.h"
 #include <stdarg.h>
+#include <assert.h>
 #include "say.h"
 
 enum { TNT_ERRMSG_MAX = 512 };
@@ -56,33 +57,24 @@ class Exception: public Object {
 	}
 
 	virtual void log() const;
-	virtual ~Exception() {}
+	virtual ~Exception();
 
-	static void init(Exception **what)
-	{
-		*what = NULL;
-	}
-	/** Clear the last error saved in the current fiber's TLS */
-	static inline void clear(Exception **what)
-	{
-		if (*what != NULL && (*what)->size > 0) {
-			(*what)->~Exception();
-			free(*what);
-		}
-		Exception::init(what);
+	void ref() {
+		++m_ref;
 	}
-	/** Move an exception from one thread to another. */
-	static void move(Exception **from, Exception **to)
-	{
-		Exception::clear(to);
-		*to = *from;
-		Exception::init(from);
+
+	void unref() {
+		assert(m_ref > 0);
+		--m_ref;
+		if (m_ref == 0)
+			delete this;
 	}
+
 protected:
 	Exception(const char *file, unsigned line);
-	/* The copy constructor is needed for C++ throw */
-	Exception(const Exception&);
 
+	/* Ref counter */
+	size_t m_ref;
 	/* file name */
 	const char *m_file;
 	/* line number */
@@ -131,9 +123,112 @@ class OutOfMemory: public SystemError {
 		    const char *object);
 };
 
+/**
+ * Diagnostics Area - a container for errors and warnings
+ */
+struct diag {
+	/* \cond private */
+	class Exception *last;
+	/* \endcond private */
+};
+
+/**
+ * Remove all errors from the diagnostics area
+ * \param diag diagnostics area
+ */
+static inline void
+diag_clear(struct diag *diag)
+{
+	if (diag->last == NULL)
+		return;
+	diag->last->unref();
+	diag->last = NULL;
+}
+
+/**
+ * Add a new error to the diagnostics area
+ * \param diag diagnostics area
+ * \param e error to add
+ */
+static inline void
+diag_add_error(struct diag *diag, Exception *e)
+{
+	assert(e != NULL);
+	e->ref();
+	diag_clear(diag);
+	diag->last = e;
+}
+
+/**
+ * Return last error
+ * \return last error
+ * \param diag diagnostics area
+ */
+static inline Exception *
+diag_last_error(struct diag *diag)
+{
+	return diag->last;
+}
+
+/**
+ * Move all errors from \a from to \a to.
+ * \param from source
+ * \param to destination
+ * \post diag_is_empty(from)
+ */
+static inline void
+diag_move(struct diag *from, struct diag *to)
+{
+	diag_clear(to);
+	if (from->last == NULL)
+		return;
+	to->last = from->last;
+	from->last = NULL;
+}
+
+/**
+ * Return true if diagnostics area is empty
+ * \param diag diagnostics area to initialize
+ */
+static inline bool
+diag_is_empty(struct diag *diag)
+{
+	return diag->last == NULL;
+}
+
+/**
+ * Create a new diagnostics area
+ * \param diag diagnostics area to initialize
+ */
+static inline void
+diag_create(struct diag *diag)
+{
+	diag->last = NULL;
+}
+
+/**
+ * Destroy diagnostics area
+ * \param diag diagnostics area to clean
+ */
+static inline void
+diag_destroy(struct diag *diag)
+{
+	diag_clear(diag);
+}
+
+/**
+ * A helper for tnt_error to avoid cyclic includes (fiber.h and exception.h)
+ * \cond false
+ * */
+struct diag *
+diag_get();
+/** \endcond */
+
 #define tnt_error(class, ...) ({					\
 	say_debug("%s at %s:%i", #class, __FILE__, __LINE__);		\
-	new class(__FILE__, __LINE__, ##__VA_ARGS__);			\
+	class *e = new class(__FILE__, __LINE__, ##__VA_ARGS__);	\
+	diag_add_error(diag_get(), e);					\
+	e;								\
 })
 
 #define tnt_raise(...) do {						\
diff --git a/src/fiber.cc b/src/fiber.cc
index f83ce7ac1ab36240c74e100ed2b950be0eb4edaf..9905aeae4ab6bd83a1d4b83dd4cd0c5d1201861d 100644
--- a/src/fiber.cc
+++ b/src/fiber.cc
@@ -220,11 +220,11 @@ fiber_join(struct fiber *fiber)
 	/* The fiber is already dead. */
 	fiber_recycle(fiber);
 
-	Exception::move(&fiber->exception, &fiber()->exception);
-	if (fiber()->exception &&
-	    typeid(*fiber()->exception) != typeid(FiberCancelException)) {
-		fiber()->exception->raise();
-	}
+	/* Move exception to the caller */
+	diag_move(&fiber->diag, &fiber()->diag);
+	Exception *e = diag_last_error(&fiber()->diag);
+	if (e != NULL && typeid(*e) != typeid(FiberCancelException))
+		e->raise();
 	fiber_testcancel();
 }
 /**
@@ -404,7 +404,7 @@ fiber_loop(void *data __attribute__((unused)))
 			 * Make sure a leftover exception does not
 			 * propagate up to the joiner.
 			 */
-			Exception::clear(&fiber->exception);
+			diag_clear(&fiber->diag);
 		} catch (FiberCancelException *e) {
 			say_info("fiber `%s' has been cancelled",
 				 fiber_name(fiber));
@@ -497,6 +497,7 @@ fiber_new(const char *name, void (*f) (va_list))
 	if (++cord->max_fid < 100)
 		cord->max_fid = 101;
 	fiber->fid = cord->max_fid;
+	diag_create(&fiber->diag);
 	fiber_set_name(fiber, name);
 	register_fid(fiber);
 
@@ -524,7 +525,7 @@ fiber_destroy(struct cord *cord, struct fiber *f)
 	rlist_del(&f->state);
 	region_destroy(&f->gc);
 	tarantool_coro_destroy(&f->coro, &cord->slabc);
-	Exception::clear(&f->exception);
+	diag_destroy(&f->diag);
 }
 
 void
@@ -555,7 +556,7 @@ cord_create(struct cord *cord, const char *name)
 	cord->call_stack_depth = 0;
 	cord->sched.fid = 1;
 	fiber_reset(&cord->sched);
-	Exception::init(&cord->sched.exception);
+	diag_create(&cord->sched.diag);
 	region_create(&cord->sched.gc, &cord->slabc);
 	fiber_set_name(&cord->sched, "sched");
 	cord->fiber = &cord->sched;
@@ -578,7 +579,7 @@ cord_destroy(struct cord *cord)
 		mh_i32ptr_delete(cord->fiber_registry);
 	}
 	region_destroy(&cord->sched.gc);
-	Exception::clear(&cord->sched.exception);
+	diag_destroy(&cord->sched.diag);
 	slab_cache_destroy(&cord->slabc);
 	ev_loop_destroy(cord->loop);
 }
@@ -616,11 +617,11 @@ void *cord_thread_func(void *p)
 		 * Clear a possible leftover exception object
 		 * to not confuse the invoker of the thread.
 		 */
-		Exception::clear(&cord->fiber->exception);
+		diag_clear(&cord->fiber->diag);
 	} catch (Exception *) {
 		/*
 		 * The exception is now available to the caller
-		 * via cord->exception.
+		 * via cord->diag.
 		 */
 		res = NULL;
 	}
@@ -653,16 +654,18 @@ cord_join(struct cord *cord)
 	assert(cord() != cord); /* Can't join self. */
 	void *retval = NULL;
 	int res = tt_pthread_join(cord->id, &retval);
-	if (res == 0 && cord->fiber->exception) {
+	if (res == 0 && !diag_is_empty(&cord->fiber->diag)) {
 		/*
 		 * cord_thread_func guarantees that
 		 * cord->exception is only set if the subject cord
 		 * has terminated with an uncaught exception,
 		 * transfer it to the caller.
 		 */
-		Exception::move(&cord->fiber->exception, &fiber()->exception);
+		diag_move(&cord->fiber->diag, &fiber()->diag);
 		cord_destroy(cord);
-		fiber()->exception->raise();
+		Exception *e = diag_last_error(&fiber()->diag);
+		if (e != NULL)
+			e->raise();
 	}
 	cord_destroy(cord);
 	return res;
@@ -702,10 +705,10 @@ cord_costart_thread_func(void *arg)
 		/* The fiber hasn't died right away at start. */
 		ev_run(loop(), 0);
 	}
-	if (f->exception &&
-	    typeid(f->exception) != typeid(FiberCancelException)) {
-		Exception::move(&f->exception, &fiber()->exception);
-		fiber()->exception->raise();
+	diag_move(&f->diag, &fiber()->diag);
+	Exception *e = diag_last_error(&fiber()->diag);
+	if (e != NULL && typeid(*e) != typeid(FiberCancelException)) {
+		e->raise();
 	}
 
 	return NULL;
diff --git a/src/fiber.h b/src/fiber.h
index a97fde5af9f90c3ccdb22ea5530ee22c58ee4984..0b489c29d184580720b362cfe9e1d4be03a3e500 100644
--- a/src/fiber.h
+++ b/src/fiber.h
@@ -158,7 +158,7 @@ struct fiber {
 	/** Fiber local storage */
 	void *fls[FIBER_KEY_MAX];
 	/** Exception which caused this fiber's death. */
-	class Exception *exception;
+	struct diag diag;
 };
 
 enum { FIBER_CALL_STACK = 16 };
diff --git a/src/lua/init.cc b/src/lua/init.cc
index 8e66de3f4e96e8313568fa495a04fa05aa4f922b..8de13af00875b00c2e1843e6e8fe33783f5d845f 100644
--- a/src/lua/init.cc
+++ b/src/lua/init.cc
@@ -392,8 +392,9 @@ extern "C" const char *
 tarantool_error_message(void)
 {
 	/* called only from error handler */
-	assert(fiber()->exception != NULL);
-	return fiber()->exception->errmsg();
+	Exception *e = diag_last_error(&fiber()->diag);
+	assert(e != NULL);
+	return e->errmsg();
 }
 
 /**
diff --git a/test/unit/fiber.cc b/test/unit/fiber.cc
index 866280a3798482c30fc74867ec5d00a4e22cdbe4..76d3e4c8e4c6d3ab11ea241c56d717d17aeb4eb0 100644
--- a/test/unit/fiber.cc
+++ b/test/unit/fiber.cc
@@ -89,7 +89,7 @@ fiber_join_test()
 	fiber_yield();
 	note("by this time the fiber should be dead already");
 	fiber_cancel(fiber);
-	Exception::clear(&fiber->exception);
+	diag_clear(&fiber->diag);
 	fiber_join(fiber);
 
 	footer();