From 682e922100c1b4813e46cd244ee036dc9c3ac7c4 Mon Sep 17 00:00:00 2001
From: Konstantin Osipov <kostja@tarantool.org>
Date: Tue, 13 Oct 2015 17:37:24 +0300
Subject: [PATCH] arm: add error factory to raise errors from plain C

Add exception methods to plain C.
Add an error factory to raise exceptions from plain C.
---
 src/box/applier.cc      | 10 +++----
 src/box/error.cc        |  7 ++---
 src/box/error.h         |  4 +--
 src/box/iproto.cc       |  6 ++--
 src/box/iproto_port.cc  |  6 ++--
 src/box/iproto_port.h   |  2 +-
 src/box/lua/call.cc     |  2 +-
 src/box/memtx_engine.cc |  8 ++---
 src/box/recovery.cc     |  2 +-
 src/box/relay.cc        |  4 +--
 src/coio.cc             |  4 +--
 src/coro.c              |  4 ++-
 src/diag.c              | 11 ++++---
 src/diag.h              | 47 +++++++++++++++++++++++------
 src/evio.cc             | 30 +++++++++----------
 src/exception.cc        | 65 ++++++++++++++++++++++++++++++++---------
 src/exception.h         |  8 ++++-
 src/fiber.cc            |  7 +----
 src/fiber.h             | 11 ++++---
 src/lua/fiber.cc        |  8 +----
 src/main.cc             |  6 ++--
 test/unit/fiber.cc      |  2 +-
 test/unit/fiber.result  |  2 --
 23 files changed, 161 insertions(+), 95 deletions(-)

diff --git a/src/box/applier.cc b/src/box/applier.cc
index 1d937b4564..dea38ed3ad 100644
--- a/src/box/applier.cc
+++ b/src/box/applier.cc
@@ -272,7 +272,7 @@ applier_subscribe(struct applier *applier, struct recovery *r)
  * in applier_f().
  */
 static inline void
-applier_log_exception(struct applier *applier, Exception *e)
+applier_log_error(struct applier *applier, struct error *e)
 {
 	if (type_cast(FiberCancelException, e))
 		return;
@@ -295,17 +295,17 @@ applier_log_exception(struct applier *applier, Exception *e)
 	default:
 		break;
 	}
-	e->log();
+	error_log(e);
 	if (type_cast(SocketError, e))
 		say_info("will retry every %i second", RECONNECT_DELAY);
 	applier->warning_said = true;
 }
 
 static inline void
-applier_disconnect(struct applier *applier, Exception *e,
+applier_disconnect(struct applier *applier, struct error *e,
 		   enum applier_state state)
 {
-	applier_log_exception(applier, e);
+	applier_log_error(applier, e);
 	coio_close(loop(), &applier->io);
 	iobuf_reset(applier->iobuf);
 	applier_set_state(applier, state);
@@ -411,7 +411,7 @@ applier_wait(struct applier *applier)
 	assert(applier->reader != NULL);
 	auto fiber_guard = make_scoped_guard([=] { applier->reader = NULL; });
 	fiber_join(applier->reader);
-	fiber_testerror();
+	diag_raise();
 }
 
 struct applier *
diff --git a/src/box/error.cc b/src/box/error.cc
index 56b319c7ed..5ef7e5ad5a 100644
--- a/src/box/error.cc
+++ b/src/box/error.cc
@@ -76,7 +76,7 @@ ClientError::log() const
 
 
 uint32_t
-ClientError::get_errcode(const Exception *e)
+ClientError::get_errcode(const struct error *e)
 {
 	ClientError *client_error = type_cast(ClientError, e);
 	if (client_error)
@@ -99,9 +99,8 @@ box_error_type(const box_error_t *e)
 }
 
 uint32_t
-box_error_code(const box_error_t *error)
+box_error_code(const box_error_t *e)
 {
-	Exception *e = (Exception *) error;
 	return ClientError::get_errcode(e);
 }
 
@@ -111,7 +110,7 @@ box_error_message(const box_error_t *error)
 	return error->errmsg;
 }
 
-const box_error_t *
+box_error_t *
 box_error_last(void)
 {
 	return diag_last_error(&fiber()->diag);
diff --git a/src/box/error.h b/src/box/error.h
index d7dc50d297..ae76cd2ad4 100644
--- a/src/box/error.h
+++ b/src/box/error.h
@@ -63,7 +63,7 @@ class ClientError: public Exception {
 	ClientError(const char *file, unsigned line, const char *msg,
 		    uint32_t errcode);
 
-	static uint32_t get_errcode(const Exception *e);
+	static uint32_t get_errcode(const struct error *e);
 private:
 	/* client errno code */
 	int m_errcode;
@@ -145,7 +145,7 @@ box_error_message(const box_error_t *error);
  *
  * \return last error.
  */
-API_EXPORT const box_error_t *
+API_EXPORT box_error_t *
 box_error_last(void);
 
 /**
diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index a0abef1fb5..9e3933b367 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -653,7 +653,7 @@ tx_process_msg(struct cmsg *m)
 				 */
 				if (port.found)
 					obuf_rollback_to_svp(out, &port.svp);
-				throw (Exception *) box_error_last();
+				diag_raise();
 			}
 			break;
 		}
@@ -666,7 +666,7 @@ tx_process_msg(struct cmsg *m)
 			assert(msg->request.type == msg->header.type);
 			struct tuple *tuple;
 			if (box_process1(&msg->request, &tuple) < 0)
-				throw (Exception *) box_error_last();
+				diag_raise();
 			struct obuf_svp svp = iproto_prepare_select(out);
 			if (tuple)
 				tuple_to_obuf(tuple, out);
@@ -997,7 +997,7 @@ iproto_set_listen(const char *uri)
 	fiber_yield();
 	if (! diag_is_empty(&msg.diag)) {
 		diag_move(&msg.diag, &fiber()->diag);
-		fiber_testerror();
+		diag_raise();
 	}
 }
 
diff --git a/src/box/iproto_port.cc b/src/box/iproto_port.cc
index 2f07732855..5cefa0f1af 100644
--- a/src/box/iproto_port.cc
+++ b/src/box/iproto_port.cc
@@ -82,9 +82,9 @@ iproto_reply_ok(struct obuf *out, uint64_t sync)
 }
 
 void
-iproto_reply_error(struct obuf *out, const Exception *e, uint64_t sync)
+iproto_reply_error(struct obuf *out, const struct error *e, uint64_t sync)
 {
-	uint32_t msg_len = strlen(e->get_errmsg());
+	uint32_t msg_len = strlen(e->errmsg);
 	uint32_t errcode = ClientError::get_errcode(e);
 
 	struct iproto_header_bin header = iproto_header_bin;
@@ -99,7 +99,7 @@ iproto_reply_error(struct obuf *out, const Exception *e, uint64_t sync)
 
 	obuf_dup(out, &header, sizeof(header));
 	obuf_dup(out, &body, sizeof(body));
-	obuf_dup(out, e->get_errmsg(), msg_len);
+	obuf_dup(out, e->errmsg, msg_len);
 }
 
 static inline struct iproto_port *
diff --git a/src/box/iproto_port.h b/src/box/iproto_port.h
index f1b62b7bd8..ab6aef3b12 100644
--- a/src/box/iproto_port.h
+++ b/src/box/iproto_port.h
@@ -86,7 +86,7 @@ iproto_reply_ok(struct obuf *out, uint64_t sync);
 
 /** Send an error packet back. */
 void
-iproto_reply_error(struct obuf *out, const Exception *e, uint64_t sync);
+iproto_reply_error(struct obuf *out, const struct error *e, uint64_t sync);
 
 struct obuf_svp
 iproto_prepare_select(struct obuf *buf);
diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc
index c6c67e04c2..54914e1363 100644
--- a/src/box/lua/call.cc
+++ b/src/box/lua/call.cc
@@ -464,7 +464,7 @@ execute_c_call(struct func *func, struct request *request, struct obuf *out)
 	}
 
 	if (rc != 0) {
-		fiber_testerror();
+		diag_raise();
 		tnt_raise(ClientError, ER_PROC_C, "unknown procedure error");
 	}
 
diff --git a/src/box/memtx_engine.cc b/src/box/memtx_engine.cc
index 7ebe795567..91aa12c064 100644
--- a/src/box/memtx_engine.cc
+++ b/src/box/memtx_engine.cc
@@ -1116,9 +1116,9 @@ MemtxEngine::waitCheckpoint()
 	/* wait for memtx-part snapshot completion */
 	int result = cord_cojoin(&m_checkpoint->cord);
 
-	Exception *e = (Exception *) diag_last_error(&fiber()->diag);
+	struct error *e = diag_last_error(&fiber()->diag);
 	if (e != NULL) {
-		e->log();
+		error_log(e);
 		result = -1;
 		SystemError *se = type_cast(SystemError, e);
 		if (se)
@@ -1163,9 +1163,9 @@ MemtxEngine::abortCheckpoint()
 		/* wait for memtx-part snapshot completion */
 		cord_cojoin(&m_checkpoint->cord);
 
-		Exception *e = (Exception *) diag_last_error(&fiber()->diag);
+		struct error *e = diag_last_error(&fiber()->diag);
 		if (e)
-			e->log();
+			error_log(e);
 		m_checkpoint->waiting_for_snap_thread = false;
 	}
 
diff --git a/src/box/recovery.cc b/src/box/recovery.cc
index d3b176d049..25a54b9a86 100644
--- a/src/box/recovery.cc
+++ b/src/box/recovery.cc
@@ -536,7 +536,7 @@ recovery_stop_local(struct recovery *r)
 		r->watcher = NULL;
 		fiber_cancel(f);
 		fiber_join(f);
-		fiber_testerror();
+		diag_raise();
 	}
 }
 
diff --git a/src/box/relay.cc b/src/box/relay.cc
index 25dd6cf065..c302f52295 100644
--- a/src/box/relay.cc
+++ b/src/box/relay.cc
@@ -111,7 +111,7 @@ relay_join(int fd, struct xrow_header *packet,
 
 	cord_costart(&relay.cord, "join", relay_join_f, &relay);
 	cord_cojoin(&relay.cord);
-	fiber_testerror();
+	diag_raise();
 	/**
 	 * Call the server-side hook which stores the replica uuid
 	 * in _cluster space after sending the last row but before
@@ -255,7 +255,7 @@ relay_subscribe(int fd, struct xrow_header *packet,
 	struct cord cord;
 	cord_costart(&cord, "subscribe", relay_subscribe_f, &relay);
 	cord_cojoin(&cord);
-	fiber_testerror();
+	diag_raise();
 }
 
 void
diff --git a/src/coio.cc b/src/coio.cc
index c803a79eae..7406d3f43c 100644
--- a/src/coio.cc
+++ b/src/coio.cc
@@ -609,8 +609,8 @@ coio_service_on_accept(struct evio_service *evio_service,
 	try {
 		iobuf = iobuf_new();
 		f = fiber_new(fiber_name, service->handler);
-	} catch (Exception *e) {
-		e->log();
+	} catch (struct error *e) {
+		error_log(e);
 		say_error("can't create a handler fiber, dropping client connection");
 		evio_close(loop(), &coio);
 		if (iobuf)
diff --git a/src/coro.c b/src/coro.c
index 1e1c783e11..5e68f40699 100644
--- a/src/coro.c
+++ b/src/coro.c
@@ -36,7 +36,7 @@
 #include <sys/mman.h>
 #include "small/slab_cache.h"
 #include "third_party/valgrind/memcheck.h"
-
+#include "diag.h"
 
 int
 tarantool_coro_create(struct tarantool_coro *coro,
@@ -53,6 +53,8 @@ tarantool_coro_create(struct tarantool_coro *coro,
 					+ slab_sizeof();
 
 	if (coro->stack == NULL) {
+		diag_set(OutOfMemory, coro->stack_size + slab_sizeof(),
+			 "runtime arena", "coro stack");
 		return -1;
 	}
 
diff --git a/src/diag.c b/src/diag.c
index 9477087a16..a63ef85ee5 100644
--- a/src/diag.c
+++ b/src/diag.c
@@ -31,14 +31,17 @@
 #include "diag.h"
 #include "fiber.h"
 
+/* Must be set by the library user */
+struct error_factory *error_factory = NULL;
+
 void
 error_create(struct error *e,
-	     void (*destroy)(struct error *),
-	     const struct type *type,
-	     const char *file,
-	     unsigned line)
+	     error_f destroy, error_f raise, error_f log,
+	     const struct type *type, const char *file, unsigned line)
 {
 	e->destroy = destroy;
+	e->raise = raise;
+	e->log = log;
 	e->type = type;
 	e->refs = 0;
 	if (file != NULL) {
diff --git a/src/diag.h b/src/diag.h
index 39bcdf1591..f2a1258959 100644
--- a/src/diag.h
+++ b/src/diag.h
@@ -34,6 +34,7 @@
 #include <stddef.h>
 #include <stdbool.h>
 #include <assert.h>
+#include "say.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -45,6 +46,11 @@ enum {
 };
 
 struct type;
+struct error;
+struct error_factory;
+extern struct error_factory *error_factory;
+
+typedef void (*error_f)(struct error *e);
 
 /**
  * Error diagnostics needs to be equally usable in C and C++
@@ -61,7 +67,9 @@ struct type;
  * (destroy) is there to gracefully delete C++ exceptions from C.
  */
 struct error {
-	void (*destroy)(struct error *e);
+	error_f destroy;
+	error_f raise;
+	error_f log;
 	const struct type *type;
 	int refs;
 	/** Line number. */
@@ -87,12 +95,22 @@ error_unref(struct error *e)
 		e->destroy(e);
 }
 
+static inline void
+error_raise(struct error *e)
+{
+	e->raise(e);
+}
+
+static inline void
+error_log(struct error *e)
+{
+	e->log(e);
+}
+
 void
 error_create(struct error *e,
-	     void (*destroy)(struct error *e),
-	     const struct type *type,
-	     const char *file,
-	     unsigned line);
+	     error_f create, error_f raise, error_f log,
+	     const struct type *type, const char *file, unsigned line);
 
 void
 error_format_msg(struct error *e, const char *format, ...);
@@ -192,13 +210,24 @@ diag_last_error(struct diag *diag)
 	return diag->last;
 }
 
-/**
- * A helper for tnt_error to avoid cyclic includes (fiber.h and exception.h)
- * \cond false
- * */
+struct error_factory {
+	struct error *(*OutOfMemory)(const char *file,
+				     unsigned line, size_t amount,
+				     const char *allocator,
+				     const char *object);
+};
+
 struct diag *
 diag_get();
 
+#define diag_set(class, ...) ({						\
+	say_debug("%s at %s:%i", #class, __FILE__, __LINE__);		\
+	struct error *e = error_factory->class(__FILE__, __LINE__,	\
+						##__VA_ARGS__);		\
+	diag_add_error(diag_get(), e);					\
+	e;								\
+})
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/src/evio.cc b/src/evio.cc
index 337a4c72d4..cb71d68c81 100644
--- a/src/evio.cc
+++ b/src/evio.cc
@@ -201,29 +201,27 @@ evio_service_bind_addr(struct evio_service *service)
 	int fd = sio_socket(service->addr.sa_family,
 		SOCK_STREAM, IPPROTO_TCP);
 
-	try {
-		evio_setsockopt_server(fd, service->addr.sa_family, SOCK_STREAM);
-
-		if (sio_bind(fd, &service->addr, service->addr_len) ||
-		    sio_listen(fd)) {
-			assert(errno == EADDRINUSE);
-			close(fd);
-			return -1;
-		}
-		say_info("%s: bound to %s", evio_service_name(service),
-			 sio_strfaddr(&service->addr, service->addr_len));
+	auto fd_guard = make_scoped_guard([=]{ close(fd); });
 
-		/* Invoke on_bind callback if it is set. */
-		if (service->on_bind)
-			service->on_bind(service->on_bind_param);
+	evio_setsockopt_server(fd, service->addr.sa_family, SOCK_STREAM);
 
-	} catch (Exception *e) {
+	if (sio_bind(fd, &service->addr, service->addr_len) ||
+	    sio_listen(fd)) {
+		assert(errno == EADDRINUSE);
 		close(fd);
-		throw;
+		return -1;
 	}
+	say_info("%s: bound to %s", evio_service_name(service),
+		 sio_strfaddr(&service->addr, service->addr_len));
+
+	/* Invoke on_bind callback if it is set. */
+	if (service->on_bind)
+		service->on_bind(service->on_bind_param);
+
 	/* Register the socket in the event loop. */
 	ev_io_set(&service->ev, fd, EV_READ);
 	ev_io_start(service->loop, &service->ev);
+	fd_guard.is_active = false;
 	return 0;
 }
 
diff --git a/src/exception.cc b/src/exception.cc
index 9659942886..0c739b6037 100644
--- a/src/exception.cc
+++ b/src/exception.cc
@@ -29,12 +29,35 @@
  * SUCH DAMAGE.
  */
 #include "exception.h"
-#include "say.h"
 
 #include <stdio.h>
 #include <string.h>
 #include <errno.h>
 
+extern "C" {
+
+static void
+exception_destroy(struct error *e)
+{
+	delete (Exception *) e;
+}
+
+static void
+exception_raise(struct error *error)
+{
+	Exception *e = (Exception *) error;
+	e->raise();
+}
+
+static void
+exception_log(struct error *error)
+{
+	Exception *e = (Exception *) error;
+	e->log();
+}
+
+} /* extern "C" */
+
 /** out_of_memory::size is zero-initialized by the linker. */
 static OutOfMemory out_of_memory(__FILE__, __LINE__,
 				 sizeof(OutOfMemory), "malloc", "exception");
@@ -72,22 +95,11 @@ Exception::~Exception()
 	}
 }
 
-extern "C" void
-exception_destroy(struct error *msg)
-{
-	delete (Exception *) msg;
-}
-
 Exception::Exception(const struct type *type_arg, const char *file,
 		     unsigned line)
 {
-	error_create(this, exception_destroy, type_arg,
-		     file, line);
-	if (this == &out_of_memory) {
-		/* A special workaround for out_of_memory static init */
-		out_of_memory.refs = 1;
-		return;
-	}
+	error_create(this, exception_destroy, exception_raise,
+		     exception_log, type_arg, file, line);
 }
 
 void
@@ -143,6 +155,18 @@ OutOfMemory::OutOfMemory(const char *file, unsigned line,
 			 (unsigned) amount, allocator, object);
 }
 
+static struct error *
+BuildOutOfMemory(const char *file, unsigned line,
+		 size_t amount, const char *allocator,
+		 const char *object)
+{
+	void *p = malloc(sizeof(OutOfMemory));
+	if (p == NULL)
+		return &out_of_memory;
+	return new (p) OutOfMemory(file, line, amount, allocator,
+				   object);
+}
+
 const struct type type_TimedOut =
 	make_type("TimedOut", &type_SystemError);
 
@@ -151,3 +175,16 @@ TimedOut::TimedOut(const char *file, unsigned line)
 {
 	m_errno = ETIMEDOUT;
 }
+
+void
+exception_init()
+{
+	static struct error_factory exception_error_factory;
+
+	exception_error_factory.OutOfMemory = BuildOutOfMemory;
+
+	error_factory = &exception_error_factory;
+
+	/* A special workaround for out_of_memory static init */
+	out_of_memory.refs = 1;
+}
diff --git a/src/exception.h b/src/exception.h
index 9ba9219965..810ecf2f66 100644
--- a/src/exception.h
+++ b/src/exception.h
@@ -35,13 +35,13 @@
 
 #include "reflection.h"
 #include "diag.h"
-#include "say.h"
 
 extern const struct type type_Exception;
 
 class Exception: public error {
 public:
 	void *operator new(size_t size);
+	void *operator new(size_t size, void *p) { (void) size; return p; }
 	void operator delete(void*);
 
 	const char *get_file() const { return file; }
@@ -92,6 +92,12 @@ class TimedOut: public SystemError {
 	virtual void raise() { throw this; }
 };
 
+/**
+ * Initialize the exception subsystem.
+ */
+void
+exception_init();
+
 #define tnt_error(class, ...) ({					\
 	say_debug("%s at %s:%i", #class, __FILE__, __LINE__);		\
 	class *e = new class(__FILE__, __LINE__, ##__VA_ARGS__);	\
diff --git a/src/fiber.cc b/src/fiber.cc
index d1f0f361c0..c9c33acf27 100644
--- a/src/fiber.cc
+++ b/src/fiber.cc
@@ -418,10 +418,6 @@ fiber_loop(void *data __attribute__((unused)))
 			 * propagate up to the joiner.
 			 */
 			diag_clear(&fiber->diag);
-		} catch (FiberCancelException *e) {
-			say_info("fiber `%s' has been cancelled",
-				 fiber_name(fiber));
-			say_info("fiber `%s': exiting", fiber_name(fiber));
 		} catch (Exception *e) {
 			/*
 			 * For joinable fibers, it's the business
@@ -495,8 +491,7 @@ fiber_new(const char *name, void (*f) (va_list))
 
 		if (tarantool_coro_create(&fiber->coro, &cord->slabc,
 					  fiber_loop, NULL)) {
-			tnt_raise(OutOfMemory, 65536,
-				  "runtime arena", "coro stack");
+			diag_raise();
 		}
 
 		region_create(&fiber->gc, &cord->slabc);
diff --git a/src/fiber.h b/src/fiber.h
index e8052b6d5e..5b9655748d 100644
--- a/src/fiber.h
+++ b/src/fiber.h
@@ -430,7 +430,9 @@ class FiberCancelException: public Exception {
 	}
 
 	virtual void log() const {
-		say_debug("FiberCancelException");
+			say_info("fiber `%s' has been cancelled",
+				 fiber_name(fiber()));
+			say_info("fiber `%s': exiting", fiber_name(fiber()));
 	}
 	virtual void raise() { throw this; }
 };
@@ -454,12 +456,13 @@ fiber_testcancel(void)
 		tnt_raise(FiberCancelException);
 }
 
+
 static inline void
-fiber_testerror(void)
+diag_raise(void)
 {
-	Exception *e = (Exception *) diag_last_error(&fiber()->diag);
+	struct error *e = diag_last_error(&fiber()->diag);
 	if (e)
-		e->raise();
+		error_raise(e);
 }
 
 #endif /* defined(__cplusplus) */
diff --git a/src/lua/fiber.cc b/src/lua/fiber.cc
index a028f4ec86..e1d7da5dbb 100644
--- a/src/lua/fiber.cc
+++ b/src/lua/fiber.cc
@@ -300,13 +300,7 @@ box_lua_fiber_run(va_list ap)
 			lua_unref(L, storage_ref);
 	});
 
-	try {
-		lbox_call(L, lua_gettop(L) - 1, LUA_MULTRET);
-	} catch (FiberCancelException *e) {
-		throw;
-	} catch (Exception *e) {
-		e->log();
-	}
+	lbox_call(L, lua_gettop(L) - 1, LUA_MULTRET);
 }
 
 /**
diff --git a/src/main.cc b/src/main.cc
index a5216eaf11..2ca520e935 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -629,6 +629,8 @@ main(int argc, char **argv)
 	main_argc = argc;
 	main_argv = argv;
 
+	exception_init();
+
 	fiber_init();
 	/* Init iobuf library with default readahead */
 	iobuf_init();
@@ -660,8 +662,8 @@ main(int argc, char **argv)
 			ev_now_update(loop());
 			ev_run(loop(), 0);
 		}
-	} catch (Exception *e) {
-		e->log();
+	} catch (struct error *e) {
+		error_log(e);
 		panic("%s", "fatal error, exiting the event loop");
 	}
 
diff --git a/test/unit/fiber.cc b/test/unit/fiber.cc
index c6a8d8983d..dc87e311bf 100644
--- a/test/unit/fiber.cc
+++ b/test/unit/fiber.cc
@@ -64,7 +64,7 @@ fiber_join_test()
 	fiber_wakeup(fiber);
 	try {
 		fiber_join(fiber);
-		fiber_testerror();
+		diag_raise();
 		fail("exception not raised", "");
 	} catch (Exception *e) {
 		note("exception propagated");
diff --git a/test/unit/fiber.result b/test/unit/fiber.result
index a0ea80cf1c..51cc228311 100644
--- a/test/unit/fiber.result
+++ b/test/unit/fiber.result
@@ -1,5 +1,3 @@
-(null): fiber `cancel' has been cancelled
-(null): fiber `cancel': exiting
 	*** fiber_join_test ***
 # exception propagated
 # cancel dead has started
-- 
GitLab