diff --git a/cfg/tarantool_box_cfg.c b/cfg/tarantool_box_cfg.c
index 7b600d2b79cc4f5ca4d5b7a1bfb9285e8f3591c9..fd6e2d864a5a405cf491488b1367efdc387f8e3b 100644
--- a/cfg/tarantool_box_cfg.c
+++ b/cfg/tarantool_box_cfg.c
@@ -119,6 +119,13 @@ fill_default_tarantool_cfg(tarantool_cfg *c) {
 	return 0;
 }
 
+void
+swap_tarantool_cfg(struct tarantool_cfg *c1, struct tarantool_cfg *c2) {
+	struct tarantool_cfg tmpcfg = *c1;
+	*c1 = *c2;
+	*c2 = tmpcfg;
+}
+
 static int
 acceptDefault_name__namespace(tarantool_cfg_namespace *c) {
 	c->enabled = -1;
diff --git a/cfg/tarantool_box_cfg.h b/cfg/tarantool_box_cfg.h
index 41cd800f948b512c21cad710a66946fac9c430f0..9122fb36795d0d33359ecea5f064853624f5edba 100644
--- a/cfg/tarantool_box_cfg.h
+++ b/cfg/tarantool_box_cfg.h
@@ -201,6 +201,8 @@ void init_tarantool_cfg(tarantool_cfg *c);
 
 int fill_default_tarantool_cfg(tarantool_cfg *c);
 
+void swap_tarantool_cfg(struct tarantool_cfg *c1, struct tarantool_cfg *c2);
+
 void parse_cfg_file_tarantool_cfg(tarantool_cfg *c, FILE *fh, int check_rdonly, int *n_accepted, int *n_skipped);
 
 void parse_cfg_buffer_tarantool_cfg(tarantool_cfg *c, char *buffer, int check_rdonly, int *n_accepted, int *n_skipped);
diff --git a/connector/c/client.c b/connector/c/client.c
index d117e3d5e3c3a915bd238961bfdd669c63dcd104..79385a4dd817b904e9c90caf67dad5bcc8d52f44 100644
--- a/connector/c/client.c
+++ b/connector/c/client.c
@@ -115,7 +115,7 @@ int tnt_execute_raw(struct tnt_connection *tnt, const char *message,
 	if (send(tnt->data_port, message, len, 0) < 0)
 		return -1;
 
-	char buf[2048];
+	static char buf[2048];
 
 	if (recv(tnt->data_port, buf, 2048, 0) < 16)
 		return -1;
@@ -126,6 +126,10 @@ int tnt_execute_raw(struct tnt_connection *tnt, const char *message,
 		/* @fixme: we may want to support big-endian some
 		 * day. */
 		tnt_res->errcode = * (uint32_t*) (buf+12); /* see iproto.h */
+		if (tnt_res->errcode)
+			tnt_res->errmsg = (const char *)(buf + 16);
+		else
+			tnt_res->errmsg = "";
 	}
 	return 0;
 }
diff --git a/connector/c/client.h b/connector/c/client.h
index f54cf7f77d2b30e0a84eca7abd11c1aa86d9b42a..ee124d868f4a5065f098ac64f934d92df4544e42 100644
--- a/connector/c/client.h
+++ b/connector/c/client.h
@@ -43,6 +43,7 @@ struct tnt_result
 {
 	/** Server error or 0. */
 	uint32_t errcode;
+	const char *errmsg;
 };
 
 
diff --git a/mod/box/client/perl/MANIFEST b/connector/perl/MANIFEST
old mode 100755
new mode 100644
similarity index 100%
rename from mod/box/client/perl/MANIFEST
rename to connector/perl/MANIFEST
diff --git a/mod/box/client/perl/Makefile.PL b/connector/perl/Makefile.PL
old mode 100755
new mode 100644
similarity index 100%
rename from mod/box/client/perl/Makefile.PL
rename to connector/perl/Makefile.PL
diff --git a/mod/box/client/perl/lib/MR/IProto.pm b/connector/perl/lib/MR/IProto.pm
similarity index 100%
rename from mod/box/client/perl/lib/MR/IProto.pm
rename to connector/perl/lib/MR/IProto.pm
diff --git a/mod/box/client/perl/lib/MR/SilverBox.pm b/connector/perl/lib/MR/SilverBox.pm
similarity index 100%
rename from mod/box/client/perl/lib/MR/SilverBox.pm
rename to connector/perl/lib/MR/SilverBox.pm
diff --git a/mod/box/client/perl/lib/MR/Storage/Const.pm b/connector/perl/lib/MR/Storage/Const.pm
similarity index 100%
rename from mod/box/client/perl/lib/MR/Storage/Const.pm
rename to connector/perl/lib/MR/Storage/Const.pm
diff --git a/mod/box/t/TBox.pm b/connector/perl/t/TBox.pm
similarity index 100%
rename from mod/box/t/TBox.pm
rename to connector/perl/t/TBox.pm
diff --git a/mod/box/t/box.pl b/connector/perl/t/box.pl
similarity index 100%
rename from mod/box/t/box.pl
rename to connector/perl/t/box.pl
diff --git a/mod/box/t/box_tree.pl b/connector/perl/t/box_tree.pl
similarity index 100%
rename from mod/box/t/box_tree.pl
rename to connector/perl/t/box_tree.pl
diff --git a/mod/box/client/ruby/README b/connector/ruby/README
similarity index 100%
rename from mod/box/client/ruby/README
rename to connector/ruby/README
diff --git a/mod/box/client/ruby/box.rb b/connector/ruby/box.rb
similarity index 100%
rename from mod/box/client/ruby/box.rb
rename to connector/ruby/box.rb
diff --git a/mod/box/client/ruby/iproto.rb b/connector/ruby/iproto.rb
similarity index 100%
rename from mod/box/client/ruby/iproto.rb
rename to connector/ruby/iproto.rb
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 86293ad18727b05c0fe6b9ca767bbbe8caa41171..8f7a5f95c0742187fff55123f81166a255954039 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -55,9 +55,9 @@ set (recompiled_core_sources
      ${CMAKE_SOURCE_DIR}/core/replicator.m
      ${CMAKE_SOURCE_DIR}/core/fiber.m PARENT_SCOPE)
 
-set (common_sources tbuf.m palloc.m util.m diagnostics.m
+set (common_sources tbuf.m palloc.m util.m
     salloc.m pickle.m coro.m stat.m log_io.m
-    log_io_remote.m iproto.m exceptions.m errcode.c)
+    log_io_remote.m iproto.m exception.m errcode.c latch.m)
 
 if (ENABLE_TRACE)
   set (common_sources ${common_sources} trace.m)
diff --git a/core/admin.m b/core/admin.m
index 73fb1654141c479ed998b3001e82aa3092658865..6ffc97035e7caef54fd337bb2d44e6639f721751 100644
--- a/core/admin.m
+++ b/core/admin.m
@@ -61,7 +61,7 @@ static const char *help =
 static const char *unknown_command = "unknown command. try typing help." CRLF;
 
 
-#line 65 "core/admin.c"
+#line 65 "core/admin.m"
 static const int admin_start = 1;
 static const int admin_first_final = 108;
 static const int admin_error = 0;
@@ -119,12 +119,12 @@ admin_dispatch(void)
 	p = fiber->rbuf->data;
 
 	
-#line 123 "core/admin.c"
+#line 123 "core/admin.m"
 	{
 	cs = admin_start;
 	}
 
-#line 128 "core/admin.c"
+#line 128 "core/admin.m"
 	{
 	if ( p == pe )
 		goto _test_eof;
@@ -283,7 +283,7 @@ st108:
 	if ( ++p == pe )
 		goto _test_eof108;
 case 108:
-#line 287 "core/admin.c"
+#line 287 "core/admin.m"
 	goto st0;
 tr13:
 #line 193 "core/admin.rl"
@@ -383,7 +383,7 @@ st7:
 	if ( ++p == pe )
 		goto _test_eof7;
 case 7:
-#line 387 "core/admin.c"
+#line 387 "core/admin.m"
 	if ( (*p) == 10 )
 		goto st108;
 	goto st0;
@@ -471,7 +471,7 @@ st16:
 	if ( ++p == pe )
 		goto _test_eof16;
 case 16:
-#line 475 "core/admin.c"
+#line 475 "core/admin.m"
 	switch( (*p) ) {
 		case 10: goto tr28;
 		case 13: goto tr29;
@@ -485,7 +485,7 @@ st17:
 	if ( ++p == pe )
 		goto _test_eof17;
 case 17:
-#line 489 "core/admin.c"
+#line 489 "core/admin.m"
 	switch( (*p) ) {
 		case 10: goto tr28;
 		case 13: goto tr29;
diff --git a/core/errcode.c b/core/errcode.c
index c7bed55d1f597e3833592122ac94df3ab0311215..3d4d534ffef30a69d5bc1305a19cfa7cb1f16795 100644
--- a/core/errcode.c
+++ b/core/errcode.c
@@ -3,7 +3,7 @@
 #define ERRCODE_RECORD_MEMBER(s, f, d) {	\
 	.errstr = #s,				\
 	.errflags = f,				\
-	.errdesc = #d				\
+	.errdesc = d				\
 },
 
 struct errcode_record tnt_error_codes[tnt_error_codes_enum_MAX] = {
diff --git a/core/exception.m b/core/exception.m
new file mode 100644
index 0000000000000000000000000000000000000000..955c91c86aec3c6629b760d4382f7bf2f6190286
--- /dev/null
+++ b/core/exception.m
@@ -0,0 +1,92 @@
+/*
+ * 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 "exception.h"
+#include "say.h"
+
+
+@implementation tnt_Exception
++ (id) alloc
+{
+	static __thread tnt_Exception *e = nil;
+
+	if ([e isKindOf:self]) {
+		*(Class *) e = self;
+	} else {
+		[e free];
+		e = [super alloc];
+	}
+	return e;
+}
+@end
+
+
+@implementation ClientError
+- (id) init: (uint32_t)errcode_, ...
+{
+	va_list ap;
+	va_start(ap, errcode_);
+	[self init: errcode_ args: ap];
+	va_end(ap);
+
+	return self;
+}
+
+
+- (id) init: (uint32_t)errcode_ args: (va_list)ap
+{
+	[super init];
+	errcode = errcode_;
+	vsnprintf(errmsg, sizeof(errmsg), tnt_errcode_desc(errcode), ap);
+	return self;
+}
+@end
+
+
+@implementation LoggedError
+- (id) init: (uint32_t) errcode_, ...
+{
+	va_list ap;
+	va_start(ap, errcode_);
+	[super init: errcode_ args: ap];
+
+	say_error("%s at %s:%d, %s", [self name], file, line, errmsg);
+
+	return self;
+}
+@end
+
+
+@implementation IllegalParams
+- (id) init: (const char*) msg
+{
+	printf("IllegalParams init\n");
+	return [super init: ER_ILLEGAL_PARAMS, msg];
+}
+@end
+
diff --git a/core/exceptions.m b/core/exceptions.m
deleted file mode 100644
index 2db8c416a0f0e106823526a8d78b105ca8d25c8b..0000000000000000000000000000000000000000
--- a/core/exceptions.m
+++ /dev/null
@@ -1,35 +0,0 @@
-#include <exceptions.h>
-#include <say.h>
-
-@implementation tnt_Exception
-+ alloc
-{
-	static __thread tnt_Exception *e = nil;
-
-	if (![e isKindOf:self]) {
-		[e free];
-		e = [super alloc];
-	}
-
-	return e;
-}
-
-- init:(const char *)p_file:(unsigned)p_line reason:(const char *)p_reason
-{
-	[super init];
-
-	file = p_file;
-	line = p_line;
-
-	reason = p_reason;
-
-	return self;
-}
-
-- init:(const char *)p_file:(unsigned)p_line
-{
-	return [self init:p_file:p_line reason:"unknown"];
-}
-
-@end
-
diff --git a/core/fiber.m b/core/fiber.m
index c0503b138ec7b4377192b248b2de9c71368177b1..02de9b510ced918c2e3cbfc56d11973a4864c6a0 100644
--- a/core/fiber.m
+++ b/core/fiber.m
@@ -54,9 +54,8 @@
 #include <util.h>
 #include <stat.h>
 #include <pickle.h>
-#include "diagnostics.h"
 
-@implementation tnt_FiberException
+@implementation FiberCancelException
 @end
 
 static struct fiber sched;
@@ -121,14 +120,94 @@ fiber_call(struct fiber *callee)
 	coro_transfer(&caller->coro.ctx, &callee->coro.ctx);
 }
 
+
+/** Interrupt a synchronous wait of a fiber inside the event loop.
+ * We do so by keeping an "async" event in every fiber, solely
+ * for this purpose, and raising this event here.
+ */
+
+void
+fiber_wakeup(struct fiber *f)
+{
+	ev_async_start(&f->async);
+	ev_async_send(&f->async);
+}
+
+/** Cancel the subject fiber.
+ *
+ * Note: this is not guaranteed to succeed, and requires a level
+ * of cooperation on behalf of the fiber. A fiber may opt to set
+ * FIBER_CANCELLABLE to false, and never test that it was
+ * cancelled.  Such fiber we won't be ever to cancel, ever, and
+ * for such fiber this call will lead to an infinite wait.
+ * However, fiber_testcancel() is embedded to the rest of fiber_*
+ * API (@sa yield()), which makes most of the fibers that opt in,
+ * cancellable.
+ *
+ * Currently cancellation can only be synchronous: this call
+ * returns only when the subject fiber has terminated.
+ *
+ * The fiber which is cancelled, has tnt_FiberCancelException
+ * raised in it. For cancellation to work, this exception type
+ * should be re-raised whenever (if) it is caught.
+ */
+
+void
+fiber_cancel(struct fiber *f)
+{
+	assert(fiber->fid != 0);
+	assert(!(f->flags & FIBER_CANCEL));
+
+	f->flags |= FIBER_CANCEL;
+
+	if (f->flags & FIBER_CANCELLABLE)
+		fiber_wakeup(f);
+
+	assert(f->waiter == NULL);
+	f->waiter = fiber;
+
+	@try {
+		yield();
+	}
+	@finally {
+		f->waiter = NULL;
+	}
+}
+
+
+/** Test if this fiber is in a cancellable state and was indeed
+ * cancelled, and raise an exception (tnt_FiberCancelException) if
+ * that's the case.
+ */
+
 void
-fiber_raise(struct fiber *callee)
+fiber_testcancel(void)
 {
-	callee->flags |= FIBER_RAISE;
+	if (!(fiber->flags & FIBER_CANCELLABLE))
+		return;
 
-	fiber_call(callee);
+	if (!(fiber->flags & FIBER_CANCEL))
+		return;
+
+	tnt_raise(FiberCancelException);
 }
 
+/** Change the current cancellation state of a fiber. This is not
+ * a cancellation point.
+ */
+
+void fiber_setcancelstate(bool enable)
+{
+	if (enable == true)
+		fiber->flags |= FIBER_CANCELLABLE;
+	else
+		fiber->flags &= ~FIBER_CANCELLABLE;
+}
+
+/**
+ * @note: this is a cancellation point (@sa fiber_testcancel())
+ */
+
 void
 yield(void)
 {
@@ -141,61 +220,79 @@ yield(void)
 	callee->csw++;
 	coro_transfer(&caller->coro.ctx, &callee->coro.ctx);
 
-	if (fiber->flags & FIBER_RAISE) {
-		fiber->flags &= ~FIBER_RAISE;
-
-		tnt_raise(tnt_FiberException, reason:"fiber_raise");
-	}
+	fiber_testcancel();
 }
 
+/**
+ * @note: this is a cancellation point (@sa fiber_testcancel())
+ */
+
 void
 fiber_sleep(ev_tstamp delay)
 {
 	ev_timer_set(&fiber->timer, delay, 0.);
 	ev_timer_start(&fiber->timer);
-	yield();
+	@try {
+		yield();
+	}
+	@finally {
+		ev_timer_stop(&fiber->timer);
+	}
 }
 
-
-/** Wait for a forked child to complete. */
+/** Wait for a forked child to complete.
+ * @note: this is a cancellation point (@sa fiber_testcancel()).
+*/
 
 void
 wait_for_child(pid_t pid)
 {
 	ev_child_set(&fiber->cw, pid, 0);
 	ev_child_start(&fiber->cw);
-	yield();
-	ev_child_stop(&fiber->cw);
+	@try {
+		yield();
+	}
+	@finally {
+		ev_child_stop(&fiber->cw);
+	}
 }
 
-void
-wait_for(int events)
+
+static void
+fiber_io_start(int events)
 {
 	ev_io *io = &fiber->io;
 
-	if (io->fd != fiber->fd || io->events != events) {	/* events are not monitored */
-		if (ev_is_active(io))
-			ev_io_stop(io);
-		ev_io_set(io, fiber->fd, events);
-	}
+	assert (!ev_is_active(io));
 
-	if (!ev_is_active(io))
-		ev_io_start(io);
+	ev_io_set(io, fiber->fd, events);
+	ev_io_start(io);
+}
 
-	yield();
+/** @note: this is a cancellation point.
+ */
+
+static void
+fiber_io_yield()
+{
+	assert(ev_is_active(&fiber->io));
+
+	@try {
+		yield();
+	}
+	@catch (id o)
+	{
+		ev_io_stop(&fiber->io);
+		@throw;
+	}
 }
 
-void
-unwait(int events)
+static void
+fiber_io_stop(int events)
 {
 	ev_io *io = &fiber->io;
-	assert(io->fd == fiber->fd);
 
-	if (!ev_is_active(io))
-		return;
-
-	if ((io->events & events) == 0)
-		return;
+	assert(ev_is_active(io) && io->fd == fiber->fd && (io->events & events));
 
 	ev_io_stop(io);
 }
@@ -324,7 +421,6 @@ fiber_gc(void)
 static void
 fiber_zombificate()
 {
-	diag_clear();
 	fiber_set_name(fiber, "zombie");
 	fiber->f = NULL;
 	fiber->data = NULL;
@@ -338,21 +434,19 @@ fiber_zombificate()
 static void
 fiber_loop(void *data __attribute__((unused)))
 {
-	while (42) {
+	for (;;) {
 		assert(fiber != NULL && fiber->f != NULL && fiber->fid != 0);
 		@try {
 			fiber->f(fiber->f_data);
 		}
-		@catch (tnt_FiberException *e) {
-			say_info("fiber `%s': exception `tnt_FiberException': `%s'",
-				 fiber->name, e->reason);
+		@catch (FiberCancelException *e) {
+			say_info("fiber `%s' has been cancelled", fiber->name);
+
+			if (fiber->waiter != NULL)
+				fiber_call(fiber->waiter);
+
 			say_info("fiber `%s': exiting", fiber->name);
 		}
-		@catch (tnt_Exception *e) {
-			say_error("fiber `%s': exception `%s': `%s'",
-				  fiber->name, [e name], e->reason);
-			panic("fiber `%s': exiting", fiber->name);
-		}
 		@catch (id e) {
 			say_error("fiber `%s': exception `%s'", fiber->name, [e name]);
 			panic("fiber `%s': exiting", fiber->name);
@@ -364,9 +458,10 @@ fiber_loop(void *data __attribute__((unused)))
 	}
 }
 
-
-/** Set fiber name. 
-* @Param[in] name the new name of the fiber. Truncated to FIBER_NAME_MAXLEN.
+/** Set fiber name.
+ *
+ * @param[in] name the new name of the fiber. Truncated to
+ * FIBER_NAME_MAXLEN.
 */
 
 void
@@ -403,9 +498,10 @@ fiber_create(const char *name, int fd, int inbox_size, void (*f) (void *), void
 
 		fiber_alloc(fiber);
 		ev_init(&fiber->io, (void *)ev_schedule);
+		ev_async_init(&fiber->async, (void *)ev_schedule);
 		ev_init(&fiber->timer, (void *)ev_schedule);
 		ev_init(&fiber->cw, (void *)ev_schedule);
-		fiber->io.data = fiber->timer.data = fiber->cw.data = fiber;
+		fiber->io.data = fiber->async.data = fiber->timer.data = fiber->cw.data = fiber;
 
 		SLIST_INSERT_HEAD(&fibers, fiber, link);
 	}
@@ -416,6 +512,7 @@ fiber_create(const char *name, int fd, int inbox_size, void (*f) (void *), void
 	while (++last_used_fid <= 100) ;	/* fids from 0 to 100 are reserved */
 	fiber->fid = last_used_fid;
 	fiber->flags = 0;
+	fiber->waiter = NULL;
 	fiber_set_name(fiber, name);
 	palloc_set_name(fiber->pool, fiber->name);
 	register_fid(fiber);
@@ -483,13 +580,16 @@ fiber_close(void)
 	if (fiber->fd < 0)
 		return 0;
 
-	unwait(-1);
+	/* We don't know if IO is active if there was an error. */
+	if (ev_is_active(&fiber->io))
+		fiber_io_stop(-1);
+
 	int r = close(fiber->fd);
-	if (r != -1) {
-		fiber->io.fd = fiber->fd = -1;
-		fiber->has_peer = false;
-		fiber->peer_name[0] = 0;
-	}
+
+	fiber->fd = -1;
+	fiber->has_peer = false;
+	fiber->peer_name[0] = 0;
+
 	return r;
 }
 
@@ -505,13 +605,21 @@ inbox_size(struct fiber *recipient)
 	return ring_size(recipient->inbox);
 }
 
+/**
+ * @note: this is a cancellation point (@sa fiber_testcancel())
+ */
+
 void
 wait_inbox(struct fiber *recipient)
 {
 	while (ring_size(recipient->inbox) == 0) {
 		recipient->flags |= FIBER_READING_INBOX;
-		yield();
-		recipient->flags &= ~FIBER_READING_INBOX;
+		@try {
+			yield();
+		}
+		@finally {
+			recipient->flags &= ~FIBER_READING_INBOX;
+		}
 	}
 }
 
@@ -532,14 +640,23 @@ write_inbox(struct fiber *recipient, struct tbuf *msg)
 	return true;
 }
 
+
+/**
+ * @note: this is a cancellation point (@sa fiber_testcancel())
+ */
+
 struct msg *
 read_inbox(void)
 {
 	struct ring *restrict inbox = fiber->inbox;
 	while (ring_size(inbox) == 0) {
 		fiber->flags |= FIBER_READING_INBOX;
-		yield();
-		fiber->flags &= ~FIBER_READING_INBOX;
+		@try {
+			yield();
+		}
+		@finally {
+			fiber->flags &= ~FIBER_READING_INBOX;
+		}
 	}
 
 	struct msg *msg = inbox->ring[inbox->tail];
@@ -549,14 +666,19 @@ read_inbox(void)
 	return msg;
 }
 
+/**
+ * @note: this is a cancellation point.
+ */
+
 int
 fiber_bread(struct tbuf *buf, size_t at_least)
 {
 	ssize_t r;
 	tbuf_ensure(buf, MAX(cfg.readahead, at_least));
 
+	fiber_io_start(EV_READ);
 	for (;;) {
-		wait_for(EV_READ);
+		fiber_io_yield();
 		r = read(fiber->fd, buf->data + buf->len, buf->size - buf->len);
 		if (r > 0) {
 			buf->len += r;
@@ -568,19 +690,23 @@ fiber_bread(struct tbuf *buf, size_t at_least)
 			break;
 		}
 	}
-	unwait(EV_READ);
+	fiber_io_stop(EV_READ);
 
 	return r;
 }
 
 void
-add_iov_dup(void *buf, size_t len)
+add_iov_dup(const void *buf, size_t len)
 {
 	void *copy = palloc(fiber->pool, len);
 	memcpy(copy, buf, len);
 	add_iov(copy, len);
 }
 
+/**
+ * @note: this is a cancellation point.
+ */
+
 ssize_t
 fiber_flush_output(void)
 {
@@ -588,8 +714,9 @@ fiber_flush_output(void)
 	struct iovec *iov = iovec(fiber->iov);
 	size_t iov_cnt = fiber->iov_cnt;
 
+	fiber_io_start(EV_WRITE);
 	while (iov_cnt > 0) {
-		wait_for(EV_WRITE);
+		fiber_io_yield();
 		bytes += r = writev(fiber->fd, iov, MIN(iov_cnt, IOV_MAX));
 		if (r <= 0) {
 			if (errno == EAGAIN || errno == EWOULDBLOCK)
@@ -610,7 +737,7 @@ fiber_flush_output(void)
 			}
 		}
 	}
-	unwait(EV_WRITE);
+	fiber_io_stop(EV_WRITE);
 
 	if (r < 0) {
 		size_t rem = 0;
@@ -627,16 +754,19 @@ fiber_flush_output(void)
 	return result;
 }
 
+/**
+ * @note: this is a cancellation point.
+ */
+
 ssize_t
 fiber_read(void *buf, size_t count)
 {
 	ssize_t r, done = 0;
 
-	if (count == 0)
-		return 0;
-
+	fiber_io_start(EV_READ);
 	while (count != done) {
-		wait_for(EV_READ);
+
+		fiber_io_yield();
 
 		if ((r = read(fiber->fd, buf + done, count - done)) <= 0) {
 			if (errno == EAGAIN || errno == EWOULDBLOCK)
@@ -646,22 +776,25 @@ fiber_read(void *buf, size_t count)
 		}
 		done += r;
 	}
+	fiber_io_stop(EV_READ);
 
-	unwait(EV_READ);
 	return done;
 }
 
+/**
+ * @note: this is a cancellation point.
+ */
+
 ssize_t
 fiber_write(const void *buf, size_t count)
 {
 	int r;
 	unsigned int done = 0;
 
-	if (count == 0)
-		return 0;
+	fiber_io_start(EV_WRITE);
 
 	while (count != done) {
-		wait_for(EV_WRITE);
+		fiber_io_yield();
 		if ((r = write(fiber->fd, buf + done, count - done)) == -1) {
 			if (errno == EAGAIN || errno == EWOULDBLOCK)
 				continue;
@@ -670,17 +803,18 @@ fiber_write(const void *buf, size_t count)
 		}
 		done += r;
 	}
+	fiber_io_stop(EV_WRITE);
 
-	unwait(EV_WRITE);
 	return done;
 }
 
+/**
+ * @note: this is a cancellation point.
+ */
+
 int
 fiber_connect(struct sockaddr_in *addr)
 {
-	int error;
-	socklen_t error_size = sizeof(error);
-
 	fiber->fd = socket(AF_INET, SOCK_STREAM, 0);
 	if (fiber->fd < 0)
 		goto error;
@@ -689,25 +823,32 @@ fiber_connect(struct sockaddr_in *addr)
 		goto error;
 
 	if (connect(fiber->fd, (struct sockaddr *)addr, sizeof(*addr)) < 0) {
+
 		if (errno != EINPROGRESS)
 			goto error;
-	}
 
-	wait_for(EV_WRITE);
-	if (getsockopt(fiber->fd, SOL_SOCKET, SO_ERROR, &error, &error_size) < 0)
-		goto error;
+		fiber_io_start(EV_WRITE);
+		fiber_io_yield();
+		fiber_io_stop(EV_WRITE);
 
-	assert(error_size == sizeof(error));
+		int error;
+		socklen_t error_size = sizeof(error);
 
-	if (error != 0) {
-		errno = error;
-		goto error;
+		if (getsockopt(fiber->fd, SOL_SOCKET, SO_ERROR,
+			       &error, &error_size) < 0)
+			goto error;
+
+		assert(error_size == sizeof(error));
+
+		if (error != 0) {
+			errno = error;
+			goto error;
+		}
 	}
 
-	unwait(EV_WRITE);
 	return fiber->fd;
+
       error:
-	unwait(EV_WRITE);
 	fiber_close();
 	return fiber->fd;
 }
@@ -847,7 +988,6 @@ inbox2sock(void *_data __attribute__((unused)))
 		if (fiber_write(out->data, out->len) != out->len)
 			panic("child is dead");
 		fiber_gc();
-		unwait(-1);
 	}
 }
 
@@ -923,7 +1063,11 @@ spawn_child(const char *name, int inbox_size, struct tbuf *(*handler) (void *, s
 		return c;
 	} else {
 		char child_name[sizeof(fiber->name)];
-
+		/*
+		 * Move to an own process group, to not receive
+		 * signals from the controlling tty.
+		 */
+		setpgid(0, 0);
 		salloc_destroy();
 		close_all_xcpt(2, socks[0], sayfd);
 		snprintf(child_name, sizeof(child_name), "%s/child", name);
@@ -951,8 +1095,9 @@ tcp_server_handler(void *data)
 	if (server->on_bind != NULL)
 		server->on_bind(server->data);
 
-	while (1) {
-		wait_for(EV_READ);
+	fiber_io_start(EV_READ);
+	for (;;) {
+		fiber_io_yield();
 
 		while ((fd = accept(fiber->fd, NULL, NULL)) > 0) {
 			if (set_nonblock(fd) == -1) {
@@ -981,31 +1126,49 @@ tcp_server_handler(void *data)
 			say_syserror("accept");
 			continue;
 		}
-
+		exit(EX_OSERR);
 	}
-}
-
-static void
-udp_server_handler(void *data)
-{
-	struct fiber_server *server = fiber->data;
 
-	if (fiber_serv_socket(fiber, udp_server, server->port, true, 0.1) != 0) {
-		say_error("bind port %i fail", server->port);
+	if (set_nonblock(fiber->fd) == -1)
 		exit(EX_OSERR);
+
+	memset(&sin, 0, sizeof(struct sockaddr_in));
+	sin.sin_family = AF_INET;
+	sin.sin_port = htons(server->port);
+	sin.sin_addr.s_addr = INADDR_ANY;
+
+	for (;;) {
+		if (bind(fiber->fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
+			if (errno == EADDRINUSE)
+				goto sleep_and_retry;
+			say_syserror("bind");
+			exit(EX_OSERR);
+		}
+
+		say_info("bound to UDP port %i", server->port);
+		break;
+
+	      sleep_and_retry:
+		if (!warning_said) {
+			say_warn("port %i is already in use, "
+				 "will retry binding after 0.1 seconds.", server->port);
+			warning_said = true;
+		}
+		fiber_sleep(0.1);
 	}
 
 	if (server->on_bind != NULL)
 		server->on_bind(server->data);
 
-	while (1) {
+	fiber_io_start(EV_READ);
+	for (;;) {
 #define MAXUDPPACKETLEN	128
 		char buf[MAXUDPPACKETLEN];
 		struct sockaddr_in addr;
 		socklen_t addrlen;
 		ssize_t sz;
 
-		wait_for(EV_READ);
+		fiber_io_yield();
 
 		for (;;) {
 			addrlen = sizeof(addr);
@@ -1026,6 +1189,7 @@ udp_server_handler(void *data)
 			}
 		}
 	}
+	fiber_io_stop(EV_READ);
 }
 
 struct fiber *
@@ -1036,10 +1200,11 @@ fiber_server(fiber_server_type type, int port, void (*handler) (void *data), voi
 	struct fiber_server *server;
 	struct fiber *s;
 
+	assert(type == tcp_server);
+
 	server_name = palloc(eter_pool, 64);
 	snprintf(server_name, 64, "%i/acceptor", port);
-	s = fiber_create(server_name, -1, -1,
-			 (type == tcp_server) ? tcp_server_handler : udp_server_handler, data);
+	s = fiber_create(server_name, -1, -1, tcp_server_handler, data);
 	s->data = server = palloc(eter_pool, sizeof(struct fiber_server));
 	assert(server != NULL);
 	server->port = port;
diff --git a/core/iproto.m b/core/iproto.m
index 967c793391c10071dd9f562d1c789e2a682a52c6..3e7a5a9e6304cf8feaa861e5b98807b11e3ac137 100644
--- a/core/iproto.m
+++ b/core/iproto.m
@@ -1,6 +1,6 @@
 /*
- * Copyright (C) 2010 Mail.RU
- * Copyright (C) 2010 Yuriy Vostrikov
+ * Copyright (C) 2010, 2011 Mail.RU
+ * Copyright (C) 2010, 2011 Yuriy Vostrikov
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -24,6 +24,7 @@
  * SUCH DAMAGE.
  */
 #include "iproto.h"
+#include "exception.h"
 
 #include <stdio.h>
 #include <string.h>
@@ -36,64 +37,78 @@
 
 const uint32_t msg_ping = 0xff00;
 
-static struct tbuf *
-iproto_parse(struct tbuf *in)
-{
-	if (in->len < sizeof(struct iproto_header))
-		return NULL;
-	if (in->len < sizeof(struct iproto_header) + iproto(in)->len)
-		return NULL;
-
-	return tbuf_split(in, sizeof(struct iproto_header) + iproto(in)->len);
-}
+static void iproto_reply(iproto_callback callback, struct tbuf *request);
 
 void
-iproto_interact(void *data)
+iproto_interact(iproto_callback *callback)
 {
-	uint32_t (*callback) (uint32_t msg, struct tbuf *requst_data) = data;
-	struct tbuf *request;
-	struct iproto_header_retcode *reply;
-	ssize_t r;
+	struct tbuf *in = fiber->rbuf;
+	ssize_t to_read = sizeof(struct iproto_header);
 
 	for (;;) {
-		if (fiber_bread(fiber->rbuf, sizeof(struct iproto_header)) <= 0)
+		if (to_read > 0 && fiber_bread(in, to_read) <= 0)
 			break;
-		while ((request = iproto_parse(fiber->rbuf)) != NULL) {
-			reply = palloc(fiber->pool, sizeof(*reply));
-			reply->msg_code = iproto(request)->msg_code;
-			reply->sync = iproto(request)->sync;
-
-			if (unlikely(reply->msg_code == msg_ping)) {
-				reply->len = 0;
-				add_iov(reply, sizeof(struct iproto_header));
-			} else {
-				add_iov(reply, sizeof(struct iproto_header_retcode));
-				/* j is last used iov in loop */
-				int j = fiber->iov_cnt;
-
-				/* make requst point to iproto data */
-				u32 msg_code = iproto(request)->msg_code;
-				request->len = iproto(request)->len;
-				request->data = iproto(request)->data;
-				u32 err = callback(msg_code, request);
-				reply->ret_code = tnt_errcode_val(err);
-
-				/*
-				 * retcode is uint32_t and included int struct iproto_header_retcode
-				 * but we has to count it anyway
-				 */
-				reply->len = sizeof(uint32_t);
-
-				for (; j < fiber->iov_cnt; j++)
-					reply->len += iovec(fiber->iov)[j].iov_len;
-			}
-		}
-		r = fiber_flush_output();
-		fiber_gc();
 
-		if (r < 0) {
-			say_warn("io_error: %s", strerror(errno));
+		ssize_t request_len = sizeof(struct iproto_header) + iproto(in)->len;
+		to_read = request_len - in->len;
+
+		if (to_read > 0 && fiber_bread(in, to_read) <= 0)
 			break;
+
+		struct tbuf *request = tbuf_split(in, request_len);
+		iproto_reply(*callback, request);
+
+		to_read = sizeof(struct iproto_header) - in->len;
+
+		/*
+		 * Flush output and garbage collect before reading
+		 * next header.
+		 */
+		if (to_read > 0) {
+			if (fiber_flush_output() < 0) {
+				say_warn("io_error: %s", strerror(errno));
+				break;
+			}
+			fiber_gc();
+			/* Must be reset after fiber_gc() */
+			in = fiber->rbuf;
 		}
 	}
 }
+
+/** Stack a reply to a single request to the fiber's io vector. */
+
+static void iproto_reply(iproto_callback callback, struct tbuf *request)
+{
+	struct iproto_header_retcode *reply;
+
+	reply = palloc(fiber->pool, sizeof(*reply));
+	reply->msg_code = iproto(request)->msg_code;
+	reply->sync = iproto(request)->sync;
+
+	if (unlikely(reply->msg_code == msg_ping)) {
+		reply->len = 0;
+		add_iov(reply, sizeof(struct iproto_header));
+		return;
+	}
+
+	reply->len = sizeof(uint32_t); /* ret_code */
+	add_iov(reply, sizeof(struct iproto_header_retcode));
+	size_t saved_iov_cnt = fiber->iov_cnt;
+	/* make request point to iproto data */
+	request->len = iproto(request)->len;
+	request->data = iproto(request)->data;
+
+	@try {
+		callback(reply->msg_code, request);
+		reply->ret_code = 0;
+	}
+	@catch (ClientError *e) {
+		fiber->iov->len -= (fiber->iov_cnt - saved_iov_cnt) * sizeof(struct iovec);
+		fiber->iov_cnt = saved_iov_cnt;
+		reply->ret_code = tnt_errcode_val(e->errcode);
+		add_iov_dup(e->errmsg, strlen(e->errmsg)+1);
+	}
+	for (; saved_iov_cnt < fiber->iov_cnt; saved_iov_cnt++)
+		reply->len += iovec(fiber->iov)[saved_iov_cnt].iov_len;
+}
diff --git a/core/diagnostics.h b/core/latch.m
similarity index 57%
rename from core/diagnostics.h
rename to core/latch.m
index c136c8ecdfb06f4b75fe7d86d055c53078ef6088..a9b9c17eb0b633f146a5991eb73b5138257eb817 100644
--- a/core/diagnostics.h
+++ b/core/latch.m
@@ -1,5 +1,3 @@
-#ifndef TARANTOOL_CORE_DIAGNOSTICS_H_INCLUDED
-#define TARANTOOL_CORE_DIAGNOSTICS_H_INCLUDED
 /*
  * Redistribution and use in source and binary forms, with or
  * without modification, are permitted provided that the following
@@ -23,36 +21,46 @@
  * SUCH DAMAGE.
  */
 
-/*
- * This is used globally in the program to pass around information
- * about execution errors. Each fiber has its own error context,
- * setting an error in one doesn't affect another.
- */
+#include "latch.h"
+#include "fiber.h"
 
-struct Error
+void
+tnt_latch_init(struct tnt_latch *latch)
 {
-	/** Most often contains system errno. */
-	int code;
-	/** Text description of the error. Can be NULL. */
-	const char *msg;
-};
-
-/**
- * Set the last error in the current execution context (fiber).
- * If another error was already set, it's overwritten.
- *
- * @param code  Error code.
- * @todo: think how to distinguish errno and tarantool codes here.
- * @param message  Optional text message. Can be NULL.
- */
-void diag_set_error(int code, const char *msg);
+	latch->locked = false;
+	latch->owner = NULL;
+}
 
-/** Return the last error. Return NULL if no error.
- */
-struct Error *diag_get_last_error();
+void
+tnt_latch_destroy(struct tnt_latch *latch)
+{
+	assert(latch->locked == false);
 
-/** Clear the last error, if any.
- */
-void diag_clear();
+	latch->owner = NULL;
+}
+
+int
+tnt_latch_trylock(struct tnt_latch *latch)
+{
+	if (latch->locked) {
+		assert(latch->owner != fiber);
+
+		return -1;
+	}
+
+	assert(latch->owner == NULL);
+
+	latch->locked = true;
+	latch->owner = fiber;
+
+	return 0;
+}
+
+void
+tnt_latch_unlock(struct tnt_latch *latch)
+{
+	assert(latch->owner == fiber);
 
-#endif /* TARANTOOL_CORE_DIAGNOSTICS_H_INCLUDED */
+	latch->locked = false;
+	latch->owner = NULL;
+}
diff --git a/core/log_io.m b/core/log_io.m
index 45dbe023d8d2ad12934c7f88d9f68a62c531e8f7..ba48b6847ec80929308d0192d03dee31829cb4d1 100644
--- a/core/log_io.m
+++ b/core/log_io.m
@@ -43,29 +43,22 @@
 #include <say.h>
 #include <third_party/crc32.h>
 #include <pickle.h>
-#include "diagnostics.h"
 
 const u16 snap_tag = -1;
 const u16 wal_tag = -2;
 const u64 default_cookie = 0;
 const u32 default_version = 11;
-const u32 snap_marker_v04 = -1U;
-const u64 xlog_marker_v04 = -1ULL;
-const u64 xlog_eof_marker_v04 = 0;
 const u32 marker_v11 = 0xba0babed;
 const u32 eof_marker_v11 = 0x10adab1e;
 const char *snap_suffix = ".snap";
 const char *xlog_suffix = ".xlog";
 const char *inprogress_suffix = ".inprogress";
-const char *v04 = "0.04\n";
-const char *v03 = "0.03\n";
 const char *v11 = "0.11\n";
 const char *snap_mark = "SNAP\n";
 const char *xlog_mark = "XLOG\n";
 
 #define ROW_EOF (void *)1
 
-static struct tbuf *row_reader_v04(FILE *f, struct palloc_pool *pool);
 static struct tbuf *row_reader_v11(FILE *f, struct palloc_pool *pool);
 
 struct log_io_iter {
@@ -78,19 +71,15 @@ struct log_io_iter {
 	int io_rate_limit;
 };
 
-struct row_v04 {
-	i64 lsn;		/* this used to be tid */
-	u16 type;
-	u32 len;
-	u8 data[];
-} __packed__;
 
-static inline struct row_v04 *row_v04(const struct tbuf *t)
+void
+wait_lsn_set(struct wait_lsn *wait_lsn, i64 lsn)
 {
-	return (struct row_v04 *)t->data;
+	assert(wait_lsn->waiter == NULL);
+	wait_lsn->waiter = fiber;
+	wait_lsn->lsn = lsn;
 }
 
-
 int
 confirm_lsn(struct recovery_state *r, i64 lsn)
 {
@@ -102,6 +91,14 @@ confirm_lsn(struct recovery_state *r, i64 lsn)
 				 " new:%" PRIi64 " diff: %" PRIi64,
 				 r->confirmed_lsn, lsn, lsn - r->confirmed_lsn);
 		r->confirmed_lsn = lsn;
+		/* Alert the waiter, if any. There can be holes in
+		 * confirmed_lsn, in case of disk write failure,
+		 * but wal_writer never confirms LSNs out order.
+		 */
+		if (r->wait_lsn.waiter && r->confirmed_lsn >= r->wait_lsn.lsn) {
+			fiber_call(r->wait_lsn.waiter);
+		}
+
 		return 0;
 	} else {
 		say_warn("lsn double confirmed:%" PRIi64, r->confirmed_lsn);
@@ -110,6 +107,23 @@ confirm_lsn(struct recovery_state *r, i64 lsn)
 	return -1;
 }
 
+
+/** Wait until the given LSN makes its way to disk. */
+
+void
+recovery_wait_lsn(struct recovery_state *r, i64 lsn)
+{
+	while (lsn < r->confirmed_lsn) {
+		wait_lsn_set(&r->wait_lsn, lsn);
+		@try {
+			yield();
+		} @finally {
+			wait_lsn_clear(&r->wait_lsn);
+		}
+	}
+}
+
+
 i64
 next_lsn(struct recovery_state *r, i64 new_lsn)
 {
@@ -122,21 +136,6 @@ next_lsn(struct recovery_state *r, i64 new_lsn)
 	return r->lsn;
 }
 
-static void
-xlog04_class(struct log_io_class *c)
-{
-	c->suffix = xlog_suffix;
-	c->filetype = xlog_mark;
-	c->version = v04;
-	c->reader = row_reader_v04;
-	c->marker = xlog_marker_v04;
-	c->marker_size = sizeof(xlog_marker_v04);
-	c->eof_marker = xlog_eof_marker_v04;
-	c->eof_marker_size = sizeof(xlog_eof_marker_v04);
-
-	c->rows_per_file = 50000;	/* sane defaults */
-	c->fsync_delay = 0;
-}
 
 static void
 v11_class(struct log_io_class *c)
@@ -153,51 +152,31 @@ v11_class(struct log_io_class *c)
 	c->fsync_delay = 0;
 }
 
-static struct log_io_class **
-snap_classes(row_reader snap_row_reader, const char *dirname)
+static struct log_io_class *
+snapshot_class_create(const char *dirname)
 {
-	struct log_io_class **c = calloc(3, sizeof(*c));
+	struct log_io_class *c = calloc(1, sizeof(*c));
 	if (c == NULL)
 		panic("calloc");
 
-	c[0] = calloc(1, sizeof(**c));
-	c[1] = calloc(1, sizeof(**c));
-	if (c[0] == NULL || c[1] == NULL)
-		panic("calloc");
-
-	c[0]->suffix = snap_suffix;
-	c[0]->filetype = snap_mark;
-	c[0]->version = v03;
-	c[0]->eof_marker_size = 0;	/* no end marker */
-	c[0]->marker = snap_marker_v04;
-	c[0]->marker_size = sizeof(snap_marker_v04);
-	c[0]->rows_per_file = 0;
-	c[0]->reader = snap_row_reader;
-
-	v11_class(c[1]);
-	c[1]->filetype = c[0]->filetype;
-	c[1]->suffix = c[0]->suffix;
+	v11_class(c);
+	c->filetype = snap_mark;
+	c->suffix = snap_suffix;
 
-	c[0]->dirname = c[1]->dirname = dirname ? strdup(dirname) : NULL;
+	c->dirname = dirname ? strdup(dirname) : NULL;
 	return c;
 }
 
-static struct log_io_class **
-xlog_classes(const char *dirname)
+static struct log_io_class *
+xlog_class_create(const char *dirname)
 {
-	struct log_io_class **c = calloc(3, sizeof(*c));
+	struct log_io_class *c = calloc(1, sizeof(*c));
 	if (c == NULL)
 		panic("calloc");
 
-	c[0] = calloc(1, sizeof(**c));
-	c[1] = calloc(1, sizeof(**c));
-	if (c[0] == NULL || c[1] == NULL)
-		panic("calloc");
+	v11_class(c);
 
-	xlog04_class(c[0]);
-	v11_class(c[1]);
-
-	c[0]->dirname = c[1]->dirname = dirname ? strdup(dirname) : NULL;
+	c->dirname = dirname ? strdup(dirname) : NULL;
 	return c;
 }
 
@@ -306,10 +285,6 @@ read_rows(struct log_io_iter *i)
 		eof = 1;
 		goto out;
 	}
-	if (l->class->eof_marker_size == 0 && ftello(l->f) == good_offset) {
-		eof = 1;
-		goto out;
-	}
 
       out:
 	l->rows += row_count;
@@ -461,70 +436,6 @@ find_including_file(struct log_io_class *class, i64 target_lsn)
 	return *lsn;
 }
 
-struct tbuf *
-convert_to_v11(struct tbuf *orig, u16 tag, const u64 cookie, i64 lsn)
-{
-	struct tbuf *row = tbuf_alloc(orig->pool);
-	tbuf_ensure(row, sizeof(struct row_v11));
-	row->len = sizeof(struct row_v11);
-	row_v11(row)->lsn = lsn;
-	row_v11(row)->tm = 0;
-	row_v11(row)->len = orig->len + sizeof(tag) + sizeof(cookie);
-
-	tbuf_append(row, &tag, sizeof(tag));
-	tbuf_append(row, &cookie, sizeof(cookie));
-	tbuf_append(row, orig->data, orig->len);
-	return row;
-}
-
-static struct tbuf *
-row_reader_v04(FILE *f, struct palloc_pool *pool)
-{
-	const int header_size = offsetof(struct row_v04, data);
-	struct tbuf *m = tbuf_alloc(pool);
-	u32 crc, calculated_crc;
-
-	/*
-	 * it's imposible to distinguish between EOF and bad record condition here
-	 * since bad record may have bogus length
-	 * so if record is suspicious simply return NULL to the caller
-	 */
-
-	if (fread(m->data, header_size, 1, f) != 1)
-		return ROW_EOF;
-	m->len = header_size;
-
-	/* filter out rows with definitly wrong length */
-	if (row_v04(m)->len > (1 << 20)) {
-		say_error("record too long(%" PRIi32 "), probably damaged", row_v04(m)->len);
-		return NULL;
-	}
-
-	tbuf_ensure(m, header_size + row_v04(m)->len);
-	if (fread(row_v04(m)->data, row_v04(m)->len, 1, f) != 1)
-		return ROW_EOF;
-
-	m->len += row_v04(m)->len;
-
-	if (fread(&crc, sizeof(crc), 1, f) != 1)
-		return ROW_EOF;
-
-	calculated_crc = crc32(m->data, m->len);
-	if (crc != calculated_crc) {
-		say_error("crc32 mismatch");
-		return NULL;
-	}
-
-	say_debug("read row v04 success lsn:%" PRIi64, row_v04(m)->lsn);
-
-	/* we're copying row data twice here, it's ok since this is legacy function */
-	struct tbuf *data = tbuf_alloc(pool);
-	tbuf_append(data, &row_v04(m)->type, sizeof(row_v04(m)->type));
-	tbuf_append(data, row_v04(m)->data, row_v04(m)->len);
-
-	return convert_to_v11(data, wal_tag, default_cookie, row_v04(m)->lsn);
-}
-
 static struct tbuf *
 row_reader_v11(FILE *f, struct palloc_pool *pool)
 {
@@ -619,7 +530,7 @@ close_log(struct log_io **lptr)
 			panic("can't rename 'inprogress' WAL");
 	}
 
-	if (l->class->eof_marker_size > 0 && l->mode == LOG_WRITE) {
+	if (l->mode == LOG_WRITE) {
 		if (fwrite(&l->class->eof_marker, l->class->eof_marker_size, 1, l->f) != 1)
 			say_error("can't write eof_marker");
 	}
@@ -657,24 +568,14 @@ flush_log(struct log_io *l)
 static int
 write_header(struct log_io *l)
 {
-	char buf[27];
-	time_t tm;
-
 	if (fwrite(l->class->filetype, strlen(l->class->filetype), 1, l->f) != 1)
 		return -1;
 
 	if (fwrite(l->class->version, strlen(l->class->version), 1, l->f) != 1)
 		return -1;
 
-	if (strcmp(l->class->version, v11) == 0) {
-		if (fwrite("\n", 1, 1, l->f) != 1)
-			return -1;
-	} else {
-		time(&tm);
-		ctime_r(&tm, buf);
-		if (fwrite(buf, strlen(buf), 1, l->f) != 1)
-			return -1;
-	}
+	if (fwrite("\n", 1, 1, l->f) != 1)
+		return -1;
 
 	return 0;
 }
@@ -704,16 +605,17 @@ format_filename(char *filename, struct log_io_class *class, i64 lsn, int suffix)
 }
 
 static struct log_io *
-open_for_read(struct recovery_state *recover, struct log_io_class **class, i64 lsn, int suffix,
+open_for_read(struct recovery_state *recover, struct log_io_class *class, i64 lsn, int suffix,
 	      const char *filename)
 {
 	char filetype[32], version[32], buf[256];
 	struct log_io *l = NULL;
 	char *r;
+	const char *errmsg;
 
 	l = calloc(1, sizeof(*l));
 	if (l == NULL) {
-		diag_set_error(errno, strerror(errno));
+		errmsg = strerror(errno);
 		goto error;
 	}
 	l->mode = LOG_READ;
@@ -723,7 +625,7 @@ open_for_read(struct recovery_state *recover, struct log_io_class **class, i64 l
 	/* when filename is not null it is forced open for debug reading */
 	if (filename == NULL) {
 		assert(lsn != 0);
-		format_filename(l->filename, *class, lsn, suffix);
+		format_filename(l->filename, class, lsn, suffix);
 	} else {
 		assert(lsn == 0);
 		strncpy(l->filename, filename, PATH_MAX);
@@ -733,61 +635,47 @@ open_for_read(struct recovery_state *recover, struct log_io_class **class, i64 l
 
 	l->f = fopen(l->filename, "r");
 	if (l->f == NULL) {
-		diag_set_error(errno, strerror(errno));
+		errmsg = strerror(errno);
 		goto error;
 	}
 
 	r = fgets(filetype, sizeof(filetype), l->f);
 	if (r == NULL) {
-		diag_set_error(1, "header reading failed");
+		errmsg = "header reading failed";
 		goto error;
 	}
 
 	r = fgets(version, sizeof(version), l->f);
 	if (r == NULL) {
-		diag_set_error(1, "header reading failed");
+		errmsg = "header reading failed";
 		goto error;
 	}
 
-	if (strcmp((*class)->filetype, filetype) != 0) {
-		diag_set_error(1, "unknown filetype");
+	if (strcmp(class->filetype, filetype) != 0) {
+		errmsg = "unknown filetype";
 		goto error;
 	}
 
-	while (*class) {
-		if (strcmp((*class)->version, version) == 0)
-			break;
-		class++;
-	}
-
-	if (*class == NULL) {
-		diag_set_error(1, "unknown version");
+	if (strcmp(class->version, version) != 0) {
+		errmsg = "unknown version";
 		goto error;
 	}
-	l->class = *class;
-
-	if (strcmp(version, v11) == 0) {
-		for (;;) {
-			r = fgets(buf, sizeof(buf), l->f);
-			if (r == NULL) {
-				diag_set_error(1, "header reading failed");
-				goto error;
-			}
-			if (strcmp(r, "\n") == 0 || strcmp(r, "\r\n") == 0)
-				break;
-		}
-	} else {
-		r = fgets(buf, sizeof(buf), l->f);	/* skip line with time */
+	l->class = class;
+
+	for (;;) {
+		r = fgets(buf, sizeof(buf), l->f);
 		if (r == NULL) {
-			diag_set_error(1, "header reading failed");
+			errmsg = "header reading failed";
 			goto error;
 		}
+		if (strcmp(r, "\n") == 0 || strcmp(r, "\r\n") == 0)
+			break;
 	}
 
 	return l;
       error:
 	say_error("open_for_read: failed to open `%s': %s", l->filename,
-		  diag_get_last_error()->msg);
+		  errmsg);
 	if (l != NULL) {
 		if (l->f != NULL)
 			fclose(l->f);
@@ -797,16 +685,19 @@ open_for_read(struct recovery_state *recover, struct log_io_class **class, i64 l
 }
 
 struct log_io *
-open_for_write(struct recovery_state *recover, struct log_io_class *class, i64 lsn, int suffix)
+open_for_write(struct recovery_state *recover, struct log_io_class *class, i64 lsn,
+	       int suffix, int *save_errno)
 {
 	struct log_io *l = NULL;
 	int fd;
 	char *dot;
 	bool exists;
+	const char *errmsg;
 
 	l = calloc(1, sizeof(*l));
 	if (l == NULL) {
-		diag_set_error(errno, strerror(errno));
+		*save_errno = errno;
+		errmsg = strerror(errno);
 		goto error;
 	}
 	l->mode = LOG_WRITE;
@@ -828,7 +719,8 @@ open_for_write(struct recovery_state *recover, struct log_io_class *class, i64 l
 		exists = access(l->filename, F_OK) == 0;
 		*dot = '.';
 		if (exists) {
-			diag_set_error(EEXIST, "exists");
+			*save_errno = EEXIST;
+			errmsg = "exists";
 			goto error;
 		}
 	}
@@ -839,13 +731,15 @@ open_for_write(struct recovery_state *recover, struct log_io_class *class, i64 l
 	 */
 	fd = open(l->filename, O_WRONLY | O_CREAT | O_EXCL | O_APPEND, 0664);
 	if (fd < 0) {
-		diag_set_error(errno, strerror(errno));
+		*save_errno = errno;
+		errmsg = strerror(errno);
 		goto error;
 	}
 
 	l->f = fdopen(fd, "a");
 	if (l->f == NULL) {
-		diag_set_error(errno, strerror(errno));
+		*save_errno = errno;
+		errmsg = strerror(errno);
 		goto error;
 	}
 
@@ -854,7 +748,7 @@ open_for_write(struct recovery_state *recover, struct log_io_class *class, i64 l
 	return l;
       error:
 	say_error("find_log: failed to open `%s': %s", l->filename,
-		  diag_get_last_error()->msg);
+		  errmsg);
 	if (l != NULL) {
 		if (l->f != NULL)
 			fclose(l->f);
@@ -865,23 +759,23 @@ open_for_write(struct recovery_state *recover, struct log_io_class *class, i64 l
 
 /* this little hole shouldn't be used too much */
 int
-read_log(const char *filename, row_reader reader,
+read_log(const char *filename,
 	 row_handler *xlog_handler, row_handler *snap_handler, void *state)
 {
 	struct log_io_iter i;
 	struct log_io *l;
-	struct log_io_class **c;
+	struct log_io_class *c;
 	struct tbuf *row;
 	row_handler *h;
 
 	if (strstr(filename, xlog_suffix)) {
-		c = xlog_classes(NULL);
+		c = xlog_class_create(NULL);
 		h = xlog_handler;
 	} else if (strstr(filename, snap_suffix)) {
-		c = snap_classes(reader, NULL);
+		c = snapshot_class_create(NULL);
 		h = snap_handler;
 	} else {
-		say_error("don't know what how to read `%s'", filename);
+		say_error("don't know how to read `%s'", filename);
 		return -1;
 	}
 
@@ -908,30 +802,37 @@ recover_snap(struct recovery_state *r)
 	@try {
 		memset(&i, 0, sizeof(i));
 
-		lsn = greatest_lsn(r->snap_prefered_class);
-		if (lsn <= 0)
-			tnt_raise(tnt_Exception, reason:"can't find snapshot");
+		lsn = greatest_lsn(r->snap_class);
+		if (lsn <= 0) {
+			say_error("can't find snapshot");
+			return -1;
+		}
 
 		snap = open_for_read(r, r->snap_class, lsn, 0, NULL);
-		if (snap == NULL)
-			tnt_raise(tnt_Exception, reason:"can't find/open snapshot");
+		if (snap == NULL) {
+			say_error("can't find/open snapshot");
+			return -1;
+		}
 
 		iter_open(snap, &i, read_rows);
 		say_info("recover from `%s'", snap->filename);
 
 		while ((row = iter_inner(&i, (void *)1))) {
-			if (r->row_handler(r, row) < 0)
-				tnt_raise(tnt_Exception, reason:"can't apply row");
+			if (r->row_handler(r, row) < 0) {
+				say_error("can't apply row");
+				return -1;
+			}
+		}
+		if (i.error != 0) {
+			say_error("failure reading snapshot");
+			return -1;
 		}
-		if (i.error != 0)
-			tnt_raise(tnt_Exception, reason:"error during snapshot processing");
 
 		r->lsn = r->confirmed_lsn = lsn;
 
 		return 0;
 	}
-	@catch (tnt_Exception *e) {
-		say_error("tnt_Exception: `%s'", e->reason);
+	@catch (id e) {
 		say_error("failure reading snapshot");
 
 		return -1;
@@ -974,8 +875,10 @@ recover_wal(struct recovery_state *r, struct log_io *l)
 			}
 
 			/*  after handler(r, row) returned, row may be modified, do not use it */
-			if (r->row_handler(r, row) < 0)
-				tnt_raise(tnt_Exception, reason:"can't apply row");
+			if (r->row_handler(r, row) < 0) {
+				say_error("can't apply row");
+				return -1;
+			}
 
 			if (r) {
 				next_lsn(r, lsn);
@@ -983,16 +886,17 @@ recover_wal(struct recovery_state *r, struct log_io *l)
 			}
 		}
 
-		if (i.error != 0)
-			tnt_raise(tnt_Exception, reason:"error during xlog processing");
+		if (i.error != 0) {
+			say_error("error during xlog processing");
+			return -1;
+		}
 
 		if (i.eof)
 			return LOG_EOF;
 
 		return 1;
 	}
-	@catch (tnt_Exception *e) {
-		say_error("tnt_Exception: `%s'", e->reason);
+	@catch (id e) {
 		say_error("failure reading xlog");
 
 		return -1;
@@ -1023,7 +927,7 @@ recover_remaining_wals(struct recovery_state *r)
 	size_t rows_before;
 
 	current_lsn = r->confirmed_lsn + 1;
-	wal_greatest_lsn = greatest_lsn(r->wal_prefered_class);
+	wal_greatest_lsn = greatest_lsn(r->wal_class);
 
 	/* if the caller already opened WAL for us, recover from it first */
 	if (r->current_wal != NULL)
@@ -1054,7 +958,7 @@ recover_remaining_wals(struct recovery_state *r)
 			next_wal = open_for_read(r, r->wal_class, current_lsn, -1, NULL);
 			if (next_wal == NULL) {
 				char *filename =
-					format_filename(NULL, *r->wal_class, current_lsn, -1);
+					format_filename(NULL, r->wal_class, current_lsn, -1);
 
 				say_warn("unlink broken %s wal", filename);
 				if (inprogress_log_unlink(filename) != 0)
@@ -1123,7 +1027,7 @@ recover(struct recovery_state *r, i64 lsn)
 	if (lsn == 0) {
 		result = recover_snap(r);
 		if (result < 0) {
-			if (greatest_lsn(r->snap_prefered_class) <= 0) {
+			if (greatest_lsn(r->snap_class) <= 0) {
 				say_crit("didn't you forget to initialize storage with --init-storage switch?");
 				_exit(1);
 			}
@@ -1143,17 +1047,16 @@ recover(struct recovery_state *r, i64 lsn)
 	 */
 	if (r->current_wal == NULL) {
 		i64 next_lsn = r->confirmed_lsn + 1;
-		i64 lsn = find_including_file(r->wal_prefered_class, next_lsn);
+		i64 lsn = find_including_file(r->wal_class, next_lsn);
 		if (lsn <= 0) {
 			say_error("can't find wal containing record with lsn:%" PRIi64, next_lsn);
 			result = -1;
 			goto out;
-		} else {
-			r->current_wal = open_for_read(r, r->wal_class, lsn, 0, NULL);
-			if (r->current_wal == NULL) {
-				result = -1;
-				goto out;
-			}
+		}
+		r->current_wal = open_for_read(r, r->wal_class, lsn, 0, NULL);
+		if (r->current_wal == NULL) {
+			result = -1;
+			goto out;
 		}
 	}
 
@@ -1285,9 +1188,12 @@ write_to_disk(void *_state, struct tbuf *t)
 
 	reply = tbuf_alloc(t->pool);
 
-	if (wal == NULL)
+	if (wal == NULL) {
+		int unused;
 		/* Open WAL with '.inprogress' suffix. */
-		wal = open_for_write(r, r->wal_prefered_class, wal_write_request(t)->lsn, -1);
+		wal = open_for_write(r, r->wal_class, wal_write_request(t)->lsn, -1,
+				     &unused);
+	}
 	else if (wal->rows == 1) {
 		/* rename wal after first successfull write to name without inprogress suffix*/
 		if (inprogress_log_rename(wal->filename) != 0) {
@@ -1392,26 +1298,25 @@ wal_write(struct recovery_state *r, u16 tag, u64 cookie, i64 lsn, struct tbuf *r
 
 struct recovery_state *
 recover_init(const char *snap_dirname, const char *wal_dirname,
-	     row_reader snap_row_reader, row_handler row_handler,
+	     row_handler row_handler,
 	     int rows_per_file, double fsync_delay,
 	     int inbox_size, int flags, void *data)
 {
 	struct recovery_state *r = p0alloc(eter_pool, sizeof(*r));
 
 	if (rows_per_file <= 1)
-		panic("inacceptable value of 'rows_per_file'");
+		panic("unacceptable value of 'rows_per_file'");
 
 	r->wal_timer.data = r;
 	r->row_handler = row_handler;
 	r->data = data;
 
-	r->snap_class = snap_classes(snap_row_reader, snap_dirname);
-	r->snap_prefered_class = r->snap_class[1];
+	r->snap_class = snapshot_class_create(snap_dirname);
 
-	r->wal_class = xlog_classes(wal_dirname);
-	r->wal_prefered_class = r->wal_class[1];
-	r->wal_prefered_class->rows_per_file = rows_per_file;
-	r->wal_prefered_class->fsync_delay = fsync_delay;
+	r->wal_class = xlog_class_create(wal_dirname);
+	r->wal_class->rows_per_file = rows_per_file;
+	r->wal_class->fsync_delay = fsync_delay;
+	wait_lsn_clear(&r->wait_lsn);
 
 	if ((flags & RECOVER_READONLY) == 0)
 		r->wal_writer = spawn_child("wal_writer", inbox_size, write_to_disk, r);
@@ -1422,13 +1327,8 @@ recover_init(const char *snap_dirname, const char *wal_dirname,
 void
 recovery_setup_panic(struct recovery_state *r, bool on_snap_error, bool on_wal_error)
 {
-	struct log_io_class **class;
-
-	for (class = r->wal_class; *class; class++)
-		(*class)->panic_if_error = on_wal_error;
-
-	for (class = r->snap_class; *class; class++)
-		(*class)->panic_if_error = on_snap_error;
+	r->wal_class->panic_if_error = on_wal_error;
+	r->snap_class->panic_if_error = on_snap_error;
 }
 
 static void
@@ -1516,13 +1416,13 @@ snapshot_save(struct recovery_state *r, void (*f) (struct log_io_iter *))
 	struct log_io *snap;
 	char final_filename[PATH_MAX + 1];
 	char *dot;
+	int save_errno;
 
 	memset(&i, 0, sizeof(i));
 
-	snap = open_for_write(r, r->snap_prefered_class, r->confirmed_lsn, -1);
+	snap = open_for_write(r, r->snap_class, r->confirmed_lsn, -1, &save_errno);
 	if (snap == NULL)
-		panic_status(diag_get_last_error()->code,
-			     "can't open snap for writing");
+		panic_status(save_errno, "can't open snap for writing");
 
 	iter_open(snap, &i, write_rows);
 
diff --git a/core/log_io_remote.m b/core/log_io_remote.m
index 7b1a14966221439c3a294788b4a7050ebcfe5ddc..d7508ec2ca9bff3a832c8bdeebad4d38e7a474f5 100644
--- a/core/log_io_remote.m
+++ b/core/log_io_remote.m
@@ -134,7 +134,10 @@ pull_from_remote(void *state)
 	struct tbuf *row;
 
 	for (;;) {
+		fiber_setcancelstate(true);
 		row = remote_read_row(h->r->confirmed_lsn + 1);
+		fiber_setcancelstate(false);
+
 		h->r->recovery_lag = ev_now() - row_v11(row)->tm;
 		h->r->recovery_last_update_tstamp = ev_now();
 
@@ -183,9 +186,10 @@ recover_follow_remote(struct recovery_state *r, char *ip_addr, int port,
 	struct sockaddr_in *addr;
 	struct remote_state *h;
 
-	say_crit("initializing remote hot standby, WAL replicator %s:%i", ip_addr, port);
+	say_crit("initializing the replica, WAL replicator %s:%i",
+		 ip_addr, port);
 	name = palloc(eter_pool, 64);
-	snprintf(name, 64, "remote_hot_standby/%s:%i", ip_addr, port);
+	snprintf(name, 64, "replica/%s:%i", ip_addr, port);
 
 	h = palloc(eter_pool, sizeof(*h));
 	h->r = r;
diff --git a/core/pickle.m b/core/pickle.m
index 07824ec0383e83ecede40208c93f4f95715239cd..2178089bcedae1bb512b1ca35db28fdb206aa0d4 100644
--- a/core/pickle.m
+++ b/core/pickle.m
@@ -28,11 +28,8 @@
 #include <tbuf.h>
 #include <fiber.h>
 #include <iproto.h>		/* for err codes */
-#include "errcode.h"
 #include "say.h"
-
-@implementation tnt_PickleException
-@end
+#include "exception.h"
 
 /* caller must ensure that there is space in target */
 u8 *
@@ -84,7 +81,7 @@ write_varint32(struct tbuf *b, u32 value)
 	u##bits read_u##bits(struct tbuf *b)						\
 	{										\
 		if (b->len < (bits)/8)							\
-			tnt_raise(tnt_PickleException, reason:"buffer too short");	\
+			tnt_raise(IllegalParams, :"packet too short (expected "#bits" bits)");\
 		u##bits r = *(u##bits *)b->data;					\
 		b->size -= (bits)/8;							\
 		b->len -= (bits)/8;							\
@@ -104,7 +101,7 @@ read_varint32(struct tbuf *buf)
 	int len = buf->len;
 
 	if (len < 1) {
-		tnt_raise(tnt_PickleException, reason:"buffer too short");
+		tnt_raise(IllegalParams, :"packet too short (expected 1 byte)");
 	}
 	if (!(b[0] & 0x80)) {
 		buf->data += 1;
@@ -114,7 +111,7 @@ read_varint32(struct tbuf *buf)
 	}
 
 	if (len < 2)
-		tnt_raise(tnt_PickleException, reason:"buffer too short");
+		tnt_raise(IllegalParams, :"packet too short (expected 2 bytes)");
 	if (!(b[1] & 0x80)) {
 		buf->data += 2;
 		buf->size -= 2;
@@ -122,7 +119,7 @@ read_varint32(struct tbuf *buf)
 		return (b[0] & 0x7f) << 7 | (b[1] & 0x7f);
 	}
 	if (len < 3)
-		tnt_raise(tnt_PickleException, reason:"buffer too short");
+		tnt_raise(IllegalParams, :"packet too short (expected 3 bytes)");
 	if (!(b[2] & 0x80)) {
 		buf->data += 3;
 		buf->size -= 3;
@@ -131,7 +128,7 @@ read_varint32(struct tbuf *buf)
 	}
 
 	if (len < 4)
-		tnt_raise(tnt_PickleException, reason:"buffer too short");
+		tnt_raise(IllegalParams, :"packet too short (expected 4 bytes)");
 	if (!(b[3] & 0x80)) {
 		buf->data += 4;
 		buf->size -= 4;
@@ -141,7 +138,7 @@ read_varint32(struct tbuf *buf)
 	}
 
 	if (len < 5)
-		tnt_raise(tnt_PickleException, reason:"buffer too short");
+		tnt_raise(IllegalParams, :"packet too short (expected 5 bytes)");
 	if (!(b[4] & 0x80)) {
 		buf->data += 5;
 		buf->size -= 5;
@@ -150,7 +147,7 @@ read_varint32(struct tbuf *buf)
 			(b[2] & 0x7f) << 14 | (b[3] & 0x7f) << 7 | (b[4] & 0x7f);
 	}
 
-	tnt_raise(tnt_PickleException, reason:"impossible happened");
+	tnt_raise(IllegalParams, :"incorrect BER format");
 	return 0;
 }
 
@@ -170,7 +167,7 @@ read_field(struct tbuf *buf)
 	u32 data_len = read_varint32(buf);
 
 	if (data_len > buf->len)
-		tnt_raise(tnt_PickleException, reason:"buffer too short");
+		tnt_raise(IllegalParams, :"packet too short (expected a field)");
 
 	buf->size -= data_len;
 	buf->len -= data_len;
diff --git a/core/replicator.m b/core/replicator.m
index c04cef8a27384f86be345e018bff0bbfa368e322..5bd723fdf94788b2e25a915088ae2bf48ca04478 100644
--- a/core/replicator.m
+++ b/core/replicator.m
@@ -37,6 +37,7 @@
 #include <sys/uio.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include "fiber.h"
 
 
 /** replicator process context struct */
@@ -269,13 +270,14 @@ acceptor_handler(void *data)
 
 	msg = tbuf_alloc(fiber->pool);
 
-	while (true) {
+	fiber_io_start(EV_READ);
+	for (;;) {
 		struct sockaddr_in addr;
 		socklen_t addrlen = sizeof(addr);
 		int client_sock = -1;
 
 		/* wait new connection request */
-		wait_for(EV_READ);
+		fiber_io_yield();
 
 		/* accept connection */
 		client_sock = accept(fiber->fd, &addr, &addrlen);
@@ -294,6 +296,7 @@ acceptor_handler(void *data)
 		tbuf_reset(msg);
 		wait_inbox(sender);
 	}
+	fiber_io_stop(EV_READ);
 }
 
 /** Replication sender fiber. */
diff --git a/core/salloc.m b/core/salloc.m
index e9a8e52262bde80cdd6af8d2d4edc3eb5f9b6891..c8195ce865d38327a24015442f436d443cfaad9a 100644
--- a/core/salloc.m
+++ b/core/salloc.m
@@ -191,7 +191,7 @@ format_slab(struct slab_class *class, struct slab *slab)
 }
 
 static bool
-full_formated(struct slab *slab)
+fully_formatted(struct slab *slab)
 {
 	return slab->brk + slab->class->item_size >= (void *)slab + SLAB_SIZE;
 }
@@ -284,7 +284,7 @@ salloc(size_t size)
 		VALGRIND_MAKE_MEM_UNDEFINED(item, sizeof(void *));
 	}
 
-	if (full_formated(slab) && slab->free == NULL)
+	if (fully_formatted(slab) && slab->free == NULL)
 		TAILQ_REMOVE(&class->free_slabs, slab, class_free_link);
 
 	slab->used += class->item_size + sizeof(red_zone);
@@ -302,7 +302,7 @@ sfree(void *ptr)
 	struct slab_class *class = slab->class;
 	struct slab_item *item = ptr;
 
-	if (full_formated(slab) && slab->free == NULL)
+	if (fully_formatted(slab) && slab->free == NULL)
 		TAILQ_INSERT_TAIL(&class->free_slabs, slab, class_free_link);
 
 	assert(valid_item(slab, item));
diff --git a/core/say.m b/core/say.m
index 8a29c91fa4952fe5d2fa3a9a444c5458bff10fca..c0aac43e6e21fa7b47082e43da9d5793ed28786d 100644
--- a/core/say.m
+++ b/core/say.m
@@ -85,6 +85,14 @@ say_logger_init(int nonblock)
 		if (pid == 0) {
 			close(pipefd[1]);
 			dup2(pipefd[0], STDIN_FILENO);
+			/*
+			 * Move to an own process group, to not
+			 * receive signals from the controlling
+			 * tty. This keeps the log open as long as
+			 * the parent is around. When the parent
+			 * dies, we get SIGPIPE and terminate.
+			 */
+			setpgid(0, 0);
 			execve(argv[0], argv, envp);
 		} else {
 			close(pipefd[0]);
diff --git a/core/tarantool.m b/core/tarantool.m
index 52510c2e871c6cf5c8960e410eb8b2096daf9e54..ad80f96cc33417eddd9b1d083dce9ab652541bbf 100644
--- a/core/tarantool.m
+++ b/core/tarantool.m
@@ -46,6 +46,7 @@
 #include <replicator.h>
 #include <fiber.h>
 #include <iproto.h>
+#include <latch.h>
 #include <log_io.h>
 #include <palloc.h>
 #include <salloc.h>
@@ -108,58 +109,72 @@ load_cfg(struct tarantool_cfg *conf, i32 check_rdonly)
 	return 0;
 }
 
+
 i32
 reload_cfg(struct tbuf *out)
 {
-	struct tarantool_cfg new_cfg1, new_cfg2;
-	i32 ret;
-
-	// Load with checking readonly params
-	if (dup_tarantool_cfg(&new_cfg1, &cfg) != 0) {
-		destroy_tarantool_cfg(&new_cfg1);
+	static struct tnt_latch *latch = NULL;
+	struct tarantool_cfg new_cfg, aux_cfg;
 
-		return -1;
+	if (latch == NULL) {
+		latch = palloc(eter_pool, sizeof(*latch));
+		tnt_latch_init(latch);
 	}
-	ret = load_cfg(&new_cfg1, 1);
-	if (ret == -1) {
-		tbuf_append(out, cfg_out->data, cfg_out->len);
 
-		destroy_tarantool_cfg(&new_cfg1);
+	if (tnt_latch_trylock(latch) == -1) {
+		out_warning(0, "Could not reload configuration: it is being reloaded right now");
+		tbuf_append(out, cfg_out->data, cfg_out->len);
 
 		return -1;
 	}
-	// Load without checking readonly params
-	if (fill_default_tarantool_cfg(&new_cfg2) != 0) {
-		destroy_tarantool_cfg(&new_cfg2);
 
-		return -1;
-	}
-	ret = load_cfg(&new_cfg2, 0);
-	if (ret == -1) {
-		tbuf_append(out, cfg_out->data, cfg_out->len);
+	@try {
+		init_tarantool_cfg(&new_cfg);
+		init_tarantool_cfg(&aux_cfg);
 
-		destroy_tarantool_cfg(&new_cfg1);
+		/*
+		  Prepare a copy of the original config file
+		  for confetti, so that it can compare the new
+		  file with the old one when loading the new file.
+		  Load the new file and return an error if it
+		  contains a different value for some read-only
+		  parameter.
+		*/
+		if (dup_tarantool_cfg(&aux_cfg, &cfg) != 0 ||
+		    load_cfg(&aux_cfg, 1) != 0)
+			return -1;
+		/*
+		  Load the new configuration file, but
+		  skip the check for read only parameters.
+		  new_cfg contains only defaults and
+		  new settings.
+		*/
+		if (fill_default_tarantool_cfg(&new_cfg) != 0 ||
+		    load_cfg(&new_cfg, 0) != 0)
+			return -1;
+
+		/* Check that no default value has been changed. */
+		char *diff = cmp_tarantool_cfg(&aux_cfg, &new_cfg, 1);
+		if (diff != NULL) {
+			out_warning(0, "Could not accept read only '%s' option", diff);
+			return -1;
+		}
 
-		return -1;
+		/* Now pass the config to the module, to take action. */
+		if (mod_reload_config(&cfg, &new_cfg) != 0)
+			return -1;
+		/* All OK, activate the config. */
+		swap_tarantool_cfg(&cfg, &new_cfg);
 	}
-	// Compare only readonly params
-	char *diff = cmp_tarantool_cfg(&new_cfg1, &new_cfg2, 1);
-	if (diff != NULL) {
-		destroy_tarantool_cfg(&new_cfg1);
-		destroy_tarantool_cfg(&new_cfg2);
+	@finally {
+		destroy_tarantool_cfg(&aux_cfg);
+		destroy_tarantool_cfg(&new_cfg);
 
-		out_warning(0, "Could not accept read only '%s' option", diff);
-		tbuf_append(out, cfg_out->data, cfg_out->len);
+		if (cfg_out->len != 0)
+			tbuf_append(out, cfg_out->data, cfg_out->len);
 
-		return -1;
+		tnt_latch_unlock(latch);
 	}
-	destroy_tarantool_cfg(&new_cfg1);
-
-	mod_reload_config(&cfg, &new_cfg2);
-
-	destroy_tarantool_cfg(&cfg);
-
-	cfg = new_cfg2;
 
 	return 0;
 }
@@ -224,7 +239,7 @@ snapshot(void *ev, int events __attribute__((unused)))
 static void
 sig_int(int signal)
 {
-	say_info("SIGINT or SIGTERM recieved, terminating");
+	say_info("Exiting: %s", strsignal(signal));
 
 	if (recovery_state != NULL) {
 		struct child *writer = recovery_state->wal_writer;
diff --git a/doc/box-protocol.txt b/doc/box-protocol.txt
index a81c1282e6b1a8d95cab795193e4b3d0a03e93ce..8dd73596019859896473b99154c21c8e544c0174 100644
--- a/doc/box-protocol.txt
+++ b/doc/box-protocol.txt
@@ -167,7 +167,7 @@
 ; Even this type awareness is very limited: it's only used when
 ; the field participates in an index. For example, when a numeric
 ; 32-bit index is defined on a field, and a non-32-bit value
-; is supplied for that field, an ERR_CODE_BAD_PARAMS
+; is supplied for that field, an ER_BAD_PARAMS
 ; is returned.
 ;
 
@@ -297,7 +297,7 @@
 ; it can be deduced from the error code. There are only
 ; 3 completion status codes in use:
 ; 0  - success; The only possible error code with this status is
-       0, ERR_CODE_OK
+       0, ER_OK
 ; 1  - try again; An indicator of an intermittent error.
 ;      Usually is returned when two clients attempt to change
 ;      the same tuple simultaneously.
@@ -308,45 +308,45 @@
 ;
 ;  Completion status 0 (success)
 ;  -----------------------------
-;  0x00000000 -- ERR_CODE_OK
+;  0x00000000 -- ER_OK
 ;
 ;  Completion status 1 (try again)
 ;  -------------------------------
-;  0x00000401 -- ERR_CODE_NODE_IS_RO
+;  0x00000401 -- ER_TUPLE_IS_RO
 ;                The requested data is blocked from modification
 ;
-;  0x00000601 -- ERR_CODE_NODE_IS_LOCKED
+;  0x00000601 -- ER_TUPLE_IS_LOCKED
 ;                The requested data is not available
 ;
-;  0x00000701 -- ERR_CODE_MEMORY_ISSUE
+;  0x00000701 -- ER_MEMORY_ISSUE
 ;                An error occurred when allocating memory
 ;
 ;  Completion status 2 (error)
 ;  ---------------------------
 ;
-;  0x00000102  -- ERR_CODE_NONMASTER
-;                 An attempt was made to change data on a read-only port
+;  0x00000102 -- ER_NONMASTER
+;                An attempt was made to change data on a read-only port
 ;
-;  0x00000202  -- ERR_CODE_ILLEGAL_PARAMS
-;                 Malformed query
+;  0x00000202 -- ER_ILLEGAL_PARAMS
+;                Malformed query
 ;
-;  0x00000a02 -- ERR_CODE_UNSUPPORTED_COMMAND
+;  0x00000a02 -- ER_UNSUPPORTED_COMMAND
 ;                The query is not recognized
 ;
-;  0x00001e02 -- ERR_CODE_WRONG_FIELD
+;  0x00001e02 -- ER_WRONG_FIELD
 ;                An unknown field was requested
 ;
-;  0x00001f02 -- ERR_CODE_WRONG_NUMBER
+;  0x00001f02 -- ER_WRONG_NUMBER
 ;                An out-of-range numeric value was included in the query
 ;
-;  0x00002002 -- ERR_CODE_DUPLICATE
+;  0x00002002 -- ER_DUPLICATE
 ;                An attempt was made to create an object with an existing key.
 ;
-;  0x000026002 -- ERR_CODE_WRONG_VERSION
-;                 The protocol version is not supported
+;  0x00002602 -- ER_WRONG_VERSION
+;                The protocol version is not supported
 ;
-;  0x000027002 -- ERR_CODE_UNKNOWN_ERROR
-;                 Unknown error
+;  0x00002702 -- ER_WAL_IO
+;                WAL I/O error
 ;
 ; Convenience that define hexadecimal constants for <int32>
 ; return codes (completion status + code) can be found in
diff --git a/doc/developer/CMakeLists.txt b/doc/developer/CMakeLists.txt
index 52ba4d5e1f948d8b22cb99ec74c3723b12188998..691e16c82e2c17018469853a29576e56238a4f06 100644
--- a/doc/developer/CMakeLists.txt
+++ b/doc/developer/CMakeLists.txt
@@ -1,6 +1,8 @@
-add_custom_command(OUTPUT tarantool_user_guide.html
-    COMMAND ${XSLTPROC} --xinclude -o tarantool_user_guide.html
-        http://docbook.sourceforge.net/release/xsl-ns/current/html/docbook.xsl
-        user.xml)
+add_custom_target(dev-html ALL
+    COMMAND ${XSLTPROC} --nonet
+        --stringparam collect.xref.targets "all"
+        --xinclude -o tarantool_developer_guide.html
+        ${CMAKE_SOURCE_DIR}/doc/user/tnt-html.xsl
+        developer.xml)
 
 
diff --git a/doc/developer/developer.xml b/doc/developer/developer.xml
index 64713736d2bb49ef0e618abfaa14b2e3af7c97b4..f761f76ec6ac104b3419fff4ae4833ba51675849 100644
--- a/doc/developer/developer.xml
+++ b/doc/developer/developer.xml
@@ -1,8 +1,40 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE book [
+<!ENTITY % tnt SYSTEM "../tnt.ent">
+%tnt;
+]>
 <book xmlns="http://docbook.org/ns/docbook"
+      xmlns:xlink="http://www.w3.org/1999/xlink"
       xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0">
-<title>Tarantool/Box User Guide, version 1.3.5</title>
-<xi:include href="./intro.xml"/>
+<title>Tarantool/Box Developer Guide</title>
+<chapter>
+<title>Compiling</title>
+<section>
+<title>How to fix the compile time error about missing confetti</title>
+<para>
+  An error about missing confetti:
+<programlisting>
+Generating prscfg.h, prscfg.c...
+[ 13%] Generating prscfg.h, prscfg.c
+/bin/sh: CONFETTI-NOTFOUND: not found
+</programlisting>
+This error is caused by cmake, trying to re-create generated files
+prscfg.h, prscfg.c in <filename>cfg/</filename> directory.
+These files are under revision control and normally need not to be 
+regenerated. 
+The fix is to
+<programlisting>
+<prompt>$ </prompt>git checkout cfg/prscfg.h
+<prompt>$ </prompt>git checkout cfg/prscfg.c
+<prompt>$ </prompt>touch cfg/prscfg.[hc]
+</programlisting>
+The other alternative, if you have actually modified
+<filename>core_cfg.cfg_tmpl</filename> is to install confetti from
+<link xlink:href="http://github.com/mailru/confetti"/> and let cmake use it.
+  
+  </para>
+</section>
+</chapter>
 <!--
      TOC:
 
@@ -45,3 +77,8 @@
        Glossary of terms
 -->
 </book>
+
+<!--
+vim: tw=66 syntax=docbk
+vim: spell spelllang=en_us
+-->
diff --git a/doc/sql.txt b/doc/sql.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9cbbd9a7de20e3aab1203d77a8356fb59a1169f8
--- /dev/null
+++ b/doc/sql.txt
@@ -0,0 +1,53 @@
+;  
+; Tarantool SQL parser is implemented entirely on the client side.
+; This BNF provides a reference of the supported subset of 
+; the standard SQL, to which all clients are strongly encouraged 
+; to stick.
+; 
+; Convention: UPPERCASE letters are used for terminals and literals.
+; Lowercase letters are used for <non-terminals>. SQL is
+; case-insensitive, so this convention is present only to imporve
+; legibility of the BNF.
+
+<sql> ::= <insert> | <update> | <delete> | <select>
+
+<insert> ::= INSERT [INTO] <ident> VALUES <value_list>
+
+<update> ::= UPDATE <ident> SET <update_list> <simple_where>
+
+<delete> ::= DELETE FROM <ident> <simple_where>
+
+; It's only possible to select all fields of a tuple (* for field list)
+<select> ::= SELECT * FROM <ident> <where> <opt_limit>
+
+<simple_where> ::= WHERE <predicate>
+
+<where> ::= WHERE <disjunction>
+
+<predicate> ::= <ident> = <constant>
+
+<disjunction> ::= <predicate> [{OR <predicate>}+]
+
+; LIMIT is optional
+<opt_limit> ::= | LIMIT NUM[, NUM]
+
+<value_list> ::= (<constant> [{, <constant>}+])
+
+<update_list> ::= <ident> = <constant> [{, <ident> = <constant>}+]
+
+<constant> ::= STR | NUM
+
+<ident> ::= ID
+
+; Only integer numbers, optionally signed, are supported
+NUM ::= [+-]?[0-9]+
+
+;  Strings must be single-quoted
+STR ::= '.*'
+
+; Identifiers must be standard SQL, but end with digits.
+; These digits are used to infer the namespace or index id.
+
+ID ::= [a-z_]+[0-9]+
+
+; vim: syntax=bnf
diff --git a/doc/user/CMakeLists.txt b/doc/user/CMakeLists.txt
index b48472a87601fe3fd2fa6b51b193a2ebffc449f5..54df67d597ebf95f273a816211acc5544b0712b7 100644
--- a/doc/user/CMakeLists.txt
+++ b/doc/user/CMakeLists.txt
@@ -5,6 +5,14 @@ add_custom_target(html ALL
         tnt-html.xsl
         user.xml)
 
+add_custom_target(pdf
+    COMMAND ${XSLTPROC} --nonet
+        --stringparam collect.xref.targets "all"
+        --xinclude -o tarantool_user_guide.fo
+        tnt-fo.xsl
+        user.xml
+    COMMAND fop tarantool_user_guide.fo tarantool_user_guide.pdf)
+
 add_custom_target(relink
     COMMAND ${XSLTPROC} --nonet
         --stringparam collect.xref.targets "only"
@@ -23,6 +31,18 @@ add_custom_target(html-saxon
         -r org.apache.xml.resolver.tools.CatalogResolver
         -u -o tarantool_user_guide.html user.xml tnt-html.xsl)
 
+add_custom_target(pdf-saxon
+    COMMAND java -cp "/usr/share/java/saxon.jar:/usr/share/java/xml-commons-resolver-1.1.jar:/usr/share/java/docbook-xsl-saxon.jar:/usr/share/java/xercesImpl.jar:/etc/xml/resolver:/usr/share/java/xslthl.jar"
+        -Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
+        -Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl
+        -Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XIncludeParserConfiguration
+        com.icl.saxon.StyleSheet
+        -x org.apache.xml.resolver.tools.ResolvingXMLReader
+        -y org.apache.xml.resolver.tools.ResolvingXMLReader
+        -r org.apache.xml.resolver.tools.CatalogResolver
+        -u -o tarantool_user_guide.fo user.xml tnt-fo.xsl
+    COMMAND fop tarantool_user_guide.fo tarantool_user_guide.pdf)
+
 # XMLLINT is not able to validate SCHEMATRON constraints, and
 # therefore is not a good validation tool for DocBook 5.  However,
 # it can validate the entire document, following xinclude
diff --git a/doc/user/configuration-reference.xml b/doc/user/configuration-reference.xml
index b86c48c7e7033162ef53581cfc2d9f206b82b9b1..bbb384e2259a2a4362133b7858a023bce55c43d8 100644
--- a/doc/user/configuration-reference.xml
+++ b/doc/user/configuration-reference.xml
@@ -123,7 +123,11 @@ Tarantool/Box 1.4.0-69-g45551dd
       </para>
     </listitem>
     <listitem>
-      <para><option>--init-storage</option></para>
+      <para>
+      <option xml:id="init-storage-option"
+            xreflabel="--init-storage">
+          --init-storage
+      </option></para>
       <para>Initialize the directory, specified in <emphasis>vardir</emphasis>
       configuration option by creating an empty snapshot file in
       it. If <filename>vardir</filename> doesn't contain at
@@ -140,7 +144,8 @@ Tarantool/Box 1.4.0-69-g45551dd
     </listitem>
 
     <listitem>
-      <para><option>--cat</option> <userinput>snapshot.file</userinput></para>
+      <para><option xml:id="cat-option" xreflabel="--cat">--cat</option>
+      <userinput>snapshot.file</userinput></para>
       <para>Print the snapshot file, pointed to by the argument,
       in a human-readable form. For each log record, log sequence
       number, time of entry, operation type and arguments are
@@ -180,7 +185,7 @@ lsn:4 tm:1301572313.691 t:65534 127.0.0.1:52728 UPDATE_FIELDS n:0flags:00000000
   </itemizedlist>
 </section>
 
-<section>
+<section xml:id="option-file" xreflabel="option file">
 <title>The option file</title>
   <para>
     All advanced configuration parameters must be specified in a
@@ -225,9 +230,11 @@ lsn:4 tm:1301572313.691 t:65534 127.0.0.1:52728 UPDATE_FIELDS n:0flags:00000000
     <filename xlink:href="https://github.com/mailru/tarantool/blob/master/test/box_big/tarantool.cfg">test/box_big/tarantool.cfg</filename>.
   </para>
 
-  <table frame='all'>
+  <table frame='all' pgwide='1'>
     <title>Basic parameters</title>
     <tgroup cols='6' colsep='1' rowsep='1'>
+      <colspec colnum="1" colname="col1" colwidth="2*"/>
+      <colspec colnum="6" colname="col4" colwidth="6*"/>
 
       <thead>
         <row>
@@ -310,7 +317,8 @@ lsn:4 tm:1301572313.691 t:65534 127.0.0.1:52728 UPDATE_FIELDS n:0flags:00000000
         </row>
 
         <row>
-          <entry>secondary_port</entry>
+          <entry xml:id="secondary_port"
+            xreflabel="secondary_port">secondary_port</entry>
           <entry>integer</entry>
           <entry>none</entry>
           <entry>no</entry>
@@ -320,7 +328,7 @@ lsn:4 tm:1301572313.691 t:65534 127.0.0.1:52728 UPDATE_FIELDS n:0flags:00000000
         </row>
 
         <row>
-          <entry>admin_port</entry>
+          <entry xml:id="admin_port" xreflabel="admin_port">admin_port</entry>
           <entry>integer</entry>
           <entry>none</entry>
           <entry>no</entry>
@@ -341,23 +349,27 @@ lsn:4 tm:1301572313.691 t:65534 127.0.0.1:52728 UPDATE_FIELDS n:0flags:00000000
         </row>
 
         <row>
-          <entry>custom_proc_title</entry>
+          <entry xml:id="custom_proc_title"
+                 xreflabel="custom_proc_title">
+            custom_proc_title
+          </entry>
           <entry>string</entry>
           <entry>""</entry>
           <entry>no</entry>
           <entry>no</entry>
           <entry>
-            <para>Inject the given string into process title
+            <para>Inject the given string into <olink
+            targetptr="proctitle">server process title</olink>
             (what's shown in COMMAND column of <command>ps</command>
             and <command>top</command> commands). For example,
             an unmodified Tarantool/Box process group looks like:
             </para>
 <programlisting>kostja@shmita:~$ ps -a -o command | grep box
-tarantool: box:primary pri:33013 sec:33014 adm:33015</programlisting>
+tarantool_box: primary pri:33013 sec:33014 adm:33015</programlisting>
           <para>After "sessions" custom_proc_title is injected it
           looks like:</para>
 <programlisting>kostja@shmita:~$ ps -a -o command | grep box
-tarantool: box:primary@sessions pri:33013 sec:33014 adm:33015</programlisting>
+tarantool_box: primary@sessions pri:33013 sec:33014 adm:33015</programlisting>
           </entry>
         </row>
 
@@ -365,9 +377,11 @@ tarantool: box:primary@sessions pri:33013 sec:33014 adm:33015</programlisting>
     </tgroup>
   </table>
 
-  <table frame='all'>
+  <table frame='all' pgwide='1'>
     <title>Configuring the storage</title>
     <tgroup cols='6' colsep='1' rowsep='1'>
+      <colspec colnum="1" colname="col1" colwidth="2*"/>
+      <colspec colnum="6" colname="col4" colwidth="6*"/>
 
       <thead>
         <row>
@@ -395,7 +409,7 @@ tarantool: box:primary@sessions pri:33013 sec:33014 adm:33015</programlisting>
           store tuples, <emphasis role="strong">in
           gigabytes</emphasis>. When the limit is reached, INSERT
           or UPDATE requests begin failing with error
-          <olink targetptr="ERR_CODE_MEMORY_ISSUE"/>.
+          <olink targetptr="ER_MEMORY_ISSUE"/>.
           While the server does not go
           beyond the defined limit to allocate tuples, there is
           additional memory used to store indexes and connection
@@ -447,9 +461,11 @@ tarantool: box:primary@sessions pri:33013 sec:33014 adm:33015</programlisting>
     </tgroup>
   </table>
 
-  <table frame='all'>
+  <table frame='all' pgwide='1'>
     <title>Binary logging and snapshots</title>
     <tgroup cols='6' colsep='1' rowsep='1'>
+      <colspec colnum="1" colname="col1" colwidth="2*"/>
+      <colspec colnum="6" colname="col4" colwidth="6*"/>
 
       <thead>
         <row>
@@ -522,24 +538,11 @@ tarantool: box:primary@sessions pri:33013 sec:33014 adm:33015</programlisting>
           <entry>WAL writer is a separate process whose sole
           purpose is to write the change log to disk. Every
           incoming data change is sent to this process and
-          queued for write. This parameter sets the size
-          of the queue. However, it has no effect
-          due to <link
-          xlink:href="https://bugs.launchpad.net/tarantool/+bug/791498">Bug#791498</link>.</entry>
-        </row>
-
-        <row>
-          <entry>readahead</entry>
-          <entry>integer</entry>
-          <entry>16384</entry>
-          <entry>no</entry>
-          <entry>no</entry>
-          <entry>WAL writer network read-ahead buffer size, in
-          bytes.
-            It's not recommended to change this value until
-            <link xlink:href="https://bugs.launchpad.net/tarantool/+bug/791498">Bug#791498</link>
-            is fixed.
-            </entry>
+          queued for write. This parameter affects the size of the
+          user space buffer that WAL writer uses to queue
+          write requests. By default, up to 128 client
+          connections can have pending updates waiting on disk.
+          </entry>
         </row>
 
         <row>
@@ -564,9 +567,11 @@ tarantool: box:primary@sessions pri:33013 sec:33014 adm:33015</programlisting>
     </tgroup>
   </table>
 
-  <table frame='all'>
+  <table frame='all' pgwide='1'>
     <title>Replication</title>
     <tgroup cols='6' colsep='1' rowsep='1'>
+      <colspec colnum="1" colname="col1" colwidth="2*"/>
+      <colspec colnum="6" colname="col4" colwidth="6*"/>
 
       <thead>
         <row>
@@ -581,7 +586,8 @@ tarantool: box:primary@sessions pri:33013 sec:33014 adm:33015</programlisting>
 
       <tbody>
         <row>
-          <entry>replication_port</entry>
+          <entry xml:id="replication_port"
+            xreflabel="replication_port">replication_port</entry>
           <entry>integer</entry>
           <entry>0</entry>
           <entry>no</entry>
@@ -609,7 +615,10 @@ tarantool: box:primary@sessions pri:33013 sec:33014 adm:33015</programlisting>
         </row>
 
         <row>
-          <entry>replication_source_ipaddr</entry>
+          <entry xml:id="replication_source_ipaddr"
+                 xreflabel="replication_source_ipaddr">
+              replication_source_ipaddr
+          </entry>
           <entry>string</entry>
           <entry>0</entry>
           <entry>no</entry>
@@ -621,19 +630,67 @@ tarantool: box:primary@sessions pri:33013 sec:33014 adm:33015</programlisting>
     </tgroup>
   </table>
 
-<!--
+  <table frame='all' pgwide='1'>
+    <title>Networking</title>
+    <tgroup cols='6' colsep='1' rowsep='1'>
+      <colspec colnum="1" colname="col1" colwidth="2*"/>
+      <colspec colnum="6" colname="col4" colwidth="6*"/>
 
-# delay between loop iterations
-io_collect_interval=0.0, ro
+      <thead>
+        <row>
+          <entry>Name</entry>
+          <entry>Type</entry>
+          <entry>Default</entry>
+          <entry>Required?</entry>
+          <entry>Dynamic?</entry>
+          <entry>Description</entry>
+        </row>
+      </thead>
 
-# size of listen backlog
-backlog=1024, ro
+      <tbody>
 
--->
+        <row>
+          <entry>io_collect_interval</entry>
+          <entry>float</entry>
+          <entry>0.0</entry>
+          <entry>no</entry>
+          <entry>no</entry>
+          <entry>If non-zero, a sleep given duration is
+          injected between iterations of the event loop. Can be
+          used to reduce CPU load in deployments in which the
+          number of client connections is large, but requests are
+          not so frequent (for example, each connection issuing
+          just a handful of requests per second). </entry>
+        </row>
+
+        <row>
+          <entry>readahead</entry>
+          <entry>integer</entry>
+          <entry>16384</entry>
+          <entry>no</entry>
+          <entry>no</entry>
+          <entry>The size of read-ahead buffer associated with
+              a client connection.</entry>
+        </row>
+
+        <row>
+          <entry>backlog</entry>
+          <entry>integer</entry>
+          <entry>1024</entry>
+          <entry>no</entry>
+          <entry>no</entry>
+          <entry>The size of listen backlog.</entry>
+       </row>
 
-  <table frame='all'>
+      </tbody>
+    </tgroup>
+  </table>
+
+  <table frame='all' pgwide='1'>
     <title>Logging</title>
     <tgroup cols='6' colsep='1' rowsep='1'>
+      <colspec colnum="1" colname="col1" colwidth="2*"/>
+      <colspec colnum="6" colname="col4" colwidth="6*"/>
 
       <thead>
         <row>
@@ -712,9 +769,11 @@ backlog=1024, ro
     </tgroup>
   </table>
 
-  <table frame='all'>
+  <table frame='all' pgwide='1'>
     <title>Memcached protocol support</title>
     <tgroup cols='6' colsep='1' rowsep='1'>
+      <colspec colnum="1" colname="col1" colwidth="2*"/>
+      <colspec colnum="6" colname="col4" colwidth="6*"/>
 
       <thead>
         <row>
@@ -730,19 +789,27 @@ backlog=1024, ro
       <tbody>
 
         <row>
-          <entry>memcached</entry>
+          <entry>memcached_port</entry>
           <entry>integer</entry>
           <entry>0</entry>
           <entry>no</entry>
           <entry>no</entry>
-          <entry>Turn on Memcached mode. In this mode, Tarantool
-            does not use secondary_port, and speaks memcached
-            protocol on the primary port. In particular,
-            memcached-style flags are supported, and expiration
-            time can be set on tuples. Unlike Memcached, all data
-            still goes to the binary log and to the replica, if
-            latter one is set up, which means that power outage
-            does not lead to a loss of all data.
+          <entry>
+            <anchor xml:id="memcached_port" xreflabel="memcached_port"/>
+
+          Turn on Memcached protocol support on the given
+            port. All requests on this port are directed to
+            a dedicated namespace, set in <olink
+            targetptr="memcached_namespace"/>.
+            Memcached-style flags are supported and stored
+            along with the value. The expiration time can also be
+            set and is persistent, but is ignored, unless <olink
+            targetptr="memcached_expire"/> is turned on.
+            Unlike Memcached, all data still goes to the binary
+            log and to the replica, if latter one is set up, which
+            means that power outage does not lead to loss of all
+            data. Thanks to data persistence, cache warm up time
+            is also very short.
           </entry>
         </row>
 
@@ -752,9 +819,34 @@ backlog=1024, ro
           <entry>23</entry>
           <entry>no</entry>
           <entry>no</entry>
-          <entry>Namespace id to store memcached data in. The
-          format of tuple is [key, metadata, value], with a hash
-          index based on the key. </entry>
+          <entry>
+            <anchor xml:id="memcached_namespace"
+            xreflabel="memcached_namespace"/>
+          Namespace id to store memcached data in. The
+          format of tuple is [key, metadata, value], with a HASH
+          index based on the key. Since the namespace format
+          is defined by Memcached data model, it must not be
+          previously configured.</entry>
+        </row>
+
+        <row>
+          <entry>memcached_expire</entry>
+          <entry>boolean</entry>
+          <entry>0</entry>
+          <entry>no</entry>
+          <entry>no</entry>
+          <entry>
+            <anchor xml:id="memcached_expire"
+            xreflabel="memcached_expire"/>
+            Turn on tuple time-to-live support in
+            <olink targetptr="memcached_namespace"/>. This
+            effectively turns Tarantool into a "persistent" implementation
+            of Memcached. Since a caching server has
+            significantly different purpose than a data server, in
+            this mode Tarantool turns off replication and does not
+            bind to <olink targetptr="primary_port"/> or <olink
+            targetptr="secondary_port"/>.
+          </entry>
         </row>
 
         <row>
diff --git a/doc/user/connectors.xml b/doc/user/connectors.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c150d64627043d03e888072b356c830413552ded
--- /dev/null
+++ b/doc/user/connectors.xml
@@ -0,0 +1,265 @@
+<!DOCTYPE book [
+<!ENTITY % tnt SYSTEM "../tnt.ent">
+%tnt;
+]>
+<chapter xmlns="http://docbook.org/ns/docbook" version="5.0"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xml:id="connectors">
+
+<title>Connectors</title>
+<blockquote><para>
+  This chapter documents APIs for various programming languages --
+  C, Perl, Ruby, PHP and Python.
+</para></blockquote>
+
+<para>All connectors are located in srcdir/connector directory. Apart from the native Tarantool client driver, you can always use a <emphasis role="strong">Memcached</emphasis> driver of your choice, after enabling Memcached protocol in the configuration file.</para>
+
+  <section>
+    <title>C</title>
+    <para>
+       Please see <link
+       xlink:href="https://github.com/mailru/tarantool/blob/master/connector/c/client.h"><filename>connector/c/client.h</filename></link> in the source tree.
+    </para>
+  </section>
+
+  <section>
+    <title>Perl</title>
+    <para>
+       The perl client is located in <link
+       xlink:href="https://github.com/mailru/tarantool/blob/master/connector/perl/lib/"><filename>connector/perl/lib/</filename></link>.
+    <orderedlist>
+
+    <listitem>
+      <simpara><emphasis role="strong">new</emphasis></simpara>
+<programlisting language="perl"><![CDATA[
+     my $box = MR::SilverBox->new({
+            namespaces => [ {
+                indexes => [ {
+                    index_name   => 'primary_id',
+                    keys         => [0],
+                }, {
+                    index_name   => 'primary_email',
+                    keys         => [1],
+                }, ],
+                namespace     => 0,
+                format        => 'l& SSLL',
+                default_index => 'primary_id',
+            } ],
+        },
+        servers      => $server})
+]]></programlisting>
+    </listitem>
+
+    <listitem>
+      <simpara><emphasis role="strong">Insert</emphasis></simpara>
+<programlisting language="perl">
+    $box->Insert(@tuple);
+</programlisting>
+    </listitem>
+
+    <listitem>
+      <simpara><emphasis role="strong">Select</emphasis></simpara>
+<programlisting language="perl"><![CDATA[
+    [\%tuple1, \%tuple2, ...] = $box->Select(@id);
+    \%tuple = $box->Select($id);
+    [\@tuple1, \@tuple2, ...] = $box->Select(@id, {raw => 1});
+    \@tuple = $box->Select($id, {raw => 1});
+    $box->Select($email, {use_index => 1});
+]]></programlisting>
+    </listitem>
+
+    <listitem>
+      <simpara><emphasis role="strong">Update</emphasis>
+      accepts parameters in the last argument just like Select:</simpara>
+<programlisting language="perl"><![CDATA[
+    $key = 1; # key, ID of user by default
+    $field_num = 2; # posititon in tuple, starts from 0, must be >= 1
+    $value = pack('L', 123);  # integer values must be packed
+    $box->Update($key, $field_num, $value);
+]]></programlisting>
+    </listitem>
+
+    <listitem>
+      <simpara><emphasis role="strong">Delete</emphasis></simpara>
+<programlisting language="perl"><![CDATA[
+    $box->Delete($key);
+]]></programlisting>
+    </listitem>
+
+    <listitem>
+      <simpara><emphasis role="strong">AndXorAdd</emphasis>
+      transforms the tuple field at position <quote>$field_num</quote>
+      according to formula <quote>field= ((field &amp; $and_mask) ^ $xor_mask) + $add_value</quote>.
+      </simpara>
+<programlisting language="perl"><![CDATA[
+    $box->AndXorAdd($key, $field_num, $and_mask, $xor_mask, $add_value);
+]]></programlisting>
+    </listitem>
+
+    <listitem>
+      <simpara><emphasis role="strong">Bit</emphasis>
+      performs a bitwise operation on field at position
+      <quote>$field_num</quote>. Unused arguments can be omitted.
+      Note: <quote>set</quote> has a higher precedence than
+      <quote>bit_set</quote> and <quote>bit_clear</quote>.
+      </simpara>
+<programlisting language="perl"><![CDATA[
+     $box->Bit($key, $field_num, bit_clear => $clear_mask, bit_set => $set_mask, set => $set_value);
+     $box->Bit($key, $field_num, bit_set => $set_mask);
+]]></programlisting>
+    </listitem>
+
+    <listitem>
+      <simpara><emphasis role="strong">Num</emphasis>
+      performs a numeric update operation on field at position
+      <quote>$field_num</quote>. Unused arguments can be omitted.
+      Note: again, <quote>set</quote> has a higher precedence than
+      <quote>num_add</quote> and <quote>num_sub</quote>.
+      </simpara>
+<programlisting language="perl"><![CDATA[
+     $box->Num($key, $field_num, set => $set_value, num_add => $add_value, num_sub => $sub_value);
+]]></programlisting>
+    </listitem>
+
+    <listitem>
+      <simpara><emphasis role="strong">Flag</emphasis>
+      sets or clears flags on a tuple, the calling convention 
+      is the same as for <quote>Bit</quote> and <quote>Num</quote>
+      operations.
+      </simpara>
+<programlisting language="perl"><![CDATA[
+     $box->Flags(bit_set => $set_mask, bit_clear => $clear_mask);
+]]></programlisting>
+    </listitem>
+
+    </orderedlist>
+
+    </para>
+  </section>
+
+  <section>
+    <title>PHP</title>
+    <para>
+       @tba
+    </para>
+  </section>
+
+  <section>
+    <title>Python</title>
+    <para>
+       @tba
+    </para>
+  </section>
+
+  <section>
+    <title>Ruby</title>
+    <para>
+       You need <emphasis role="strong">Ruby 1.9</emphasis> or later
+       to use this connector. Connector sources are located in <link
+       xlink:href="https://github.com/mailru/tarantool/blob/master/connector/ruby/box.rb"><filename>connector/ruby/box.rb</filename></link>.
+    </para>
+    <para>
+       Assume, for the sake of example, that Tarantool has the
+       following namespace configuration:
+<programlisting language="c">
+primary_port = 33013 
+admin_port = 33015 
+log_level = 3
+slab_alloc_arena = 0.1
+
+namespace[0].enabled = 1
+namespace[0].index[0].type = "NUM"
+namespace[0].index[0].key_fields[0].fieldno = 0
+
+namespace[0].index[1].type = "STR"
+namespace[0].index[1].key_fields[0].fieldno = 1
+</programlisting>
+        The only defined namespace will be used to store user
+        account information, such as id, name, email, and other
+        properties. User ID is used for the primary key, but
+        it's also possible to find a user by name.
+   </para>
+   <para>In Ruby, a helper class is defined to present
+     Tarantool to the rest of the application as a typical object
+     container. 
+<example>
+<title>userbox.rb</title>
+<programlisting language="ruby"><![CDATA[require 'box'
+
+class UserBox < Box
+  def initialize(host)
+    super(host, :namespace => 0)
+  end
+
+  def insert(user)
+    case user
+    when Hash then super [user[:uid], user[:email], user[:name], user[:apple_count]]
+    when Array then super user
+    else fail "don't know what to do with #{user.class}"
+    end
+  end
+
+  def update_fields(key, *ops)
+    mapping = {:uid => 0, :email => 1, :name => 2, :apple_count => 3}
+    ops.map do |op|
+      op[0] = mapping[op[0]] if op.is_a? Array
+    end
+
+    super key, *ops
+  end
+
+  def unpack_tuple!(data)
+    tuple = super data
+    { :uid => tuple[0].unpack(?L)[0],
+      :email => tuple[1],
+      :name => tuple[2],
+      :apple_count => tuple[3].unpack(?L)[0]
+    }
+  end
+end]]></programlisting></example>
+     Here's how this helper class can be used:
+<programlisting><prompt>kostja@shmita:~$ </prompt><command>irb</command>
+<![CDATA[>> # Connect to the server
+>> require 'userbox'
+=> true
+>> b = UserBox.new 'localhost:33013'
+=> #<UserBox:0x870fd48 @namespace=1, @end_point=["localhost", 33013], @sock=#<TCPSocket:0x870f85c>
+>> # Insert a few users
+>> b.insert :uid => 1, :email => 'pupkin@mail.ru', :name => 'Vasya', :apple_count => 1
+=> 1
+>> b.insert :uid => 2, :email => 'masha@mail.ru', :name => 'Masha', :apple_count => 0
+=> 1
+>> b.insert :uid => 3, :email => 'petya@mail.ru', :name => 'Petya', :apple_count => 3
+=> 1
+>> # Perform selects
+>> b.select 1
+=> [{:uid=>1, :email=>"pupkin@mail.ru", :name=>"Vasya", :apple_count=>1}]
+>> b.select 1,2,3
+=> [{:uid=>1, :email=>"pupkin@mail.ru", :name=>"Vasya", :apple_count=>1}, {:uid=>2, :email=>"masha@mail.ru", :name=>"Masha", :apple_count=>0}, {:uid=>3, :email=>"petya@mail.ru", :name=>"Petya", :apple_count=>3}]
+>> # It's possible to retrieve records by email using second index
+>> b.select 'pupkin@mail.ru', 'petya@mail.ru', :index => 1
+=> [{:uid=>1, :email=>"pupkin@mail.ru", :name=>"Vasya", :apple_count=>1}, {:uid=>3, :email=>"petya@mail.ru", :name=>"Petya", :apple_count=>3}]
+Delete
+>> b.delete 2
+=> 1
+>> # Update values
+>> b.update_fields 1, [:apple_count, :add, 2 ]
+=> 1
+>> b.select 1
+=> [{:uid=>1, :email=>"pupkin@mail.ru", :name=>"Vasya", :apple_count=>3}]
+>> # It's possible to do several updates in a single atomic command
+>> b.update_fields 3, [:apple_count, :add, 10], [:name, :set, "foobar"]
+=> 1
+>> b.select 3
+=> [{:uid=>3, :email=>"petya@mail.ru", :name=>"foobar", :apple_count=>13}]
+]]>
+</programlisting>
+   </para>
+  </section>
+
+</chapter>
+
+<!--
+vim: tw=66 syntax=docbk
+vim: spell spelllang=en_us
+-->
diff --git a/doc/user/data-model.xml b/doc/user/data-model.xml
index 1e00090e359c9e107551225c55dedf5ec1c5e7e4..81ab0c789c1f0d9b0d1172ea93352f22a8d788aa 100644
--- a/doc/user/data-model.xml
+++ b/doc/user/data-model.xml
@@ -61,7 +61,7 @@
   Found 1 tuple:
   [1]
   tarantool> insert into t0 values ('hello')
-  An error occurred: ERR_CODE_ILLEGAL_PARAMS, 'Illegal parameters'
+  An error occurred: ER_ILLEGAL_PARAMS, 'Illegal parameters'
   tarantool> insert into t0 values (1, 'hello')
   Insert OK, 1 row affected
   tarantool> select * from t0 where k0=1 
@@ -109,7 +109,7 @@
 <para>
   Since not all NoSQL operations can be expressed in SQL, to gain
   complete access to data manipulation functionality one must use
-  a Perl, Python, Ruby or C connector. The client/server protocol
+  a <olink targetptr="connectors">Perl, Python, Ruby or C connector</olink>. The client/server protocol
   is open and documented: an annotated BNF can be found in the
   source tree, file <filename
   xlink:href="https://github.com/mailru/tarantool/blob/master/doc/box-protocol.txt" xlink:title="A complete BNF of Tarantool client/server protocol">doc/protocol.txt</filename>.
diff --git a/doc/user/errcode.xml b/doc/user/errcode.xml
index 5d23b526e4a53b29d8bf2a5ffbbe068229daafcc..69cdee6393a856ffe8b6058dc9072b81eb0fe775 100644
--- a/doc/user/errcode.xml
+++ b/doc/user/errcode.xml
@@ -9,61 +9,61 @@ which is normally more descriptive than error code,
 is not present in server response. The actual message may contain
 a file name, a detailed reason or operating system error code.
 All such messages, however, are logged in the error log. When
-using memcached protocol, the error message is sent to the
+using <emphasis role="strong">Memcached</emphasis> protocol, the error message is sent to the
 client along with the code. Below follow only general descriptions
 of existing codes.</para>
 <variablelist>
 <title>List of error codes</title>
 
   <varlistentry>
-    <term xml:id="ERR_CODE_NONMASTER" xreflabel="ERR_CODE_NONMASTER">ERR_CODE_NONMASTER</term>
+    <term xml:id="ER_NONMASTER" xreflabel="ER_NONMASTER">ER_NONMASTER</term>
     <listitem><para>Attempt to execute an update over the
     secondary (read-only) port or on a running replica.
     </para></listitem>
   </varlistentry>
 
   <varlistentry>
-    <term xml:id="ERR_CODE_ILLEGAL_PARAMS" xreflabel="ERR_CODE_ILLEGAL_PARAMS">ERR_CODE_ILLEGAL_PARAMS</term>
+    <term xml:id="ER_ILLEGAL_PARAMS" xreflabel="ER_ILLEGAL_PARAMS">ER_ILLEGAL_PARAMS</term>
     <listitem><para>Illegal parameters. Malformed protocol
     message.
     </para></listitem>
   </varlistentry>
 
   <varlistentry>
-    <term xml:id="ERR_CODE_NODE_IS_RO" xreflabel="ERR_CODE_NODE_IS_RO">ERR_CODE_NODE_IS_RO</term>
+    <term xml:id="ER_TUPLE_IS_RO" xreflabel="ER_TUPLE_IS_RO">ER_TUPLE_IS_RO</term>
     <listitem><para>A parallel update of a tuple is in progress. Try again.
     </para></listitem>
   </varlistentry>
 
   <varlistentry>
-    <term xml:id="ERR_CODE_MEMORY_ISSUE" xreflabel="ERR_CODE_MEMORY_ISSUE">ERR_CODE_MEMORY_ISSUE</term>
+    <term xml:id="ER_MEMORY_ISSUE" xreflabel="ER_MEMORY_ISSUE">ER_MEMORY_ISSUE</term>
     <listitem><para>Out of memory: <olink targetptr="slab_alloc_arena"/> limit is reached.
     </para></listitem>
   </varlistentry>
 
   <varlistentry>
-    <term xml:id="ERR_CODE_WAL_IO" xreflabel="ERR_CODE_WAL_IO">ERR_CODE_WAL_IO</term>
+    <term xml:id="ER_WAL_IO" xreflabel="ER_WAL_IO">ER_WAL_IO</term>
     <listitem><para>Failed to record the change in the write ahead
     log. Some sort of disk error.
     </para></listitem>
   </varlistentry>
 
   <varlistentry>
-    <term xml:id="ERR_CODE_INDEX_VIOLATION" xreflabel="ERR_CODE_INDEX_VIOLATION">ERR_CODE_INDEX_VIOLATION</term>
+    <term xml:id="ER_INDEX_VIOLATION" xreflabel="ER_INDEX_VIOLATION">ER_INDEX_VIOLATION</term>
     <listitem><para>A unique index constraint violation: a tuple with the same
     key is already present in the index.
     </para></listitem>
   </varlistentry>
 
   <varlistentry>
-    <term xml:id="ERR_CODE_NO_SUCH_NAMESPACE" xreflabel="ERR_CODE_NO_SUCH_NAMESPACE">ERR_CODE_NO_SUCH_NAMESPACE</term>
+    <term xml:id="ER_NO_SUCH_NAMESPACE" xreflabel="ER_NO_SUCH_NAMESPACE">ER_NO_SUCH_NAMESPACE</term>
     <listitem><para>Attempt to access a namespace that is not
     configured (doesn't exist).
     </para></listitem>
   </varlistentry>
 
   <varlistentry>
-    <term xml:id="ERR_CODE_NO_SUCH_INDEX" xreflabel="ERR_CODE_NO_SUCH_INDEX">ERR_CODE_NO_SUCH_INDEX</term>
+    <term xml:id="ER_NO_SUCH_INDEX" xreflabel="ER_NO_SUCH_INDEX">ER_NO_SUCH_INDEX</term>
     <listitem><para>No index with the given id exists.
     </para></listitem>
   </varlistentry>
diff --git a/doc/user/html-highlight.xsl b/doc/user/html-highlight.xsl
index 30943c01d88011c2519f83fe8f56c24fee1885d6..02cb8b611b8213911cc5707cdf7f92d3472e0588 100644
--- a/doc/user/html-highlight.xsl
+++ b/doc/user/html-highlight.xsl
@@ -25,7 +25,7 @@ xmlns:xslthl="http://xslthl.sf.net" xmlns="http://www.w3.org/1999/xhtml" exclude
 </xsl:template>
 
 <xsl:template match="xslthl:comment" mode="xslthl">
-  <i class="hl-comment" style="color: blue"><xsl:apply-templates mode="xslthl"/></i>
+  <i class="hl-comment" style="color: silver"><xsl:apply-templates mode="xslthl"/></i>
 </xsl:template>
 
 <xsl:template match="xslthl:directive" mode="xslthl">
diff --git a/doc/user/language-reference.xml b/doc/user/language-reference.xml
index 2115197c7dd927d0983d9dc8c48187f72dc14455..5163f54e41c8a9898efba6480d849b58c55f85aa 100644
--- a/doc/user/language-reference.xml
+++ b/doc/user/language-reference.xml
@@ -16,96 +16,303 @@
   Unlike many other key/value servers, Tarantool uses
   different TCP ports and client/server protocols for data
   manipulation and administrative statements.
-  On start up, the server connects to three TCP ports:
+  On start up, the server can connect to up to five TCP ports:
   <itemizedlist>
     <listitem><para>
       Read/write data port, to handle INSERTs, UPDATEs,
-      DELETEs and SELECTs.
+      DELETEs and SELECTs. This port speaks the native
+      Tarantool protocol, and provides full access to the server.
       </para>
       <para>
       The default value of the port is <literal>33013</literal>,
-      as defined in <emphasis>primary_port</emphasis>
+      as defined in <olink targetptr="primary_port"/>
       configuration option.
     </para></listitem>
     <listitem><para>
       Read only port, which only accepts SELECTs, port number
       <literal>33014</literal>, as defined in
-      <emphasis>secondary_port</emphasis> configuration option.
+      <olink targetptr="secondary_port"/> configuration option.
     </para></listitem>
     <listitem><para>
       Administrative port, which defaults to <literal>33015</literal>,
-      and is defined in <emphasis>admin_port</emphasis>
+      and is defined in <olink targetptr="admin_port"/>
       configuration option.
     </para></listitem>
+    <listitem><para>Replication port (see <olink
+        targetptr="replication_port"/>), by default set to
+        <literal>33016</literal>, used to send updates to
+        replicas. Replication is optional, and if this port is not
+        set in the option file, the corresponding server process
+        is not started.
+      </para></listitem>
+    <listitem><para>Memcached port. Optional, read-write data port
+      that speaks Memcached protocol. This port is off by default.
+    </para></listitem>
   </itemizedlist>
-
-  The data protocol is binary; a complete description is
-  provided in doc/box-protocol.txt.
-
-</para>
-<para>
-  separation of ports is done in part to allow system
-  administrators to secure the read/write port or the
-  administrative port. The values of read-only, read-write and
-  administrative ports can be specified in the configuration file.
-</para>
-<para>
-  How command line client understands what port to use.
+  In absence of authentication, this approach allows system
+  administrators to restrict access to read/write or
+  administrative ports. The client, however, has to be aware of
+  the separation, and <command>tarantool</command> command line
+  client automatically selects the correct port for you
+  with help of a simple regular expression. SELECTs, UPDATEs,
+  INSERTs and DELETEs are sent to the primary port, whereas SHOW,
+  RELOAD, SAVE and others -- to the administrative port.
 </para>
 
-<!--
 <section>
+  <title>Data manipulation</title>
 
-  <title>Data console</title>
-
-  <para>The default data port is 33014. It can be set in the
-  configuration file. Data console supports asynchronous I/O.
-  Four commands are supported. For a complete protocol manual, see
-  developers guide.
+  <para>Tarantool protocol was designed with focus on asynchronous
+    I/O and easy integration with proxies. Each client
+    request starts with a simple binary header, containing three
+    fields: request type, length, and a numeric id.
+  </para>
+  <para>
+    Four basic request types are supported: INSERT, UPDATE, DELETE
+    and SELECT. The mandatory length, present in the header,
+    allows to simplify client or proxy I/O.
+    The server response to a request always carries in its header
+    the same command type and request id.
+    The id makes it possible to always match request to a
+    response, even if the latter arrived out of order.
+  </para>
+  <para>Request type defines the format of the payload.
+    INSERTs and DELETEs can only be made by the primary key, so
+    an index id and key value are always present in these requests.
+    SELECTs and UPDATEs can use secondary keys. UPDATE only needs to
+    list the fields that are actually changed. With this one
+    exception, all commands operate on whole tuple(s).
+  </para>
+  <para>Unless implementing a client driver, one needn't
+    concern him or her self with the complications of the binary
+    protocol. <olink targetptr="connectors">Language-specific
+    drivers</olink> provide a friendly way to store domain
+    language data structures in Tarantool, and the command line
+    client supports a subset of the standard SQL.
+    A complete description of both, the binary protocol and
+    the supported SQL, is maintained in annotated Backus-Naur
+    form in the source tree: please see
+    <link xlink:href="https://github.com/mailru/tarantool/blob/master/doc/box-protocol.txt"><filename>doc/box-protocol.txt</filename></link>
+    and
+    <link xlink:href="https://github.com/mailru/tarantool/blob/master/doc/sql.txt"><filename>doc/sql.txt</filename></link> respectively.
   </para>
+  <section>
+    <title>Memcached protocol</title>
+      <para>If full access to Tarantool functionality is not needed,
+      or there is no readily available connector for the
+      programming language in use, any existing client driver for
+      Memcached will make do as a Tarantool connector.
+      To enable Memcached protocol, turn on
+      <olink targetptr="memcached_port"/> in the option file.
+      Since Memcached has no notion of namespaces or secondary
+      indexes, this port only makes it possible to access one
+      dedicated namespace (see <olink
+      targetptr="memcached_namespace"/>) via its primary key.
+      Unless tuple expiration is enabled with <olink
+      targetptr="memcached_expire"/>, TTL part of the message is
+      stored but ignored.
+  </para>
+  </section>
+</section>
 
+<section>
+  <title>Writing stored procedures in Lua</title>
+  <para>
+    Lua.
+  </para>
 </section>
 
 
--->
 <section>
   <title>Administrative console</title>
   <para>
-    The administrative console uses a simple text protocol, so you
-    can connect to it using any <command>telnet</command> client,
-    or a tool like <command>rlwrap</command>, if access to
-    readline features is desired. Additionally, Tarantool features
-    its own Python-based client, located in directory <filename>test</filename>, <command>tarantool</command>.
-    Tarantool replies to administrative command in YAML.
+    The administrative console uses a simple text protocol.
+    All commands are case-insensitive.
+    You can connect to the administrative port using any
+    <command>telnet</command> client, or a tool like
+    <command>rlwrap</command>, if access to readline features is
+    desired. Additionally, <command>tarantool</command>, the
+    SQL-capable command line client,
+    understands all administrative statements
+    and automatically directs them to the administrative port.
+    The server response to an administrative command, even though
+    is always in plain text, can be quite complex.
+    It is encoded using YAML markup to simplify automated parsing.
   </para>
+  <para>To learn about all supported administrative commands, you
+    can type <emphasis role="tntadmin">help</emphasis> in the
+    administrative console. A reference description also follows
+    below:</para>
+
+  <variablelist>
+
+    <varlistentry>
+      <term xml:id="save-snapshot" xreflabel="SAVE SNAPSHOT">
+        <emphasis role="tntadmin">save snapshot</emphasis>
+      </term>
+      <listitem><para>
+        Take a snapshot of all data and store it in
+        <filename><olink
+        targetptr="snap_dir"/>/&lt;latest-lsn&gt;.snap</filename>.
+        To take a snapshot, Tarantool forks and quickly
+        <function>munmap(2)</function>s all memory except one that
+        stores tuples. Since all modern operating systems support
+        virtual memory copy-on-write, this effectively creates a
+        consistent snapshot of all tuples in the child process,
+        which is then written to disk tuple by tuple. Since a
+        snapshot is written sequentially, you can expect a very
+        high write performance (averaging to 70MB/second on modern
+        disks), which means an average database instance gets
+        saved in a matter of minutes.  Note, that as long as there
+        are any changes to the parent memory through concurrent
+        updates, there are going to be page splits, and therefore
+        you need to have some extra free memory to run this
+        command. 15%-30% of <olink targetptr="slab_alloc_arena"/>
+        is, on average, sufficient.
+      </para></listitem>
+    </varlistentry>
+
+
+    <varlistentry>
+      <term xml:id="reload-configuration" xreflabel="RELOAD CONFIGURATION">
+        <emphasis role="tntadmin">reload configuration</emphasis>
+      </term>
+      <listitem><para>
+        Re-read the configuration file. If the file contains
+        changes to dynamic parameters, update the runtime
+        settings. If configuration syntax is incorrect, or a
+        read-only parameter is changed, produce an error and do
+        nothing.
+      </para></listitem>
+    </varlistentry>
+
+    <varlistentry>
+      <term xml:id="show-configuration" xreflabel="SHOW CONFIGURATION">
+        <emphasis role="tntadmin">show configuration</emphasis>
+      </term>
+      <listitem><para>
+        Show the current settings. Displays all settings,
+        including those that have default values and thus are not
+        necessarily present in the configuration file.
+      </para></listitem>
+    </varlistentry>
+
+    <varlistentry>
+      <term xml:id="show-info" xreflabel="SHOW INFO">
+        <emphasis role="tntadmin">show info</emphasis>
+      </term>
+      <listitem><para>
+<programlisting>
+tarantool> show info
+---
+info:
+  version: "1.4.0-30-ge500b95"
+  uptime: 441524
+  pid: 16180
+  wal_writer_pid: 16182
+  lsn: 15481913304
+  recovery_lag: 0.000
+  recovery_last_update: 1306964594.980
+  status: primary
+</programlisting>
+      </para>
+      <para>
+        <emphasis role="strong">recovery_lag</emphasis> holds the
+        difference (in seconds) between the current time on the
+        machine (wall clock time) and the time stamp of the last
+        applied record.  In replication setup, this difference can
+        indicate the delay taking place before a change is
+        applied to a replica.
+      </para>
+      <para>
+        <emphasis role="strong">recovery_last_update</emphasis> is
+        the wall clock time of the last change recorded in the
+        write ahead log.
+      </para>
+      <para>
+        <emphasis role="strong">status</emphasis> is
+        either "primary" or "hot_standby/&lt;hostname&gt;".
+      </para>
+
+      </listitem>
+    </varlistentry>
+
+    <varlistentry>
+      <term xml:id="show-stat" xreflabel="SHOW STAT">
+        <emphasis role="tntadmin">show stat</emphasis>
+      </term>
+      <listitem><para>
+<programlisting>
+tarantool> show stat
+---
+statistics:
+  INSERT:        { rps:  139  , total:  48207694    }
+  SELECT_LIMIT:  { rps:  0    , total:  0           }
+  SELECT:        { rps:  1246 , total:  388322317   }
+  UPDATE_FIELDS: { rps:  1874 , total:  743350520   }
+  DELETE:        { rps:  147  , total:  48902544    }
+</programlisting>
+      </para></listitem>
+    </varlistentry>
+
+    <varlistentry>
+      <term xml:id="show-slab" xreflabel="SHOW SLAB">
+        <emphasis role="tntadmin">show slab</emphasis>
+      </term>
+      <listitem>
+        <para>
+          Show the statistics of the slab allocator. The slab
+          allocator is the main allocator used to store tuples.
+          This can be used to monitor the total memory use and
+          memory fragmentation.
+        </para>
+        <para>
+          <emphasis role="strong">items_used</emphasis> contains
+          the % of <olink targetptr="slab_alloc_arena"/> already
+          used to store tuples.
+        </para>
+        <para>
+          <emphasis role="strong">arena_used</emphasis> contains
+          the % of <olink targetptr="slab_alloc_arena"/> that is
+          already distributed to the slab allocator.
+        </para>
+      </listitem>
+    </varlistentry>
+
+    <varlistentry>
+      <term xml:id="show-palloc" xreflabel="SHOW PALLOC">
+        <emphasis role="tntadmin">show palloc</emphasis>
+      </term>
+      <listitem><para>
+        A pool allocator is used for temporary memory, when
+        serving client requests. Every fiber has an own
+        pool. Shows the current state of pools of all fibers.
+      </para></listitem>
+    </varlistentry>
+
+    <varlistentry>
+      <term xml:id="save-coredump" xreflabel="SAVE COREDUMP">
+        <emphasis role="tntadmin">SAVE COREDUMP</emphasis>
+      </term>
+      <listitem><para>
+        Fork and dump a core. Since Tarantool stores all tuples
+        in memory, it can take some time. Mainly useful for
+        debugging.
+      </para></listitem>
+    </varlistentry>
+
+    <varlistentry>
+      <term xml:id="show-fiber" xreflabel="SHOW FIBER">
+        <emphasis role="tntadmin">show fiber</emphasis>
+      </term>
+      <listitem><para>
+        Show all running fibers, with their stack.
+        Mainly useful for debugging.
+      </para></listitem>
+    </varlistentry>
+
+  </variablelist>
 
-  <para><emphasis xml:id="reload-configuration" xreflabel="RELOAD CONFIGURATION" role="tntadmin">reload
-  configuration</emphasis> Re-read the configuration file. If the
-  file contains changes to dynamic parameters, update the runtime
-  settings. If configuration syntax is incorrect, or a read-only parameter is    changed, produce an error and do nothing.</para>
-  <para><emphasis xml:id="show-configuration" xreflabel="SHOW CONFIGURATION" role="tntadmin">show configuration</emphasis> Show the current settings. Displays all
-  settings, including those that have default values and thus are
-  not necessarily present in the configuration file.</para>
-
-  <para><emphasis xml:id="save-snapshot" xreflabel="SAVE SNAPSHOT"
-  role="tntadmin">save snaspshot</emphasis>
-  Take a snapshot of all data and store it in
-  <filename><olink targetptr="snap_dir"/>/&lt;latest-lsn&gt;.snap</filename>.
-    To take a snapshot, Tarantool forks and quickly munmap(2)s all
-    memory except one that stores tuples. Since all modern operating
-    systems support virtual memory copy-on-write, this effectively
-    creates a consistent snapshot of all tuples in the child
-    process, which is then written to disk tuple by tuple. Since a
-    snapshot is written
-    sequentially, you can expect a very high write performance
-    (averaging to 70MB/second on modern disks), which means an
-    average database instance gets saved in a matter of minutes.
-    Note, that as long as there are any changes to the parent
-    memory through concurrent updates, there are going to be page
-    splits, and therefore you need to have some extra free memory
-    to run this command. 15%-30% of <olink
-    targetptr="slab_alloc_arena"/> is, on average, sufficient.
-  </para>
 </section>
 </chapter>
 
diff --git a/doc/user/namespace.xml b/doc/user/namespace.xml
index 1310bea3104cb10ced4f5c416971772a2a37e5ea..781a800174d4db5828e319d9c3fb04ccc3b33159 100644
--- a/doc/user/namespace.xml
+++ b/doc/user/namespace.xml
@@ -1,5 +1,5 @@
 <para xmlns="http://docbook.org/ns/docbook" version="5.0"
-      xmlns:xi="http://www.w3.org/2001/XInclude" 
+      xmlns:xi="http://www.w3.org/2001/XInclude"
       xmlns:xlink="http://www.w3.org/1999/xlink"
       xml:id="namespace">
 <bridgehead>Namespace settings explained</bridgehead>
@@ -59,11 +59,31 @@ a minimal storage configuration looks like below:
 <programlisting language="c">
 namespace[0].enabled = 1
 namespace[0].index[0].type = HASH
+namespace[0].index[0].unique = 1
 namespace[0].index[0].key_field[0].fieldno = 0
 namespace[0].index[0].key_field[0].type = NUM64
 </programlisting>
-Other namespace properties are set in the same way.
-When defining a namespace, please be aware of these restrictions:
+The parameters listed above are mandatory. Other namespace
+properties are set in the same way.
+An alternative syntax, mainly useful when defining large namespaces, exists:
+<programlisting language="c">
+namespace[0] = {
+    enabled = 1,
+    index = [
+        {
+            type = HASH,
+            key_field = [
+                {
+                    fieldno = 0,
+                    type = NUM64
+                }
+            ]
+        }
+    ]
+}
+</programlisting>
+When defining a namespace,
+please be aware of these restrictions:
 <itemizedlist>
   <listitem><simpara>at least one namespace must be
     configured,</simpara></listitem>
@@ -73,7 +93,7 @@ When defining a namespace, please be aware of these restrictions:
   <listitem><simpara>"unique" property doesn't have a default, and
     must be set explicitly,
   </simpara></listitem>
-  <listitem><simpara>namespace configuration can not be changed 
+  <listitem><simpara>namespace configuration can not be changed
     dynamically, currently you need to restart the server even to
     disable or enable a namespace,
   </simpara></listitem>
diff --git a/doc/user/preface.xml b/doc/user/preface.xml
index 34c7bd612fdca285a856eceacdf356713b31edcb..09b6362682ebb1e06a9afe1224ae526ec0396d9c 100644
--- a/doc/user/preface.xml
+++ b/doc/user/preface.xml
@@ -74,7 +74,8 @@
     one of the leading Russian web content providers. At Mail.Ru,
     the sowtware serves the <quote>hottest</quote>
     data, such as online users and their sessions, online application
-    properties, the map between users and their serving shards.
+    properties, mapping between users and their serving shards,
+    and so on.
   </para>
 
   <para>
@@ -136,11 +137,11 @@
   </para>
   <para>
     <emphasis role="strong">Caution:</emphasis> To prevent spam, Launchpad
-    mailing list software silently drops all mail sent by
-    unregistered users. Registration also
-    allows you to report bug and create feature requests.
+    mailing list software silently drops all mail sent from
+    non-registered email addresses. Launchpad registration also
+    allows you to report bugs and create feature requests.
     You can always check whether or not your mail has been
-    delivered in the public mailing list archive, <link
+    delivered to the mailing list in the public list archive, <link
     xlink:href="https://lists.launchpad.net/tarantool-developers"/>.
   </para>
 </section>
diff --git a/doc/user/proctitle.xml b/doc/user/proctitle.xml
new file mode 100644
index 0000000000000000000000000000000000000000..23be03ca579c40af3de1405af5036f46f19011af
--- /dev/null
+++ b/doc/user/proctitle.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE book [
+<!ENTITY % tnt SYSTEM "../tnt.ent">
+%tnt;
+]>
+<appendix xmlns="http://docbook.org/ns/docbook" version="5.0"
+      xmlns:xlink="http://www.w3.org/1999/xlink"
+      xml:id="proctitle">
+
+<title>Server process titles</title>
+
+<para>
+  Linux and FreeBSD operating systems allow a running process to
+  modify its title, which otherwise contains the program name.
+  Tarantool uses this feature to aid to needs of system
+  administration, such as figuring out what services are running
+  on a host, TCP/IP ports in use, et&#160;cetera.
+</para>
+<para>
+  Tarantool process title follows the following naming scheme:
+  <command><replaceable>program_name</replaceable>: <replaceable>role</replaceable>[@<olink targetptr="custom_proc_title"/>] [ports in use]</command>
+</para>
+<para>
+  <emphasis role="strong">program_name</emphasis> is typically
+  <command>tarantool_box</command>. The role can be one of the
+  following:
+  <itemizedlist>
+    <listitem><para>
+      <emphasis role="strong">primary</emphasis> -- the master node,
+    </para></listitem>
+    <listitem><para>
+      <emphasis role="strong">replica/<constant>IP</constant>:<constant>port</constant></emphasis> -- a replication node,
+    </para></listitem>
+    <listitem><para>
+      <emphasis role="strong">wal_writer</emphasis> -- a write
+      ahead log management process (always pairs up with the main
+      process, be it primary or replica).
+    </para></listitem>
+    <listitem><para>
+      <emphasis role="strong">replication_server </emphasis>--
+      runs only if <olink targetptr="replication_port"/> is set,
+      accepts connections on this port and creates a 
+    </para></listitem>
+    <listitem><para>
+      <emphasis role="strong">replication_relay </emphasis>-- a
+      process that servers a single replication connection.
+    </para></listitem>
+  </itemizedlist>
+  Possible port names are: <quote>pri</quote> for
+  <olink targetptr="primary_port"/>, <quote>sec</quote> for <olink
+  targetptr="secondary_port"/>, <quote>adm</quote> for <olink
+  targetptr="admin_port"/> and <quote>memcached</quote> for <olink
+  targetptr="memcached_port"/>.
+</para>
+<para>
+  For example:
+  <itemizedlist>
+    <listitem><para>
+      <command>tarantool_box: primary pri:50000 sec:50001 adm:50002</command>
+    </para></listitem>
+    <listitem><para>
+      <command>tarantool_box: primary@infobox pri:15013 sec:15523 adm:10012</command>
+    </para></listitem>
+    <listitem><para>
+      <command>tarantool_box: wal_writer</command>
+    </para></listitem>
+  </itemizedlist>
+</para>
+</appendix>
+
+<!--
+vim: tw=66 syntax=docbk
+vim: spell spelllang=en_us
+-->
diff --git a/doc/user/replication.xml b/doc/user/replication.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ffa5a607dac466754e8a49724191fbe9821d2e6e
--- /dev/null
+++ b/doc/user/replication.xml
@@ -0,0 +1,164 @@
+<!DOCTYPE book [
+<!ENTITY % tnt SYSTEM "../tnt.ent">
+%tnt;
+]>
+<chapter xmlns="http://docbook.org/ns/docbook" version="5.0"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xml:id="replication">
+
+<title>Replication</title>
+<blockquote><para>
+  To set up replication, it's necessary to prepare the master,
+  configure a replica, and establish procedures for recovery from
+  a degraded state.
+</para></blockquote>
+
+<section>
+  <title>Setting up the master</title>
+  <para>
+    To prepare the master for connections from replica, it's only
+    necessary to enable <olink targetptr="replication_port"/> in
+    the configuration file. An example configuration file can be
+    found in <link
+    xlink:href="https://github.com/mailru/tarantool/blob/master/test/box_replication/tarantool.cfg"><filename>test/box_replication/tarantool.cfg</filename></link>. A master with enabled replication_port can accept connections
+    from as many replicas as necessary on that port. Each replica
+    has its own replication state.
+  </para>
+</section>
+<section>
+  <title>Setting up a replica</title>
+  <para>
+    Replica gets all updates from the master by simply shipping
+    the write ahead log (WAL) and applying it.
+    Each record in the WAL has a log sequence number
+    (LSN), operation type (INSERT, UPDATE, DELETE) and a
+    timestamp.
+  </para>
+  <para>
+    For replication to work correctly, the latest LSN
+    on the replica must match or fall behind the latest LSN
+    on the master. If the replica has its own updates,
+    this leads to it getting out of sync, since
+    updates from the master having identical LSNs are
+    not applied. Indeed, if replication is on, Tarantool
+    does not accept updates, even on its <olink
+    targetptr="primary_port"/>.
+  </para>
+  <para>
+    Tarantool always requires a valid snapshot file to boot from:
+    it should be prepared with <olink targetptr="init-storage-option"/> option    or copied from the master.
+  </para>
+  <para>
+    To start replication, configure <olink
+    targetptr="replication_source_port"/> and <olink
+    targetptr="replication_source_ipaddr"/>.
+    Other parameters can also be changed, but existing namespaces and
+    their primary keys on the replica must be identical to ones on the
+    master.
+  </para>
+  <para>
+    Once connected to the master, the replica requests all changes
+    that happened after the latest local LSN. It is therefore
+    necessary to keep WAL files on the master host as long as
+    there are replicas that haven't applied them yet.  An example
+    configuration can be found in <link
+    xlink:href="https://github.com/mailru/tarantool/blob/master/test/box_replication/tarantool_slave.cfg"><filename>test/box_replication/tarantool_slave.cfg</filename></link>.
+  </para>
+  <para>
+    In absence of WALs, a replica can be "seeded" at any time
+    with a snapshot file, manually copied from the master.
+  </para>
+  <note><simpara>
+    Replication parameters are "dynamic", which allows the
+    replica to become a master and vice versa with help of
+    <olink targetptr="reload-configuration"/> statement.
+  </simpara></note>
+
+</section>
+<section>
+  <title>Recovering from a degraded state</title>
+  <para>
+    "Degraded state" is a situation when the master becomes
+    unavailable -- either due to hardware or network failure, or a
+    programming bug. There is no reliable way for a replica to detect
+    that the master is gone for all, since sources of failure and
+    replication environments vary significantly.
+  </para>
+  <para>
+    A separate monitoring script (or scripts, if decision making
+    quorum is desirable) is necessary to detect a master failure.
+    Such script would typically try to update a tuple in an
+    auxiliary namespace on the master, and raise alarm if a
+    network or disk error persists longer than is acceptable.
+  </para>
+  <para>
+    When a master failure is detected, the following needs
+    to be done:
+    <itemizedlist>
+      <listitem>
+        <para>
+          First and foremost, make sure that the master does not
+          accepts updates. This is necessary to prevent the
+          situation when, should the master failure end up being
+          transient, some updates still go to the master, while
+          others already end up on the replica.
+        </para>
+        <para>
+          If the master is available, the easiest way to turn
+          on read-only mode is to turn Tarantool into a replica of
+          itself. This can be done by setting master's <olink
+          targetptr="replication_source_ipaddr"/> and <olink
+          targetptr="replication_source_port"/> to point to self.
+        </para>
+        <para>
+          If the master is not available, best bet is to log into
+          the machine and kill the server, or change the
+          machine's network configuration (DNS, IP address).
+        </para>
+        <para>
+          If the machine is not available, it's perhaps prudent
+          to power it off.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Record the replica's LSN, by issuing <olink
+          targetptr="show-info"/>. This LSN may prove useful if
+          there are updates on the master that never reached
+          the replica.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Propagate the replica to become a master. This is done
+          by setting <olink targetptr="replication_source_port"/>
+          on replica to <constant>0</constant>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Change the application configuration to point to the new
+          master. This can be done either by changing the
+          application's internal routing table, or by setting up
+          old master's IP address on the new master's machine, or
+          using some other approach.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Recover the old master. If there are updates that didn't
+          make it to the new master, they have to be applied
+          manually. You can use <olink targetptr="cat-option"/>
+          option to read server logs.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </para>
+</section>
+
+</chapter>
+
+<!--
+vim: tw=66 syntax=docbk
+vim: spell spelllang=en_us
+-->
diff --git a/doc/user/target.db b/doc/user/target.db
index ff99ff1b803f520c7e643fc84b1546a807392282..96165a1666494db2645dc659e3f2ff60537752a2 100644
--- a/doc/user/target.db
+++ b/doc/user/target.db
@@ -1 +1 @@
-<div element="book" href="#tarantool-user-guide" number="" targetptr="tarantool-user-guide"><ttl>Tarantool/Box User Guide, version 1.4.1-31-g8a3bd2c</ttl><xreftext>Tarantool/Box User Guide, version 1.4.1-31-g8a3bd2c</xreftext><div element="chapter" href="#id502411" number="1"><ttl>Preface</ttl><xreftext>Chapter 1, <i>Preface</i></xreftext><div element="section" href="#preface" number="" targetptr="preface"><ttl>Tarantool/Box: an overview</ttl><xreftext>the section called “Tarantool/Box: an overview”</xreftext></div><div element="section" href="#id502454" number=""><ttl>Conventions</ttl><xreftext>the section called “Conventions”</xreftext></div><div element="section" href="#id502528" number=""><ttl>Reporting bugs</ttl><xreftext>the section called “Reporting bugs”</xreftext></div></div><div element="chapter" href="#id503666" number="2"><ttl>Getting started</ttl><xreftext>Chapter 2, <i>Getting started</i></xreftext></div><div element="chapter" href="#id504673" number="3"><ttl>Dynamic data model</ttl><xreftext>Chapter 3, <i>Dynamic data model</i></xreftext></div><div element="chapter" href="#language-reference" number="4" targetptr="language-reference"><ttl>Language reference</ttl><xreftext>Chapter 4, <i>Language reference</i></xreftext><div element="section" href="#id502876" number=""><ttl>Administrative console</ttl><xreftext>the section called “Administrative console”</xreftext><obj element="emphasis" href="#reload-configuration" number="" targetptr="reload-configuration"><ttl>???TITLE???</ttl><xreftext>RELOAD CONFIGURATION</xreftext></obj><obj element="emphasis" href="#show-configuration" number="" targetptr="show-configuration"><ttl>???TITLE???</ttl><xreftext>SHOW CONFIGURATION</xreftext></obj><obj element="emphasis" href="#save-snapshot" number="" targetptr="save-snapshot"><ttl>???TITLE???</ttl><xreftext>SAVE SNAPSHOT</xreftext></obj></div></div><div element="chapter" href="#configuration-reference" number="5" targetptr="configuration-reference"><ttl>Configuration reference</ttl><xreftext>Chapter 5, <i>Configuration reference</i></xreftext><div element="section" href="#id504902" number=""><ttl>Command line options</ttl><xreftext>the section called “Command line options”</xreftext><obj element="listitem" href="#help-option" number="" targetptr="help-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#version-option" number="" targetptr="version-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#config-option" number="" targetptr="config-option"><ttl>???TITLE???</ttl><xreftext/></obj></div><div element="section" href="#id504287" number=""><ttl>The option file</ttl><xreftext>the section called “The option file”</xreftext><obj element="table" href="#id507149" number="5.1"><ttl>Basic parameters</ttl><xreftext>Table 5.1, “Basic parameters”</xreftext></obj><obj element="entry" href="#wal_dir" number="" targetptr="wal_dir"><ttl>???TITLE???</ttl><xreftext>wal_dir</xreftext></obj><obj element="entry" href="#snap_dir" number="" targetptr="snap_dir"><ttl>???TITLE???</ttl><xreftext>snap_dir</xreftext></obj><obj element="entry" href="#primary_port" number="" targetptr="primary_port"><ttl>???TITLE???</ttl><xreftext>primary_port</xreftext></obj><obj element="table" href="#id507511" number="5.2"><ttl>Configuring the storage</ttl><xreftext>Table 5.2, “Configuring the storage”</xreftext></obj><obj element="anchor" href="#slab_alloc_arena" number="" targetptr="slab_alloc_arena"><ttl>???TITLE???</ttl><xreftext>slab_alloc_arena</xreftext></obj><obj element="para" href="#namespace" number="" targetptr="namespace"><ttl>???TITLE???</ttl><xreftext>the section called “The option file”</xreftext></obj><obj element="table" href="#id507782" number="5.3"><ttl>Binary logging and snapshots</ttl><xreftext>Table 5.3, “Binary logging and snapshots”</xreftext></obj><obj element="table" href="#id508062" number="5.4"><ttl>Replication</ttl><xreftext>Table 5.4, “Replication”</xreftext></obj><obj element="entry" href="#replication_source_port" number="" targetptr="replication_source_port"><ttl>???TITLE???</ttl><xreftext>replication_source_port</xreftext></obj><obj element="table" href="#id508228" number="5.5"><ttl>Logging</ttl><xreftext>Table 5.5, “Logging”</xreftext></obj><obj element="table" href="#id508414" number="5.6"><ttl>Memcached protocol support</ttl><xreftext>Table 5.6, “Memcached protocol support”</xreftext></obj></div></div><div element="appendix" href="#errcode" number="A" targetptr="errcode"><ttl>List of error codes</ttl><xreftext>Appendix A, <i>List of error codes</i></xreftext><obj element="term" href="#ERR_CODE_NONMASTER" number="" targetptr="ERR_CODE_NONMASTER"><ttl>???TITLE???</ttl><xreftext>ERR_CODE_NONMASTER</xreftext></obj><obj element="term" href="#ERR_CODE_ILLEGAL_PARAMS" number="" targetptr="ERR_CODE_ILLEGAL_PARAMS"><ttl>???TITLE???</ttl><xreftext>ERR_CODE_ILLEGAL_PARAMS</xreftext></obj><obj element="term" href="#ERR_CODE_NODE_IS_RO" number="" targetptr="ERR_CODE_NODE_IS_RO"><ttl>???TITLE???</ttl><xreftext>ERR_CODE_NODE_IS_RO</xreftext></obj><obj element="term" href="#ERR_CODE_MEMORY_ISSUE" number="" targetptr="ERR_CODE_MEMORY_ISSUE"><ttl>???TITLE???</ttl><xreftext>ERR_CODE_MEMORY_ISSUE</xreftext></obj><obj element="term" href="#ERR_CODE_WAL_IO" number="" targetptr="ERR_CODE_WAL_IO"><ttl>???TITLE???</ttl><xreftext>ERR_CODE_WAL_IO</xreftext></obj><obj element="term" href="#ERR_CODE_INDEX_VIOLATION" number="" targetptr="ERR_CODE_INDEX_VIOLATION"><ttl>???TITLE???</ttl><xreftext>ERR_CODE_INDEX_VIOLATION</xreftext></obj><obj element="term" href="#ERR_CODE_NO_SUCH_NAMESPACE" number="" targetptr="ERR_CODE_NO_SUCH_NAMESPACE"><ttl>???TITLE???</ttl><xreftext>ERR_CODE_NO_SUCH_NAMESPACE</xreftext></obj><obj element="term" href="#ERR_CODE_NO_SUCH_INDEX" number="" targetptr="ERR_CODE_NO_SUCH_INDEX"><ttl>???TITLE???</ttl><xreftext>ERR_CODE_NO_SUCH_INDEX</xreftext></obj></div></div>
+<div element="book" href="#tarantool-user-guide" number="" targetptr="tarantool-user-guide"><ttl>Tarantool/Box User Guide, version 1.4.1-60-g1b45271</ttl><xreftext>Tarantool/Box User Guide, version 1.4.1-60-g1b45271</xreftext><div element="chapter" href="#id529141" number="1"><ttl>Preface</ttl><xreftext>Chapter 1, <i>Preface</i></xreftext><div element="section" href="#preface" number="" targetptr="preface"><ttl>Tarantool/Box: an overview</ttl><xreftext>the section called “Tarantool/Box: an overview”</xreftext></div><div element="section" href="#id528112" number=""><ttl>Conventions</ttl><xreftext>the section called “Conventions”</xreftext></div><div element="section" href="#id528186" number=""><ttl>Reporting bugs</ttl><xreftext>the section called “Reporting bugs”</xreftext></div></div><div element="chapter" href="#id530352" number="2"><ttl>Getting started</ttl><xreftext>Chapter 2, <i>Getting started</i></xreftext></div><div element="chapter" href="#id530367" number="3"><ttl>Dynamic data model</ttl><xreftext>Chapter 3, <i>Dynamic data model</i></xreftext></div><div element="chapter" href="#language-reference" number="4" targetptr="language-reference"><ttl>Language reference</ttl><xreftext>Chapter 4, <i>Language reference</i></xreftext><div element="section" href="#id528516" number=""><ttl>Data manipulation</ttl><xreftext>the section called “Data manipulation”</xreftext><div element="section" href="#id528567" number=""><ttl>Memcached protocol</ttl><xreftext>the section called “Memcached protocol”</xreftext></div></div><div element="section" href="#id528598" number=""><ttl>Writing stored procedures in Lua</ttl><xreftext>the section called “Writing stored procedures in Lua”</xreftext></div><div element="section" href="#id528609" number=""><ttl>Administrative console</ttl><xreftext>the section called “Administrative console”</xreftext><obj element="term" href="#save-snapshot" number="" targetptr="save-snapshot"><ttl>???TITLE???</ttl><xreftext>SAVE SNAPSHOT</xreftext></obj><obj element="term" href="#reload-configuration" number="" targetptr="reload-configuration"><ttl>???TITLE???</ttl><xreftext>RELOAD CONFIGURATION</xreftext></obj><obj element="term" href="#show-configuration" number="" targetptr="show-configuration"><ttl>???TITLE???</ttl><xreftext>SHOW CONFIGURATION</xreftext></obj><obj element="term" href="#show-info" number="" targetptr="show-info"><ttl>???TITLE???</ttl><xreftext>SHOW INFO</xreftext></obj><obj element="term" href="#show-stat" number="" targetptr="show-stat"><ttl>???TITLE???</ttl><xreftext>SHOW STAT</xreftext></obj><obj element="term" href="#show-slab" number="" targetptr="show-slab"><ttl>???TITLE???</ttl><xreftext>SHOW SLAB</xreftext></obj><obj element="term" href="#show-palloc" number="" targetptr="show-palloc"><ttl>???TITLE???</ttl><xreftext>SHOW PALLOC</xreftext></obj><obj element="term" href="#save-coredump" number="" targetptr="save-coredump"><ttl>???TITLE???</ttl><xreftext>SAVE COREDUMP</xreftext></obj><obj element="term" href="#show-fiber" number="" targetptr="show-fiber"><ttl>???TITLE???</ttl><xreftext>SHOW FIBER</xreftext></obj></div></div><div element="chapter" href="#replication" number="5" targetptr="replication"><ttl>Replication</ttl><xreftext>Chapter 5, <i>Replication</i></xreftext><div element="section" href="#id530956" number=""><ttl>Setting up the master</ttl><xreftext>the section called “Setting up the master”</xreftext></div><div element="section" href="#id530980" number=""><ttl>Setting up a replica</ttl><xreftext>the section called “Setting up a replica”</xreftext></div><div element="section" href="#id531067" number=""><ttl>Recovering from a degraded state</ttl><xreftext>the section called “Recovering from a degraded state”</xreftext></div></div><div element="chapter" href="#configuration-reference" number="6" targetptr="configuration-reference"><ttl>Configuration reference</ttl><xreftext>Chapter 6, <i>Configuration reference</i></xreftext><div element="section" href="#id533750" number=""><ttl>Command line options</ttl><xreftext>the section called “Command line options”</xreftext><obj element="listitem" href="#help-option" number="" targetptr="help-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#version-option" number="" targetptr="version-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#config-option" number="" targetptr="config-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="option" href="#init-storage-option" number="" targetptr="init-storage-option"><ttl>???TITLE???</ttl><xreftext>--init-storage</xreftext></obj><obj element="option" href="#cat-option" number="" targetptr="cat-option"><ttl>???TITLE???</ttl><xreftext>--cat</xreftext></obj></div><div element="section" href="#option-file" number="" targetptr="option-file"><ttl>The option file</ttl><xreftext>option file</xreftext><obj element="table" href="#id533835" number="6.1"><ttl>Basic parameters</ttl><xreftext>Table 6.1, “Basic parameters”</xreftext></obj><obj element="entry" href="#wal_dir" number="" targetptr="wal_dir"><ttl>???TITLE???</ttl><xreftext>wal_dir</xreftext></obj><obj element="entry" href="#snap_dir" number="" targetptr="snap_dir"><ttl>???TITLE???</ttl><xreftext>snap_dir</xreftext></obj><obj element="entry" href="#primary_port" number="" targetptr="primary_port"><ttl>???TITLE???</ttl><xreftext>primary_port</xreftext></obj><obj element="entry" href="#secondary_port" number="" targetptr="secondary_port"><ttl>???TITLE???</ttl><xreftext>secondary_port</xreftext></obj><obj element="entry" href="#admin_port" number="" targetptr="admin_port"><ttl>???TITLE???</ttl><xreftext>admin_port</xreftext></obj><obj element="entry" href="#custom_proc_title" number="" targetptr="custom_proc_title"><ttl>???TITLE???</ttl><xreftext>custom_proc_title</xreftext></obj><obj element="table" href="#id534240" number="6.2"><ttl>Configuring the storage</ttl><xreftext>Table 6.2, “Configuring the storage”</xreftext></obj><obj element="anchor" href="#slab_alloc_arena" number="" targetptr="slab_alloc_arena"><ttl>???TITLE???</ttl><xreftext>slab_alloc_arena</xreftext></obj><obj element="para" href="#namespace" number="" targetptr="namespace"><ttl>???TITLE???</ttl><xreftext>the section called “The option file”</xreftext></obj><obj element="table" href="#id534541" number="6.3"><ttl>Binary logging and snapshots</ttl><xreftext>Table 6.3, “Binary logging and snapshots”</xreftext></obj><obj element="table" href="#id534803" number="6.4"><ttl>Replication</ttl><xreftext>Table 6.4, “Replication”</xreftext></obj><obj element="entry" href="#replication_port" number="" targetptr="replication_port"><ttl>???TITLE???</ttl><xreftext>replication_port</xreftext></obj><obj element="entry" href="#replication_source_port" number="" targetptr="replication_source_port"><ttl>???TITLE???</ttl><xreftext>replication_source_port</xreftext></obj><obj element="entry" href="#replication_source_ipaddr" number="" targetptr="replication_source_ipaddr"><ttl>???TITLE???</ttl><xreftext>replication_source_ipaddr</xreftext></obj><obj element="table" href="#id534997" number="6.5"><ttl>Networking</ttl><xreftext>Table 6.5, “Networking”</xreftext></obj><obj element="table" href="#id535153" number="6.6"><ttl>Logging</ttl><xreftext>Table 6.6, “Logging”</xreftext></obj><obj element="table" href="#id535361" number="6.7"><ttl>Memcached protocol support</ttl><xreftext>Table 6.7, “Memcached protocol support”</xreftext></obj><obj element="anchor" href="#memcached_port" number="" targetptr="memcached_port"><ttl>???TITLE???</ttl><xreftext>memcached_port</xreftext></obj><obj element="anchor" href="#memcached_namespace" number="" targetptr="memcached_namespace"><ttl>???TITLE???</ttl><xreftext>memcached_namespace</xreftext></obj><obj element="anchor" href="#memcached_expire" number="" targetptr="memcached_expire"><ttl>???TITLE???</ttl><xreftext>memcached_expire</xreftext></obj></div></div><div element="chapter" href="#connectors" number="7" targetptr="connectors"><ttl>Connectors</ttl><xreftext>Chapter 7, <i>Connectors</i></xreftext><div element="section" href="#id533584" number=""><ttl>C</ttl><xreftext>the section called “C”</xreftext></div><div element="section" href="#id530738" number=""><ttl>Perl</ttl><xreftext>the section called “Perl”</xreftext></div><div element="section" href="#id532206" number=""><ttl>PHP</ttl><xreftext>the section called “PHP”</xreftext></div><div element="section" href="#id532494" number=""><ttl>Python</ttl><xreftext>the section called “Python”</xreftext></div><div element="section" href="#id532505" number=""><ttl>Ruby</ttl><xreftext>the section called “Ruby”</xreftext><obj element="example" href="#id532546" number="7.1"><ttl>userbox.rb</ttl><xreftext>Example 7.1, “userbox.rb”</xreftext></obj></div></div><div element="appendix" href="#proctitle" number="A" targetptr="proctitle"><ttl>Server process titles</ttl><xreftext>Appendix A, <i>Server process titles</i></xreftext></div><div element="appendix" href="#errcode" number="B" targetptr="errcode"><ttl>List of error codes</ttl><xreftext>Appendix B, <i>List of error codes</i></xreftext><obj element="term" href="#ER_NONMASTER" number="" targetptr="ER_NONMASTER"><ttl>???TITLE???</ttl><xreftext>ER_NONMASTER</xreftext></obj><obj element="term" href="#ER_ILLEGAL_PARAMS" number="" targetptr="ER_ILLEGAL_PARAMS"><ttl>???TITLE???</ttl><xreftext>ER_ILLEGAL_PARAMS</xreftext></obj><obj element="term" href="#ER_TUPLE_IS_RO" number="" targetptr="ER_TUPLE_IS_RO"><ttl>???TITLE???</ttl><xreftext>ER_TUPLE_IS_RO</xreftext></obj><obj element="term" href="#ER_MEMORY_ISSUE" number="" targetptr="ER_MEMORY_ISSUE"><ttl>???TITLE???</ttl><xreftext>ER_MEMORY_ISSUE</xreftext></obj><obj element="term" href="#ER_WAL_IO" number="" targetptr="ER_WAL_IO"><ttl>???TITLE???</ttl><xreftext>ER_WAL_IO</xreftext></obj><obj element="term" href="#ER_INDEX_VIOLATION" number="" targetptr="ER_INDEX_VIOLATION"><ttl>???TITLE???</ttl><xreftext>ER_INDEX_VIOLATION</xreftext></obj><obj element="term" href="#ER_NO_SUCH_NAMESPACE" number="" targetptr="ER_NO_SUCH_NAMESPACE"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_NAMESPACE</xreftext></obj><obj element="term" href="#ER_NO_SUCH_INDEX" number="" targetptr="ER_NO_SUCH_INDEX"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_INDEX</xreftext></obj></div></div>
diff --git a/doc/user/tnt-fo.xsl b/doc/user/tnt-fo.xsl
new file mode 100644
index 0000000000000000000000000000000000000000..6952f3bfca3077cdead3a217cddc2168f961b70a
--- /dev/null
+++ b/doc/user/tnt-fo.xsl
@@ -0,0 +1,14 @@
+<?xml version='1.0'?> 
+<xsl:stylesheet  
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
+    xmlns:xslthl="http://xslthl.sf.net">
+
+<xsl:import href="http://docbook.sourceforge.net/release/xsl-ns/current/fo/docbook.xsl"/> 
+
+<xsl:param name="generate.toc" select="'book toc'"/>
+<xsl:param name="fop1.extentions">1</xsl:param>
+<xsl:param name="paper.type">A4</xsl:param>
+<xsl:param name="highlight.source" select="1"/>
+<xsl:param name="highlight.xslthl.config">file:////usr/share/xml/docbook/stylesheet/docbook-xsl-ns/highlighting/xslthl-config.xml</xsl:param>
+
+</xsl:stylesheet>
diff --git a/doc/user/tnt-html.xsl b/doc/user/tnt-html.xsl
index dd3ce1cbb9482e2c0e1409b7bcb60d88b56add1e..e1edda21310f5920444787276722d153b1ee8a57 100644
--- a/doc/user/tnt-html.xsl
+++ b/doc/user/tnt-html.xsl
@@ -4,12 +4,29 @@
     xmlns:xslthl="http://xslthl.sf.net">
 
 <xsl:import href="http://docbook.sourceforge.net/release/xsl-ns/current/html/docbook.xsl"/> 
-<xsl:import href="html-highlight.xsl"/>
 
+<xsl:import href="html-highlight.xsl"/>
 
 <xsl:param name="generate.toc" select="'book toc'"/>
 <xsl:param name="html.stylesheet" select="'tnt.css'"/> 
 <xsl:param name="highlight.source" select="1"/>
 <xsl:param name="highlight.xslthl.config">file:////usr/share/xml/docbook/stylesheet/docbook-xsl-ns/highlighting/xslthl-config.xml</xsl:param>
 
+<!-- Add Google Analytics to the generated html-->
+
+<xsl:template name="user.head.content">
+  <script type="text/javascript">
+
+    var _gaq = _gaq || [];
+    _gaq.push(['_setAccount', 'UA-22120502-1']);
+    _gaq.push(['_trackPageview']);
+
+    (function() {
+      var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+      ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+      var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+    })();
+
+  </script>
+</xsl:template>
 </xsl:stylesheet>
diff --git a/doc/user/tutorial.xml b/doc/user/tutorial.xml
index f38b015635e4c815999fab38393f52052cc8527b..201be01f5a66114f60cd95d3e02c35d6d6395067 100644
--- a/doc/user/tutorial.xml
+++ b/doc/user/tutorial.xml
@@ -41,7 +41,7 @@
 <para>
   After download, unpack the binary package, an own directory will be created:
   <programlisting>
-  <prompt>$ </prompt><command>tar</command> <option>zxvf</option> <filename>package-name.tar.gz</filename></programlisting>
+  <prompt>$ </prompt><command>tar</command> <option>zxvf</option> <filename><replaceable><replaceable>package-name</replaceable></replaceable>.tar.gz</filename></programlisting>
   The binary download contains just two subdirectories:
   <filename>bin</filename> and <filename>doc</filename>.
   The server, by default, looks for its configuration file in
@@ -50,7 +50,7 @@
   <filename>bin</filename>, thus the server can be started
   right out of it:
   <programlisting>
-  <prompt>$ </prompt><command>cd package-name/bin &amp;&amp; ./tarantool_box</command>
+  <prompt>$ </prompt><command>cd <replaceable>package-name</replaceable>/bin &amp;&amp; ./tarantool_box</command>
   ...
   1301424353.416 3459 104/33013/acceptor _ I> I am primary
   1301424353.416 3459 1/sched _ I> initialized</programlisting>
@@ -61,7 +61,7 @@
   Once the server is started, you can connect to it and issue
   queries using a command line client implemented in Python:
   <programlisting>
-  <prompt>$ </prompt><command>cd package-name/bin &amp;&amp; ./tarantool</command>
+  <prompt>$ </prompt><command>cd <replaceable>package-name</replaceable>/bin &amp;&amp; ./tarantool</command>
   <prompt>tarantool> </prompt> <userinput>show info</userinput>
   <computeroutput>
   ---
@@ -82,7 +82,7 @@
   necessary: configuration and build. The easiest way to configure
   a source directory with CMake is by requesting an in-source build:
   <programlisting>
-  <prompt>$ </prompt><command>cd</command> <filename>package-name</filename> &amp;&amp; <command>cmake</command> .</programlisting>
+  <prompt>$ </prompt><command>cd</command> <filename><replaceable>package-name</replaceable></filename> &amp;&amp; <command>cmake</command> .</programlisting>
   Upon successful configuration, CMake prints the status
   of optional features:
   <programlisting>
diff --git a/doc/user/user.xml b/doc/user/user.xml
index 02e677b59e01e1931acb5159065c31f5354aef3c..2ae51a5595c943134b669b9915ee4ff5a08947aa 100644
--- a/doc/user/user.xml
+++ b/doc/user/user.xml
@@ -12,64 +12,13 @@
 <xi:include href="tutorial.xml"/>
 <xi:include href="data-model.xml"/>
 <xi:include href="language-reference.xml"/>
-<xi:include href="configuration-reference.xml"/>
-<!--
 <xi:include href="replication.xml"/>
-<xi:include href="memcached.xml"/>
-<xi:include href="configuration.xml"/>
-<xi:include href="logging.xml"/>
+<xi:include href="configuration-reference.xml"/>
 <xi:include href="connectors.xml"/>
-<xi:include href="faq.xml"/>
--->
+<xi:include href="proctitle.xml"/>
 <xi:include href="errcode.xml"/>
 <!--
-     TOC:
-
-     Introduction
-       What it can basically do; 
-
-     Installation and Running
-       Command line options reference
- 
-     Data console and administrative console
-       Data operations
-       Administrative console operations
-         Format of response
-
-     Setting up replication
-       Master-slave replication
-
-     Support of memcached protocol
-
-     Configuration reference
-
-     Log file messages
-
-     Format of the binary log/snapshot
-
-     Tarantool command line shell
-
-     Connectors
-       C
-       Ruby 
-         Example
-       Perl
-
-     Product release cycle
-
-     Frequently Asked Questions
-       Q. What's on your roadmap?
-       Q. I found a bug. What to do?
-       What can possibly go wrong during compile:
-       as exception - old python
-       - confetti not found - explain what is going on
-       gcc compilation error: type 'VERBOSE=1 make' and
-       check out the exact error.
-
-     Appendixes:
-       Configuration parameters index
-       Server error codes
-       Glossary of terms
+<xi:include href="faq.xml"/>
 -->
 </book>
 <!--
diff --git a/include/errcode.h b/include/errcode.h
index ba7ad4e1e750e48606e7c040a8391caa2075f947..d1944d81d5395f9910441d9ee85c8a6375367c62 100644
--- a/include/errcode.h
+++ b/include/errcode.h
@@ -11,94 +11,91 @@ struct errcode_record {
 	uint8_t errflags;
 };
 
+enum { TNT_ERRMSG_MAX = 512 };
+
 /*
  * To add a new error code to Tarantool, extend this array. Please
- * try to reuse empty slots (ERR_CODE_UNUSED*), if there are any
- * left.
+ * try to reuse empty slots (ER_UNUSED*), if there are any left.
  *
  * !IMPORTANT! Currently you need to manually update the user
- * guide (doc/user/errcode.xml) and the Python command line client
- * (test/lib/sql_ast.py) with each added error code. Please don't
- * forget to do it!
+ * guide (doc/user/errcode.xml) with each added error code.
+ * Please don't forget to do it!
  */
 
 #define ERROR_CODES(_)					    \
-	/*  0 */_(ERR_CODE_OK,				0, "OK") \
-	/*  1 */_(ERR_CODE_NONMASTER,			2, "Non master connection, but it should be") \
-	/*  2 */_(ERR_CODE_ILLEGAL_PARAMS,		2, "Illegal parameters") \
-	/*  3 */_(ERR_CODE_BAD_UID,			2, "Uid is not from this storage range") \
-	/*  4 */_(ERR_CODE_NODE_IS_RO,			1, "Node is marked as read-only") \
-	/*  5 */_(ERR_CODE_NODE_IS_NOT_LOCKED,		1, "Node isn't locked") \
-	/*  6 */_(ERR_CODE_NODE_IS_LOCKED,		1, "Node is locked") \
-	/*  7 */_(ERR_CODE_MEMORY_ISSUE,		1, "Some memory issue") \
-	/*  8 */_(ERR_CODE_BAD_INTEGRITY,		2, "Bad graph integrity") \
-	/*  9 */_(ERR_CODE_UNUSED9,			0, "Unused9") \
-	/* 10 */_(ERR_CODE_UNSUPPORTED_COMMAND,		2, "Unsupported command") \
+	/*  0 */_(ER_OK,			0, "OK") \
+	/*  1 */_(ER_NONMASTER,			2, "Non master connection, but it should be") \
+	/*  2 */_(ER_ILLEGAL_PARAMS,		2, "Illegal parameters, %s") \
+	/*  3 */_(ER_BAD_UID,			2, "Uid is not from this storage range") \
+	/*  4 */_(ER_TUPLE_IS_RO,		1, "Tuple is marked as read-only") \
+	/*  5 */_(ER_TUPLE_IS_NOT_LOCKED,	1, "Tuple isn't locked") \
+	/*  6 */_(ER_TUPLE_IS_LOCKED,		1, "Tuple is locked") \
+	/*  7 */_(ER_MEMORY_ISSUE,		1, "Failed to allocate %u bytes in %s for %s") \
+	/*  8 */_(ER_BAD_INTEGRITY,		2, "Bad graph integrity") \
+	/*  9 */_(ER_UNUSED9,			0, "Unused9") \
+	/* 10 */_(ER_UNSUPPORTED_COMMAND,	2, "Unsupported command") \
 		/* silverproxy error codes */ \
-	/* 11 */_(ERR_CODE_RESERVED11,			0, "Reserved11") \
-	/* 12 */_(ERR_CODE_RESERVED12,			0, "Reserved12") \
-	/* 13 */_(ERR_CODE_RESERVED13,			0, "Reserved13") \
-	/* 14 */_(ERR_CODE_RESERVED14,			0, "Reserved14") \
-	/* 15 */_(ERR_CODE_RESERVED15,			0, "Reserved15") \
-	/* 16 */_(ERR_CODE_RESERVED16,			0, "Reserved16") \
-	/* 17 */_(ERR_CODE_RESERVED17,			0, "Reserved17") \
-	/* 18 */_(ERR_CODE_RESERVED18,			0, "Reserved18") \
-	/* 19 */_(ERR_CODE_RESERVED19,			0, "Reserved19") \
-	/* 20 */_(ERR_CODE_RESERVED20,			0, "Reserved20") \
-	/* 21 */_(ERR_CODE_RESERVED21,			0, "Reserved21") \
-	/* 22 */_(ERR_CODE_RESERVED22,			0, "Reserved22") \
-	/* 23 */_(ERR_CODE_RESERVED23,			0, "Reserved23") \
+	/* 11 */_(ER_RESERVED11,		0, "Reserved11") \
+	/* 12 */_(ER_RESERVED12,		0, "Reserved12") \
+	/* 13 */_(ER_RESERVED13,		0, "Reserved13") \
+	/* 14 */_(ER_RESERVED14,		0, "Reserved14") \
+	/* 15 */_(ER_RESERVED15,		0, "Reserved15") \
+	/* 16 */_(ER_RESERVED16,		0, "Reserved16") \
+	/* 17 */_(ER_RESERVED17,		0, "Reserved17") \
+	/* 18 */_(ER_RESERVED18,		0, "Reserved18") \
+	/* 19 */_(ER_RESERVED19,		0, "Reserved19") \
+	/* 20 */_(ER_RESERVED20,		0, "Reserved20") \
+	/* 21 */_(ER_RESERVED21,		0, "Reserved21") \
+	/* 22 */_(ER_RESERVED22,		0, "Reserved22") \
+	/* 23 */_(ER_RESERVED23,		0, "Reserved23") \
 		/* end of silverproxy error codes */ \
-	/* 24 */_(ERR_CODE_CANNOT_REGISTER,		1, "Can't register new user") \
-	/* 25 */_(ERR_CODE_UNUSED25,			0, "Unused25") \
-	/* 26 */_(ERR_CODE_CANNOT_INIT_ALERT_ID,	1, "Can't generate alert id") \
-	/* 27 */_(ERR_CODE_CANNOT_DEL,			2, "Can't del node") \
-	/* 28 */_(ERR_CODE_USER_NOT_REGISTERED,		2, "User isn't registered") \
+	/* 24 */_(ER_CANNOT_REGISTER,		1, "Can't register new user") \
+	/* 25 */_(ER_UNUSED25,			0, "Unused25") \
+	/* 26 */_(ER_CANNOT_INIT_ALERT_ID,	1, "Can't generate alert id") \
+	/* 27 */_(ER_CANNOT_DEL,		2, "Can't del node") \
+	/* 28 */_(ER_USER_NOT_REGISTERED,	2, "User isn't registered") \
 		/* silversearch error codes */ \
-	/* 29 */_(ERR_CODE_SYNTAX_ERROR,		2, "Syntax error in query") \
-	/* 30 */_(ERR_CODE_WRONG_FIELD,			2, "Unknown field") \
-	/* 31 */_(ERR_CODE_WRONG_NUMBER,		2, "Number value is out of range") \
-	/* 32 */_(ERR_CODE_DUPLICATE,			2, "Insert already existing object") \
-	/* 33 */_(ERR_CODE_UNUSED32,			0, "Unused33") \
-	/* 34 */_(ERR_CODE_UNSUPPORTED_ORDER,		2, "Can not order result") \
-	/* 35 */_(ERR_CODE_MULTIWRITE,			2, "Multiple to update/delete") \
-	/* 36 */_(ERR_CODE_NOTHING,			0, "Nothing to do (not an error)") \
-	/* 37 */_(ERR_CODE_UPDATE_ID,			2, "Id's update") \
-	/* 38 */_(ERR_CODE_WRONG_VERSION,		2, "Unsupported version of protocol") \
+	/* 29 */_(ER_SYNTAX_ERROR,		2, "Syntax error in query") \
+	/* 30 */_(ER_WRONG_FIELD,		2, "Unknown field") \
+	/* 31 */_(ER_WRONG_NUMBER,		2, "Number value is out of range") \
+	/* 32 */_(ER_DUPLICATE,			2, "Insert already existing object") \
+	/* 33 */_(ER_UNUSED32,			0, "Unused33") \
+	/* 34 */_(ER_UNSUPPORTED_ORDER,		2, "Can not order result") \
+	/* 35 */_(ER_MULTIWRITE,		2, "Multiple to update/delete") \
+	/* 36 */_(ER_NOTHING,			0, "Nothing to do (not an error)") \
+	/* 37 */_(ER_UPDATE_ID,			2, "Id's update") \
+	/* 38 */_(ER_WRONG_VERSION,		2, "Unsupported version of protocol") \
 		/* end of silversearch error codes */					\
-	/* 39 */_(ERR_CODE_WAL_IO,			2, "Failed to write to disk") \
-	/* 40 */_(ERR_CODE_UNUSED40,			0, "Unused40") \
-	/* 41 */_(ERR_CODE_UNUSED41,			0, "Unused41") \
-	/* 42 */_(ERR_CODE_UNUSED42,			0, "Unused42") \
-	/* 43 */_(ERR_CODE_UNUSED43,			0, "Unused43") \
-	/* 44 */_(ERR_CODE_UNUSED44,			0, "Unused44") \
-	/* 45 */_(ERR_CODE_UNUSED45,			0, "Unused45") \
-	/* 46 */_(ERR_CODE_UNUSED46,			0, "Unused46") \
-	/* 47 */_(ERR_CODE_UNUSED47,			0, "Unused47") \
-	/* 48 */_(ERR_CODE_UNUSED48,			0, "Unused48") \
-        /* 49 */_(ERR_CODE_NODE_NOT_FOUND,		2, "Node isn't found") \
-	/* 50 */_(ERR_CODE_UNUSED50,			0, "Unused50") \
-	/* 51 */_(ERR_CODE_UNUSED51,			0, "Unused51") \
-	/* 52 */_(ERR_CODE_UNUSED52,			0, "Unused52") \
-	/* 53 */_(ERR_CODE_UNUSED53,			0, "Unused53") \
-	/* 54 */_(ERR_CODE_UNUSED54,			0, "Unused54") \
-	/* 55 */_(ERR_CODE_NODE_FOUND,			2, "Node is found") \
-	/* 56 */_(ERR_CODE_INDEX_VIOLATION,		2, "Constraint violation") \
-	/* 57 */_(ERR_CODE_NO_SUCH_NAMESPACE,		2, "There is no such namespace") \
-	        _(ERR_CODE_NO_SUCH_INDEX,		2, "No index with the given id is defined")
+	/* 39 */_(ER_WAL_IO,			2, "Failed to write to disk") \
+	/* 40 */_(ER_UNUSED40,			0, "Unused40") \
+	/* 41 */_(ER_UNUSED41,			0, "Unused41") \
+	/* 42 */_(ER_UNUSED42,			0, "Unused42") \
+	/* 43 */_(ER_UNUSED43,			0, "Unused43") \
+	/* 44 */_(ER_UNUSED44,			0, "Unused44") \
+	/* 45 */_(ER_UNUSED45,			0, "Unused45") \
+	/* 46 */_(ER_UNUSED46,			0, "Unused46") \
+	/* 47 */_(ER_UNUSED47,			0, "Unused47") \
+	/* 48 */_(ER_UNUSED48,			0, "Unused48") \
+	/* 49 */_(ER_TUPLE_NOT_FOUND,		2, "Tuple doesn't exist") \
+	/* 50 */_(ER_UNUSED50,			0, "Unused50") \
+	/* 51 */_(ER_UNUSED51,			0, "Unused51") \
+	/* 52 */_(ER_NAMESPACE_DISABLED,	2, "Namespace %u is disabled") \
+	/* 53 */_(ER_NO_SUCH_INDEX,		2, "No index #%u is defined in namespace %u") \
+	/* 54 */_(ER_NO_SUCH_FIELD,		2, "Field %u was not found in the tuple") \
+	/* 55 */_(ER_TUPLE_FOUND,		2, "Tuple already exists") \
+	/* 56 */_(ER_INDEX_VIOLATION,		2, "Duplicate key exists in a unique index") \
+	/* 57 */_(ER_NO_SUCH_NAMESPACE,		2, "Namespace %u does not exists")
 
 
 /*
- * !IMPORTANT! Please see an instruction how to add !IMPORTANT!
- * !IMPORTANT! new errors at start of the file !IMPORTANT!
+ * !IMPORTANT! Please follow instructions at start of the file
+ * when adding new errors.
  */
 
 ENUM0(tnt_error_codes_enum, ERROR_CODES);
 extern struct errcode_record tnt_error_codes[];
 
-/**
- * Return a string representation of error name, e.g.
- * "ERR_CODE_OK".
+/** Return a string representation of error name, e.g. "ER_OK".
  */
 
 static inline const char *tnt_errcode_str(uint32_t errcode)
diff --git a/include/exception.h b/include/exception.h
new file mode 100644
index 0000000000000000000000000000000000000000..0cf698ed86cfd69c4c27ba258b7e28eb8eca51ba
--- /dev/null
+++ b/include/exception.h
@@ -0,0 +1,96 @@
+#ifndef TARANTOOL_EXCEPTION_H_INCLUDED
+#define TARANTOOL_EXCEPTION_H_INCLUDED
+/*
+ * 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.
+ */
+#import <objc/Object.h>
+#include <stdarg.h>
+#include "errcode.h"
+
+/** The base class for all exceptions.
+ *
+ * Note: implements garbage collection (see +alloc
+ * implementation).
+ */
+@interface tnt_Exception: Object {
+	@public
+		const char *file;
+		unsigned line;
+}
+
++ (id) alloc;
+@end
+
+
+/** Errors that should make it to the client.
+ */
+@interface ClientError: tnt_Exception {
+	@public
+		uint32_t errcode;
+		char errmsg[TNT_ERRMSG_MAX];
+}
+
+- (id) init: (uint32_t)errcode_, ...;
+- (id) init: (uint32_t)errcode_ args: (va_list)ap;
+@end
+
+
+/** Additionally log this error in the log file. */
+@interface LoggedError: ClientError
+- (id) init: (uint32_t)errcode, ...;
+@end
+
+
+/** A handy wrapper for ER_ILLEGAL_PARAMS, which is used very
+ * often.
+ */
+@interface IllegalParams: LoggedError
+- (id) init: (const char *)msg;
+@end
+
+
+/**
+ * A helper macro to add __FILE__ and __LINE__ information to
+ * raised exceptions.
+ *
+ * Usage:
+ *
+ * tnt_raise(tnt_Exception);
+ * tnt_raise(LoggedError, :"invalid argument %d", argno);
+ */
+#define tnt_raise(...) tnt_raise0(__VA_ARGS__)
+#define tnt_raise0(class, ...) do {					\
+	say_debug("%s at %s:%i", #class, __FILE__, __LINE__);		\
+	class *exception = [class alloc];				\
+	exception->file = __FILE__;					\
+	exception->line = __LINE__;					\
+	[exception init __VA_ARGS__];					\
+	@throw exception;						\
+} while (0)
+
+#endif /* TARANTOOL_EXCEPTION_H_INCLUDED */
diff --git a/include/exceptions.h b/include/exceptions.h
deleted file mode 100644
index f78206c65d14d836156612270f8a18cc0885a249..0000000000000000000000000000000000000000
--- a/include/exceptions.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#ifndef TARANTOOL_EXCEPTIONS_H
-#define TARANTOOL_EXCEPTIONS_H
-
-#include <objc/Object.h>
-
-@interface tnt_Exception: Object {
-	@public
-		const char *file;
-		unsigned line;
-		const char *reason;
-}
-
-+ alloc;
-
-- init:(const char *)p_file:(unsigned)p_line reason:(const char*)p_reason;
-- init:(const char *)p_file:(unsigned)p_line;
-@end
-
-#define tnt_raise(class, message) {					\
-	say_debug("tnt_raise %s at %s:%i", #class, __FILE__, __LINE__);	\
-	@throw [[class alloc] init:__FILE__:__LINE__ message];		\
-}
-
-#endif
diff --git a/include/fiber.h b/include/fiber.h
index cc84c51b9314eee065a6ebb676b222f86f906f6f..4a11eb4e988b1fe7d433bbc8c2d7dab492dfd44c 100644
--- a/include/fiber.h
+++ b/include/fiber.h
@@ -40,14 +40,21 @@
 #include <util.h>
 #include "third_party/queue.h"
 
-#include <exceptions.h>
+#include "exception.h"
 
 #define FIBER_NAME_MAXLEN 32
 
 #define FIBER_READING_INBOX 0x1
-#define FIBER_RAISE	    0x2
+/** Can this fiber be cancelled? */
+#define FIBER_CANCELLABLE   0x2
+/** Indicates that a fiber has been cancelled. */
+#define FIBER_CANCEL        0x4
 
-@interface tnt_FiberException: tnt_Exception
+/** This is thrown by fiber_* API calls when the fiber is
+ * cancelled.
+ */
+
+@interface FiberCancelException: tnt_Exception
 @end
 
 struct msg {
@@ -62,6 +69,7 @@ struct ring {
 
 struct fiber {
 	ev_io io;
+	ev_async async;
 #ifdef ENABLE_BACKTRACE
 	void *last_stack_frame;
 #endif
@@ -96,6 +104,8 @@ struct fiber {
 	char peer_name[32];
 
 	u32 flags;
+
+	struct fiber *waiter;
 };
 
 SLIST_HEAD(, fiber) fibers, zombie_fibers;
@@ -118,9 +128,7 @@ extern struct fiber *fiber;
 void fiber_init(void);
 struct fiber *fiber_create(const char *name, int fd, int inbox_size, void (*f) (void *), void *);
 void fiber_set_name(struct fiber *fiber, const char *name);
-void wait_for(int events);
 void wait_for_child(pid_t pid);
-void unwait(int events);
 void yield(void);
 void fiber_destroy_all();
 
@@ -149,7 +157,7 @@ inline static void add_iov(const void *buf, size_t len)
 	add_iov_unsafe(buf, len);
 }
 
-void add_iov_dup(void *buf, size_t len);
+void add_iov_dup(const void *buf, size_t len);
 bool write_inbox(struct fiber *recipient, struct tbuf *msg);
 int inbox_size(struct fiber *recipient);
 void wait_inbox(struct fiber *recipient);
@@ -162,7 +170,22 @@ ssize_t fiber_flush_output(void);
 void fiber_cleanup(void);
 void fiber_gc(void);
 void fiber_call(struct fiber *callee);
-void fiber_raise(struct fiber *callee);
+void fiber_wakeup(struct fiber *f);
+/** Cancel a fiber. A cancelled fiber will have
+ * tnt_FiberCancelException raised in it.
+ *
+ * A fiber can be cancelled only if it is
+ * FIBER_CANCELLABLE flag is set.
+ */
+void fiber_cancel(struct fiber *f);
+/** Check if the current fiber has been cancelled.  Raises
+ * tnt_FiberCancelException
+ */
+void fiber_testcancel(void);
+/** Make it possible or not possible to cancel the current
+ * fiber.
+ */
+void fiber_setcancelstate(bool enable);
 int fiber_connect(struct sockaddr_in *addr);
 void fiber_sleep(ev_tstamp s);
 void fiber_info(struct tbuf *out);
@@ -170,10 +193,12 @@ int set_nonblock(int sock);
 
 typedef enum fiber_server_type {
 	tcp_server,
-	udp_server
 } fiber_server_type;
 
-struct fiber *fiber_server(fiber_server_type type, int port, void (*handler) (void *), void *,
+typedef void (*fiber_server_callback)(void *);
+
+struct fiber *fiber_server(fiber_server_type type, int port,
+			   fiber_server_callback callback, void *,
 			   void (*on_bind) (void *));
 
 /**
diff --git a/include/iproto.h b/include/iproto.h
index d6f794cc56ae61911d60dc42c344a7e1748cc5e7..34d7fa69f9a1285991a8efed45ca55ee9e34a4b6 100644
--- a/include/iproto.h
+++ b/include/iproto.h
@@ -44,7 +44,6 @@ struct iproto_header_retcode {
 	uint32_t len;
 	uint32_t sync;
 	uint32_t ret_code;
-	uint8_t data[];
 } __attribute__((packed));
 
 static inline struct iproto_header *iproto(const struct tbuf *t)
@@ -52,11 +51,9 @@ static inline struct iproto_header *iproto(const struct tbuf *t)
 	return (struct iproto_header *)t->data;
 }
 
-struct iproto_interactor;
 
-struct iproto_interactor
-*iproto_interactor(uint32_t (*interact) (uint32_t msg, uint8_t *data, size_t len));
+typedef void (*iproto_callback) (uint32_t msg_code, struct tbuf *request);
 
-void iproto_interact(void *);
+void iproto_interact(iproto_callback *callback);
 
 #endif
diff --git a/core/diagnostics.m b/include/latch.h
similarity index 50%
rename from core/diagnostics.m
rename to include/latch.h
index 3266ae52a02a94a948749d66f4f810d816ccd2b8..fc723045c57730fa2e2b83cadbcc7427156fbf57 100644
--- a/core/diagnostics.m
+++ b/include/latch.h
@@ -1,3 +1,5 @@
+#ifndef TARANTOOL_LATCH_H_INCLUDED
+#define TARANTOOL_LATCH_H_INCLUDED
 /*
  * Redistribution and use in source and binary forms, with or
  * without modification, are permitted provided that the following
@@ -20,73 +22,45 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
-#include "diagnostics.h"
-#include "fiber.h"
-#include <errno.h>
-#include <string.h>
-#include <stdlib.h>
+#include <stdbool.h>
 
-static struct Error oom_error = { ENOMEM, "Out of memory" };
+struct fiber;
 
-/**
- * Allocate error on heap. Errors are expected to be rare and
- * small, thus we don't care much about allocation speed, and
- * memory fragmentation should be negligible.
+/*
+ * Internal implementation of a container for a mutex like object
+ * with similar interface. It's used boolean variable because of
+ * single threaded nature of tarantool. But it's rather simple to change
+ * this variable to a mutex object to maintain multi threaded approach.
  */
+struct tnt_latch {
+	bool locked;
 
-static struct Error *error_create(int code, const char *msg_arg)
-{
-	/* Just something large enough. */
-	const int MAX_MSGLEN = 200;
-
-	if (msg_arg == NULL)
-		msg_arg = "";
-
-	size_t msglen = strlen(msg_arg);
-	char *msg;
-
-	if (msglen > MAX_MSGLEN)
-		msglen = MAX_MSGLEN;
-
-	struct Error *error = malloc(sizeof(struct Error) + msglen + 1);
-
-	if (error == NULL)
-		return &oom_error;
-
-	msg = (char *)(error + 1);
-	strncpy(msg, msg_arg, msglen);
-	msg[msglen] = '\0';
-
-	error->code = code;
-	error->msg = msg;
-
-	return error;
-}
-
-
-static void error_destroy(struct Error *error)
-{
-	if (error != &oom_error)
-		free(error);
-}
-
+	struct fiber *owner;
+};
 
-void diag_set_error(int code, const char *message)
-{
-	if (fiber->diagnostics)
-		diag_clear();
-	fiber->diagnostics = error_create(code, message);
-}
-
-
-struct Error *diag_get_last_error()
-{
-	return fiber->diagnostics;
-}
+/**
+ * Initialize the given latch.
+ *
+ * @param latch Latch to be initialized.
+ */
+void tnt_latch_init(struct tnt_latch *latch);
+/**
+ * Destroy the given latch.
+ */
+void tnt_latch_destroy(struct tnt_latch *latch);
+/**
+ * Set the latch to the locked state. If it's already locked
+ * returns -1 value immediately otherwise returns 0.
+ *
+ * @param latch Latch to be locked.
+ */
+int tnt_latch_trylock(struct tnt_latch *latch);
+/**
+ * Unlock the locked latch.
+ *
+ * @param latch Latch to be unlocked.
+ */
+void tnt_latch_unlock(struct tnt_latch *latch);
 
 
-void diag_clear()
-{
-	error_destroy(fiber->diagnostics);
-	fiber->diagnostics = NULL;
-}
+#endif /* TARANTOOL_LATCH_H_INCLUDED */
diff --git a/include/log_io.h b/include/log_io.h
index 34b0b4ce5f60321b1d0c1e6e6ff0ce21808c0d34..55a1a1655000118164d41d47e22cd75e60a26299 100644
--- a/include/log_io.h
+++ b/include/log_io.h
@@ -65,6 +65,26 @@ struct log_io_class {
 	const char *dirname;
 };
 
+
+/** A "condition variable" that allows fibers to wait when a given
+ * LSN makes it to disk.
+ */
+
+struct wait_lsn {
+	struct fiber *waiter;
+	i64 lsn;
+};
+
+void
+wait_lsn_set(struct wait_lsn *wait_lsn, i64 lsn);
+
+inline static void
+wait_lsn_clear(struct wait_lsn *wait_lsn)
+{
+	wait_lsn->waiter = NULL;
+	wait_lsn->lsn = 0LL;
+}
+
 struct log_io {
 	struct log_io_class *class;
 	FILE *f;
@@ -82,7 +102,8 @@ struct recovery_state {
 	i64 lsn, confirmed_lsn;
 
 	struct log_io *current_wal;	/* the WAL we'r currently reading/writing from/to */
-	struct log_io_class **snap_class, **wal_class, *snap_prefered_class, *wal_prefered_class;
+	struct log_io_class *snap_class;
+	struct log_io_class *wal_class;
 	struct child *wal_writer;
 
 	/* row_handler will be presented by most recent format of data
@@ -94,6 +115,7 @@ struct recovery_state {
 
 	int snap_io_rate_limit;
 	u64 cookie;
+	struct wait_lsn wait_lsn;
 
 	bool finalize;
 
@@ -129,7 +151,7 @@ static inline struct row_v11 *row_v11(const struct tbuf *t)
 struct tbuf *convert_to_v11(struct tbuf *orig, u16 tag, u64 cookie, i64 lsn);
 
 struct recovery_state *recover_init(const char *snap_dirname, const char *xlog_dirname,
-				    row_reader snap_row_reader, row_handler row_handler,
+				    row_handler row_handler,
 				    int rows_per_file, double fsync_delay, int inbox_size,
 				    int flags, void *data);
 int recover(struct recovery_state *, i64 lsn);
@@ -141,8 +163,9 @@ void recovery_setup_panic(struct recovery_state *r, bool on_snap_error, bool on_
 
 int confirm_lsn(struct recovery_state *r, i64 lsn);
 int64_t next_lsn(struct recovery_state *r, i64 new_lsn);
+void recovery_wait_lsn(struct recovery_state *r, i64 lsn);
 
-int read_log(const char *filename, row_reader reader,
+int read_log(const char *filename,
 	     row_handler xlog_handler, row_handler snap_handler, void *state);
 
 int default_remote_row_handler(struct recovery_state *r, struct tbuf *row);
diff --git a/include/pickle.h b/include/pickle.h
index 6e2a9ec4a1a84c87429a4c3e0c4765b9bc9bbccf..8a51cde6729449e62e3dcb03da258b8bbf4652cf 100644
--- a/include/pickle.h
+++ b/include/pickle.h
@@ -28,10 +28,6 @@
 #include <stdbool.h>
 
 #include <util.h>
-#include <exceptions.h>
-
-@interface tnt_PickleException: tnt_Exception
-@end
 
 struct tbuf;
 
diff --git a/include/tarantool.h b/include/tarantool.h
index fe58812ea1692509ebea0f5d9debb647ef5f1da5..29301ecdb9c8f8ce602ef8086ca4f56a8246a0e5 100644
--- a/include/tarantool.h
+++ b/include/tarantool.h
@@ -36,7 +36,7 @@ struct tarantool_cfg;
 
 extern const char *mod_name;
 i32 mod_check_config(struct tarantool_cfg *conf);
-void mod_reload_config(struct tarantool_cfg *old_conf, struct tarantool_cfg *new_conf);
+i32 mod_reload_config(struct tarantool_cfg *old_conf, struct tarantool_cfg *new_conf);
 int mod_cat(const char *filename);
 void mod_snapshot(struct log_io_iter *);
 void mod_info(struct tbuf *out);
diff --git a/mod/box/box.h b/mod/box/box.h
index b68c6134177e0c6d0b19c474af0010f6ceca24be..8dd66e3c83febe4903bd076168406e095f2ef81d 100644
--- a/mod/box/box.h
+++ b/mod/box/box.h
@@ -27,18 +27,9 @@
  */
 
 #include <mod/box/index.h>
-#include <exceptions.h>
+#include "exception.h"
 #include <tbuf.h>
 
-@interface tnt_BoxException: tnt_Exception {
-	@public
-		u32 errcode;
-}
-
-
-- init:(const char *)p_file:(unsigned)p_line reason:(const char *)p_reason errcode:(u32)p_errcode;
-- init:(const char *)p_file:(unsigned)p_line errcode:(u32)p_errcode;
-@end
 
 extern bool box_updates_allowed;
 void memcached_handler(void * /* data */);
@@ -81,7 +72,6 @@ struct box_txn {
 	struct box_tuple *tuple;
 	struct box_tuple *lock_tuple;
 
-	size_t saved_iov_cnt;
 	struct tbuf req;
 
 	bool in_recover;
@@ -139,7 +129,7 @@ enum box_mode {
 ENUM(messages, MESSAGES);
 
 struct box_txn *txn_alloc(u32 flags);
-u32 box_process(struct box_txn *txn, u32 op, struct tbuf *request_data);
+void box_process(struct box_txn *txn, u32 op, struct tbuf *request_data);
 
 void tuple_txn_ref(struct box_txn *txn, struct box_tuple *tuple);
 void txn_cleanup(struct box_txn *txn);
diff --git a/mod/box/box.m b/mod/box/box.m
index 6847e81587d564269278e1a2868e2a979153174a..c00618428d685dc64c047ad5b7d4851d4f9827a9 100644
--- a/mod/box/box.m
+++ b/mod/box/box.m
@@ -31,6 +31,7 @@
 #include <errno.h>
 #include <arpa/inet.h>
 
+#include <cfg/warning.h>
 #include <errcode.h>
 #include <fiber.h>
 #include <iproto.h>
@@ -46,9 +47,12 @@
 #include <cfg/tarantool_box_cfg.h>
 #include <mod/box/index.h>
 
+static void box_process_ro(u32 op, struct tbuf *request_data);
+static void box_process_rw(u32 op, struct tbuf *request_data);
+
 const char *mod_name = "Box";
 
-bool box_updates_allowed = false;
+iproto_callback rw_callback = box_process_ro;
 static char *status = "unknown";
 
 static int stat_base;
@@ -57,6 +61,8 @@ STRS(messages, MESSAGES);
 const int MEMCACHED_NAMESPACE = 23;
 static char *custom_proc_title;
 
+static struct fiber *remote_recover;
+
 /* hooks */
 typedef int (*box_hook_t) (struct box_txn * txn);
 
@@ -84,26 +90,6 @@ struct box_snap_row {
 	u8 data[];
 } __attribute__((packed));
 
-@implementation tnt_BoxException
-- init:(const char *)p_file:(unsigned)p_line reason:(const char *)p_reason errcode:(u32)p_errcode
-{
-	[super init:p_file:p_line reason:p_reason];
-
-	errcode = p_errcode;
-
-	if (errcode != ERR_CODE_NODE_IS_RO)
-		say_error("tnt_BoxException: %s/`%s' at %s:%i",
-			  tnt_errcode_desc(errcode), reason, file, line);
-
-	return self;
-}
-
-- init:(const char *)p_file:(unsigned)p_line errcode:(u32)p_errcode
-{
-	return [self init:p_file:p_line reason:"unknown" errcode:p_errcode];
-}
-
-@end
 
 static inline struct box_snap_row *
 box_snap_row(const struct tbuf *t)
@@ -113,20 +99,6 @@ box_snap_row(const struct tbuf *t)
 
 static void tuple_add_iov(struct box_txn *txn, struct box_tuple *tuple);
 
-box_hook_t *before_commit_update_hook;
-
-static void
-run_hooks(struct box_txn *txn, box_hook_t * hook)
-{
-	if (hook != NULL) {
-		for (int i = 0; hook[i] != NULL; i++) {
-			int result = (*hook[i]) (txn);
-			if (result != ERR_CODE_OK)
-				tnt_raise(tnt_BoxException,
-					  reason:"hook returned error" errcode:result);
-		}
-	}
-}
 
 void *
 next_field(void *f)
@@ -153,7 +125,7 @@ static void
 lock_tuple(struct box_txn *txn, struct box_tuple *tuple)
 {
 	if (tuple->flags & WAL_WAIT)
-		tnt_raise(tnt_BoxException, reason:"tuple is locked" errcode:ERR_CODE_NODE_IS_RO);
+		tnt_raise(ClientError, :ER_TUPLE_IS_RO);
 
 	say_debug("lock_tuple(%p)", tuple);
 	txn->lock_tuple = tuple;
@@ -212,11 +184,11 @@ tuple_print(struct tbuf *buf, uint8_t cardinality, void *f)
 static struct box_tuple *
 tuple_alloc(size_t size)
 {
-	struct box_tuple *tuple = salloc(sizeof(struct box_tuple) + size);
+	size_t total = sizeof(struct box_tuple) + size;
+	struct box_tuple *tuple = salloc(total);
 
 	if (tuple == NULL)
-		tnt_raise(tnt_BoxException,
-			  reason:"can't allocate tuple" errcode:ERR_CODE_MEMORY_ISSUE);
+		tnt_raise(LoggedError, :ER_MEMORY_ISSUE, total, "slab allocator", "tuple");
 
 	tuple->flags = tuple->refs = 0;
 	tuple->flags |= NEW;
@@ -260,11 +232,10 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data)
 {
 	assert(data != NULL);
 	if (cardinality == 0)
-		tnt_raise(tnt_BoxException,
-			  reason:"cardinality can't be equal to 0" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"tuple cardinality is 0");
+
 	if (data->len == 0 || data->len != valid_tuple(data, cardinality))
-		tnt_raise(tnt_BoxException,
-			  reason:"tuple encoding error" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"incorrect tuple length");
 
 	txn->tuple = tuple_alloc(data->len);
 	tuple_txn_ref(txn, txn->tuple);
@@ -277,14 +248,12 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data)
 		tuple_txn_ref(txn, txn->old_tuple);
 
 	if (txn->flags & BOX_ADD && txn->old_tuple != NULL)
-		tnt_raise(tnt_BoxException, reason:"tuple found" errcode:ERR_CODE_NODE_FOUND);
+		tnt_raise(ClientError, :ER_TUPLE_FOUND);
 
 	if (txn->flags & BOX_REPLACE && txn->old_tuple == NULL)
-		tnt_raise(tnt_BoxException,
-			  reason:"tuple not found" errcode:ERR_CODE_NODE_NOT_FOUND);
+		tnt_raise(ClientError, :ER_TUPLE_NOT_FOUND);
 
 	validate_indexes(txn);
-	run_hooks(txn, before_commit_update_hook);
 
 	if (txn->old_tuple != NULL) {
 #ifndef NDEBUG
@@ -360,12 +329,10 @@ static void
 do_field_arith(u8 op, struct tbuf *field, void *arg, u32 arg_size)
 {
 	if (field->len != 4)
-		tnt_raise(tnt_BoxException,
-			  reason:"num op on field with length != 4"
-			  errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"numeric operation on a field with length != 4");
+
 	if (arg_size != 4)
-		tnt_raise(tnt_BoxException,
-			  reason:"num op with arg not u32" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"the argument of a numeric operation is not a 4-byte int");
 
 	switch (op) {
 	case 1:
@@ -404,8 +371,7 @@ do_field_splice(struct tbuf *field, void *args_data, u32 args_data_size)
 	length_field = read_field(&args);
 	list_field = read_field(&args);
 	if (args.len != 0)
-		tnt_raise(tnt_BoxException,
-			  reason:"do_field_splice: bad args" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"field splice: bad arguments");
 
 	offset_size = load_varint32(&offset_field);
 	if (offset_size == 0)
@@ -414,16 +380,14 @@ do_field_splice(struct tbuf *field, void *args_data, u32 args_data_size)
 		offset = pick_u32(offset_field, &offset_field);
 		if (offset < 0) {
 			if (field->len < -offset)
-				tnt_raise(tnt_BoxException,
-					  reason:"do_field_splice: noffset is negative"
-					  errcode:ERR_CODE_ILLEGAL_PARAMS);
+				tnt_raise(IllegalParams,
+					  :"field splice: offset is negative");
 			noffset = offset + field->len;
 		} else
 			noffset = offset;
 	} else
-		tnt_raise(tnt_BoxException,
-			  reason:"do_field_splice: bad size of offset field"
-			  errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"field splice: wrong size of offset");
+
 	if (noffset > field->len)
 		noffset = field->len;
 
@@ -432,9 +396,8 @@ do_field_splice(struct tbuf *field, void *args_data, u32 args_data_size)
 		nlength = field->len - noffset;
 	else if (length_size == sizeof(length)) {
 		if (offset_size == 0)
-			tnt_raise(tnt_BoxException,
-				  reason:"do_field_splice: offset field is empty but length is not"
-				  errcode:ERR_CODE_ILLEGAL_PARAMS);
+			tnt_raise(IllegalParams,
+				  :"field splice: offset is empty but length is not");
 
 		length = pick_u32(length_field, &length_field);
 		if (length < 0) {
@@ -445,21 +408,17 @@ do_field_splice(struct tbuf *field, void *args_data, u32 args_data_size)
 		} else
 			nlength = length;
 	} else
-		tnt_raise(tnt_BoxException,
-			  reason:"do_field_splice: bad size of length field"
-			  errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"field splice: wrong size of length");
+
 	if (nlength > (field->len - noffset))
 		nlength = field->len - noffset;
 
 	list_size = load_varint32(&list_field);
 	if (list_size > 0 && length_size == 0)
-		tnt_raise(tnt_BoxException,
-			  reason:"do_field_splice: length field is empty but list is not"
-			  errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams,
+			  :"field splice: length field is empty but list is not");
 	if (list_size > (UINT32_MAX - (field->len - nlength)))
-		tnt_raise(tnt_BoxException,
-			  reason:"do_field_splice: list_size is too long"
-			  errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"field splice: list_size is too long");
 
 	say_debug("do_field_splice: noffset = %i, nlength = %i, list_size = %u",
 		  noffset, nlength, list_size);
@@ -484,17 +443,17 @@ prepare_update_fields(struct box_txn *txn, struct tbuf *data)
 
 	u32 key_len = read_u32(data);
 	if (key_len != 1)
-		tnt_raise(tnt_BoxException,
-			  reason:"key must be single valued" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"key must be single valued");
+
 	key = read_field(data);
 	op_cnt = read_u32(data);
 
 	if (op_cnt > 128)
-		tnt_raise(tnt_BoxException, reason:"too many ops" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"too many operations for update");
 	if (op_cnt == 0)
-		tnt_raise(tnt_BoxException, reason:"no ops" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"no operations for update");
 	if (key == NULL)
-		tnt_raise(tnt_BoxException, reason:"invalid key" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"invalid key");
 
 	txn->old_tuple = txn->index->find(txn->index, key);
 	if (txn->old_tuple == NULL) {
@@ -526,17 +485,14 @@ prepare_update_fields(struct box_txn *txn, struct tbuf *data)
 		field_no = read_u32(data);
 
 		if (field_no >= txn->old_tuple->cardinality)
-			tnt_raise(tnt_BoxException,
-				  reason:"update of field beyond tuple cardinality"
-				  errcode:ERR_CODE_ILLEGAL_PARAMS);
+			tnt_raise(ClientError, :ER_NO_SUCH_FIELD, field_no);
 
 		struct tbuf *sptr_field = fields[field_no];
 
 		op = read_u8(data);
 		if (op > 5)
-			tnt_raise(tnt_BoxException,
-				  reason:"op is not 0, 1, 2, 3, 4 or 5"
-				  errcode:ERR_CODE_ILLEGAL_PARAMS);
+			tnt_raise(IllegalParams, :"op is not 0, 1, 2, 3, 4 or 5");
+
 		arg = read_field(data);
 		arg_size = load_varint32(&arg);
 
@@ -560,8 +516,7 @@ prepare_update_fields(struct box_txn *txn, struct tbuf *data)
 	}
 
 	if (data->len != 0)
-		tnt_raise(tnt_BoxException,
-			  reason:"can't unpack request" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"can't unpack request");
 
 	size_t bsize = 0;
 	for (int i = 0; i < txn->old_tuple->cardinality; i++)
@@ -578,11 +533,9 @@ prepare_update_fields(struct box_txn *txn, struct tbuf *data)
 	}
 
 	validate_indexes(txn);
-	run_hooks(txn, before_commit_update_hook);
 
 	if (data->len != 0)
-		tnt_raise(tnt_BoxException,
-			  reason:"can't unpack request" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"can't unpack request");
 
 out:
 	if (!(txn->flags & BOX_QUIET)) {
@@ -617,9 +570,7 @@ process_select(struct box_txn *txn, u32 limit, u32 offset, struct tbuf *data)
 	uint32_t *found;
 	u32 count = read_u32(data);
 	if (count == 0)
-		tnt_raise(tnt_BoxException,
-			  reason:"tuple count must be greater than zero"
-			  errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"tuple count must be positive");
 
 	found = palloc(fiber->pool, sizeof(*found));
 	add_iov(found, sizeof(*found));
@@ -667,9 +618,8 @@ process_select(struct box_txn *txn, u32 limit, u32 offset, struct tbuf *data)
 
 			u32 key_len = read_u32(data);
 			if (key_len != 1)
-				tnt_raise(tnt_BoxException,
-					  reason:"key must be single valued"
-					  errcode:ERR_CODE_ILLEGAL_PARAMS);
+				tnt_raise(IllegalParams, :"key must be single valued");
+
 			void *key = read_field(data);
 			tuple = txn->index->find(txn->index, key);
 			if (tuple == NULL || tuple->flags & GHOST)
@@ -686,8 +636,7 @@ process_select(struct box_txn *txn, u32 limit, u32 offset, struct tbuf *data)
 	}
 
 	if (data->len != 0)
-		tnt_raise(tnt_BoxException,
-			  reason:"can't unpack request" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"can't unpack request");
 }
 
 static void __attribute__((noinline))
@@ -742,47 +691,43 @@ txn_alloc(u32 flags)
 }
 
 /**
-  * Validate the request and start a transaction associated with that request.
-  * 
-  * This function does too much:
-  * - parses the request,
-  * - takes a "savepoint" of fiber->iov_cnt,
-  * - performs a "name resolution", i.e. find the namespace object
+  * Validate the request and start a transaction associated with
+  * that request:
+  *
+  * - parse the request,
+  * - perform a "name resolution", i.e. find the namespace object
   *   associated with the request.
-  * @todo: split in smaller blocks. 
   */
 
 static void
 txn_begin(struct box_txn *txn, u16 op, struct tbuf *data)
 {
-	txn->saved_iov_cnt = fiber->iov_cnt;
-
 	txn->op = op;
 	txn->req = (struct tbuf){ .data = data->data, .len = data->len };
 	txn->n = read_u32(data);
+
 	if (txn->n < 0 || txn->n > namespace_count - 1)
-		tnt_raise(tnt_BoxException,
-			  reason:"bad namespace number" errcode:ERR_CODE_NO_SUCH_NAMESPACE);
-	txn->index = &namespace[txn->n].index[0];
-
-	if (!namespace[txn->n].enabled) {
-		say_warn("namespace %i is not enabled", txn->n);
-		tnt_raise(tnt_BoxException,
-			  reason:"namespace is not enabled" errcode:ERR_CODE_NO_SUCH_NAMESPACE);
-	}
+		tnt_raise(ClientError, :ER_NO_SUCH_NAMESPACE, txn->n);
 
 	txn->namespace = &namespace[txn->n];
+
+	if (!txn->namespace->enabled)
+		tnt_raise(ClientError, :ER_NAMESPACE_DISABLED, txn->n);
+
+	txn->index = txn->namespace->index;
 }
 
 void
 txn_cleanup(struct box_txn *txn)
 {
 	/*
-	 * txn_cleanup maybe called twice in following scenario:
-	 * several request processed by single iproto loop run
-	 * first one successed, but the last one fails with OOM
-	 * in this case fiber perform fiber_cleanup for every registered callback
-	 * we should not not run cleanup twice.
+	 * txn_cleanup() may get called twice in the following
+	 * scenario: Several requests are processed by a single
+	 * iproto loop iteration. The first few requests are
+	 * handled successfully, but the next one fails with an
+	 * OOM. In this case, fiber performs fiber_cleanup() for
+	 * every registered callback. We should not run
+	 * txn_cleanup() twice.
 	 */
 	if (txn->op == 0)
 		return;
@@ -819,9 +764,11 @@ txn_commit(struct box_txn *txn)
 			tbuf_append(t, txn->req.data, txn->req.len);
 
 			i64 lsn = next_lsn(recovery_state, 0);
-			if (!wal_write(recovery_state, wal_tag, fiber->cookie, lsn, t))
-				tnt_raise(tnt_BoxException, errcode:ERR_CODE_WAL_IO);
+			bool res = !wal_write(recovery_state, wal_tag,
+					      fiber->cookie, lsn, t);
 			confirm_lsn(recovery_state, lsn);
+			if (res)
+				tnt_raise(LoggedError, :ER_WAL_IO);
 		}
 
 		unlock_tuples(txn);
@@ -844,8 +791,6 @@ txn_abort(struct box_txn *txn)
 	if (txn->op == 0)
 		return;
 
-	fiber->iov_cnt = txn->saved_iov_cnt;
-
 	if (!op_is_select(txn->op)) {
 		say_debug("box_rollback(op:%s)", messages_strs[txn->op]);
 
@@ -859,13 +804,13 @@ txn_abort(struct box_txn *txn)
 }
 
 static void
-box_dispach(struct box_txn *txn, struct tbuf *data)
+box_dispatch(struct box_txn *txn, struct tbuf *data)
 {
 	u32 cardinality;
 	void *key;
 	u32 key_len;
 
-	say_debug("box_dispach(%i)", txn->op);
+	say_debug("box_dispatch(%i)", txn->op);
 
 	switch (txn->op) {
 	case INSERT:
@@ -873,23 +818,18 @@ box_dispach(struct box_txn *txn, struct tbuf *data)
 		cardinality = read_u32(data);
 		if (namespace[txn->n].cardinality > 0
 		    && namespace[txn->n].cardinality != cardinality)
-			tnt_raise(tnt_BoxException,
-				  reason:"tuple cardinality must match namespace cardinality"
-				  errcode:ERR_CODE_ILLEGAL_PARAMS);
+			tnt_raise(IllegalParams, :"tuple cardinality must match namespace cardinality");
 		prepare_replace(txn, cardinality, data);
 		break;
 
 	case DELETE:
 		key_len = read_u32(data);
 		if (key_len != 1)
-			tnt_raise(tnt_BoxException,
-				  reason:"key must be single valued"
-				  errcode:ERR_CODE_ILLEGAL_PARAMS);
+			tnt_raise(IllegalParams, :"key must be single valued");
 
 		key = read_field(data);
 		if (data->len != 0)
-			tnt_raise(tnt_BoxException,
-				  reason:"can't unpack request" errcode:ERR_CODE_ILLEGAL_PARAMS);
+			tnt_raise(IllegalParams, :"can't unpack request");
 
 		prepare_delete(txn, key);
 		break;
@@ -899,14 +839,12 @@ box_dispach(struct box_txn *txn, struct tbuf *data)
 			u32 offset = read_u32(data);
 			u32 limit = read_u32(data);
 
-			if (i > MAX_IDX)
-				tnt_raise(tnt_BoxException,
-					  reason:"index too big" errcode:ERR_CODE_NO_SUCH_INDEX);
+			if (i > MAX_IDX ||
+			    namespace[txn->n].index[i].key_cardinality == 0) {
+
+				tnt_raise(LoggedError, :ER_NO_SUCH_INDEX, i, txn->n);
+			}
 			txn->index = &namespace[txn->n].index[i];
-			if (txn->index->key_cardinality == 0)
-				tnt_raise(tnt_BoxException,
-					  reason:"index is invalid"
-					  errcode:ERR_CODE_NO_SUCH_INDEX);
 
 			process_select(txn, limit, offset, data);
 			break;
@@ -918,8 +856,8 @@ box_dispach(struct box_txn *txn, struct tbuf *data)
 		break;
 
 	default:
-		say_error("silverbox_dispach: unsupported command = %" PRIi32 "", txn->op);
-		tnt_raise(tnt_BoxException, errcode:ERR_CODE_ILLEGAL_PARAMS);
+		say_error("box_dispatch: unsupported command = %" PRIi32 "", txn->op);
+		tnt_raise(IllegalParams, :"unsupported command code, check the error log");
 	}
 }
 
@@ -1013,22 +951,6 @@ box_xlog_sprint(struct tbuf *buf, const struct tbuf *t)
 	return 0;
 }
 
-struct tbuf *
-box_snap_reader(FILE *f, struct palloc_pool *pool)
-{
-	struct tbuf *row = tbuf_alloc(pool);
-	const int header_size = sizeof(*box_snap_row(row));
-
-	tbuf_reserve(row, header_size);
-	if (fread(row->data, header_size, 1, f) != 1)
-		return NULL;
-
-	tbuf_reserve(row, box_snap_row(row)->data_size);
-	if (fread(box_snap_row(row)->data, box_snap_row(row)->data_size, 1, f) != 1)
-		return NULL;
-
-	return convert_to_v11(row, snap_tag, default_cookie, 0);
-}
 
 static int
 snap_print(struct recovery_state *r __attribute__((unused)), struct tbuf *t)
@@ -1064,8 +986,6 @@ xlog_print(struct recovery_state *r __attribute__((unused)), struct tbuf *t)
 static void
 custom_init(void)
 {
-	before_commit_update_hook = calloc(1, sizeof(box_hook_t));
-
 	if (cfg.namespace == NULL)
 		panic("at least one namespace must be configured");
 
@@ -1199,7 +1119,8 @@ custom_init(void)
 	}
 }
 
-u32
+
+void
 box_process(struct box_txn *txn, u32 op, struct tbuf *request_data)
 {
 	ev_tstamp start = ev_now(), stop;
@@ -1208,64 +1129,36 @@ box_process(struct box_txn *txn, u32 op, struct tbuf *request_data)
 
 	@try {
 		txn_begin(txn, op, request_data);
-		box_dispach(txn, request_data);
+		box_dispatch(txn, request_data);
 		txn_commit(txn);
-
-		return ERR_CODE_OK;
-	}
-	@catch (tnt_PickleException *e) {
-		txn_abort(txn);
-
-		say_error("tnt_PickleException: `%s' at %s:%i", e->reason, e->file, e->line);
-
-		return ERR_CODE_ILLEGAL_PARAMS;
-	}
-	@catch (tnt_BoxException *e) {
-		txn_abort(txn);
-
-		if (e->errcode != ERR_CODE_NODE_IS_RO)
-			say_error("tnt_BoxException: %s/`%s' at %s:%i",
-				  tnt_errcode_desc(e->errcode),
-				  e->reason, e->file, e->line);
-
-		return e->errcode;
 	}
 	@catch (id e) {
 		txn_abort(txn);
-
 		@throw;
 	}
 	@finally {
 		stop = ev_now();
 		if (stop - start > cfg.too_long_threshold)
-			say_warn("too long %s: %.3f sec", messages_strs[txn->op], stop - start);
+			say_warn("too long %s: %.3f sec", messages_strs[op], stop - start);
 	}
 }
 
-static u32
+static void
 box_process_ro(u32 op, struct tbuf *request_data)
 {
-	if (!op_is_select(op)) {
-		say_error("can't process %i command on RO port", op);
-
-		return ERR_CODE_NONMASTER;
-	}
+	if (!op_is_select(op))
+		tnt_raise(LoggedError, :ER_NONMASTER);
 
 	return box_process(txn_alloc(0), op, request_data);
 }
 
-static u32
+static void
 box_process_rw(u32 op, struct tbuf *request_data)
 {
-	if (!op_is_select(op) && !box_updates_allowed) {
-		say_error("can't process %i command, updates are disallowed", op);
-
-		return ERR_CODE_NONMASTER;
-	}
-
 	return box_process(txn_alloc(0), op, request_data);
 }
 
+
 static struct tbuf *
 convert_snap_row_to_wal(struct tbuf *t)
 {
@@ -1304,8 +1197,12 @@ recover_row(struct recovery_state *r __attribute__((unused)), struct tbuf *t)
 
 	op = read_u16(t);
 
-	if (box_process(txn, op, t) != ERR_CODE_OK)
+	@try {
+		box_process(txn, op, t);
+	}
+	@catch (id e) {
 		return -1;
+	}
 
 	return 0;
 }
@@ -1321,36 +1218,68 @@ title(const char *fmt, ...)
 	va_end(ap);
 
 	if (cfg.memcached)
-		set_proc_title("memcached:%s%s pri:%i adm:%i",
+		set_proc_title("%s%s memcached:%i adm:%i",
 			       buf, custom_proc_title, cfg.primary_port, cfg.admin_port);
 	else
-		set_proc_title("box:%s%s pri:%i sec:%i adm:%i",
+		set_proc_title("%s%s pri:%i sec:%i adm:%i",
 			       buf, custom_proc_title,
 			       cfg.primary_port, cfg.secondary_port, cfg.admin_port);
 }
 
 static void
-box_bound_to_primary(void *data __attribute__((unused)))
+remote_recovery_restart(struct tarantool_cfg *conf)
 {
-	recover_finalize(recovery_state);
+	if (remote_recover) {
+		say_info("shutting down the replica");
+		fiber_call(remote_recover);
+	}
 
-	if (cfg.remote_hot_standby) {
-		say_info("starting remote hot standby");
-		status = palloc(eter_pool, 64);
-		snprintf(status, 64, "hot_standby/%s:%i%s", cfg.replication_source_ipaddr,
-			 cfg.replication_source_port, custom_proc_title);
-		recover_follow_remote(recovery_state, cfg.replication_source_ipaddr, cfg.replication_source_port,
-				      default_remote_row_handler);
-
-		title("hot_standby/%s:%i", cfg.replication_source_ipaddr, cfg.replication_source_port);
+	say_info("starting the replica");
+	remote_recover = recover_follow_remote(recovery_state,
+					       conf->replication_source_ipaddr,
+					       conf->replication_source_port,
+					       default_remote_row_handler);
+
+	status = palloc(eter_pool, 64);
+	snprintf(status, 64, "replica/%s:%i%s", conf->wal_feeder_ipaddr,
+		 conf->wal_feeder_port, custom_proc_title);
+	title("replica/%s:%i%s", conf->wal_feeder_ipaddr, conf->wal_feeder_port,
+	      custom_proc_title);
+}
+
+static void
+box_master_or_slave(struct tarantool_cfg *conf)
+{
+	if (conf->remote_hot_standby) {
+		rw_callback = box_process_ro;
+
+		recovery_wait_lsn(recovery_state, recovery_state->lsn);
+
+		remote_recovery_restart(conf);
 	} else {
+		if (remote_recover) {
+			say_info("shuting down the replica");
+			fiber_cancel(remote_recover);
+
+			remote_recover = NULL;
+		}
+		rw_callback = box_process_rw;
+
 		say_info("I am primary");
+
 		status = "primary";
-		box_updates_allowed = true;
 		title("primary");
 	}
 }
 
+static void
+box_bound_to_primary(void *data __attribute__((unused)))
+{
+	recover_finalize(recovery_state);
+
+	box_master_or_slave(&cfg);
+}
+
 static void
 memcached_bound_to_primary(void *data __attribute__((unused)))
 {
@@ -1363,21 +1292,49 @@ memcached_bound_to_primary(void *data __attribute__((unused)))
 }
 
 i32
-mod_check_config(struct tarantool_cfg *conf __attribute__((unused)))
+mod_check_config(struct tarantool_cfg *conf)
 {
+	if (conf->remote_hot_standby > 0 && conf->local_hot_standby > 0) {
+		out_warning(0, "Remote and local hot standby modes "
+			       "can't be enabled simultaneously");
+
+		return -1;
+	}
+
 	return 0;
 }
 
-void
-mod_reload_config(struct tarantool_cfg *old_conf __attribute__((unused)),
-		  struct tarantool_cfg *new_conf __attribute__((unused)))
+i32
+mod_reload_config(struct tarantool_cfg *old_conf, struct tarantool_cfg *new_conf)
 {
-	return;
+	if (old_conf->remote_hot_standby != new_conf->remote_hot_standby) {
+		if (recovery_state->finalize != true) {
+			out_warning(0, "Could not propagate %s before local recovery finished",
+				    old_conf->remote_hot_standby == true ? "slave to master" :
+				    "master to slave");
+
+			return -1;
+		}
+	}
+
+	if (old_conf->remote_hot_standby != new_conf->remote_hot_standby) {
+		/* Local recovery must be finalized at this point */
+		assert(recovery_state->finalize == true);
+
+		box_master_or_slave(new_conf);
+	} else if (old_conf->remote_hot_standby > 0 &&
+		   (strcmp(old_conf->wal_feeder_ipaddr, new_conf->wal_feeder_ipaddr) != 0 ||
+		    old_conf->wal_feeder_port != new_conf->wal_feeder_port))
+		remote_recovery_restart(new_conf);
+
+	return 0;
 }
 
 void
 mod_init(void)
 {
+	static iproto_callback ro_callback = box_process_ro;
+
 	stat_base = stat_register(messages_strs, messages_MAX);
 
 	namespace = palloc(eter_pool, sizeof(struct namespace) * namespace_count);
@@ -1412,8 +1369,7 @@ mod_init(void)
 	}
 
 	recovery_state = recover_init(cfg.snap_dir, cfg.wal_dir,
-				      box_snap_reader, recover_row,
-				      cfg.rows_per_wal, cfg.wal_fsync_delay,
+				      recover_row, cfg.rows_per_wal, cfg.wal_fsync_delay,
 				      cfg.wal_writer_inbox_size,
 				      init_storage ? RECOVER_READONLY : 0, NULL);
 
@@ -1458,7 +1414,7 @@ mod_init(void)
 	recover(recovery_state, 0);
 	stat_cleanup(stat_base, messages_MAX);
 
-	title("build_indexes");
+	title("building indexes");
 
 	build_indexes();
 
@@ -1467,8 +1423,8 @@ mod_init(void)
 	if (cfg.local_hot_standby) {
 		say_info("starting local hot standby");
 		recover_follow(recovery_state, cfg.wal_dir_rescan_delay);
-		status = "hot_standby/local";
-		title("hot_standby/local");
+		status = "hot_standby";
+		title("hot_standby");
 	}
 
 	if (cfg.memcached != 0) {
@@ -1476,12 +1432,14 @@ mod_init(void)
 			     memcached_bound_to_primary);
 	} else {
 		if (cfg.secondary_port != 0)
-			fiber_server(tcp_server, cfg.secondary_port, iproto_interact,
-				     box_process_ro, NULL);
+			fiber_server(tcp_server, cfg.secondary_port,
+				     (fiber_server_callback) iproto_interact,
+				     &ro_callback, NULL);
 
 		if (cfg.primary_port != 0)
-			fiber_server(tcp_server, cfg.primary_port, iproto_interact, box_process_rw,
-				     box_bound_to_primary);
+			fiber_server(tcp_server, cfg.primary_port,
+				     (fiber_server_callback) iproto_interact,
+				     &rw_callback, box_bound_to_primary);
 	}
 
 	say_info("initialized");
@@ -1490,7 +1448,7 @@ mod_init(void)
 int
 mod_cat(const char *filename)
 {
-	return read_log(filename, box_snap_reader, xlog_print, snap_print, NULL);
+	return read_log(filename, xlog_print, snap_print, NULL);
 }
 
 void
diff --git a/mod/box/index.m b/mod/box/index.m
index df74b4d2b2966a5afbbb5005fe6a3444f5b18089..8f23b75e30da9d1f075dfd9cb09e56452c2e4196 100644
--- a/mod/box/index.m
+++ b/mod/box/index.m
@@ -156,8 +156,8 @@ index_find_hash_by_tuple(struct index *self, struct box_tuple *tuple)
 {
 	void *key = tuple_field(tuple, self->key_field->fieldno);
 	if (key == NULL)
-		tnt_raise(tnt_BoxException,
-		          reason:"invalid tuple, can't find key" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(ClientError, :ER_NO_SUCH_FIELD, self->key_field->fieldno);
+
 	return self->find(self, key);
 }
 
@@ -169,8 +169,7 @@ index_find_hash_num(struct index *self, void *key)
 	u32 num = *(u32 *)key;
 
 	if (key_size != 4)
-		tnt_raise(tnt_BoxException,
-		          reason:"key is not u32" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"key is not u32");
 
 	assoc_find(int_ptr_map, self->idx.int_hash, num, ret);
 #ifdef DEBUG
@@ -187,8 +186,7 @@ index_find_hash_num64(struct index *self, void *key)
 	u64 num = *(u64 *)key;
 
 	if (key_size != 8)
-		tnt_raise(tnt_BoxException,
-		          reason:"key is not u64" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"key is not u64");
 
 	assoc_find(int64_ptr_map, self->idx.int64_hash, num, ret);
 #ifdef DEBUG
@@ -303,8 +301,7 @@ index_remove_hash_num(struct index *self, struct box_tuple *tuple)
 	u32 num = *(u32 *)key;
 
 	if (key_size != 4)
-		tnt_raise(tnt_BoxException,
-			  reason:"key is not u32" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"key is not u32");
 	assoc_delete(int_ptr_map, self->idx.int_hash, num);
 #ifdef DEBUG
 	say_debug("index_remove_hash_num(self:%p, key:%i)", self, num);
@@ -319,8 +316,7 @@ index_remove_hash_num64(struct index *self, struct box_tuple *tuple)
 	u64 num = *(u64 *)key;
 
 	if (key_size != 8)
-		tnt_raise(tnt_BoxException,
-			  reason:"key is not u64" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"key is not u64");
 	assoc_delete(int64_ptr_map, self->idx.int64_hash, num);
 #ifdef DEBUG
 	say_debug("index_remove_hash_num(self:%p, key:%"PRIu64")", self, num);
@@ -353,8 +349,7 @@ index_replace_hash_num(struct index *self, struct box_tuple *old_tuple, struct b
 	u32 num = *(u32 *)key;
 
 	if (key_size != 4)
-		tnt_raise(tnt_BoxException,
-			  reason:"key is not u32" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"key is not u32");
 
 	if (old_tuple != NULL) {
 		void *old_key = tuple_field(old_tuple, self->key_field->fieldno);
@@ -378,8 +373,7 @@ index_replace_hash_num64(struct index *self, struct box_tuple *old_tuple, struct
 	u64 num = *(u64 *)key;
 
 	if (key_size != 8)
-		tnt_raise(tnt_BoxException,
-			  reason:"key is not u64" errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(IllegalParams, :"key is not u64");
 
 	if (old_tuple != NULL) {
 		void *old_key = tuple_field(old_tuple, self->key_field->fieldno);
@@ -401,9 +395,7 @@ index_replace_hash_str(struct index *self, struct box_tuple *old_tuple, struct b
 	void *key = tuple_field(tuple, self->key_field->fieldno);
 
 	if (key == NULL)
-		tnt_raise(tnt_BoxException,
-			  reason:"Supplied tuple misses a field which is part of an index"
-			  errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(ClientError, :ER_NO_SUCH_FIELD, self->key_field->fieldno);
 
 	if (old_tuple != NULL) {
 		void *old_key = tuple_field(old_tuple, self->key_field->fieldno);
@@ -422,9 +414,7 @@ static void
 index_replace_tree_str(struct index *self, struct box_tuple *old_tuple, struct box_tuple *tuple)
 {
 	if (tuple->cardinality < self->field_cmp_order_cnt)
-		tnt_raise(tnt_BoxException,
-			  reason:"Supplied tuple misses a field which is part of an index"
-			  errcode:ERR_CODE_ILLEGAL_PARAMS);
+		tnt_raise(ClientError, :ER_NO_SUCH_FIELD, self->field_cmp_order_cnt);
 
 	struct tree_index_member *member = tuple2tree_index_member(self, tuple, NULL);
 
@@ -463,8 +453,7 @@ validate_indexes(struct box_txn *txn)
 		foreach_index(txn->n, index) {
 			for (u32 f = 0; f < index->key_cardinality; ++f) {
 				if (index->key_field[f].fieldno >= txn->tuple->cardinality)
-					tnt_raise(tnt_BoxException,
-						  reason:"tuple is too short" errcode:ERR_CODE_ILLEGAL_PARAMS);
+					tnt_raise(IllegalParams, :"tuple must have all indexed fields");
 
 				if (index->key_field[f].type == STRING)
 					continue;
@@ -473,14 +462,10 @@ validate_indexes(struct box_txn *txn)
 				u32 len = load_varint32(&field);
 
 				if (index->key_field[f].type == NUM && len != sizeof(u32))
-					tnt_raise(tnt_BoxException,
-						  reason:"field must be NUM"
-						  errcode:ERR_CODE_ILLEGAL_PARAMS);
+					tnt_raise(IllegalParams, :"field must be NUM");
 
 				if (index->key_field[f].type == NUM64 && len != sizeof(u64))
-					tnt_raise(tnt_BoxException,
-						  reason:"field must be NUM64"
-						  errcode:ERR_CODE_ILLEGAL_PARAMS);
+					tnt_raise(IllegalParams, :"field must be NUM64");
 			}
 			if (index->type == TREE && index->unique == false)
 				/* Don't check non unique indexes */
@@ -489,9 +474,7 @@ validate_indexes(struct box_txn *txn)
 			struct box_tuple *tuple = index->find_by_tuple(index, txn->tuple);
 
 			if (tuple != NULL && tuple != txn->old_tuple)
-				tnt_raise(tnt_BoxException,
-					  reason:"duplicate key in a unique index"
-					  errcode:ERR_CODE_INDEX_VIOLATION);
+				tnt_raise(ClientError, :ER_INDEX_VIOLATION);
 		}
 	}
 }
diff --git a/mod/box/memcached.m b/mod/box/memcached.m
index 2950030bbfb9830d6c0f53c244bfb9ad1c1c4523..649532593997ab14e9c1722a7bcca5b4f5c9b694 100644
--- a/mod/box/memcached.m
+++ b/mod/box/memcached.m
@@ -1,8 +1,8 @@
 
 #line 1 "mod/box/memcached.rl"
 /*
- * Copyright (C) 2010 Mail.RU
- * Copyright (C) 2010 Yuriy Vostrikov
+ * Copyright (C) 2010, 2011 Mail.RU
+ * Copyright (C) 2010, 2011 Yuriy Vostrikov
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -89,7 +89,7 @@ natoq(const u8 *start, const u8 *end)
 	return num;
 }
 
-static int
+static void
 store(struct box_txn *txn, void *key, u32 exptime, u32 flags, u32 bytes, u8 *data)
 {
 	u32 box_flags = BOX_QUIET, cardinality = 4;
@@ -121,10 +121,10 @@ store(struct box_txn *txn, void *key, u32 exptime, u32 flags, u32 bytes, u8 *dat
 	int key_len = load_varint32(&key);
 	say_debug("memcached/store key:(%i)'%.*s' exptime:%"PRIu32" flags:%"PRIu32" cas:%"PRIu64,
 		  key_len, key_len, (u8 *)key, exptime, flags, cas);
-	return box_process(txn, INSERT, req); /* FIXME: handle RW/RO */
+	box_process(txn, INSERT, req); /* FIXME: handle RW/RO */
 }
 
-static int
+static void
 delete(struct box_txn *txn, void *key)
 {
 	u32 key_len = 1;
@@ -134,7 +134,7 @@ delete(struct box_txn *txn, void *key)
 	tbuf_append(req, &key_len, sizeof(key_len));
 	tbuf_append_field(req, key);
 
-	return box_process(txn, DELETE, req);
+	box_process(txn, DELETE, req);
 }
 
 static struct box_tuple *
@@ -250,31 +250,32 @@ memcached_dispatch(struct box_txn *txn)
 
 	say_debug("memcached_dispatch '%.*s'", MIN((int)(pe - p), 40) , p);
 
-#define STORE ({									\
-	stats.cmd_set++;								\
-	if (bytes > (1<<20)) {								\
-		add_iov("SERVER_ERROR object too large for cache\r\n", 41);		\
-	} else {									\
-		u32 ret_code;								\
-		if ((ret_code = store(txn, key, exptime, flags, bytes, data)) == 0) {	\
-			stats.total_items++;						\
-			add_iov("STORED\r\n", 8);					\
-		} else {								\
-			add_iov("SERVER_ERROR ", 13);					\
-			add_iov(tnt_errcode_desc(ret_code),			\
-				strlen(tnt_errcode_desc(ret_code)));		\
-			add_iov("\r\n", 2);						\
-		}									\
-	}										\
-})
+#define STORE									\
+do {										\
+	stats.cmd_set++;							\
+	if (bytes > (1<<20)) {							\
+		add_iov("SERVER_ERROR object too large for cache\r\n", 41);	\
+	} else {								\
+		@try {								\
+			store(txn, key, exptime, flags, bytes, data);		\
+			stats.total_items++;					\
+			add_iov("STORED\r\n", 8);				\
+		}								\
+		@catch (ClientError *e) {					\
+			add_iov("SERVER_ERROR ", 13);				\
+			add_iov(e->errmsg, strlen(e->errmsg));			\
+			add_iov("\r\n", 2);					\
+		}								\
+	}									\
+} while (0)
 
 	
-#line 273 "mod/box/memcached.m"
+#line 274 "mod/box/memcached.m"
 	{
 	cs = memcached_start;
 	}
 
-#line 278 "mod/box/memcached.m"
+#line 279 "mod/box/memcached.m"
 	{
 	if ( p == pe )
 		goto _test_eof;
@@ -332,7 +333,7 @@ case 5:
 		goto st0;
 	goto tr15;
 tr15:
-#line 489 "mod/box/memcached.rl"
+#line 494 "mod/box/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -349,7 +350,7 @@ st6:
 	if ( ++p == pe )
 		goto _test_eof6;
 case 6:
-#line 353 "mod/box/memcached.m"
+#line 354 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st7;
 	goto st0;
@@ -363,49 +364,49 @@ case 7:
 		goto tr17;
 	goto st0;
 tr17:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st8;
 st8:
 	if ( ++p == pe )
 		goto _test_eof8;
 case 8:
-#line 374 "mod/box/memcached.m"
+#line 375 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto tr18;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st8;
 	goto st0;
 tr18:
-#line 512 "mod/box/memcached.rl"
+#line 517 "mod/box/memcached.rl"
 	{flags = natoq(fstart, p);}
 	goto st9;
 st9:
 	if ( ++p == pe )
 		goto _test_eof9;
 case 9:
-#line 388 "mod/box/memcached.m"
+#line 389 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st9;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr21;
 	goto st0;
 tr21:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st10;
 st10:
 	if ( ++p == pe )
 		goto _test_eof10;
 case 10:
-#line 402 "mod/box/memcached.m"
+#line 403 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto tr22;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st10;
 	goto st0;
 tr22:
-#line 505 "mod/box/memcached.rl"
+#line 510 "mod/box/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -416,21 +417,21 @@ st11:
 	if ( ++p == pe )
 		goto _test_eof11;
 case 11:
-#line 420 "mod/box/memcached.m"
+#line 421 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st11;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr25;
 	goto st0;
 tr25:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st12;
 st12:
 	if ( ++p == pe )
 		goto _test_eof12;
 case 12:
-#line 434 "mod/box/memcached.m"
+#line 435 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr26;
 		case 13: goto tr27;
@@ -440,11 +441,11 @@ case 12:
 		goto st12;
 	goto st0;
 tr26:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -465,13 +466,13 @@ tr26:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 268 "mod/box/memcached.rl"
+#line 269 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -482,9 +483,9 @@ tr26:
 		}
 	goto st197;
 tr30:
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -505,13 +506,13 @@ tr30:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 268 "mod/box/memcached.rl"
+#line 269 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -522,11 +523,11 @@ tr30:
 		}
 	goto st197;
 tr39:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -547,13 +548,13 @@ tr39:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 268 "mod/box/memcached.rl"
+#line 269 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -564,11 +565,11 @@ tr39:
 		}
 	goto st197;
 tr58:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -589,13 +590,13 @@ tr58:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 297 "mod/box/memcached.rl"
+#line 298 "mod/box/memcached.rl"
 	{
 			struct tbuf *b;
 			void *value;
@@ -624,9 +625,9 @@ tr58:
 		}
 	goto st197;
 tr62:
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -647,13 +648,13 @@ tr62:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 297 "mod/box/memcached.rl"
+#line 298 "mod/box/memcached.rl"
 	{
 			struct tbuf *b;
 			void *value;
@@ -682,11 +683,11 @@ tr62:
 		}
 	goto st197;
 tr71:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -707,13 +708,13 @@ tr71:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 297 "mod/box/memcached.rl"
+#line 298 "mod/box/memcached.rl"
 	{
 			struct tbuf *b;
 			void *value;
@@ -742,11 +743,11 @@ tr71:
 		}
 	goto st197;
 tr91:
-#line 514 "mod/box/memcached.rl"
+#line 519 "mod/box/memcached.rl"
 	{cas = natoq(fstart, p);}
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -767,13 +768,13 @@ tr91:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 286 "mod/box/memcached.rl"
+#line 287 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -786,9 +787,9 @@ tr91:
 		}
 	goto st197;
 tr95:
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -809,13 +810,13 @@ tr95:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 286 "mod/box/memcached.rl"
+#line 287 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -828,11 +829,11 @@ tr95:
 		}
 	goto st197;
 tr105:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -853,13 +854,13 @@ tr105:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 286 "mod/box/memcached.rl"
+#line 287 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -872,17 +873,17 @@ tr105:
 		}
 	goto st197;
 tr118:
-#line 515 "mod/box/memcached.rl"
+#line 520 "mod/box/memcached.rl"
 	{incr = natoq(fstart, p);}
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 324 "mod/box/memcached.rl"
+#line 325 "mod/box/memcached.rl"
 	{
 			struct meta *m;
 			struct tbuf *b;
@@ -920,12 +921,16 @@ tr118:
 					bytes = b->len;
 
 					stats.cmd_set++;
-					if (store(txn, key, exptime, flags, bytes, data) == 0) {
+					@try {
+						store(txn, key, exptime, flags, bytes, data);
 						stats.total_items++;
 						add_iov(b->data, b->len);
 						add_iov("\r\n", 2);
-					} else {
-						add_iov("SERVER_ERROR\r\n", 14);
+					}
+					@catch (ClientError *e) {
+						add_iov("SERVER_ERROR ", 13);
+						add_iov(e->errmsg, strlen(e->errmsg));
+						add_iov("\r\n", 2);
 					}
 				} else {
 					add_iov("CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 62);
@@ -935,15 +940,15 @@ tr118:
 		}
 	goto st197;
 tr122:
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 324 "mod/box/memcached.rl"
+#line 325 "mod/box/memcached.rl"
 	{
 			struct meta *m;
 			struct tbuf *b;
@@ -981,12 +986,16 @@ tr122:
 					bytes = b->len;
 
 					stats.cmd_set++;
-					if (store(txn, key, exptime, flags, bytes, data) == 0) {
+					@try {
+						store(txn, key, exptime, flags, bytes, data);
 						stats.total_items++;
 						add_iov(b->data, b->len);
 						add_iov("\r\n", 2);
-					} else {
-						add_iov("SERVER_ERROR\r\n", 14);
+					}
+					@catch (ClientError *e) {
+						add_iov("SERVER_ERROR ", 13);
+						add_iov(e->errmsg, strlen(e->errmsg));
+						add_iov("\r\n", 2);
 					}
 				} else {
 					add_iov("CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 62);
@@ -996,17 +1005,17 @@ tr122:
 		}
 	goto st197;
 tr132:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 324 "mod/box/memcached.rl"
+#line 325 "mod/box/memcached.rl"
 	{
 			struct meta *m;
 			struct tbuf *b;
@@ -1044,12 +1053,16 @@ tr132:
 					bytes = b->len;
 
 					stats.cmd_set++;
-					if (store(txn, key, exptime, flags, bytes, data) == 0) {
+					@try {
+						store(txn, key, exptime, flags, bytes, data);
 						stats.total_items++;
 						add_iov(b->data, b->len);
 						add_iov("\r\n", 2);
-					} else {
-						add_iov("SERVER_ERROR\r\n", 14);
+					}
+					@catch (ClientError *e) {
+						add_iov("SERVER_ERROR ", 13);
+						add_iov(e->errmsg, strlen(e->errmsg));
+						add_iov("\r\n", 2);
 					}
 				} else {
 					add_iov("CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 62);
@@ -1059,107 +1072,107 @@ tr132:
 		}
 	goto st197;
 tr141:
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 375 "mod/box/memcached.rl"
+#line 380 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST || expired(tuple)) {
 				add_iov("NOT_FOUND\r\n", 11);
 			} else {
-				u32 ret_code;
-				if ((ret_code = delete(txn, key)) == 0)
+				@try {
+					delete(txn, key);
 					add_iov("DELETED\r\n", 9);
-				else {
+				}
+				@catch (ClientError *e) {
 					add_iov("SERVER_ERROR ", 13);
-					add_iov(tnt_errcode_desc(ret_code),
-						strlen(tnt_errcode_desc(ret_code)));
+					add_iov(e->errmsg, strlen(e->errmsg));
 					add_iov("\r\n", 2);
 				}
 			}
 		}
 	goto st197;
 tr146:
-#line 505 "mod/box/memcached.rl"
+#line 510 "mod/box/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
 				exptime = exptime + ev_now();
 		}
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 375 "mod/box/memcached.rl"
+#line 380 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST || expired(tuple)) {
 				add_iov("NOT_FOUND\r\n", 11);
 			} else {
-				u32 ret_code;
-				if ((ret_code = delete(txn, key)) == 0)
+				@try {
+					delete(txn, key);
 					add_iov("DELETED\r\n", 9);
-				else {
+				}
+				@catch (ClientError *e) {
 					add_iov("SERVER_ERROR ", 13);
-					add_iov(tnt_errcode_desc(ret_code),
-						strlen(tnt_errcode_desc(ret_code)));
+					add_iov(e->errmsg, strlen(e->errmsg));
 					add_iov("\r\n", 2);
 				}
 			}
 		}
 	goto st197;
 tr157:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 375 "mod/box/memcached.rl"
+#line 380 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST || expired(tuple)) {
 				add_iov("NOT_FOUND\r\n", 11);
 			} else {
-				u32 ret_code;
-				if ((ret_code = delete(txn, key)) == 0)
+				@try {
+					delete(txn, key);
 					add_iov("DELETED\r\n", 9);
-				else {
+				}
+				@catch (ClientError *e) {
 					add_iov("SERVER_ERROR ", 13);
-					add_iov(tnt_errcode_desc(ret_code),
-						strlen(tnt_errcode_desc(ret_code)));
+					add_iov(e->errmsg, strlen(e->errmsg));
 					add_iov("\r\n", 2);
 				}
 			}
 		}
 	goto st197;
 tr169:
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 470 "mod/box/memcached.rl"
+#line 475 "mod/box/memcached.rl"
 	{
 			if (flush_delay > 0) {
 				struct fiber *f = fiber_create("flush_all", -1, -1, flush_all, (void *)flush_delay);
@@ -1171,17 +1184,17 @@ tr169:
 		}
 	goto st197;
 tr174:
-#line 516 "mod/box/memcached.rl"
+#line 521 "mod/box/memcached.rl"
 	{flush_delay = natoq(fstart, p);}
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 470 "mod/box/memcached.rl"
+#line 475 "mod/box/memcached.rl"
 	{
 			if (flush_delay > 0) {
 				struct fiber *f = fiber_create("flush_all", -1, -1, flush_all, (void *)flush_delay);
@@ -1193,17 +1206,17 @@ tr174:
 		}
 	goto st197;
 tr185:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 470 "mod/box/memcached.rl"
+#line 475 "mod/box/memcached.rl"
 	{
 			if (flush_delay > 0) {
 				struct fiber *f = fiber_create("flush_all", -1, -1, flush_all, (void *)flush_delay);
@@ -1215,15 +1228,15 @@ tr185:
 		}
 	goto st197;
 tr195:
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 393 "mod/box/memcached.rl"
+#line 398 "mod/box/memcached.rl"
 	{
 			txn->op = SELECT;
 			fiber_register_cleanup((void *)txn_cleanup, txn);
@@ -1302,25 +1315,25 @@ tr195:
 		}
 	goto st197;
 tr213:
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 484 "mod/box/memcached.rl"
+#line 489 "mod/box/memcached.rl"
 	{
 			return 0;
 		}
 	goto st197;
 tr233:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -1341,13 +1354,13 @@ tr233:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 277 "mod/box/memcached.rl"
+#line 278 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -1358,9 +1371,9 @@ tr233:
 		}
 	goto st197;
 tr237:
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -1381,13 +1394,13 @@ tr237:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 277 "mod/box/memcached.rl"
+#line 278 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -1398,11 +1411,11 @@ tr237:
 		}
 	goto st197;
 tr246:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -1423,13 +1436,13 @@ tr246:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 277 "mod/box/memcached.rl"
+#line 278 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -1440,11 +1453,11 @@ tr246:
 		}
 	goto st197;
 tr263:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -1465,22 +1478,22 @@ tr263:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 263 "mod/box/memcached.rl"
+#line 264 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			STORE;
 		}
 	goto st197;
 tr267:
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -1501,24 +1514,24 @@ tr267:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 263 "mod/box/memcached.rl"
+#line 264 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			STORE;
 		}
 	goto st197;
 tr276:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 518 "mod/box/memcached.rl"
+#line 523 "mod/box/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -1539,28 +1552,28 @@ tr276:
 				goto exit;
 			}
 		}
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 263 "mod/box/memcached.rl"
+#line 264 "mod/box/memcached.rl"
 	{
 			key = read_field(keys);
 			STORE;
 		}
 	goto st197;
 tr281:
-#line 545 "mod/box/memcached.rl"
+#line 550 "mod/box/memcached.rl"
 	{ p++; }
-#line 539 "mod/box/memcached.rl"
+#line 544 "mod/box/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 480 "mod/box/memcached.rl"
+#line 485 "mod/box/memcached.rl"
 	{
 			print_stats();
 		}
@@ -1569,33 +1582,33 @@ st197:
 	if ( ++p == pe )
 		goto _test_eof197;
 case 197:
-#line 1573 "mod/box/memcached.m"
+#line 1586 "mod/box/memcached.m"
 	goto st0;
 tr27:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st13;
 tr40:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
 	goto st13;
 st13:
 	if ( ++p == pe )
 		goto _test_eof13;
 case 13:
-#line 1587 "mod/box/memcached.m"
+#line 1600 "mod/box/memcached.m"
 	if ( (*p) == 10 )
 		goto tr30;
 	goto st0;
 tr28:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st14;
 st14:
 	if ( ++p == pe )
 		goto _test_eof14;
 case 14:
-#line 1599 "mod/box/memcached.m"
+#line 1612 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 32: goto st14;
 		case 110: goto st15;
@@ -1688,18 +1701,18 @@ case 26:
 		goto tr45;
 	goto st0;
 tr45:
-#line 553 "mod/box/memcached.rl"
+#line 558 "mod/box/memcached.rl"
 	{append = true; }
 	goto st27;
 tr209:
-#line 554 "mod/box/memcached.rl"
+#line 559 "mod/box/memcached.rl"
 	{append = false;}
 	goto st27;
 st27:
 	if ( ++p == pe )
 		goto _test_eof27;
 case 27:
-#line 1703 "mod/box/memcached.m"
+#line 1716 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 13: goto st0;
 		case 32: goto st27;
@@ -1708,7 +1721,7 @@ case 27:
 		goto st0;
 	goto tr46;
 tr46:
-#line 489 "mod/box/memcached.rl"
+#line 494 "mod/box/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -1725,7 +1738,7 @@ st28:
 	if ( ++p == pe )
 		goto _test_eof28;
 case 28:
-#line 1729 "mod/box/memcached.m"
+#line 1742 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st29;
 	goto st0;
@@ -1739,49 +1752,49 @@ case 29:
 		goto tr49;
 	goto st0;
 tr49:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st30;
 st30:
 	if ( ++p == pe )
 		goto _test_eof30;
 case 30:
-#line 1750 "mod/box/memcached.m"
+#line 1763 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto tr50;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st30;
 	goto st0;
 tr50:
-#line 512 "mod/box/memcached.rl"
+#line 517 "mod/box/memcached.rl"
 	{flags = natoq(fstart, p);}
 	goto st31;
 st31:
 	if ( ++p == pe )
 		goto _test_eof31;
 case 31:
-#line 1764 "mod/box/memcached.m"
+#line 1777 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st31;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr53;
 	goto st0;
 tr53:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st32;
 st32:
 	if ( ++p == pe )
 		goto _test_eof32;
 case 32:
-#line 1778 "mod/box/memcached.m"
+#line 1791 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto tr54;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st32;
 	goto st0;
 tr54:
-#line 505 "mod/box/memcached.rl"
+#line 510 "mod/box/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -1792,21 +1805,21 @@ st33:
 	if ( ++p == pe )
 		goto _test_eof33;
 case 33:
-#line 1796 "mod/box/memcached.m"
+#line 1809 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st33;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr57;
 	goto st0;
 tr57:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st34;
 st34:
 	if ( ++p == pe )
 		goto _test_eof34;
 case 34:
-#line 1810 "mod/box/memcached.m"
+#line 1823 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr58;
 		case 13: goto tr59;
@@ -1816,30 +1829,30 @@ case 34:
 		goto st34;
 	goto st0;
 tr59:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st35;
 tr72:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
 	goto st35;
 st35:
 	if ( ++p == pe )
 		goto _test_eof35;
 case 35:
-#line 1831 "mod/box/memcached.m"
+#line 1844 "mod/box/memcached.m"
 	if ( (*p) == 10 )
 		goto tr62;
 	goto st0;
 tr60:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st36;
 st36:
 	if ( ++p == pe )
 		goto _test_eof36;
 case 36:
-#line 1843 "mod/box/memcached.m"
+#line 1856 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 32: goto st36;
 		case 110: goto st37;
@@ -1929,7 +1942,7 @@ case 47:
 		goto st0;
 	goto tr76;
 tr76:
-#line 489 "mod/box/memcached.rl"
+#line 494 "mod/box/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -1946,7 +1959,7 @@ st48:
 	if ( ++p == pe )
 		goto _test_eof48;
 case 48:
-#line 1950 "mod/box/memcached.m"
+#line 1963 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st49;
 	goto st0;
@@ -1960,49 +1973,49 @@ case 49:
 		goto tr78;
 	goto st0;
 tr78:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st50;
 st50:
 	if ( ++p == pe )
 		goto _test_eof50;
 case 50:
-#line 1971 "mod/box/memcached.m"
+#line 1984 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto tr79;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st50;
 	goto st0;
 tr79:
-#line 512 "mod/box/memcached.rl"
+#line 517 "mod/box/memcached.rl"
 	{flags = natoq(fstart, p);}
 	goto st51;
 st51:
 	if ( ++p == pe )
 		goto _test_eof51;
 case 51:
-#line 1985 "mod/box/memcached.m"
+#line 1998 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st51;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr82;
 	goto st0;
 tr82:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st52;
 st52:
 	if ( ++p == pe )
 		goto _test_eof52;
 case 52:
-#line 1999 "mod/box/memcached.m"
+#line 2012 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto tr83;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st52;
 	goto st0;
 tr83:
-#line 505 "mod/box/memcached.rl"
+#line 510 "mod/box/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -2013,49 +2026,49 @@ st53:
 	if ( ++p == pe )
 		goto _test_eof53;
 case 53:
-#line 2017 "mod/box/memcached.m"
+#line 2030 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st53;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr86;
 	goto st0;
 tr86:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st54;
 st54:
 	if ( ++p == pe )
 		goto _test_eof54;
 case 54:
-#line 2031 "mod/box/memcached.m"
+#line 2044 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto tr87;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st54;
 	goto st0;
 tr87:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st55;
 st55:
 	if ( ++p == pe )
 		goto _test_eof55;
 case 55:
-#line 2045 "mod/box/memcached.m"
+#line 2058 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st55;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr90;
 	goto st0;
 tr90:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st56;
 st56:
 	if ( ++p == pe )
 		goto _test_eof56;
 case 56:
-#line 2059 "mod/box/memcached.m"
+#line 2072 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr91;
 		case 13: goto tr92;
@@ -2065,30 +2078,30 @@ case 56:
 		goto st56;
 	goto st0;
 tr106:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
 	goto st57;
 tr92:
-#line 514 "mod/box/memcached.rl"
+#line 519 "mod/box/memcached.rl"
 	{cas = natoq(fstart, p);}
 	goto st57;
 st57:
 	if ( ++p == pe )
 		goto _test_eof57;
 case 57:
-#line 2080 "mod/box/memcached.m"
+#line 2093 "mod/box/memcached.m"
 	if ( (*p) == 10 )
 		goto tr95;
 	goto st0;
 tr93:
-#line 514 "mod/box/memcached.rl"
+#line 519 "mod/box/memcached.rl"
 	{cas = natoq(fstart, p);}
 	goto st58;
 st58:
 	if ( ++p == pe )
 		goto _test_eof58;
 case 58:
-#line 2092 "mod/box/memcached.m"
+#line 2105 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr95;
 		case 13: goto st57;
@@ -2149,14 +2162,14 @@ case 65:
 	}
 	goto st0;
 tr107:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
 	goto st66;
 st66:
 	if ( ++p == pe )
 		goto _test_eof66;
 case 66:
-#line 2160 "mod/box/memcached.m"
+#line 2173 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr95;
 		case 13: goto st57;
@@ -2194,18 +2207,18 @@ case 70:
 		goto tr113;
 	goto st0;
 tr113:
-#line 562 "mod/box/memcached.rl"
+#line 567 "mod/box/memcached.rl"
 	{incr_sign = -1;}
 	goto st71;
 tr202:
-#line 561 "mod/box/memcached.rl"
+#line 566 "mod/box/memcached.rl"
 	{incr_sign = 1; }
 	goto st71;
 st71:
 	if ( ++p == pe )
 		goto _test_eof71;
 case 71:
-#line 2209 "mod/box/memcached.m"
+#line 2222 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 13: goto st0;
 		case 32: goto st71;
@@ -2214,7 +2227,7 @@ case 71:
 		goto st0;
 	goto tr114;
 tr114:
-#line 489 "mod/box/memcached.rl"
+#line 494 "mod/box/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -2231,7 +2244,7 @@ st72:
 	if ( ++p == pe )
 		goto _test_eof72;
 case 72:
-#line 2235 "mod/box/memcached.m"
+#line 2248 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st73;
 	goto st0;
@@ -2245,14 +2258,14 @@ case 73:
 		goto tr117;
 	goto st0;
 tr117:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st74;
 st74:
 	if ( ++p == pe )
 		goto _test_eof74;
 case 74:
-#line 2256 "mod/box/memcached.m"
+#line 2269 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr118;
 		case 13: goto tr119;
@@ -2262,30 +2275,30 @@ case 74:
 		goto st74;
 	goto st0;
 tr133:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
 	goto st75;
 tr119:
-#line 515 "mod/box/memcached.rl"
+#line 520 "mod/box/memcached.rl"
 	{incr = natoq(fstart, p);}
 	goto st75;
 st75:
 	if ( ++p == pe )
 		goto _test_eof75;
 case 75:
-#line 2277 "mod/box/memcached.m"
+#line 2290 "mod/box/memcached.m"
 	if ( (*p) == 10 )
 		goto tr122;
 	goto st0;
 tr120:
-#line 515 "mod/box/memcached.rl"
+#line 520 "mod/box/memcached.rl"
 	{incr = natoq(fstart, p);}
 	goto st76;
 st76:
 	if ( ++p == pe )
 		goto _test_eof76;
 case 76:
-#line 2289 "mod/box/memcached.m"
+#line 2302 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr122;
 		case 13: goto st75;
@@ -2346,14 +2359,14 @@ case 83:
 	}
 	goto st0;
 tr134:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
 	goto st84;
 st84:
 	if ( ++p == pe )
 		goto _test_eof84;
 case 84:
-#line 2357 "mod/box/memcached.m"
+#line 2370 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr122;
 		case 13: goto st75;
@@ -2400,7 +2413,7 @@ case 89:
 		goto st0;
 	goto tr140;
 tr140:
-#line 489 "mod/box/memcached.rl"
+#line 494 "mod/box/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -2417,7 +2430,7 @@ st90:
 	if ( ++p == pe )
 		goto _test_eof90;
 case 90:
-#line 2421 "mod/box/memcached.m"
+#line 2434 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr141;
 		case 13: goto st91;
@@ -2425,7 +2438,7 @@ case 90:
 	}
 	goto st0;
 tr147:
-#line 505 "mod/box/memcached.rl"
+#line 510 "mod/box/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -2433,14 +2446,14 @@ tr147:
 		}
 	goto st91;
 tr158:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
 	goto st91;
 st91:
 	if ( ++p == pe )
 		goto _test_eof91;
 case 91:
-#line 2444 "mod/box/memcached.m"
+#line 2457 "mod/box/memcached.m"
 	if ( (*p) == 10 )
 		goto tr141;
 	goto st0;
@@ -2458,14 +2471,14 @@ case 92:
 		goto tr144;
 	goto st0;
 tr144:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st93;
 st93:
 	if ( ++p == pe )
 		goto _test_eof93;
 case 93:
-#line 2469 "mod/box/memcached.m"
+#line 2482 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr146;
 		case 13: goto tr147;
@@ -2475,7 +2488,7 @@ case 93:
 		goto st93;
 	goto st0;
 tr148:
-#line 505 "mod/box/memcached.rl"
+#line 510 "mod/box/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -2486,7 +2499,7 @@ st94:
 	if ( ++p == pe )
 		goto _test_eof94;
 case 94:
-#line 2490 "mod/box/memcached.m"
+#line 2503 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr141;
 		case 13: goto st91;
@@ -2547,14 +2560,14 @@ case 101:
 	}
 	goto st0;
 tr159:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
 	goto st102;
 st102:
 	if ( ++p == pe )
 		goto _test_eof102;
 case 102:
-#line 2558 "mod/box/memcached.m"
+#line 2571 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr141;
 		case 13: goto st91;
@@ -2628,18 +2641,18 @@ case 111:
 	}
 	goto st0;
 tr186:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
 	goto st112;
 tr175:
-#line 516 "mod/box/memcached.rl"
+#line 521 "mod/box/memcached.rl"
 	{flush_delay = natoq(fstart, p);}
 	goto st112;
 st112:
 	if ( ++p == pe )
 		goto _test_eof112;
 case 112:
-#line 2643 "mod/box/memcached.m"
+#line 2656 "mod/box/memcached.m"
 	if ( (*p) == 10 )
 		goto tr169;
 	goto st0;
@@ -2657,14 +2670,14 @@ case 113:
 		goto tr172;
 	goto st0;
 tr172:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st114;
 st114:
 	if ( ++p == pe )
 		goto _test_eof114;
 case 114:
-#line 2668 "mod/box/memcached.m"
+#line 2681 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr174;
 		case 13: goto tr175;
@@ -2674,14 +2687,14 @@ case 114:
 		goto st114;
 	goto st0;
 tr176:
-#line 516 "mod/box/memcached.rl"
+#line 521 "mod/box/memcached.rl"
 	{flush_delay = natoq(fstart, p);}
 	goto st115;
 st115:
 	if ( ++p == pe )
 		goto _test_eof115;
 case 115:
-#line 2685 "mod/box/memcached.m"
+#line 2698 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr169;
 		case 13: goto st112;
@@ -2742,14 +2755,14 @@ case 122:
 	}
 	goto st0;
 tr187:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
 	goto st123;
 st123:
 	if ( ++p == pe )
 		goto _test_eof123;
 case 123:
-#line 2753 "mod/box/memcached.m"
+#line 2766 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr169;
 		case 13: goto st112;
@@ -2780,18 +2793,18 @@ case 126:
 	}
 	goto st0;
 tr191:
-#line 558 "mod/box/memcached.rl"
+#line 563 "mod/box/memcached.rl"
 	{show_cas = false;}
 	goto st127;
 tr198:
-#line 559 "mod/box/memcached.rl"
+#line 564 "mod/box/memcached.rl"
 	{show_cas = true;}
 	goto st127;
 st127:
 	if ( ++p == pe )
 		goto _test_eof127;
 case 127:
-#line 2795 "mod/box/memcached.m"
+#line 2808 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 13: goto st0;
 		case 32: goto st127;
@@ -2800,7 +2813,7 @@ case 127:
 		goto st0;
 	goto tr193;
 tr193:
-#line 489 "mod/box/memcached.rl"
+#line 494 "mod/box/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -2817,7 +2830,7 @@ st128:
 	if ( ++p == pe )
 		goto _test_eof128;
 case 128:
-#line 2821 "mod/box/memcached.m"
+#line 2834 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr195;
 		case 13: goto st129;
@@ -3024,7 +3037,7 @@ case 155:
 		goto st0;
 	goto tr222;
 tr222:
-#line 489 "mod/box/memcached.rl"
+#line 494 "mod/box/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -3041,7 +3054,7 @@ st156:
 	if ( ++p == pe )
 		goto _test_eof156;
 case 156:
-#line 3045 "mod/box/memcached.m"
+#line 3058 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st157;
 	goto st0;
@@ -3055,49 +3068,49 @@ case 157:
 		goto tr224;
 	goto st0;
 tr224:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st158;
 st158:
 	if ( ++p == pe )
 		goto _test_eof158;
 case 158:
-#line 3066 "mod/box/memcached.m"
+#line 3079 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto tr225;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st158;
 	goto st0;
 tr225:
-#line 512 "mod/box/memcached.rl"
+#line 517 "mod/box/memcached.rl"
 	{flags = natoq(fstart, p);}
 	goto st159;
 st159:
 	if ( ++p == pe )
 		goto _test_eof159;
 case 159:
-#line 3080 "mod/box/memcached.m"
+#line 3093 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st159;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr228;
 	goto st0;
 tr228:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st160;
 st160:
 	if ( ++p == pe )
 		goto _test_eof160;
 case 160:
-#line 3094 "mod/box/memcached.m"
+#line 3107 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto tr229;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st160;
 	goto st0;
 tr229:
-#line 505 "mod/box/memcached.rl"
+#line 510 "mod/box/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -3108,21 +3121,21 @@ st161:
 	if ( ++p == pe )
 		goto _test_eof161;
 case 161:
-#line 3112 "mod/box/memcached.m"
+#line 3125 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st161;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr232;
 	goto st0;
 tr232:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st162;
 st162:
 	if ( ++p == pe )
 		goto _test_eof162;
 case 162:
-#line 3126 "mod/box/memcached.m"
+#line 3139 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr233;
 		case 13: goto tr234;
@@ -3132,30 +3145,30 @@ case 162:
 		goto st162;
 	goto st0;
 tr234:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st163;
 tr247:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
 	goto st163;
 st163:
 	if ( ++p == pe )
 		goto _test_eof163;
 case 163:
-#line 3147 "mod/box/memcached.m"
+#line 3160 "mod/box/memcached.m"
 	if ( (*p) == 10 )
 		goto tr237;
 	goto st0;
 tr235:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st164;
 st164:
 	if ( ++p == pe )
 		goto _test_eof164;
 case 164:
-#line 3159 "mod/box/memcached.m"
+#line 3172 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 32: goto st164;
 		case 110: goto st165;
@@ -3247,7 +3260,7 @@ case 175:
 		goto st0;
 	goto tr252;
 tr252:
-#line 489 "mod/box/memcached.rl"
+#line 494 "mod/box/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -3264,7 +3277,7 @@ st176:
 	if ( ++p == pe )
 		goto _test_eof176;
 case 176:
-#line 3268 "mod/box/memcached.m"
+#line 3281 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st177;
 	goto st0;
@@ -3278,49 +3291,49 @@ case 177:
 		goto tr254;
 	goto st0;
 tr254:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st178;
 st178:
 	if ( ++p == pe )
 		goto _test_eof178;
 case 178:
-#line 3289 "mod/box/memcached.m"
+#line 3302 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto tr255;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st178;
 	goto st0;
 tr255:
-#line 512 "mod/box/memcached.rl"
+#line 517 "mod/box/memcached.rl"
 	{flags = natoq(fstart, p);}
 	goto st179;
 st179:
 	if ( ++p == pe )
 		goto _test_eof179;
 case 179:
-#line 3303 "mod/box/memcached.m"
+#line 3316 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st179;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr258;
 	goto st0;
 tr258:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st180;
 st180:
 	if ( ++p == pe )
 		goto _test_eof180;
 case 180:
-#line 3317 "mod/box/memcached.m"
+#line 3330 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto tr259;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st180;
 	goto st0;
 tr259:
-#line 505 "mod/box/memcached.rl"
+#line 510 "mod/box/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -3331,21 +3344,21 @@ st181:
 	if ( ++p == pe )
 		goto _test_eof181;
 case 181:
-#line 3335 "mod/box/memcached.m"
+#line 3348 "mod/box/memcached.m"
 	if ( (*p) == 32 )
 		goto st181;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr262;
 	goto st0;
 tr262:
-#line 488 "mod/box/memcached.rl"
+#line 493 "mod/box/memcached.rl"
 	{ fstart = p; }
 	goto st182;
 st182:
 	if ( ++p == pe )
 		goto _test_eof182;
 case 182:
-#line 3349 "mod/box/memcached.m"
+#line 3362 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 10: goto tr263;
 		case 13: goto tr264;
@@ -3355,30 +3368,30 @@ case 182:
 		goto st182;
 	goto st0;
 tr264:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st183;
 tr277:
-#line 547 "mod/box/memcached.rl"
+#line 552 "mod/box/memcached.rl"
 	{ noreply = true; }
 	goto st183;
 st183:
 	if ( ++p == pe )
 		goto _test_eof183;
 case 183:
-#line 3370 "mod/box/memcached.m"
+#line 3383 "mod/box/memcached.m"
 	if ( (*p) == 10 )
 		goto tr267;
 	goto st0;
 tr265:
-#line 513 "mod/box/memcached.rl"
+#line 518 "mod/box/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st184;
 st184:
 	if ( ++p == pe )
 		goto _test_eof184;
 case 184:
-#line 3382 "mod/box/memcached.m"
+#line 3395 "mod/box/memcached.m"
 	switch( (*p) ) {
 		case 32: goto st184;
 		case 110: goto st185;
@@ -3674,7 +3687,7 @@ case 196:
 	_out: {}
 	}
 
-#line 572 "mod/box/memcached.rl"
+#line 577 "mod/box/memcached.rl"
 
 
 	if (!done) {
@@ -3798,8 +3811,13 @@ memcached_expire(void *data __attribute__((unused)))
 
 		while (keys_to_delete->len > 0) {
 			struct box_txn *txn = txn_alloc(BOX_QUIET);
-			delete(txn, read_field(keys_to_delete));
-			expired_keys++;
+			@try {
+				delete(txn, read_field(keys_to_delete));
+				expired_keys++;
+			}
+			@catch (id e) {
+				/* The error is already logged. */
+			}
 		}
 		stat_collect(stat_base, MEMC_EXPIRED_KEYS, expired_keys);
 
@@ -3816,5 +3834,5 @@ memcached_expire(void *data __attribute__((unused)))
  * Local Variables:
  * mode: c
  * End:
- * vim: syntax=c
+ * vim: syntax=objc
  */
diff --git a/mod/box/memcached.rl b/mod/box/memcached.rl
index 766e2b8d842fb943a8ca9ee52ff8ab030d311c51..a698f056f776e0be2a5ddae3e8657aafa1cd3411 100644
--- a/mod/box/memcached.rl
+++ b/mod/box/memcached.rl
@@ -1,6 +1,6 @@
 /*
- * Copyright (C) 2010 Mail.RU
- * Copyright (C) 2010 Yuriy Vostrikov
+ * Copyright (C) 2010, 2011 Mail.RU
+ * Copyright (C) 2010, 2011 Yuriy Vostrikov
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -80,7 +80,7 @@ natoq(const u8 *start, const u8 *end)
 	return num;
 }
 
-static int
+static void
 store(struct box_txn *txn, void *key, u32 exptime, u32 flags, u32 bytes, u8 *data)
 {
 	u32 box_flags = BOX_QUIET, cardinality = 4;
@@ -112,10 +112,10 @@ store(struct box_txn *txn, void *key, u32 exptime, u32 flags, u32 bytes, u8 *dat
 	int key_len = load_varint32(&key);
 	say_debug("memcached/store key:(%i)'%.*s' exptime:%"PRIu32" flags:%"PRIu32" cas:%"PRIu64,
 		  key_len, key_len, (u8 *)key, exptime, flags, cas);
-	return box_process(txn, INSERT, req); /* FIXME: handle RW/RO */
+	box_process(txn, INSERT, req); /* FIXME: handle RW/RO */
 }
 
-static int
+static void
 delete(struct box_txn *txn, void *key)
 {
 	u32 key_len = 1;
@@ -125,7 +125,7 @@ delete(struct box_txn *txn, void *key)
 	tbuf_append(req, &key_len, sizeof(key_len));
 	tbuf_append_field(req, key);
 
-	return box_process(txn, DELETE, req);
+	box_process(txn, DELETE, req);
 }
 
 static struct box_tuple *
@@ -241,23 +241,24 @@ memcached_dispatch(struct box_txn *txn)
 
 	say_debug("memcached_dispatch '%.*s'", MIN((int)(pe - p), 40) , p);
 
-#define STORE ({									\
-	stats.cmd_set++;								\
-	if (bytes > (1<<20)) {								\
-		add_iov("SERVER_ERROR object too large for cache\r\n", 41);		\
-	} else {									\
-		u32 ret_code;								\
-		if ((ret_code = store(txn, key, exptime, flags, bytes, data)) == 0) {	\
-			stats.total_items++;						\
-			add_iov("STORED\r\n", 8);					\
-		} else {								\
-			add_iov("SERVER_ERROR ", 13);					\
-			add_iov(tnt_errcode_desc(ret_code),			\
-				strlen(tnt_errcode_desc(ret_code)));		\
-			add_iov("\r\n", 2);						\
-		}									\
-	}										\
-})
+#define STORE									\
+do {										\
+	stats.cmd_set++;							\
+	if (bytes > (1<<20)) {							\
+		add_iov("SERVER_ERROR object too large for cache\r\n", 41);	\
+	} else {								\
+		@try {								\
+			store(txn, key, exptime, flags, bytes, data);		\
+			stats.total_items++;					\
+			add_iov("STORED\r\n", 8);				\
+		}								\
+		@catch (ClientError *e) {					\
+			add_iov("SERVER_ERROR ", 13);				\
+			add_iov(e->errmsg, strlen(e->errmsg));			\
+			add_iov("\r\n", 2);					\
+		}								\
+	}									\
+} while (0)
 
 	%%{
 		action set {
@@ -358,12 +359,16 @@ memcached_dispatch(struct box_txn *txn)
 					bytes = b->len;
 
 					stats.cmd_set++;
-					if (store(txn, key, exptime, flags, bytes, data) == 0) {
+					@try {
+						store(txn, key, exptime, flags, bytes, data);
 						stats.total_items++;
 						add_iov(b->data, b->len);
 						add_iov("\r\n", 2);
-					} else {
-						add_iov("SERVER_ERROR\r\n", 14);
+					}
+					@catch (ClientError *e) {
+						add_iov("SERVER_ERROR ", 13);
+						add_iov(e->errmsg, strlen(e->errmsg));
+						add_iov("\r\n", 2);
 					}
 				} else {
 					add_iov("CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 62);
@@ -378,13 +383,13 @@ memcached_dispatch(struct box_txn *txn)
 			if (tuple == NULL || tuple->flags & GHOST || expired(tuple)) {
 				add_iov("NOT_FOUND\r\n", 11);
 			} else {
-				u32 ret_code;
-				if ((ret_code = delete(txn, key)) == 0)
+				@try {
+					delete(txn, key);
 					add_iov("DELETED\r\n", 9);
-				else {
+				}
+				@catch (ClientError *e) {
 					add_iov("SERVER_ERROR ", 13);
-					add_iov(tnt_errcode_desc(ret_code),
-						strlen(tnt_errcode_desc(ret_code)));
+					add_iov(e->errmsg, strlen(e->errmsg));
 					add_iov("\r\n", 2);
 				}
 			}
@@ -692,8 +697,13 @@ memcached_expire(void *data __attribute__((unused)))
 
 		while (keys_to_delete->len > 0) {
 			struct box_txn *txn = txn_alloc(BOX_QUIET);
-			delete(txn, read_field(keys_to_delete));
-			expired_keys++;
+			@try {
+				delete(txn, read_field(keys_to_delete));
+				expired_keys++;
+			}
+			@catch (id e) {
+				/* The error is already logged. */
+			}
 		}
 		stat_collect(stat_base, MEMC_EXPIRED_KEYS, expired_keys);
 
@@ -710,5 +720,5 @@ memcached_expire(void *data __attribute__((unused)))
  * Local Variables:
  * mode: c
  * End:
- * vim: syntax=c
+ * vim: syntax=objc
  */
diff --git a/test/box/protocol.c b/test/box/protocol.c
index 6bc2e864a436b389966404ea8797607983ff4093..89d039f8c7328fbee56f0c9181d83a4fff70af9f 100644
--- a/test/box/protocol.c
+++ b/test/box/protocol.c
@@ -1,6 +1,6 @@
 #include <connector/c/client.h>
-#include <include/errcode.h>
 #include <stdio.h>
+#include "errcode.h"
 
 /** Server connection. Reused between tests. */
 struct tnt_connection *conn;
@@ -32,8 +32,7 @@ void test_bug702397()
 	struct tnt_result tnt_res;
 	int res = tnt_execute_raw(conn, message, sizeof message, &tnt_res);
 	printf("return_code: %s, %s\n",
-	       tnt_errcode_str(tnt_res.errcode >> 8),
-	       tnt_errcode_desc(tnt_res.errcode >> 8));
+	       tnt_errcode_str(tnt_res.errcode >> 8), tnt_res.errmsg);
 }
 
 
@@ -54,8 +53,7 @@ void test_bug702399()
 	struct tnt_result tnt_res;
 	int res = tnt_execute_raw(conn, message, sizeof message, &tnt_res);
 	printf("return_code: %s, %s\n",
-	       tnt_errcode_str(tnt_res.errcode >> 8),
-	       tnt_errcode_desc(tnt_res.errcode >> 8));
+	       tnt_errcode_str(tnt_res.errcode >> 8), tnt_res.errmsg);
 }
 
 
diff --git a/test/box/protocol.result b/test/box/protocol.result
index 20c738386346dc379dccfdef1da43804dfe130c8..9e1d518d63cc7f2c73b73a961878f5dd43d82d21 100644
--- a/test/box/protocol.result
+++ b/test/box/protocol.result
@@ -1,3 +1,3 @@
 return_code: 0
-return_code: ERR_CODE_ILLEGAL_PARAMS, "Illegal parameters"
-return_code: ERR_CODE_NO_SUCH_INDEX, "No index with the given id is defined"
+return_code: ER_ILLEGAL_PARAMS, Illegal parameters, tuple count must be positive
+return_code: ER_NO_SUCH_INDEX, No index #1 is defined in namespace 0
diff --git a/test/box/sql.result b/test/box/sql.result
index 233a1eafedb5f10720fba8649d5f3847ae603468..9c8e9b3d2bb84519fda5335edbb5fa45ee55fbc4 100644
--- a/test/box/sql.result
+++ b/test/box/sql.result
@@ -2,7 +2,7 @@ ping
 ok
 ---
 select * from t0
-An error occurred: ERR_CODE_ILLEGAL_PARAMS, 'Illegal parameters'
+An error occurred: ER_ILLEGAL_PARAMS, 'Illegal parameters, key is not u32�'
 insert into t0 values (1, 'I am a tuple')
 Insert OK, 1 row affected
 select * from t0 where k0 = 1
@@ -44,7 +44,7 @@ select * from t0 where k0 = 1
 Found 1 tuple:
 [1, 'I am the newest tuple']
 update t0 set k1 = 'Huh', k2 = 'Oh-ho-ho' where k0=1
-An error occurred: ERR_CODE_ILLEGAL_PARAMS, 'Illegal parameters'
+An error occurred: ER_NO_SUCH_FIELD, 'Field 2 was not found in the tuple�'
 select * from t0 where k0 = 1
 Found 1 tuple:
 [1, 'I am the newest tuple']
@@ -82,11 +82,11 @@ Found 1 tuple:
 #
 
 select * from t1 where k0 = 0
-An error occurred: ERR_CODE_NO_SUCH_NAMESPACE, 'No namespace with specified id exists'
+An error occurred: ER_NAMESPACE_DISABLED, 'Namespace 1 is disabled�'
 select * from t65537 where k0 = 0
-An error occurred: ERR_CODE_NO_SUCH_NAMESPACE, 'No namespace with specified id exists'
+An error occurred: ER_NO_SUCH_NAMESPACE, 'Namespace 65537 does not exists�'
 select * from t4294967295 where k0 = 0
-An error occurred: ERR_CODE_NO_SUCH_NAMESPACE, 'No namespace with specified id exists'
+An error occurred: ER_NO_SUCH_NAMESPACE, 'Namespace 4294967295 does not exists�'
 
 #
 # A test case for: http://bugs.launchpad.net/bugs/735140
@@ -102,7 +102,7 @@ select * from t0 where k1='Britney'
 Found 1 tuple:
 ['Spears', 'Britney']
 insert into t0 values ('Spears')
-An error occurred: ERR_CODE_ILLEGAL_PARAMS, 'Illegal parameters'
+An error occurred: ER_ILLEGAL_PARAMS, 'Illegal parameters, tuple must have all indexed fields�'
 select * from t0 where k0='Spears'
 Found 1 tuple:
 ['Spears', 'Britney']
diff --git a/test/box_big/sql.result b/test/box_big/sql.result
index 1e57a1e562278586922cd9cb82717b0656f06a4f..a1505ec6579473dfb26239d9a4b75cd20f26c527 100644
--- a/test/box_big/sql.result
+++ b/test/box_big/sql.result
@@ -58,11 +58,11 @@ Delete OK, 1 row affected
 # get away with it.
 #
 insert into t0 values ('Britney')
-An error occurred: ERR_CODE_ILLEGAL_PARAMS, 'Illegal parameters'
+An error occurred: ER_ILLEGAL_PARAMS, 'Illegal parameters, tuple must have all indexed fields�'
 select * from t0 where k1='Anything'
 No match
 insert into t0 values ('Stephanie')
-An error occurred: ERR_CODE_ILLEGAL_PARAMS, 'Illegal parameters'
+An error occurred: ER_ILLEGAL_PARAMS, 'Illegal parameters, tuple must have all indexed fields�'
 select * from t0 where k1='Anything'
 No match
 insert into t0 values ('Spears', 'Britney')
diff --git a/test/box_replication/feeder.cfg b/test/box_replication/feeder.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..90b488bca00e8be0418ff4dc7d4a91b091169902
--- /dev/null
+++ b/test/box_replication/feeder.cfg
@@ -0,0 +1,8 @@
+pid_file = "tarantool.pid"
+
+logger="tee -a tarantool.log"
+
+wal_feeder_bind_ipaddr = "127.0.0.1"
+wal_feeder_bind_port = 33016
+
+wal_feeder_dir = "../"
diff --git a/test/box_replication/feeder_secondary.cfg b/test/box_replication/feeder_secondary.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..83f9c44440203d6146647dcb8dd8b82dcfa4e69d
--- /dev/null
+++ b/test/box_replication/feeder_secondary.cfg
@@ -0,0 +1,8 @@
+pid_file = "tarantool.pid"
+
+logger="tee -a tarantool.log"
+
+wal_feeder_bind_ipaddr = "127.0.0.1"
+wal_feeder_bind_port = 33026
+
+wal_feeder_dir = "../"
diff --git a/test/box_replication/feeder_slave.cfg b/test/box_replication/feeder_slave.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..39c6e32caa4439c903de2c58d813085d66791768
--- /dev/null
+++ b/test/box_replication/feeder_slave.cfg
@@ -0,0 +1,8 @@
+pid_file = "tarantool.pid"
+
+logger="tee -a tarantool.log"
+
+wal_feeder_bind_ipaddr = "127.0.0.1"
+wal_feeder_bind_port = 33116
+
+wal_feeder_dir = "../"
diff --git a/test/box_replication/tarantool.cfg b/test/box_replication/tarantool.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..9f75124040313b3cb13d4cdbdc1771d286e746dd
--- /dev/null
+++ b/test/box_replication/tarantool.cfg
@@ -0,0 +1,13 @@
+slab_alloc_arena=0.05
+pid_file = "tarantool.pid"
+
+primary_port = 33013
+secondary_port = 33014
+admin_port = 33015
+
+logger="tee -a tarantool.log"
+namespace[0].enabled = 1
+namespace[0].index[0].type = "HASH"
+namespace[0].index[0].unique = 1
+namespace[0].index[0].key_field[0].fieldno = 0
+namespace[0].index[0].key_field[0].type = "NUM"
diff --git a/test/box_replication/tarantool_beholder.cfg b/test/box_replication/tarantool_beholder.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..6e74dbd891b52c979fc2ca76e87062f21cc9ab24
--- /dev/null
+++ b/test/box_replication/tarantool_beholder.cfg
@@ -0,0 +1,13 @@
+slab_alloc_arena=0.05
+pid_file = "tarantool.pid"
+
+primary_port = 33013
+secondary_port = 33024
+admin_port = 33025
+
+logger="tee -a tarantool.log"
+namespace[0].enabled = 1
+namespace[0].index[0].type = "HASH"
+namespace[0].index[0].unique = 1
+namespace[0].index[0].key_field[0].fieldno = 0
+namespace[0].index[0].key_field[0].type = "NUM"
diff --git a/test/box_replication/tarantool_beholder_to_slave.cfg b/test/box_replication/tarantool_beholder_to_slave.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..9b97eba8a467cbdecdfe2286c2f1f3bb4ae18e00
--- /dev/null
+++ b/test/box_replication/tarantool_beholder_to_slave.cfg
@@ -0,0 +1,17 @@
+slab_alloc_arena=0.05
+pid_file = "tarantool.pid"
+
+primary_port = 33013
+secondary_port = 33024
+admin_port = 33025
+
+logger="tee -a tarantool.log"
+namespace[0].enabled = 1
+namespace[0].index[0].type = "HASH"
+namespace[0].index[0].unique = 1
+namespace[0].index[0].key_field[0].fieldno = 0
+namespace[0].index[0].key_field[0].type = "NUM"
+
+remote_hot_standby = 1
+wal_feeder_ipaddr = "127.0.0.1"
+wal_feeder_port = 33116
diff --git a/test/box_replication/tarantool_slave.cfg b/test/box_replication/tarantool_slave.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..0630c863552a63ff3d788347eb7b884020251f6f
--- /dev/null
+++ b/test/box_replication/tarantool_slave.cfg
@@ -0,0 +1,17 @@
+slab_alloc_arena=0.05
+pid_file = "tarantool.pid"
+
+primary_port = 33113
+secondary_port = 33114
+admin_port = 33115
+
+logger="tee -a tarantool.log"
+namespace[0].enabled = 1
+namespace[0].index[0].type = "HASH"
+namespace[0].index[0].unique = 1
+namespace[0].index[0].key_field[0].fieldno = 0
+namespace[0].index[0].key_field[0].type = "NUM"
+
+remote_hot_standby = 1
+wal_feeder_ipaddr = "127.0.0.1"
+wal_feeder_port = 33016
diff --git a/test/box_replication/tarantool_slave_secondary_feeder_replication.cfg b/test/box_replication/tarantool_slave_secondary_feeder_replication.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..cd918ba1226da0b3e0b4114e91f2bc10ebf035dd
--- /dev/null
+++ b/test/box_replication/tarantool_slave_secondary_feeder_replication.cfg
@@ -0,0 +1,17 @@
+slab_alloc_arena=0.05
+pid_file = "tarantool.pid"
+
+primary_port = 33113
+secondary_port = 33114
+admin_port = 33115
+
+logger="tee -a tarantool.log"
+namespace[0].enabled = 1
+namespace[0].index[0].type = "HASH"
+namespace[0].index[0].unique = 1
+namespace[0].index[0].key_field[0].fieldno = 0
+namespace[0].index[0].key_field[0].type = "NUM"
+
+remote_hot_standby = 1
+wal_feeder_ipaddr = "127.0.0.1"
+wal_feeder_port = 33026
diff --git a/test/box_replication/tarantool_slave_to_master.cfg b/test/box_replication/tarantool_slave_to_master.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..33eaa9799bb24ba56ed5df8a76eaaa153e6dd366
--- /dev/null
+++ b/test/box_replication/tarantool_slave_to_master.cfg
@@ -0,0 +1,13 @@
+slab_alloc_arena=0.05
+pid_file = "tarantool.pid"
+
+primary_port = 33113
+secondary_port = 33114
+admin_port = 33115
+
+logger="tee -a tarantool.log"
+namespace[0].enabled = 1
+namespace[0].index[0].type = "HASH"
+namespace[0].index[0].unique = 1
+namespace[0].index[0].key_field[0].fieldno = 0
+namespace[0].index[0].key_field[0].type = "NUM"
diff --git a/test/box_replication/tarantool_to_slave.cfg b/test/box_replication/tarantool_to_slave.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..fe58a84e4108d2fc959a332d1a6a74f5e08857f8
--- /dev/null
+++ b/test/box_replication/tarantool_to_slave.cfg
@@ -0,0 +1,17 @@
+slab_alloc_arena=0.05
+pid_file = "tarantool.pid"
+
+primary_port = 33013
+secondary_port = 33014
+admin_port = 33015
+
+logger="tee -a tarantool.log"
+namespace[0].enabled = 1
+namespace[0].index[0].type = "HASH"
+namespace[0].index[0].unique = 1
+namespace[0].index[0].key_field[0].fieldno = 0
+namespace[0].index[0].key_field[0].type = "NUM"
+
+remote_hot_standby = 1
+wal_feeder_ipaddr = "127.0.0.1"
+wal_feeder_port = 33116
diff --git a/test/lib/box.py b/test/lib/box.py
index edc3f0f90a0612113964ad501a13b4bbc2c433db..a2603ccd6854df89735489c63253e1a638120a3b 100644
--- a/test/lib/box.py
+++ b/test/lib/box.py
@@ -31,6 +31,9 @@ class Box(TarantoolConnection):
     res = ""
     while len(res) < length:
       buf = self.socket.recv(length - len(res))
+      if not buf:
+        raise RuntimeError("Got EOF from socket, the server has "
+                           "probably crashed")
       res = res + buf
     return res
 
diff --git a/test/lib/server.py b/test/lib/server.py
index f15cd9c94340d8f4e514149402e7eb595065c653..e7892ff1f39531b61d6da761922ab0403480d467 100644
--- a/test/lib/server.py
+++ b/test/lib/server.py
@@ -23,6 +23,15 @@ def wait_until_connected(port):
     except socket.error as e:
       time.sleep(0.001)
 
+def check_port(port):
+  """Check if the port we're connecting to is available"""
+  try:
+    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    sock.connect(("localhost", port))
+  except socket.error as e:
+     return
+  raise RuntimeError("The server is already running on port {0}".format(port))
+
 def prepare_gdb(args):
   """Prepare server startup arguments to run under gdb."""
   if "TERM" in os.environ:
@@ -33,7 +42,7 @@ def prepare_gdb(args):
   if term not in ["xterm", "rxvt", "urxvt", "gnome-terminal", "konsole"]:
     raise RuntimeError("--gdb: unsupported terminal {0}".format(term))
 
-  args = [ term, "-e ", "gdb", "-ex", "break main", "-ex", "run" ] + args
+  args = [ term, "-e", "gdb", "-ex", "break main", "-ex", "run" ] + args
   return args
 
 def prepare_valgrind(args, valgrind_log):
@@ -141,12 +150,8 @@ class Server(object):
 
     if self.gdb == True:
         raise RuntimeError("'--gdb' and '--start-and-exit' can't be defined together")
-    pid = os.fork()
-    if pid > 0:
-      os.wait()
-    else:
-      with daemon.DaemonContext(working_directory = self.vardir):
-        os.execvp(args[0], args)
+    with daemon.DaemonContext(working_directory = self.vardir):
+      os.execvp(args[0], args)
 
   def prepare_args(self):
     return [self.binary]
@@ -165,6 +170,7 @@ class Server(object):
       version = self.version()
       print "Starting {0} {1}.".format(os.path.basename(self.binary),
                                        version)
+    check_port(self.port)
     args = self.prepare_args()
     if self.gdb:
       args = prepare_gdb(args)
@@ -178,21 +184,31 @@ class Server(object):
     wait_until_connected(self.port)
 # Set is_started flag, to nicely support cleanup during an exception.
     self.is_started = True
+    with open(self.pidfile) as f:
+      self.pid = int(f.read())
 
   def stop(self, silent=True):
     """Stop server instance. Do nothing if the server is not started,
     to properly shut down the server in case of an exception during
     start up."""
-    if self.is_started:
-      if not silent:
-        print "Stopping the server..."
-      if self.process == None:
-        self.kill_old_server()
-      self.process.terminate()
+    if not self.is_started:
+       if not silent:
+         print "The server is not started."
+       return
+    if not silent:
+      print "Stopping the server..."
+
+    if self.process == None:
+      self.kill_old_server()
+    else:
+      self.kill_server()
+
+    if self.gdb:
+      self.process.expect(pexpect.EOF, timeout = 1 << 30)
+    else:
       self.process.expect(pexpect.EOF)
-      self.is_started = False
-    elif not silent:
-      print "The server is not started."
+    self.is_started = False
+    self.pid = None
 
   def deploy(self, config=None, binary=None, vardir=None,
              mem=None, start_and_exit=None, gdb=None, valgrind=None, silent=True,
@@ -224,6 +240,14 @@ class Server(object):
                                 stderr = subprocess.STDOUT).stdout.read()
       print output
 
+  def kill_server(self):
+    """Kill a server which was started correctly"""
+    try:
+      os.kill(self.pid, signal.SIGTERM)
+    except OSError as e:
+      print e
+      pass
+
   def kill_old_server(self, silent=True):
     """Kill old server instance if it exists."""
     if os.access(self.pidfile, os.F_OK) == False:
diff --git a/test/lib/sql_ast.py b/test/lib/sql_ast.py
index 45050ad085dd0de6f054a3fd338e591620294d88..39d563f2c95b51959363b370a1f4886c65f320c7 100644
--- a/test/lib/sql_ast.py
+++ b/test/lib/sql_ast.py
@@ -24,39 +24,43 @@ DELETE_REQUEST_TYPE = 20
 PING_REQUEST_TYPE = 65280
 
 ER = {
- 0x00000000: ("ERR_CODE_OK"                  , "OK")                                      ,
- 0x00000102: ("ERR_CODE_NONMASTER"           ,  "Non master connection, but it should be"),
- 0x00000202: ("ERR_CODE_ILLEGAL_PARAMS"      ,  "Illegal parameters")                     ,
- 0x00000302: ("ERR_CODE_BAD_UID"             ,  "Uid not from this storage range")        ,
- 0x00000401: ("ERR_CODE_NODE_IS_RO"          ,  "Node is marked as read-only")            ,
- 0x00000501: ("ERR_CODE_NODE_IS_NOT_LOCKED"  ,  "Node isn't locked")                      ,
- 0x00000601: ("ERR_CODE_NODE_IS_LOCKED"      ,  "Node is locked")                         ,
- 0x00000701: ("ERR_CODE_MEMORY_ISSUE"        ,  "Some memory issues")                     ,
- 0x00000802: ("ERR_CODE_BAD_INTEGRITY"       ,  "Bad graph integrity")                    ,
- 0x00000a02: ("ERR_CODE_UNSUPPORTED_COMMAND" ,  "Unsupported command")                    ,
- 0x00001801: ("ERR_CODE_CANNOT_REGISTER"     ,  "Can not register new user")              ,
- 0x00001a01: ("ERR_CODE_CANNOT_INIT_ALERT_ID",  "Can not generate alert id")              ,
- 0x00001b02: ("ERR_CODE_CANNOT_DEL"          ,  "Can\'t del node")                        ,
- 0x00001c02: ("ERR_CODE_USER_NOT_REGISTERED" ,  "User isn\'t registered")                 ,
- 0x00001d02: ("ERR_CODE_SYNTAX_ERROR"        ,  "Syntax error in query")                  ,
- 0x00001e02: ("ERR_CODE_WRONG_FIELD"         ,  "Unknown field")                          ,
- 0x00001f02: ("ERR_CODE_WRONG_NUMBER"        ,  "Number value is out of range")           ,
- 0x00002002: ("ERR_CODE_DUPLICATE"           ,  "Insert already existing object")         ,
- 0x00002202: ("ERR_CODE_UNSUPPORTED_ORDER"   ,  "Can not order result")                   ,
- 0x00002302: ("ERR_CODE_MULTIWRITE"          ,  "Multiple to update/delete")              ,
- 0x00002400: ("ERR_CODE_NOTHING"             ,  "nothing to do (not an error)")           ,
- 0x00002502: ("ERR_CODE_UPDATE_ID"           ,  "id\'s update")                           ,
- 0x00002602: ("ERR_CODE_WRONG_VERSION"       ,  "Unsupported version of protocol")        ,
- 0x00002702: ("ERR_CODE_UNKNOWN_ERROR"       ,  "")                                       ,
- 0x00003102: ("ERR_CODE_NODE_NOT_FOUND"      ,  "")                                       ,
- 0x00003702: ("ERR_CODE_NODE_FOUND"          ,  "")                                       ,
- 0x00003802: ("ERR_CODE_INDEX_VIOLATION"     ,  "")                                       ,
- 0x00003902: ("ERR_CODE_NO_SUCH_NAMESPACE"   ,  "No namespace with specified id exists")  ,
+    0: "ER_OK"                  ,
+    1: "ER_NONMASTER"           ,
+    2: "ER_ILLEGAL_PARAMS"      ,
+    3: "ER_BAD_UID"             ,
+    4: "ER_TUPLE_IS_RO"         ,
+    5: "ER_TUPLE_IS_NOT_LOCKED" ,
+    6: "ER_TUPLE_IS_LOCKED"     ,
+    7: "ER_MEMORY_ISSUE"        ,
+    8: "ER_BAD_INTEGRITY"       ,
+   10: "ER_UNSUPPORTED_COMMAND" ,
+   24: "ER_CANNOT_REGISTER"     ,
+   26: "ER_CANNOT_INIT_ALERT_ID",
+   27: "ER_CANNOT_DEL"          ,
+   28: "ER_USER_NOT_REGISTERED" ,
+   29: "ER_SYNTAX_ERROR"        ,
+   30: "ER_WRONG_FIELD"         ,
+   31: "ER_WRONG_NUMBER"        ,
+   32: "ER_DUPLICATE"           ,
+   34: "ER_UNSUPPORTED_ORDER"   ,
+   35: "ER_MULTIWRITE"          ,
+   36: "ER_NOTHING"             ,
+   37: "ER_UPDATE_ID"           ,
+   38: "ER_WRONG_VERSION"       ,
+   39: "ER_WAL_IO"              ,
+   49: "ER_TUPLE_NOT_FOUND"     ,
+   52: "ER_NAMESPACE_DISABLED"  ,
+   53: "ER_NO_SUCH_INDEX"       ,
+   54: "ER_NO_SUCH_FIELD"       ,
+   55: "ER_TUPLE_FOUND"         ,
+   56: "ER_INDEX_VIOLATION"     ,
+   57: "ER_NO_SUCH_NAMESPACE"
 }
 
-def format_error(return_code):
-  return "An error occurred: {0}, \'{1}'".format(ER[return_code][0],
-                                                 ER[return_code][1])
+
+def format_error(return_code, response):
+  return "An error occurred: {0}, \'{1}'".format(ER[return_code >> 8],
+                                                 response[4:])
 
 
 def save_varint32(value):
@@ -185,9 +189,9 @@ class StatementInsert(StatementPing):
   def unpack(self, response):
     (return_code,) = struct.unpack("<L", response[:4])
     if return_code:
-      return format_error(return_code)
-    (result_code, row_count) = struct.unpack("<LL", response)
-    return "Insert OK, {0} row affected".format(row_count)
+      return format_error(return_code, response)
+    (tuple_count,) = struct.unpack("<L", response[4:8])
+    return "Insert OK, {0} row affected".format(tuple_count)
 
 
 class StatementUpdate(StatementPing):
@@ -212,9 +216,9 @@ class StatementUpdate(StatementPing):
   def unpack(self, response):
     (return_code,) = struct.unpack("<L", response[:4])
     if return_code:
-      return format_error(return_code)
-    (result_code, row_count) = struct.unpack("<LL", response)
-    return "Update OK, {0} row affected".format(row_count)
+      return format_error(return_code, response)
+    (tuple_count,) = struct.unpack("<L", response[4:8])
+    return "Update OK, {0} row affected".format(tuple_count)
 
 class StatementDelete(StatementPing):
   reqeust_type = DELETE_REQUEST_TYPE
@@ -235,9 +239,9 @@ class StatementDelete(StatementPing):
   def unpack(self, response):
     (return_code,) = struct.unpack("<L", response[:4])
     if return_code:
-      return format_error(return_code)
-    (result_code, row_count) = struct.unpack("<LL", response)
-    return "Delete OK, {0} row affected".format(row_count)
+      return format_error(return_code, response)
+    (tuple_count,) = struct.unpack("<L", response[4:8])
+    return "Delete OK, {0} row affected".format(tuple_count)
 
 class StatementSelect(StatementPing):
   reqeust_type = SELECT_REQUEST_TYPE
@@ -274,9 +278,9 @@ class StatementSelect(StatementPing):
     return buf[:offset]
 
   def unpack(self, response):
-    if len(response) == 4:
-      (return_code,) = struct.unpack("<L", response[:4])
-      return format_error(return_code)
+    (return_code,) = struct.unpack("<L", response[:4])
+    if return_code:
+      return format_error(return_code, response)
     (tuple_count,) = struct.unpack("<L", response[4:8])
     tuples = []
     offset = 8
diff --git a/test/lib/tarantool_box_server.py b/test/lib/tarantool_box_server.py
index 04bfa7ffef64cb39e074dfc4ad9f219e036001ac..87b600e1e4b25793b198b2efcfa856d291314962 100644
--- a/test/lib/tarantool_box_server.py
+++ b/test/lib/tarantool_box_server.py
@@ -34,12 +34,10 @@ class TarantoolBoxServer(TarantoolServer):
                           stdout = subprocess.PIPE,
                           stderr = subprocess.PIPE)
 
-  def wait_sync(self, lsn):
-    synced = 0
-    while synced == 0:
-      synced = 1
+  def wait_lsn(self, lsn):
+    while True:
       data = self.admin.execute("show info\n", silent=True)
       info = yaml.load(data)["info"]
-      if (info["lsn"] != lsn):
-        synced = 0
-	time.sleep(0.1)
+      if (int(info["lsn"]) >= lsn):
+        break
+      time.sleep(0.01)
diff --git a/test/tarantool b/test/tarantool
index 96202b7237104539d57f380ac90968ec55988c5e..a54adbb03ba09cb40e7633c43c73c5ac4b83ac3b 100755
--- a/test/tarantool
+++ b/test/tarantool
@@ -1,4 +1,4 @@
-#! /usr/bin/python 
+#!/usr/bin/env python 
 """A simplistic client for tarantool/box: administrative
 console and SQL client.
 
diff --git a/test/test-run.py b/test/test-run.py
index 71461d81acc490ff3e399f08e260d840eca5cdd5..a511941b205b7d52b8a0de5002cd97991501567d 100755
--- a/test/test-run.py
+++ b/test/test-run.py
@@ -1,4 +1,4 @@
-#! /usr/bin/python
+#!/usr/bin/env python
 """Tarantool regression test suite front-end."""
 
 __author__ = "Konstantin Osipov <kostja.osipov@gmail.com>"
diff --git a/third_party/proctitle.c b/third_party/proctitle.c
index f2872b66ec08ac01fe140a7026ddb6746a6ff921..1a580765ad2aa21aaca3c2f379711fc50b09d5f7 100644
--- a/third_party/proctitle.c
+++ b/third_party/proctitle.c
@@ -28,6 +28,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <limits.h>
 
 #ifdef HAVE_SYS_PSTAT_H
 #include <sys/pstat.h>		/* for HP-UX */
@@ -216,7 +217,18 @@ init_set_proc_title(int argc, char **argv)
 	 */
 	ps_buffer_fixed_size = 0;
 #else
-	snprintf(ps_buffer, ps_buffer_size, "tarantool: ");
+	{
+		char basename_buf[PATH_MAX];
+
+		/*
+		 * At least partially mimic FreeBSD, which for
+		 * ./a.out outputs:
+		 *
+		 * a.out: custom title here (a.out)
+	         */
+		snprintf(basename_buf, sizeof basename_buf, "%s", argv[0]);
+		snprintf(ps_buffer, ps_buffer_size, "%s: ", basename(basename_buf));
+	}
 
 	ps_buffer_fixed_size = strlen(ps_buffer);