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();