From 62efbe3e7fff6a85aeb5fbbe8c28495f37276743 Mon Sep 17 00:00:00 2001
From: Konstantin Osipov <kostja@tarantool.org>
Date: Fri, 12 Oct 2012 23:36:09 +0400
Subject: [PATCH] Introduce struct iobuf. Extract buffered fiber I/O. Change
 memcached, admin and iproto connection to use the buffered IO. Make
 coio_writev accept a hint about the total size. Remove fiber iov.

---
 include/coio.h               |   2 +-
 include/coio_buf.h           |  30 +-
 include/fiber.h              |  55 +-
 include/fio.h                |   2 +-
 include/iobuf.h              | 213 ++++++++
 include/iproto.h             |  13 +-
 mod/box/box.m                |  10 +-
 mod/box/memcached-grammar.m  | 977 ++++++++++++++++++-----------------
 mod/box/memcached-grammar.rl |  93 ++--
 mod/box/memcached.m          | 114 ++--
 mod/box/port.h               |  11 +-
 mod/box/port.m               |  48 +-
 src/CMakeLists.txt           |   1 +
 src/admin.m                  | 122 ++---
 src/admin.rl                 |  18 +-
 src/coio.m                   |  22 +-
 src/coio_buf.m               |   1 -
 src/fiber.m                  | 104 +---
 src/iobuf.m                  | 339 ++++++++++++
 src/iproto.m                 |  82 +--
 src/palloc.m                 |   8 +-
 src/replica.m                |  36 +-
 src/tarantool_lua.m          |   1 +
 test/box/admin.result        |   1 +
 test/box/admin.test          |   1 +
 test/lib/admin_connection.py |   2 +-
 26 files changed, 1383 insertions(+), 923 deletions(-)
 create mode 100644 include/iobuf.h
 create mode 100644 src/iobuf.m

diff --git a/include/coio.h b/include/coio.h
index 5907e8c070..6bbd26d112 100644
--- a/include/coio.h
+++ b/include/coio.h
@@ -87,7 +87,7 @@ void
 coio_write(struct coio *coio, const void *buf, size_t sz);
 
 ssize_t
-coio_writev(struct coio *coio, struct iovec *iov, int iovcnt);
+coio_writev(struct coio *coio, struct iovec *iov, int iovcnt, size_t size);
 
 void
 coio_service_init(struct coio_service *service, const char *name,
diff --git a/include/coio_buf.h b/include/coio_buf.h
index 8c8e8ffb5d..94d23bc28e 100644
--- a/include/coio_buf.h
+++ b/include/coio_buf.h
@@ -29,34 +29,26 @@
  * SUCH DAMAGE.
  */
 #include "coio.h"
-#include "tbuf.h"
+#include "iobuf.h"
 /** Buffered cooperative IO */
 
-extern int coio_readahead;
-
-static inline void
-coio_binit(int readahead)
-{
-	coio_readahead =  readahead;
-}
-
+/** Read at least sz bytes, buffered. Return 0 in case of EOF. */
 static inline ssize_t
-coio_bread(struct coio *coio, struct tbuf *buf, size_t sz)
+coio_bread(struct coio *coio, struct ibuf *buf, size_t sz)
 {
-	tbuf_ensure(buf, MAX(sz, coio_readahead));
-	ssize_t n = coio_read_ahead(coio, tbuf_end(buf),
-				    sz, tbuf_unused(buf));
-	buf->size += n;
+	ibuf_reserve(buf, sz);
+	ssize_t n = coio_read_ahead(coio, buf->end, sz, ibuf_unused(buf));
+	buf->end += n;
 	return n;
 }
 
+/** Read at least sz bytes, buffered. Throw an exception in case of EOF. */
 static inline ssize_t
-coio_breadn(struct coio *coio, struct tbuf *buf, size_t sz)
+coio_breadn(struct coio *coio, struct ibuf *buf, size_t sz)
 {
-	tbuf_ensure(buf, MAX(sz, coio_readahead));
-	ssize_t n = coio_readn_ahead(coio, tbuf_end(buf),
-				     sz, tbuf_unused(buf));
-	buf->size += n;
+	ibuf_reserve(buf, sz);
+	ssize_t n = coio_readn_ahead(coio, buf->end, sz, ibuf_unused(buf));
+	buf->end += n;
 	return n;
 }
 
diff --git a/include/fiber.h b/include/fiber.h
index 2c196f2253..d5ddda671c 100644
--- a/include/fiber.h
+++ b/include/fiber.h
@@ -33,11 +33,7 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include <unistd.h>
-#include <sys/uio.h>
-#include <netinet/in.h>
-
 #include <tarantool_ev.h>
-#include <tbuf.h>
 #include <coro.h>
 #include <util.h>
 #include "third_party/queue.h"
@@ -76,11 +72,6 @@ struct fiber {
 	ev_timer timer;
 	ev_child cw;
 
-	struct tbuf iov;
-	size_t iov_cnt;
-	struct tbuf rbuf;
-	struct tbuf cleanup;
-
 	SLIST_ENTRY(fiber) link, zombie_link;
 
 	/* ASCIIZ name of this fiber. */
@@ -91,14 +82,6 @@ struct fiber {
 	struct fiber *waiter;
 };
 
-static inline struct iovec *iovec(const struct tbuf *t)
-{
-	return (struct iovec *)t->data;
-}
-
-typedef void (*fiber_cleanup_handler) (void *);
-void fiber_register_cleanup(fiber_cleanup_handler handler, void *data);
-
 extern __thread struct fiber *fiber;
 
 void fiber_init(void);
@@ -115,43 +98,6 @@ fiber_yield_to(struct fiber *f);
 
 void fiber_destroy_all();
 
-inline static void iov_add_unsafe(const void *buf, size_t len)
-{
-	struct iovec *v;
-	assert(tbuf_unused(&fiber->iov) >= sizeof(*v));
-	v = tbuf_end(&fiber->iov);
-	v->iov_base = (void *)buf;
-	v->iov_len = len;
-	fiber->iov.size += sizeof(*v);
-	fiber->iov_cnt++;
-}
-
-inline static void iov_ensure(size_t count)
-{
-	tbuf_ensure(&fiber->iov, sizeof(struct iovec) * count);
-}
-
-/* Add to fiber's iov vector. */
-inline static void iov_add(const void *buf, size_t len)
-{
-	iov_ensure(1);
-	iov_add_unsafe(buf, len);
-}
-
-inline static void iov_dup(const void *buf, size_t len)
-{
-	void *copy = palloc(fiber->gc_pool, len);
-	memcpy(copy, buf, len);
-	iov_add(copy, len);
-}
-
-struct coio;
-/* Reset the fiber's iov vector. */
-ssize_t iov_flush(struct coio *coio);
-/* Write everything in the fiber's iov vector to fiber socket. */
-void iov_reset();
-
-void fiber_cleanup(void);
 void fiber_gc(void);
 void fiber_call(struct fiber *callee, ...);
 void fiber_wakeup(struct fiber *f);
@@ -172,6 +118,7 @@ void fiber_testcancel(void);
  */
 void fiber_setcancelstate(bool enable);
 void fiber_sleep(ev_tstamp s);
+struct tbuf;
 void fiber_info(struct tbuf *out);
 void
 fiber_schedule(ev_watcher *watcher, int event __attribute__((unused)));
diff --git a/include/fio.h b/include/fio.h
index e6a30fafed..f2c2a072d6 100644
--- a/include/fio.h
+++ b/include/fio.h
@@ -149,7 +149,7 @@ struct fio_batch
 	ssize_t bytes;
 	/** Total number of batched rows.*/
 	int rows;
-	/** A cap on how many can be batched. Can be set to INT_MAX. */
+	/** A cap on how many rows can be batched. Can be set to INT_MAX. */
 	int max_rows;
 	/** A system cap on how many rows can be batched. */
 	long max_iov;
diff --git a/include/iobuf.h b/include/iobuf.h
new file mode 100644
index 0000000000..34bdad90db
--- /dev/null
+++ b/include/iobuf.h
@@ -0,0 +1,213 @@
+#ifndef TARANTOOL_IOBUF_H_INCLUDED
+#define TARANTOOL_IOBUF_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.
+ */
+#include <sys/uio.h>
+#include "util.h"
+#include "third_party/queue.h"
+
+/** {{{ Input buffer.
+ *
+ * Continuous piece of memory to store input.
+ * Allocated in factors of cfg.readahead.
+ * Maintains position of the data "to be processed".
+ *
+ * Typical use case:
+ *
+ * struct ibuf *in;
+ * coio_bread(coio, in, request_len);
+ * if (ibuf_size(in) >= request_len) {
+ *	process_request(in->pos, request_len);
+ *	in->pos += request_len;
+ * }
+ */
+struct ibuf
+{
+	struct palloc_pool *pool;
+	char *buf;
+	/** Start of input. */
+	char *pos;
+	/** End of useful input */
+	char *end;
+	/** Buffer size. */
+	size_t capacity;
+};
+
+/** Reserve space for sz bytes in the input buffer. */
+void
+ibuf_reserve(struct ibuf *ibuf, size_t sz);
+
+/** How much data is read and is not parsed yet. */
+static inline size_t
+ibuf_size(struct ibuf *ibuf)
+{
+	return ibuf->end - ibuf->pos;
+}
+
+/** How much data can we fit beyond buf->end */
+static inline size_t
+ibuf_unused(struct ibuf *ibuf)
+{
+	return ibuf->buf + ibuf->capacity - ibuf->end;
+}
+
+/* }}} */
+
+/* {{{ Output buffer. */
+
+enum { IOBUF_IOV_MAX = 32 };
+
+/**
+ * An output buffer is an array of struct iovec vectors
+ * for writev().
+ * Each buffer is allocated on palloc pool.
+ * Buffer size grows by a factor of 2. With this growth factor,
+ * the number of used buffers is unlikely to ever exceed the
+ * hard limit of IOBUF_IOV_MAX. If it does, an exception is
+ * raised.
+ */
+struct obuf
+{
+	struct palloc_pool *pool;
+	/* How many bytes are in the buffer. */
+	size_t size;
+	/** Position of the "current" iovec. */
+	size_t pos;
+	/** How many bytes are actually allocated for each iovec. */
+	size_t capacity[IOBUF_IOV_MAX];
+	/**
+	 * List of iovec vectors, each vector is at least twice
+	 * as big as the previous one. The vector following the
+	 * last allocated one is always zero-initialized
+	 * (iov_base = NULL, iov_len = 0).
+	 */
+	struct iovec iov[IOBUF_IOV_MAX];
+};
+
+/** How many bytes are in the output buffer. */
+static inline size_t
+obuf_size(struct obuf *obuf)
+{
+	return obuf->size;
+}
+/**
+ * Reserve size bytes in the output buffer
+ * and return a pointer to the reserved
+ * data. Returns a pointer to a continuous piece of
+ * memory.
+ * Typical use case:
+ * void *psize = obuf_book(buf, sizeof(uint32_t));
+ * for (...)
+ *	obuf_dup(buf, ...);
+ * uint32_t total = obuf_size(buf);
+ * memcpy(psize, &total, sizeof(total);
+ * iobuf_flush();
+ */
+void *
+obuf_book(struct obuf *obuf, size_t size);
+
+/** Append data to the output buffer. */
+void
+obuf_dup(struct obuf *obuf, void *data, size_t size);
+
+/**
+ * Output buffer savepoint. It's possible to
+ * save the current buffer state in a savepoint
+ * and roll back to the saved state at any time
+ * before iobuf_flush()
+ */
+struct obuf_svp
+{
+	size_t pos;
+	size_t iov_len;
+	size_t size;
+};
+
+static inline struct obuf_svp
+obuf_create_svp(struct obuf *buf)
+{
+	struct obuf_svp svp = {
+		.pos = buf->pos, .iov_len = buf->iov[buf->pos].iov_len,
+		.size = buf->size
+	};
+	return svp;
+}
+
+/** Forget anything added to output buffer after the savepoint. */
+void
+obuf_rollback_to_svp(struct obuf *buf, struct obuf_svp *svp);
+
+/* }}} */
+
+/** {{{  Input/output pair. */
+struct iobuf
+{
+	/** Used for iobuf cache. */
+	SLIST_ENTRY(iobuf) next;
+	/** Input buffer. */
+	struct ibuf in;
+	/** Output buffer. */
+	struct obuf out;
+};
+
+/** Create an instance of input/output buffer. */
+struct iobuf *
+iobuf_create(const char *name);
+
+/** Destroy an input/output buffer. */
+void
+iobuf_destroy(struct iobuf *iobuf);
+
+struct coio;
+
+/** Flush output using cooperative I/O and garbage collect.
+ * @return number of bytes written
+ */
+ssize_t
+iobuf_flush(struct iobuf *iobuf, struct coio *coio);
+
+/**
+ * Must be called when we are done sending all output,
+ * and there is likely no cached input.
+ * Is automatically called by iobuf_flush().
+ */
+void
+iobuf_gc(struct iobuf *iobuf);
+
+extern int cfg_readahead;
+
+static inline void
+iobuf_init_readahead(int readahead)
+{
+	cfg_readahead =  readahead;
+}
+
+/* }}} */
+
+#endif /* TARANTOOL_IOBUF_H_INCLUDED */
diff --git a/include/iproto.h b/include/iproto.h
index e36b463bef..2168d11544 100644
--- a/include/iproto.h
+++ b/include/iproto.h
@@ -29,8 +29,10 @@
  * SUCH DAMAGE.
  */
 #include <stdint.h>
+#include <stdarg.h>
 
-#include <tbuf.h> /* for struct tbuf */
+struct tbuf;
+struct obuf;
 
 enum {
 	/** Maximal iproto package body length (2GiB) */
@@ -56,13 +58,8 @@ struct iproto_header_retcode {
 	uint32_t ret_code;
 } __attribute__((packed));
 
-static inline struct iproto_header *iproto(const struct tbuf *t)
-{
-	return (struct iproto_header *)t->data;
-}
-
-
-typedef void (*iproto_callback) (uint32_t msg_code, struct tbuf *request);
+typedef void (*iproto_callback)(struct obuf *obuf,
+				uint32_t msg_code, struct tbuf *request);
 
 void iproto_interact(va_list ap);
 
diff --git a/mod/box/box.m b/mod/box/box.m
index 6f54431f05..5dd51e7cd0 100644
--- a/mod/box/box.m
+++ b/mod/box/box.m
@@ -127,15 +127,17 @@ box_process_ro(struct txn *txn, struct port *port,
 
 
 static void
-iproto_primary_port_handler(u32 op, struct tbuf *request_data)
+iproto_primary_port_handler(struct obuf *out, u32 op,
+			    struct tbuf *request_data)
 {
-	box_process(txn_begin(), port_iproto_create(), op, request_data);
+	box_process(txn_begin(), port_iproto_create(out), op, request_data);
 }
 
 static void
-iproto_secondary_port_handler(u32 op, struct tbuf *request_data)
+iproto_secondary_port_handler(struct obuf *out, u32 op,
+			      struct tbuf *request_data)
 {
-	box_process_ro(txn_begin(), port_iproto_create(), op, request_data);
+	box_process_ro(txn_begin(), port_iproto_create(out), op, request_data);
 }
 
 static void
diff --git a/mod/box/memcached-grammar.m b/mod/box/memcached-grammar.m
index 67f9295a4f..90594c4766 100644
--- a/mod/box/memcached-grammar.m
+++ b/mod/box/memcached-grammar.m
@@ -42,11 +42,11 @@ static const int memcached_en_main = 1;
 
 
 static int __attribute__((noinline))
-memcached_dispatch(struct coio *coio)
+memcached_dispatch(struct coio *coio, struct iobuf *iobuf)
 {
 	int cs;
-	u8 *p, *pe;
-	u8 *fstart;
+	char *p, *pe;
+	char *fstart;
 	struct tbuf *keys = tbuf_alloc(fiber->gc_pool);
 	void *key;
 	bool append, show_cas;
@@ -54,24 +54,27 @@ memcached_dispatch(struct coio *coio)
 	u64 cas, incr;
 	u32 flags, exptime, bytes;
 	bool noreply = false;
-	u8 *data = NULL;
+	char *data = NULL;
 	bool done = false;
-	size_t saved_iov_cnt = fiber->iov_cnt;
 	uintptr_t flush_delay = 0;
 	size_t keys_count = 0;
+	struct ibuf *in = &iobuf->in;
+	struct obuf *out = &iobuf->out;
+	/* Savepoint for 'noreply' */
+	struct obuf_svp obuf_svp = obuf_create_svp(out);
 
-	p = fiber->rbuf.data;
-	pe = fiber->rbuf.data + fiber->rbuf.size;
+	p = in->pos;
+	pe = in->end;
 
 	say_debug("memcached_dispatch '%.*s'", MIN((int)(pe - p), 40) , p);
 
 	
-#line 70 "mod/box/memcached-grammar.m"
+#line 73 "mod/box/memcached-grammar.m"
 	{
 	cs = memcached_start;
 	}
 
-#line 75 "mod/box/memcached-grammar.m"
+#line 78 "mod/box/memcached-grammar.m"
 	{
 	if ( p == pe )
 		goto _test_eof;
@@ -143,7 +146,7 @@ case 5:
 		goto st0;
 	goto tr15;
 tr15:
-#line 220 "mod/box/memcached-grammar.rl"
+#line 223 "mod/box/memcached-grammar.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -160,7 +163,7 @@ st6:
 	if ( ++p == pe )
 		goto _test_eof6;
 case 6:
-#line 164 "mod/box/memcached-grammar.m"
+#line 167 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st7;
 	goto st0;
@@ -174,49 +177,49 @@ case 7:
 		goto tr17;
 	goto st0;
 tr17:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st8;
 st8:
 	if ( ++p == pe )
 		goto _test_eof8;
 case 8:
-#line 185 "mod/box/memcached-grammar.m"
+#line 188 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr18;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st8;
 	goto st0;
 tr18:
-#line 243 "mod/box/memcached-grammar.rl"
+#line 246 "mod/box/memcached-grammar.rl"
 	{flags = natoq(fstart, p);}
 	goto st9;
 st9:
 	if ( ++p == pe )
 		goto _test_eof9;
 case 9:
-#line 199 "mod/box/memcached-grammar.m"
+#line 202 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st9;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr21;
 	goto st0;
 tr21:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st10;
 st10:
 	if ( ++p == pe )
 		goto _test_eof10;
 case 10:
-#line 213 "mod/box/memcached-grammar.m"
+#line 216 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr22;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st10;
 	goto st0;
 tr22:
-#line 236 "mod/box/memcached-grammar.rl"
+#line 239 "mod/box/memcached-grammar.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -227,21 +230,21 @@ st11:
 	if ( ++p == pe )
 		goto _test_eof11;
 case 11:
-#line 231 "mod/box/memcached-grammar.m"
+#line 234 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st11;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr25;
 	goto st0;
 tr25:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st12;
 st12:
 	if ( ++p == pe )
 		goto _test_eof12;
 case 12:
-#line 245 "mod/box/memcached-grammar.m"
+#line 248 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr26;
 		case 13: goto tr27;
@@ -251,20 +254,23 @@ case 12:
 		goto st12;
 	goto st0;
 tr26:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -274,35 +280,38 @@ tr26:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 65 "mod/box/memcached-grammar.rl"
+#line 68 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple != NULL && !expired(tuple))
-				iov_add("NOT_STORED\r\n", 12);
+				obuf_dup(out, "NOT_STORED\r\n", 12);
 			else
 				STORE;
 		}
 	goto st197;
 tr30:
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -312,37 +321,40 @@ tr30:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 65 "mod/box/memcached-grammar.rl"
+#line 68 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple != NULL && !expired(tuple))
-				iov_add("NOT_STORED\r\n", 12);
+				obuf_dup(out, "NOT_STORED\r\n", 12);
 			else
 				STORE;
 		}
 	goto st197;
 tr39:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -352,37 +364,40 @@ tr39:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 65 "mod/box/memcached-grammar.rl"
+#line 68 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple != NULL && !expired(tuple))
-				iov_add("NOT_STORED\r\n", 12);
+				obuf_dup(out, "NOT_STORED\r\n", 12);
 			else
 				STORE;
 		}
 	goto st197;
 tr58:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -392,13 +407,13 @@ tr58:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 94 "mod/box/memcached-grammar.rl"
+#line 97 "mod/box/memcached-grammar.rl"
 	{
 			struct tbuf *b;
 			void *value;
@@ -407,7 +422,7 @@ tr58:
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST) {
-				iov_add("NOT_STORED\r\n", 12);
+				obuf_dup(out, "NOT_STORED\r\n", 12);
 			} else {
 				value = tuple_field(tuple, 3);
 				value_len = load_varint32(&value);
@@ -427,18 +442,21 @@ tr58:
 		}
 	goto st197;
 tr62:
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -448,13 +466,13 @@ tr62:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 94 "mod/box/memcached-grammar.rl"
+#line 97 "mod/box/memcached-grammar.rl"
 	{
 			struct tbuf *b;
 			void *value;
@@ -463,7 +481,7 @@ tr62:
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST) {
-				iov_add("NOT_STORED\r\n", 12);
+				obuf_dup(out, "NOT_STORED\r\n", 12);
 			} else {
 				value = tuple_field(tuple, 3);
 				value_len = load_varint32(&value);
@@ -483,20 +501,23 @@ tr62:
 		}
 	goto st197;
 tr71:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -506,13 +527,13 @@ tr71:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 94 "mod/box/memcached-grammar.rl"
+#line 97 "mod/box/memcached-grammar.rl"
 	{
 			struct tbuf *b;
 			void *value;
@@ -521,7 +542,7 @@ tr71:
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST) {
-				iov_add("NOT_STORED\r\n", 12);
+				obuf_dup(out, "NOT_STORED\r\n", 12);
 			} else {
 				value = tuple_field(tuple, 3);
 				value_len = load_varint32(&value);
@@ -541,20 +562,23 @@ tr71:
 		}
 	goto st197;
 tr91:
-#line 245 "mod/box/memcached-grammar.rl"
+#line 248 "mod/box/memcached-grammar.rl"
 	{cas = natoq(fstart, p);}
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -564,37 +588,40 @@ tr91:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 83 "mod/box/memcached-grammar.rl"
+#line 86 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || expired(tuple))
-				iov_add("NOT_FOUND\r\n", 11);
+				obuf_dup(out, "NOT_FOUND\r\n", 11);
 			else if (meta(tuple)->cas != cas)
-				iov_add("EXISTS\r\n", 8);
+				obuf_dup(out, "EXISTS\r\n", 8);
 			else
 				STORE;
 		}
 	goto st197;
 tr95:
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -604,39 +631,42 @@ tr95:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 83 "mod/box/memcached-grammar.rl"
+#line 86 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || expired(tuple))
-				iov_add("NOT_FOUND\r\n", 11);
+				obuf_dup(out, "NOT_FOUND\r\n", 11);
 			else if (meta(tuple)->cas != cas)
-				iov_add("EXISTS\r\n", 8);
+				obuf_dup(out, "EXISTS\r\n", 8);
 			else
 				STORE;
 		}
 	goto st197;
 tr105:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -646,36 +676,36 @@ tr105:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 83 "mod/box/memcached-grammar.rl"
+#line 86 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || expired(tuple))
-				iov_add("NOT_FOUND\r\n", 11);
+				obuf_dup(out, "NOT_FOUND\r\n", 11);
 			else if (meta(tuple)->cas != cas)
-				iov_add("EXISTS\r\n", 8);
+				obuf_dup(out, "EXISTS\r\n", 8);
 			else
 				STORE;
 		}
 	goto st197;
 tr118:
-#line 246 "mod/box/memcached-grammar.rl"
+#line 249 "mod/box/memcached-grammar.rl"
 	{incr = natoq(fstart, p);}
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 121 "mod/box/memcached-grammar.rl"
+#line 124 "mod/box/memcached-grammar.rl"
 	{
 			struct meta *m;
 			struct tbuf *b;
@@ -686,7 +716,7 @@ tr118:
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST || expired(tuple)) {
-				iov_add("NOT_FOUND\r\n", 11);
+				obuf_dup(out, "NOT_FOUND\r\n", 11);
 			} else {
 				m = meta(tuple);
 				field = tuple_field(tuple, 3);
@@ -716,31 +746,31 @@ tr118:
 					@try {
 						store(key, exptime, flags, bytes, data);
 						stats.total_items++;
-						iov_add(b->data, b->size);
-						iov_add("\r\n", 2);
+						obuf_dup(out, b->data, b->size);
+						obuf_dup(out, "\r\n", 2);
 					}
 					@catch (ClientError *e) {
-						iov_add("SERVER_ERROR ", 13);
-						iov_add(e->errmsg, strlen(e->errmsg));
-						iov_add("\r\n", 2);
+						obuf_dup(out, "SERVER_ERROR ", 13);
+						obuf_dup(out, e->errmsg, strlen(e->errmsg));
+						obuf_dup(out, "\r\n", 2);
 					}
 				} else {
-					iov_add("CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 62);
+					obuf_dup(out, "CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 62);
 				}
 			}
 
 		}
 	goto st197;
 tr122:
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 121 "mod/box/memcached-grammar.rl"
+#line 124 "mod/box/memcached-grammar.rl"
 	{
 			struct meta *m;
 			struct tbuf *b;
@@ -751,7 +781,7 @@ tr122:
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST || expired(tuple)) {
-				iov_add("NOT_FOUND\r\n", 11);
+				obuf_dup(out, "NOT_FOUND\r\n", 11);
 			} else {
 				m = meta(tuple);
 				field = tuple_field(tuple, 3);
@@ -781,33 +811,33 @@ tr122:
 					@try {
 						store(key, exptime, flags, bytes, data);
 						stats.total_items++;
-						iov_add(b->data, b->size);
-						iov_add("\r\n", 2);
+						obuf_dup(out, b->data, b->size);
+						obuf_dup(out, "\r\n", 2);
 					}
 					@catch (ClientError *e) {
-						iov_add("SERVER_ERROR ", 13);
-						iov_add(e->errmsg, strlen(e->errmsg));
-						iov_add("\r\n", 2);
+						obuf_dup(out, "SERVER_ERROR ", 13);
+						obuf_dup(out, e->errmsg, strlen(e->errmsg));
+						obuf_dup(out, "\r\n", 2);
 					}
 				} else {
-					iov_add("CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 62);
+					obuf_dup(out, "CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 62);
 				}
 			}
 
 		}
 	goto st197;
 tr132:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 121 "mod/box/memcached-grammar.rl"
+#line 124 "mod/box/memcached-grammar.rl"
 	{
 			struct meta *m;
 			struct tbuf *b;
@@ -818,7 +848,7 @@ tr132:
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST || expired(tuple)) {
-				iov_add("NOT_FOUND\r\n", 11);
+				obuf_dup(out, "NOT_FOUND\r\n", 11);
 			} else {
 				m = meta(tuple);
 				field = tuple_field(tuple, 3);
@@ -848,215 +878,218 @@ tr132:
 					@try {
 						store(key, exptime, flags, bytes, data);
 						stats.total_items++;
-						iov_add(b->data, b->size);
-						iov_add("\r\n", 2);
+						obuf_dup(out, b->data, b->size);
+						obuf_dup(out, "\r\n", 2);
 					}
 					@catch (ClientError *e) {
-						iov_add("SERVER_ERROR ", 13);
-						iov_add(e->errmsg, strlen(e->errmsg));
-						iov_add("\r\n", 2);
+						obuf_dup(out, "SERVER_ERROR ", 13);
+						obuf_dup(out, e->errmsg, strlen(e->errmsg));
+						obuf_dup(out, "\r\n", 2);
 					}
 				} else {
-					iov_add("CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 62);
+					obuf_dup(out, "CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 62);
 				}
 			}
 
 		}
 	goto st197;
 tr141:
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 176 "mod/box/memcached-grammar.rl"
+#line 179 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST || expired(tuple)) {
-				iov_add("NOT_FOUND\r\n", 11);
+				obuf_dup(out, "NOT_FOUND\r\n", 11);
 			} else {
 				@try {
 					delete(key);
-					iov_add("DELETED\r\n", 9);
+					obuf_dup(out, "DELETED\r\n", 9);
 				}
 				@catch (ClientError *e) {
-					iov_add("SERVER_ERROR ", 13);
-					iov_add(e->errmsg, strlen(e->errmsg));
-					iov_add("\r\n", 2);
+					obuf_dup(out, "SERVER_ERROR ", 13);
+					obuf_dup(out, e->errmsg, strlen(e->errmsg));
+					obuf_dup(out, "\r\n", 2);
 				}
 			}
 		}
 	goto st197;
 tr146:
-#line 236 "mod/box/memcached-grammar.rl"
+#line 239 "mod/box/memcached-grammar.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
 				exptime = exptime + ev_now();
 		}
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 176 "mod/box/memcached-grammar.rl"
+#line 179 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST || expired(tuple)) {
-				iov_add("NOT_FOUND\r\n", 11);
+				obuf_dup(out, "NOT_FOUND\r\n", 11);
 			} else {
 				@try {
 					delete(key);
-					iov_add("DELETED\r\n", 9);
+					obuf_dup(out, "DELETED\r\n", 9);
 				}
 				@catch (ClientError *e) {
-					iov_add("SERVER_ERROR ", 13);
-					iov_add(e->errmsg, strlen(e->errmsg));
-					iov_add("\r\n", 2);
+					obuf_dup(out, "SERVER_ERROR ", 13);
+					obuf_dup(out, e->errmsg, strlen(e->errmsg));
+					obuf_dup(out, "\r\n", 2);
 				}
 			}
 		}
 	goto st197;
 tr157:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 176 "mod/box/memcached-grammar.rl"
+#line 179 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST || expired(tuple)) {
-				iov_add("NOT_FOUND\r\n", 11);
+				obuf_dup(out, "NOT_FOUND\r\n", 11);
 			} else {
 				@try {
 					delete(key);
-					iov_add("DELETED\r\n", 9);
+					obuf_dup(out, "DELETED\r\n", 9);
 				}
 				@catch (ClientError *e) {
-					iov_add("SERVER_ERROR ", 13);
-					iov_add(e->errmsg, strlen(e->errmsg));
-					iov_add("\r\n", 2);
+					obuf_dup(out, "SERVER_ERROR ", 13);
+					obuf_dup(out, e->errmsg, strlen(e->errmsg));
+					obuf_dup(out, "\r\n", 2);
 				}
 			}
 		}
 	goto st197;
 tr169:
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 205 "mod/box/memcached-grammar.rl"
+#line 208 "mod/box/memcached-grammar.rl"
 	{
 			struct fiber *f = fiber_create("flush_all", flush_all);
 			fiber_call(f, flush_delay);
-			iov_add("OK\r\n", 4);
+			obuf_dup(out, "OK\r\n", 4);
 		}
 	goto st197;
 tr174:
-#line 247 "mod/box/memcached-grammar.rl"
+#line 250 "mod/box/memcached-grammar.rl"
 	{flush_delay = natoq(fstart, p);}
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 205 "mod/box/memcached-grammar.rl"
+#line 208 "mod/box/memcached-grammar.rl"
 	{
 			struct fiber *f = fiber_create("flush_all", flush_all);
 			fiber_call(f, flush_delay);
-			iov_add("OK\r\n", 4);
+			obuf_dup(out, "OK\r\n", 4);
 		}
 	goto st197;
 tr185:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 205 "mod/box/memcached-grammar.rl"
+#line 208 "mod/box/memcached-grammar.rl"
 	{
 			struct fiber *f = fiber_create("flush_all", flush_all);
 			fiber_call(f, flush_delay);
-			iov_add("OK\r\n", 4);
+			obuf_dup(out, "OK\r\n", 4);
 		}
 	goto st197;
 tr195:
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 194 "mod/box/memcached-grammar.rl"
+#line 197 "mod/box/memcached-grammar.rl"
 	{
 			@try {
-				memcached_get(keys_count, keys, show_cas);
+				memcached_get(out, keys_count, keys, show_cas);
 			} @catch (ClientError *e) {
-				iov_reset();
-				iov_add("SERVER_ERROR ", 13);
-				iov_add(e->errmsg, strlen(e->errmsg));
-				iov_add("\r\n", 2);
+				obuf_rollback_to_svp(out, &obuf_svp);
+				obuf_dup(out, "SERVER_ERROR ", 13);
+				obuf_dup(out, e->errmsg, strlen(e->errmsg));
+				obuf_dup(out, "\r\n", 2);
 			}
 		}
 	goto st197;
 tr213:
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 215 "mod/box/memcached-grammar.rl"
+#line 218 "mod/box/memcached-grammar.rl"
 	{
 			return -1;
 		}
 	goto st197;
 tr233:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -1066,35 +1099,38 @@ tr233:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 74 "mod/box/memcached-grammar.rl"
+#line 77 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || expired(tuple))
-				iov_add("NOT_STORED\r\n", 12);
+				obuf_dup(out, "NOT_STORED\r\n", 12);
 			else
 				STORE;
 		}
 	goto st197;
 tr237:
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -1104,37 +1140,40 @@ tr237:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 74 "mod/box/memcached-grammar.rl"
+#line 77 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || expired(tuple))
-				iov_add("NOT_STORED\r\n", 12);
+				obuf_dup(out, "NOT_STORED\r\n", 12);
 			else
 				STORE;
 		}
 	goto st197;
 tr246:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -1144,37 +1183,40 @@ tr246:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 74 "mod/box/memcached-grammar.rl"
+#line 77 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || expired(tuple))
-				iov_add("NOT_STORED\r\n", 12);
+				obuf_dup(out, "NOT_STORED\r\n", 12);
 			else
 				STORE;
 		}
 	goto st197;
 tr263:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -1184,31 +1226,34 @@ tr263:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 60 "mod/box/memcached-grammar.rl"
+#line 63 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			STORE;
 		}
 	goto st197;
 tr267:
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -1218,33 +1263,36 @@ tr267:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 60 "mod/box/memcached-grammar.rl"
+#line 63 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			STORE;
 		}
 	goto st197;
 tr276:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 249 "mod/box/memcached-grammar.rl"
+#line 252 "mod/box/memcached-grammar.rl"
 	{
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -1254,63 +1302,63 @@ tr276:
 				goto exit;
 			}
 		}
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 60 "mod/box/memcached-grammar.rl"
+#line 63 "mod/box/memcached-grammar.rl"
 	{
 			key = read_field(keys);
 			STORE;
 		}
 	goto st197;
 tr281:
-#line 274 "mod/box/memcached-grammar.rl"
+#line 280 "mod/box/memcached-grammar.rl"
 	{ p++; }
-#line 268 "mod/box/memcached-grammar.rl"
+#line 274 "mod/box/memcached-grammar.rl"
 	{
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
-#line 211 "mod/box/memcached-grammar.rl"
+#line 214 "mod/box/memcached-grammar.rl"
 	{
-			print_stats();
+			print_stats(out);
 		}
 	goto st197;
 st197:
 	if ( ++p == pe )
 		goto _test_eof197;
 case 197:
-#line 1288 "mod/box/memcached-grammar.m"
+#line 1336 "mod/box/memcached-grammar.m"
 	goto st0;
 tr27:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
 	goto st13;
 tr40:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st13;
 st13:
 	if ( ++p == pe )
 		goto _test_eof13;
 case 13:
-#line 1302 "mod/box/memcached-grammar.m"
+#line 1350 "mod/box/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr30;
 	goto st0;
 tr28:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
 	goto st14;
 st14:
 	if ( ++p == pe )
 		goto _test_eof14;
 case 14:
-#line 1314 "mod/box/memcached-grammar.m"
+#line 1362 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 32: goto st14;
 		case 78: goto st15;
@@ -1424,18 +1472,18 @@ case 26:
 		goto tr45;
 	goto st0;
 tr45:
-#line 282 "mod/box/memcached-grammar.rl"
+#line 288 "mod/box/memcached-grammar.rl"
 	{append = true; }
 	goto st27;
 tr209:
-#line 283 "mod/box/memcached-grammar.rl"
+#line 289 "mod/box/memcached-grammar.rl"
 	{append = false;}
 	goto st27;
 st27:
 	if ( ++p == pe )
 		goto _test_eof27;
 case 27:
-#line 1439 "mod/box/memcached-grammar.m"
+#line 1487 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 13: goto st0;
 		case 32: goto st27;
@@ -1444,7 +1492,7 @@ case 27:
 		goto st0;
 	goto tr46;
 tr46:
-#line 220 "mod/box/memcached-grammar.rl"
+#line 223 "mod/box/memcached-grammar.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -1461,7 +1509,7 @@ st28:
 	if ( ++p == pe )
 		goto _test_eof28;
 case 28:
-#line 1465 "mod/box/memcached-grammar.m"
+#line 1513 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st29;
 	goto st0;
@@ -1475,49 +1523,49 @@ case 29:
 		goto tr49;
 	goto st0;
 tr49:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st30;
 st30:
 	if ( ++p == pe )
 		goto _test_eof30;
 case 30:
-#line 1486 "mod/box/memcached-grammar.m"
+#line 1534 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr50;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st30;
 	goto st0;
 tr50:
-#line 243 "mod/box/memcached-grammar.rl"
+#line 246 "mod/box/memcached-grammar.rl"
 	{flags = natoq(fstart, p);}
 	goto st31;
 st31:
 	if ( ++p == pe )
 		goto _test_eof31;
 case 31:
-#line 1500 "mod/box/memcached-grammar.m"
+#line 1548 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st31;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr53;
 	goto st0;
 tr53:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st32;
 st32:
 	if ( ++p == pe )
 		goto _test_eof32;
 case 32:
-#line 1514 "mod/box/memcached-grammar.m"
+#line 1562 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr54;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st32;
 	goto st0;
 tr54:
-#line 236 "mod/box/memcached-grammar.rl"
+#line 239 "mod/box/memcached-grammar.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -1528,21 +1576,21 @@ st33:
 	if ( ++p == pe )
 		goto _test_eof33;
 case 33:
-#line 1532 "mod/box/memcached-grammar.m"
+#line 1580 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st33;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr57;
 	goto st0;
 tr57:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st34;
 st34:
 	if ( ++p == pe )
 		goto _test_eof34;
 case 34:
-#line 1546 "mod/box/memcached-grammar.m"
+#line 1594 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr58;
 		case 13: goto tr59;
@@ -1552,30 +1600,30 @@ case 34:
 		goto st34;
 	goto st0;
 tr59:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
 	goto st35;
 tr72:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st35;
 st35:
 	if ( ++p == pe )
 		goto _test_eof35;
 case 35:
-#line 1567 "mod/box/memcached-grammar.m"
+#line 1615 "mod/box/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr62;
 	goto st0;
 tr60:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
 	goto st36;
 st36:
 	if ( ++p == pe )
 		goto _test_eof36;
 case 36:
-#line 1579 "mod/box/memcached-grammar.m"
+#line 1627 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 32: goto st36;
 		case 78: goto st37;
@@ -1682,7 +1730,7 @@ case 47:
 		goto st0;
 	goto tr76;
 tr76:
-#line 220 "mod/box/memcached-grammar.rl"
+#line 223 "mod/box/memcached-grammar.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -1699,7 +1747,7 @@ st48:
 	if ( ++p == pe )
 		goto _test_eof48;
 case 48:
-#line 1703 "mod/box/memcached-grammar.m"
+#line 1751 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st49;
 	goto st0;
@@ -1713,49 +1761,49 @@ case 49:
 		goto tr78;
 	goto st0;
 tr78:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st50;
 st50:
 	if ( ++p == pe )
 		goto _test_eof50;
 case 50:
-#line 1724 "mod/box/memcached-grammar.m"
+#line 1772 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr79;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st50;
 	goto st0;
 tr79:
-#line 243 "mod/box/memcached-grammar.rl"
+#line 246 "mod/box/memcached-grammar.rl"
 	{flags = natoq(fstart, p);}
 	goto st51;
 st51:
 	if ( ++p == pe )
 		goto _test_eof51;
 case 51:
-#line 1738 "mod/box/memcached-grammar.m"
+#line 1786 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st51;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr82;
 	goto st0;
 tr82:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st52;
 st52:
 	if ( ++p == pe )
 		goto _test_eof52;
 case 52:
-#line 1752 "mod/box/memcached-grammar.m"
+#line 1800 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr83;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st52;
 	goto st0;
 tr83:
-#line 236 "mod/box/memcached-grammar.rl"
+#line 239 "mod/box/memcached-grammar.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -1766,49 +1814,49 @@ st53:
 	if ( ++p == pe )
 		goto _test_eof53;
 case 53:
-#line 1770 "mod/box/memcached-grammar.m"
+#line 1818 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st53;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr86;
 	goto st0;
 tr86:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st54;
 st54:
 	if ( ++p == pe )
 		goto _test_eof54;
 case 54:
-#line 1784 "mod/box/memcached-grammar.m"
+#line 1832 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr87;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st54;
 	goto st0;
 tr87:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
 	goto st55;
 st55:
 	if ( ++p == pe )
 		goto _test_eof55;
 case 55:
-#line 1798 "mod/box/memcached-grammar.m"
+#line 1846 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st55;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr90;
 	goto st0;
 tr90:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st56;
 st56:
 	if ( ++p == pe )
 		goto _test_eof56;
 case 56:
-#line 1812 "mod/box/memcached-grammar.m"
+#line 1860 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr91;
 		case 13: goto tr92;
@@ -1818,30 +1866,30 @@ case 56:
 		goto st56;
 	goto st0;
 tr106:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st57;
 tr92:
-#line 245 "mod/box/memcached-grammar.rl"
+#line 248 "mod/box/memcached-grammar.rl"
 	{cas = natoq(fstart, p);}
 	goto st57;
 st57:
 	if ( ++p == pe )
 		goto _test_eof57;
 case 57:
-#line 1833 "mod/box/memcached-grammar.m"
+#line 1881 "mod/box/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr95;
 	goto st0;
 tr93:
-#line 245 "mod/box/memcached-grammar.rl"
+#line 248 "mod/box/memcached-grammar.rl"
 	{cas = natoq(fstart, p);}
 	goto st58;
 st58:
 	if ( ++p == pe )
 		goto _test_eof58;
 case 58:
-#line 1845 "mod/box/memcached-grammar.m"
+#line 1893 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr95;
 		case 13: goto st57;
@@ -1915,14 +1963,14 @@ case 65:
 	}
 	goto st0;
 tr107:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st66;
 st66:
 	if ( ++p == pe )
 		goto _test_eof66;
 case 66:
-#line 1926 "mod/box/memcached-grammar.m"
+#line 1974 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr95;
 		case 13: goto st57;
@@ -1966,18 +2014,18 @@ case 70:
 		goto tr113;
 	goto st0;
 tr113:
-#line 291 "mod/box/memcached-grammar.rl"
+#line 297 "mod/box/memcached-grammar.rl"
 	{incr_sign = -1;}
 	goto st71;
 tr202:
-#line 290 "mod/box/memcached-grammar.rl"
+#line 296 "mod/box/memcached-grammar.rl"
 	{incr_sign = 1; }
 	goto st71;
 st71:
 	if ( ++p == pe )
 		goto _test_eof71;
 case 71:
-#line 1981 "mod/box/memcached-grammar.m"
+#line 2029 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 13: goto st0;
 		case 32: goto st71;
@@ -1986,7 +2034,7 @@ case 71:
 		goto st0;
 	goto tr114;
 tr114:
-#line 220 "mod/box/memcached-grammar.rl"
+#line 223 "mod/box/memcached-grammar.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -2003,7 +2051,7 @@ st72:
 	if ( ++p == pe )
 		goto _test_eof72;
 case 72:
-#line 2007 "mod/box/memcached-grammar.m"
+#line 2055 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st73;
 	goto st0;
@@ -2017,14 +2065,14 @@ case 73:
 		goto tr117;
 	goto st0;
 tr117:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st74;
 st74:
 	if ( ++p == pe )
 		goto _test_eof74;
 case 74:
-#line 2028 "mod/box/memcached-grammar.m"
+#line 2076 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr118;
 		case 13: goto tr119;
@@ -2034,30 +2082,30 @@ case 74:
 		goto st74;
 	goto st0;
 tr133:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st75;
 tr119:
-#line 246 "mod/box/memcached-grammar.rl"
+#line 249 "mod/box/memcached-grammar.rl"
 	{incr = natoq(fstart, p);}
 	goto st75;
 st75:
 	if ( ++p == pe )
 		goto _test_eof75;
 case 75:
-#line 2049 "mod/box/memcached-grammar.m"
+#line 2097 "mod/box/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr122;
 	goto st0;
 tr120:
-#line 246 "mod/box/memcached-grammar.rl"
+#line 249 "mod/box/memcached-grammar.rl"
 	{incr = natoq(fstart, p);}
 	goto st76;
 st76:
 	if ( ++p == pe )
 		goto _test_eof76;
 case 76:
-#line 2061 "mod/box/memcached-grammar.m"
+#line 2109 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr122;
 		case 13: goto st75;
@@ -2131,14 +2179,14 @@ case 83:
 	}
 	goto st0;
 tr134:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st84;
 st84:
 	if ( ++p == pe )
 		goto _test_eof84;
 case 84:
-#line 2142 "mod/box/memcached-grammar.m"
+#line 2190 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr122;
 		case 13: goto st75;
@@ -2191,7 +2239,7 @@ case 89:
 		goto st0;
 	goto tr140;
 tr140:
-#line 220 "mod/box/memcached-grammar.rl"
+#line 223 "mod/box/memcached-grammar.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -2208,7 +2256,7 @@ st90:
 	if ( ++p == pe )
 		goto _test_eof90;
 case 90:
-#line 2212 "mod/box/memcached-grammar.m"
+#line 2260 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr141;
 		case 13: goto st91;
@@ -2216,7 +2264,7 @@ case 90:
 	}
 	goto st0;
 tr147:
-#line 236 "mod/box/memcached-grammar.rl"
+#line 239 "mod/box/memcached-grammar.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -2224,14 +2272,14 @@ tr147:
 		}
 	goto st91;
 tr158:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st91;
 st91:
 	if ( ++p == pe )
 		goto _test_eof91;
 case 91:
-#line 2235 "mod/box/memcached-grammar.m"
+#line 2283 "mod/box/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr141;
 	goto st0;
@@ -2250,14 +2298,14 @@ case 92:
 		goto tr144;
 	goto st0;
 tr144:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st93;
 st93:
 	if ( ++p == pe )
 		goto _test_eof93;
 case 93:
-#line 2261 "mod/box/memcached-grammar.m"
+#line 2309 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr146;
 		case 13: goto tr147;
@@ -2267,7 +2315,7 @@ case 93:
 		goto st93;
 	goto st0;
 tr148:
-#line 236 "mod/box/memcached-grammar.rl"
+#line 239 "mod/box/memcached-grammar.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -2278,7 +2326,7 @@ st94:
 	if ( ++p == pe )
 		goto _test_eof94;
 case 94:
-#line 2282 "mod/box/memcached-grammar.m"
+#line 2330 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr141;
 		case 13: goto st91;
@@ -2352,14 +2400,14 @@ case 101:
 	}
 	goto st0;
 tr159:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st102;
 st102:
 	if ( ++p == pe )
 		goto _test_eof102;
 case 102:
-#line 2363 "mod/box/memcached-grammar.m"
+#line 2411 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr141;
 		case 13: goto st91;
@@ -2447,18 +2495,18 @@ case 111:
 	}
 	goto st0;
 tr186:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st112;
 tr175:
-#line 247 "mod/box/memcached-grammar.rl"
+#line 250 "mod/box/memcached-grammar.rl"
 	{flush_delay = natoq(fstart, p);}
 	goto st112;
 st112:
 	if ( ++p == pe )
 		goto _test_eof112;
 case 112:
-#line 2462 "mod/box/memcached-grammar.m"
+#line 2510 "mod/box/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr169;
 	goto st0;
@@ -2477,14 +2525,14 @@ case 113:
 		goto tr172;
 	goto st0;
 tr172:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st114;
 st114:
 	if ( ++p == pe )
 		goto _test_eof114;
 case 114:
-#line 2488 "mod/box/memcached-grammar.m"
+#line 2536 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr174;
 		case 13: goto tr175;
@@ -2494,14 +2542,14 @@ case 114:
 		goto st114;
 	goto st0;
 tr176:
-#line 247 "mod/box/memcached-grammar.rl"
+#line 250 "mod/box/memcached-grammar.rl"
 	{flush_delay = natoq(fstart, p);}
 	goto st115;
 st115:
 	if ( ++p == pe )
 		goto _test_eof115;
 case 115:
-#line 2505 "mod/box/memcached-grammar.m"
+#line 2553 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr169;
 		case 13: goto st112;
@@ -2575,14 +2623,14 @@ case 122:
 	}
 	goto st0;
 tr187:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st123;
 st123:
 	if ( ++p == pe )
 		goto _test_eof123;
 case 123:
-#line 2586 "mod/box/memcached-grammar.m"
+#line 2634 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr169;
 		case 13: goto st112;
@@ -2618,18 +2666,18 @@ case 126:
 	}
 	goto st0;
 tr191:
-#line 287 "mod/box/memcached-grammar.rl"
+#line 293 "mod/box/memcached-grammar.rl"
 	{show_cas = false;}
 	goto st127;
 tr198:
-#line 288 "mod/box/memcached-grammar.rl"
+#line 294 "mod/box/memcached-grammar.rl"
 	{show_cas = true;}
 	goto st127;
 st127:
 	if ( ++p == pe )
 		goto _test_eof127;
 case 127:
-#line 2633 "mod/box/memcached-grammar.m"
+#line 2681 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 13: goto st0;
 		case 32: goto st127;
@@ -2638,7 +2686,7 @@ case 127:
 		goto st0;
 	goto tr193;
 tr193:
-#line 220 "mod/box/memcached-grammar.rl"
+#line 223 "mod/box/memcached-grammar.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -2655,7 +2703,7 @@ st128:
 	if ( ++p == pe )
 		goto _test_eof128;
 case 128:
-#line 2659 "mod/box/memcached-grammar.m"
+#line 2707 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr195;
 		case 13: goto st129;
@@ -2898,7 +2946,7 @@ case 155:
 		goto st0;
 	goto tr222;
 tr222:
-#line 220 "mod/box/memcached-grammar.rl"
+#line 223 "mod/box/memcached-grammar.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -2915,7 +2963,7 @@ st156:
 	if ( ++p == pe )
 		goto _test_eof156;
 case 156:
-#line 2919 "mod/box/memcached-grammar.m"
+#line 2967 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st157;
 	goto st0;
@@ -2929,49 +2977,49 @@ case 157:
 		goto tr224;
 	goto st0;
 tr224:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st158;
 st158:
 	if ( ++p == pe )
 		goto _test_eof158;
 case 158:
-#line 2940 "mod/box/memcached-grammar.m"
+#line 2988 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr225;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st158;
 	goto st0;
 tr225:
-#line 243 "mod/box/memcached-grammar.rl"
+#line 246 "mod/box/memcached-grammar.rl"
 	{flags = natoq(fstart, p);}
 	goto st159;
 st159:
 	if ( ++p == pe )
 		goto _test_eof159;
 case 159:
-#line 2954 "mod/box/memcached-grammar.m"
+#line 3002 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st159;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr228;
 	goto st0;
 tr228:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st160;
 st160:
 	if ( ++p == pe )
 		goto _test_eof160;
 case 160:
-#line 2968 "mod/box/memcached-grammar.m"
+#line 3016 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr229;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st160;
 	goto st0;
 tr229:
-#line 236 "mod/box/memcached-grammar.rl"
+#line 239 "mod/box/memcached-grammar.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -2982,21 +3030,21 @@ st161:
 	if ( ++p == pe )
 		goto _test_eof161;
 case 161:
-#line 2986 "mod/box/memcached-grammar.m"
+#line 3034 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st161;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr232;
 	goto st0;
 tr232:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st162;
 st162:
 	if ( ++p == pe )
 		goto _test_eof162;
 case 162:
-#line 3000 "mod/box/memcached-grammar.m"
+#line 3048 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr233;
 		case 13: goto tr234;
@@ -3006,30 +3054,30 @@ case 162:
 		goto st162;
 	goto st0;
 tr234:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
 	goto st163;
 tr247:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st163;
 st163:
 	if ( ++p == pe )
 		goto _test_eof163;
 case 163:
-#line 3021 "mod/box/memcached-grammar.m"
+#line 3069 "mod/box/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr237;
 	goto st0;
 tr235:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
 	goto st164;
 st164:
 	if ( ++p == pe )
 		goto _test_eof164;
 case 164:
-#line 3033 "mod/box/memcached-grammar.m"
+#line 3081 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 32: goto st164;
 		case 78: goto st165;
@@ -3138,7 +3186,7 @@ case 175:
 		goto st0;
 	goto tr252;
 tr252:
-#line 220 "mod/box/memcached-grammar.rl"
+#line 223 "mod/box/memcached-grammar.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -3155,7 +3203,7 @@ st176:
 	if ( ++p == pe )
 		goto _test_eof176;
 case 176:
-#line 3159 "mod/box/memcached-grammar.m"
+#line 3207 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st177;
 	goto st0;
@@ -3169,49 +3217,49 @@ case 177:
 		goto tr254;
 	goto st0;
 tr254:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st178;
 st178:
 	if ( ++p == pe )
 		goto _test_eof178;
 case 178:
-#line 3180 "mod/box/memcached-grammar.m"
+#line 3228 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr255;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st178;
 	goto st0;
 tr255:
-#line 243 "mod/box/memcached-grammar.rl"
+#line 246 "mod/box/memcached-grammar.rl"
 	{flags = natoq(fstart, p);}
 	goto st179;
 st179:
 	if ( ++p == pe )
 		goto _test_eof179;
 case 179:
-#line 3194 "mod/box/memcached-grammar.m"
+#line 3242 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st179;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr258;
 	goto st0;
 tr258:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st180;
 st180:
 	if ( ++p == pe )
 		goto _test_eof180;
 case 180:
-#line 3208 "mod/box/memcached-grammar.m"
+#line 3256 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr259;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st180;
 	goto st0;
 tr259:
-#line 236 "mod/box/memcached-grammar.rl"
+#line 239 "mod/box/memcached-grammar.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -3222,21 +3270,21 @@ st181:
 	if ( ++p == pe )
 		goto _test_eof181;
 case 181:
-#line 3226 "mod/box/memcached-grammar.m"
+#line 3274 "mod/box/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st181;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr262;
 	goto st0;
 tr262:
-#line 219 "mod/box/memcached-grammar.rl"
+#line 222 "mod/box/memcached-grammar.rl"
 	{ fstart = p; }
 	goto st182;
 st182:
 	if ( ++p == pe )
 		goto _test_eof182;
 case 182:
-#line 3240 "mod/box/memcached-grammar.m"
+#line 3288 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr263;
 		case 13: goto tr264;
@@ -3246,30 +3294,30 @@ case 182:
 		goto st182;
 	goto st0;
 tr264:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
 	goto st183;
 tr277:
-#line 276 "mod/box/memcached-grammar.rl"
+#line 282 "mod/box/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st183;
 st183:
 	if ( ++p == pe )
 		goto _test_eof183;
 case 183:
-#line 3261 "mod/box/memcached-grammar.m"
+#line 3309 "mod/box/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr267;
 	goto st0;
 tr265:
-#line 244 "mod/box/memcached-grammar.rl"
+#line 247 "mod/box/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
 	goto st184;
 st184:
 	if ( ++p == pe )
 		goto _test_eof184;
 case 184:
-#line 3273 "mod/box/memcached-grammar.m"
+#line 3321 "mod/box/memcached-grammar.m"
 	switch( (*p) ) {
 		case 32: goto st184;
 		case 78: goto st185;
@@ -3584,7 +3632,7 @@ case 196:
 	_out: {}
 	}
 
-#line 301 "mod/box/memcached-grammar.rl"
+#line 307 "mod/box/memcached-grammar.rl"
 
 
 	if (!done) {
@@ -3592,22 +3640,21 @@ case 196:
 		if (pe - p > (1 << 20)) {
 		exit:
 			say_warn("memcached proto error");
-			iov_add("ERROR\r\n", 7);
+			obuf_dup(out, "ERROR\r\n", 7);
 			stats.bytes_written += 7;
 			return -1;
 		}
 		char *r;
 		if ((r = memmem(p, pe - p, "\r\n", 2)) != NULL) {
-			tbuf_peek(&fiber->rbuf, r + 2 - (char *)fiber->rbuf.data);
-			iov_add("CLIENT_ERROR bad command line format\r\n", 38);
+			in->pos = r + 2;
+			obuf_dup(out, "CLIENT_ERROR bad command line format\r\n", 38);
 			return 1;
 		}
 		return 0;
 	}
 
 	if (noreply) {
-		fiber->iov_cnt = saved_iov_cnt;
-		fiber->iov.size = saved_iov_cnt * sizeof(struct iovec);
+		obuf_rollback_to_svp(out, &obuf_svp);
 	}
 	return 1;
 }
diff --git a/mod/box/memcached-grammar.rl b/mod/box/memcached-grammar.rl
index cbcad97260..88e6bbd7c9 100644
--- a/mod/box/memcached-grammar.rl
+++ b/mod/box/memcached-grammar.rl
@@ -33,11 +33,11 @@
 }%%
 
 static int __attribute__((noinline))
-memcached_dispatch(struct coio *coio)
+memcached_dispatch(struct coio *coio, struct iobuf *iobuf)
 {
 	int cs;
-	u8 *p, *pe;
-	u8 *fstart;
+	char *p, *pe;
+	char *fstart;
 	struct tbuf *keys = tbuf_alloc(fiber->gc_pool);
 	void *key;
 	bool append, show_cas;
@@ -45,14 +45,17 @@ memcached_dispatch(struct coio *coio)
 	u64 cas, incr;
 	u32 flags, exptime, bytes;
 	bool noreply = false;
-	u8 *data = NULL;
+	char *data = NULL;
 	bool done = false;
-	size_t saved_iov_cnt = fiber->iov_cnt;
 	uintptr_t flush_delay = 0;
 	size_t keys_count = 0;
+	struct ibuf *in = &iobuf->in;
+	struct obuf *out = &iobuf->out;
+	/* Savepoint for 'noreply' */
+	struct obuf_svp obuf_svp = obuf_create_svp(out);
 
-	p = fiber->rbuf.data;
-	pe = fiber->rbuf.data + fiber->rbuf.size;
+	p = in->pos;
+	pe = in->end;
 
 	say_debug("memcached_dispatch '%.*s'", MIN((int)(pe - p), 40) , p);
 
@@ -66,7 +69,7 @@ memcached_dispatch(struct coio *coio)
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple != NULL && !expired(tuple))
-				iov_add("NOT_STORED\r\n", 12);
+				obuf_dup(out, "NOT_STORED\r\n", 12);
 			else
 				STORE;
 		}
@@ -75,7 +78,7 @@ memcached_dispatch(struct coio *coio)
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || expired(tuple))
-				iov_add("NOT_STORED\r\n", 12);
+				obuf_dup(out, "NOT_STORED\r\n", 12);
 			else
 				STORE;
 		}
@@ -84,9 +87,9 @@ memcached_dispatch(struct coio *coio)
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || expired(tuple))
-				iov_add("NOT_FOUND\r\n", 11);
+				obuf_dup(out, "NOT_FOUND\r\n", 11);
 			else if (meta(tuple)->cas != cas)
-				iov_add("EXISTS\r\n", 8);
+				obuf_dup(out, "EXISTS\r\n", 8);
 			else
 				STORE;
 		}
@@ -99,7 +102,7 @@ memcached_dispatch(struct coio *coio)
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST) {
-				iov_add("NOT_STORED\r\n", 12);
+				obuf_dup(out, "NOT_STORED\r\n", 12);
 			} else {
 				value = tuple_field(tuple, 3);
 				value_len = load_varint32(&value);
@@ -128,7 +131,7 @@ memcached_dispatch(struct coio *coio)
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST || expired(tuple)) {
-				iov_add("NOT_FOUND\r\n", 11);
+				obuf_dup(out, "NOT_FOUND\r\n", 11);
 			} else {
 				m = meta(tuple);
 				field = tuple_field(tuple, 3);
@@ -158,16 +161,16 @@ memcached_dispatch(struct coio *coio)
 					@try {
 						store(key, exptime, flags, bytes, data);
 						stats.total_items++;
-						iov_add(b->data, b->size);
-						iov_add("\r\n", 2);
+						obuf_dup(out, b->data, b->size);
+						obuf_dup(out, "\r\n", 2);
 					}
 					@catch (ClientError *e) {
-						iov_add("SERVER_ERROR ", 13);
-						iov_add(e->errmsg, strlen(e->errmsg));
-						iov_add("\r\n", 2);
+						obuf_dup(out, "SERVER_ERROR ", 13);
+						obuf_dup(out, e->errmsg, strlen(e->errmsg));
+						obuf_dup(out, "\r\n", 2);
 					}
 				} else {
-					iov_add("CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 62);
+					obuf_dup(out, "CLIENT_ERROR cannot increment or decrement non-numeric value\r\n", 62);
 				}
 			}
 
@@ -177,39 +180,39 @@ memcached_dispatch(struct coio *coio)
 			key = read_field(keys);
 			struct tuple *tuple = find(key);
 			if (tuple == NULL || tuple->flags & GHOST || expired(tuple)) {
-				iov_add("NOT_FOUND\r\n", 11);
+				obuf_dup(out, "NOT_FOUND\r\n", 11);
 			} else {
 				@try {
 					delete(key);
-					iov_add("DELETED\r\n", 9);
+					obuf_dup(out, "DELETED\r\n", 9);
 				}
 				@catch (ClientError *e) {
-					iov_add("SERVER_ERROR ", 13);
-					iov_add(e->errmsg, strlen(e->errmsg));
-					iov_add("\r\n", 2);
+					obuf_dup(out, "SERVER_ERROR ", 13);
+					obuf_dup(out, e->errmsg, strlen(e->errmsg));
+					obuf_dup(out, "\r\n", 2);
 				}
 			}
 		}
 
 		action get {
 			@try {
-				memcached_get(keys_count, keys, show_cas);
+				memcached_get(out, keys_count, keys, show_cas);
 			} @catch (ClientError *e) {
-				iov_reset();
-				iov_add("SERVER_ERROR ", 13);
-				iov_add(e->errmsg, strlen(e->errmsg));
-				iov_add("\r\n", 2);
+				obuf_rollback_to_svp(out, &obuf_svp);
+				obuf_dup(out, "SERVER_ERROR ", 13);
+				obuf_dup(out, e->errmsg, strlen(e->errmsg));
+				obuf_dup(out, "\r\n", 2);
 			}
 		}
 
 		action flush_all {
 			struct fiber *f = fiber_create("flush_all", flush_all);
 			fiber_call(f, flush_delay);
-			iov_add("OK\r\n", 4);
+			obuf_dup(out, "OK\r\n", 4);
 		}
 
 		action stats {
-			print_stats();
+			print_stats(out);
 		}
 
 		action quit {
@@ -247,14 +250,17 @@ memcached_dispatch(struct coio *coio)
 		flush_delay = digit+ >fstart %{flush_delay = natoq(fstart, p);};
 
 		action read_data {
-			size_t parsed = p - (u8 *)fiber->rbuf.data;
-			while (fiber->rbuf.size - parsed < bytes + 2) {
-				if (coio_bread(coio, &fiber->rbuf, bytes + 2 - (pe - p)) <= 0)
+			size_t parsed = p - in->pos;
+			while (ibuf_size(in) - parsed < bytes + 2) {
+				if (coio_bread(coio, in, bytes + 2 - (pe - p)) <= 0)
 					return -1;
 			}
-
-			p = fiber->rbuf.data + parsed;
-			pe = fiber->rbuf.data + fiber->rbuf.size;
+			/*
+			 * Buffered read may have reallocated the
+			 * buffer.
+			 */
+			p = in->pos + parsed;
+			pe = in->end;
 
 			data = p;
 
@@ -267,8 +273,8 @@ memcached_dispatch(struct coio *coio)
 
 		action done {
 			done = true;
-			stats.bytes_read += p - (u8 *)fiber->rbuf.data;
-			tbuf_peek(&fiber->rbuf, p - (u8 *)fiber->rbuf.data);
+			stats.bytes_read += p - in->pos;
+			in->pos = p;
 		}
 
 		eol = ("\r\n" | "\n") @{ p++; };
@@ -305,22 +311,21 @@ memcached_dispatch(struct coio *coio)
 		if (pe - p > (1 << 20)) {
 		exit:
 			say_warn("memcached proto error");
-			iov_add("ERROR\r\n", 7);
+			obuf_dup(out, "ERROR\r\n", 7);
 			stats.bytes_written += 7;
 			return -1;
 		}
 		char *r;
 		if ((r = memmem(p, pe - p, "\r\n", 2)) != NULL) {
-			tbuf_peek(&fiber->rbuf, r + 2 - (char *)fiber->rbuf.data);
-			iov_add("CLIENT_ERROR bad command line format\r\n", 38);
+			in->pos = r + 2;
+			obuf_dup(out, "CLIENT_ERROR bad command line format\r\n", 38);
 			return 1;
 		}
 		return 0;
 	}
 
 	if (noreply) {
-		fiber->iov_cnt = saved_iov_cnt;
-		fiber->iov.size = saved_iov_cnt * sizeof(struct iovec);
+		obuf_rollback_to_svp(out, &obuf_svp);
 	}
 	return 1;
 }
diff --git a/mod/box/memcached.m b/mod/box/memcached.m
index 9c1fdc4bf2..50aa60cd05 100644
--- a/mod/box/memcached.m
+++ b/mod/box/memcached.m
@@ -71,16 +71,18 @@ struct meta {
 } __packed__;
 
 static u64
-natoq(const u8 *start, const u8 *end)
+natoq(const char *start, const char *end)
 {
 	u64 num = 0;
-	while (start < end)
-		num = num * 10 + (*start++ - '0');
+	while (start < end) {
+		u8 code = *start++;
+		num = num * 10 + (code - '0');
+	}
 	return num;
 }
 
 static void
-store(void *key, u32 exptime, u32 flags, u32 bytes, u8 *data)
+store(void *key, u32 exptime, u32 flags, u32 bytes, const char *data)
 {
 	u32 box_flags = 0;
 	u32 field_count = 4;
@@ -111,7 +113,7 @@ store(void *key, u32 exptime, u32 flags, u32 bytes, u8 *data)
 
 	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);
+		  key_len, key_len, (char*) key, exptime, flags, cas);
 	/*
 	 * Use a box dispatch wrapper which handles correctly
 	 * read-only/read-write modes.
@@ -191,45 +193,44 @@ salloc_stat_memcached_cb(const struct slab_class_stats *cstat, void *cb_ctx)
 }
 
 static void
-print_stats()
+print_stats(struct obuf *out)
 {
-	struct tbuf *out = tbuf_alloc(fiber->gc_pool);
+	struct tbuf *buf = tbuf_alloc(fiber->gc_pool);
 
 	struct salloc_stat_memcached_cb_ctx memstats;
 	memstats.bytes_used = memstats.items = 0;
 	salloc_stat(salloc_stat_memcached_cb, NULL, &memstats);
 
-	tbuf_printf(out, "STAT pid %"PRIu32"\r\n", (u32)getpid());
-	tbuf_printf(out, "STAT uptime %"PRIu32"\r\n", (u32)tarantool_uptime());
-	tbuf_printf(out, "STAT time %"PRIu32"\r\n", (u32)ev_now());
-	tbuf_printf(out, "STAT version 1.2.5 (tarantool/box)\r\n");
-	tbuf_printf(out, "STAT pointer_size %"PRI_SZ"\r\n", sizeof(void *)*8);
-	tbuf_printf(out, "STAT curr_items %"PRIu64"\r\n", memstats.items);
-	tbuf_printf(out, "STAT total_items %"PRIu64"\r\n", stats.total_items);
-	tbuf_printf(out, "STAT bytes %"PRIu64"\r\n", memstats.bytes_used);
-	tbuf_printf(out, "STAT curr_connections %"PRIu32"\r\n", stats.curr_connections);
-	tbuf_printf(out, "STAT total_connections %"PRIu32"\r\n", stats.total_connections);
-	tbuf_printf(out, "STAT connection_structures %"PRIu32"\r\n", stats.curr_connections); /* lie a bit */
-	tbuf_printf(out, "STAT cmd_get %"PRIu64"\r\n", stats.cmd_get);
-	tbuf_printf(out, "STAT cmd_set %"PRIu64"\r\n", stats.cmd_set);
-	tbuf_printf(out, "STAT get_hits %"PRIu64"\r\n", stats.get_hits);
-	tbuf_printf(out, "STAT get_misses %"PRIu64"\r\n", stats.get_misses);
-	tbuf_printf(out, "STAT evictions %"PRIu64"\r\n", stats.evictions);
-	tbuf_printf(out, "STAT bytes_read %"PRIu64"\r\n", stats.bytes_read);
-	tbuf_printf(out, "STAT bytes_written %"PRIu64"\r\n", stats.bytes_written);
-	tbuf_printf(out, "STAT limit_maxbytes %"PRIu64"\r\n", (u64)(cfg.slab_alloc_arena * (1 << 30)));
-	tbuf_printf(out, "STAT threads 1\r\n");
-	tbuf_printf(out, "END\r\n");
-	iov_add(out->data, out->size);
+	tbuf_printf(buf, "STAT pid %"PRIu32"\r\n", (u32)getpid());
+	tbuf_printf(buf, "STAT uptime %"PRIu32"\r\n", (u32)tarantool_uptime());
+	tbuf_printf(buf, "STAT time %"PRIu32"\r\n", (u32)ev_now());
+	tbuf_printf(buf, "STAT version 1.2.5 (tarantool/box)\r\n");
+	tbuf_printf(buf, "STAT pointer_size %"PRI_SZ"\r\n", sizeof(void *)*8);
+	tbuf_printf(buf, "STAT curr_items %"PRIu64"\r\n", memstats.items);
+	tbuf_printf(buf, "STAT total_items %"PRIu64"\r\n", stats.total_items);
+	tbuf_printf(buf, "STAT bytes %"PRIu64"\r\n", memstats.bytes_used);
+	tbuf_printf(buf, "STAT curr_connections %"PRIu32"\r\n", stats.curr_connections);
+	tbuf_printf(buf, "STAT total_connections %"PRIu32"\r\n", stats.total_connections);
+	tbuf_printf(buf, "STAT connection_structures %"PRIu32"\r\n", stats.curr_connections); /* lie a bit */
+	tbuf_printf(buf, "STAT cmd_get %"PRIu64"\r\n", stats.cmd_get);
+	tbuf_printf(buf, "STAT cmd_set %"PRIu64"\r\n", stats.cmd_set);
+	tbuf_printf(buf, "STAT get_hits %"PRIu64"\r\n", stats.get_hits);
+	tbuf_printf(buf, "STAT get_misses %"PRIu64"\r\n", stats.get_misses);
+	tbuf_printf(buf, "STAT evictions %"PRIu64"\r\n", stats.evictions);
+	tbuf_printf(buf, "STAT bytes_read %"PRIu64"\r\n", stats.bytes_read);
+	tbuf_printf(buf, "STAT bytes_written %"PRIu64"\r\n", stats.bytes_written);
+	tbuf_printf(buf, "STAT limit_maxbytes %"PRIu64"\r\n", (u64)(cfg.slab_alloc_arena * (1 << 30)));
+	tbuf_printf(buf, "STAT threads 1\r\n");
+	tbuf_printf(buf, "END\r\n");
+	obuf_dup(out, buf->data, buf->size);
 }
 
-void memcached_get(size_t keys_count, struct tbuf *keys,
+void memcached_get(struct obuf *out, size_t keys_count, struct tbuf *keys,
 		   bool show_cas)
 {
 	stat_collect(stat_base, MEMC_GET, 1);
 	stats.cmd_get++;
 	say_debug("ensuring space for %"PRI_SZ" keys", keys_count);
-	iov_ensure(keys_count * 5 + 1);
 	while (keys_count-- > 0) {
 		struct tuple *tuple;
 		struct meta *m;
@@ -279,23 +280,21 @@ void memcached_get(size_t keys_count, struct tbuf *keys,
 		stats.get_hits++;
 		stat_collect(stat_base, MEMC_GET_HIT, 1);
 
-		iov_ref_tuple(tuple);
-
 		if (show_cas) {
 			struct tbuf *b = tbuf_alloc(fiber->gc_pool);
-			tbuf_printf(b, "VALUE %.*s %"PRIu32" %"PRIu32" %"PRIu64"\r\n", key_len, (u8 *)key, m->flags, value_len, m->cas);
-			iov_add_unsafe(b->data, b->size);
+			tbuf_printf(b, "VALUE %.*s %"PRIu32" %"PRIu32" %"PRIu64"\r\n", key_len, (char*) key, m->flags, value_len, m->cas);
+			obuf_dup(out, b->data, b->size);
 			stats.bytes_written += b->size;
 		} else {
-			iov_add_unsafe("VALUE ", 6);
-			iov_add_unsafe(key, key_len);
-			iov_add_unsafe(suffix, suffix_len);
+			obuf_dup(out, "VALUE ", 6);
+			obuf_dup(out, key, key_len);
+			obuf_dup(out, suffix, suffix_len);
 		}
-		iov_add_unsafe(value, value_len);
-		iov_add_unsafe("\r\n", 2);
+		obuf_dup(out, value, value_len);
+		obuf_dup(out, "\r\n", 2);
 		stats.bytes_written += value_len + 2;
 	}
-	iov_add_unsafe("END\r\n", 5);
+	obuf_dup(out, "END\r\n", 5);
 	stats.bytes_written += 5;
 }
 
@@ -317,17 +316,17 @@ flush_all(va_list ap)
 do {										\
 	stats.cmd_set++;							\
 	if (bytes > (1<<20)) {							\
-		iov_add("SERVER_ERROR object too large for cache\r\n", 41);	\
+		obuf_dup(out, "SERVER_ERROR object too large for cache\r\n", 41);\
 	} else {								\
 		@try {								\
 			store(key, exptime, flags, bytes, data);		\
 			stats.total_items++;					\
-			iov_add("STORED\r\n", 8);				\
+			obuf_dup(out, "STORED\r\n", 8);				\
 		}								\
 		@catch (ClientError *e) {					\
-			iov_add("SERVER_ERROR ", 13);				\
-			iov_add(e->errmsg, strlen(e->errmsg));			\
-			iov_add("\r\n", 2);					\
+			obuf_dup(out, "SERVER_ERROR ", 13);			\
+			obuf_dup(out, e->errmsg, strlen(e->errmsg));		\
+			obuf_dup(out, "\r\n", 2);				\
 		}								\
 	}									\
 } while (0)
@@ -335,19 +334,20 @@ do {										\
 #include "memcached-grammar.m"
 
 void
-memcached_loop(struct coio *coio)
+memcached_loop(struct coio *coio, struct iobuf *iobuf)
 {
 	int rc;
 	int bytes_written;
 	int batch_count;
+	struct ibuf *in = &iobuf->in;
 
 	for (;;) {
 		batch_count = 0;
-		if (coio_bread(coio, &fiber->rbuf, 1) <= 0)
+		if (coio_bread(coio, in, 1) <= 0)
 			return;
 
 	dispatch:
-		rc = memcached_dispatch(coio);
+		rc = memcached_dispatch(coio, iobuf);
 		if (rc < 0) {
 			say_debug("negative dispatch, closing connection");
 			return;
@@ -359,16 +359,15 @@ memcached_loop(struct coio *coio)
 		if (rc == 1) {
 			batch_count++;
 			/* some unparsed commands remain and batch count less than 20 */
-			if (fiber->rbuf.size > 0 && batch_count < 20)
+			if (ibuf_size(in) > 0 && batch_count < 20)
 				goto dispatch;
 		}
 
-		bytes_written = iov_flush(coio);
-
-		stats.bytes_written += bytes_written;
+		bytes_written = iobuf_flush(iobuf, coio);
 		fiber_gc();
+		stats.bytes_written += bytes_written;
 
-		if (rc == 1 && fiber->rbuf.size > 0) {
+		if (rc == 1 && ibuf_size(in) > 0) {
 			batch_count = 0;
 			goto dispatch;
 		}
@@ -379,21 +378,22 @@ memcached_loop(struct coio *coio)
 void memcached_handler(va_list ap)
 {
 	struct coio coio = va_arg(ap, struct coio);
+	struct iobuf *iobuf = va_arg(ap, struct iobuf *);
 	stats.total_connections++;
 	stats.curr_connections++;
 
 	@try {
-		memcached_loop(&coio);
-		iov_flush(&coio);
+		memcached_loop(&coio, iobuf);
+		iobuf_flush(iobuf, &coio);
 	} @catch (FiberCancelException *e) {
 		@throw;
 	} @catch (tnt_Exception *e) {
 		[e log];
 	} @finally {
-		iov_reset();
 		fiber_sleep(0.01);
 		stats.curr_connections--;
 		coio_close(&coio);
+		iobuf_destroy(iobuf);
 	}
 }
 
diff --git a/mod/box/port.h b/mod/box/port.h
index 2e43ec0091..bb5ef9f16e 100644
--- a/mod/box/port.h
+++ b/mod/box/port.h
@@ -45,13 +45,6 @@ struct port
 	struct port_vtab *vtab;
 };
 
-/**
- * A hack to keep tuples alive until iov_flush(fiber->iovec).
- * Is internal to port_iproto implementation, but is also
- * used in memcached.m, which doesn't use fiber->iovec.
- */
-void iov_ref_tuple(struct tuple *tuple);
-
 static inline void
 port_eof(struct port *port)
 {
@@ -74,7 +67,9 @@ port_null_eof(struct port *port __attribute__((unused)));
  */
 extern struct port port_null;
 
+struct obuf;
+
 struct port *
-port_iproto_create();
+port_iproto_create(struct obuf *buf);
 
 #endif /* INCLUDES_TARANTOOL_BOX_PORT_H */
diff --git a/mod/box/port.m b/mod/box/port.m
index 987c25abb8..fbf5b5aa34 100644
--- a/mod/box/port.m
+++ b/mod/box/port.m
@@ -27,18 +27,10 @@
  * SUCH DAMAGE.
  */
 #include "port.h"
-#include <pickle.h>
-#include <fiber.h>
-#include <tarantool_lua.h>
 #include "tuple.h"
-#include "box_lua.h"
-#include "lua.h"
-#include "lauxlib.h"
-#include "lualib.h"
-#include "lj_obj.h"
-#include "lj_ctype.h"
-#include "lj_cdata.h"
-#include "lj_cconv.h"
+#include "iobuf.h"
+#include "fiber.h"
+#include "request.h"
 
 /*
   For tuples of size below this threshold, when sending a tuple
@@ -53,19 +45,6 @@
 */
 const int BOX_REF_THRESHOLD = 8196;
 
-static void
-tuple_unref(void *tuple)
-{
-	tuple_ref((struct tuple *) tuple, -1);
-}
-
-void
-iov_ref_tuple(struct tuple *tuple)
-{
-	tuple_ref(tuple, 1);
-	fiber_register_cleanup(tuple_unref, tuple);
-}
-
 void
 port_null_eof(struct port *port __attribute__((unused)))
 {
@@ -90,8 +69,10 @@ struct port port_null = {
 struct port_iproto
 {
 	struct port_vtab *vtab;
+	struct obuf *buf;
 	/** Number of found tuples. */
 	u32 found;
+	void *pfound;
 };
 
 static inline struct port_iproto *
@@ -106,7 +87,9 @@ port_iproto_eof(struct port *ptr)
 	struct port_iproto *port = port_iproto(ptr);
 	/* found == 0 means add_tuple wasn't called at all. */
 	if (port->found == 0)
-		iov_dup(&port->found, sizeof(port->found));
+		obuf_dup(port->buf, &port->found, sizeof(port->found));
+	else
+		memcpy(port->pfound, &port->found, sizeof(port->found));
 }
 
 static void
@@ -115,17 +98,10 @@ port_iproto_add_tuple(struct port *ptr, struct tuple *tuple, u32 flags)
 	struct port_iproto *port = port_iproto(ptr);
 	if (++port->found == 1) {
 		/* Found the first tuple, add tuple count. */
-		iov_add(&port->found, sizeof(port->found));
+		port->pfound = obuf_book(port->buf, sizeof(port->found));
 	}
 	if (flags & BOX_RETURN_TUPLE) {
-		size_t len = tuple_len(tuple);
-
-		if (len > BOX_REF_THRESHOLD) {
-			iov_ref_tuple(tuple);
-			iov_add(&tuple->bsize, len);
-		} else {
-			iov_dup(&tuple->bsize, len);
-		}
+		obuf_dup(port->buf, &tuple->bsize, tuple_len(tuple));
 	}
 }
 
@@ -135,11 +111,13 @@ static struct port_vtab port_iproto_vtab = {
 };
 
 struct port *
-port_iproto_create()
+port_iproto_create(struct obuf *buf)
 {
 	struct port_iproto *port = palloc(fiber->gc_pool, sizeof(struct port_iproto));
 	port->vtab = &port_iproto_vtab;
+	port->buf = buf;
 	port->found = 0;
+	port->pfound = NULL;
 	return (struct port *) port;
 }
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index cf0c4ec67d..49000643c7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -86,6 +86,7 @@ set (common_sources
      sio.m
      evio.m
      coio.m
+     iobuf.m
      coio_buf.m
      salloc.m
      pickle.m
diff --git a/src/admin.m b/src/admin.m
index cfc9d66955..8ccc370283 100644
--- a/src/admin.m
+++ b/src/admin.m
@@ -198,8 +198,9 @@ show_stat(struct tbuf *buf)
 }
 
 static int
-admin_dispatch(struct coio *coio, lua_State *L)
+admin_dispatch(struct coio *coio, struct iobuf *iobuf, lua_State *L)
 {
+	struct ibuf *in = &iobuf->in;
 	struct tbuf *out = tbuf_alloc(fiber->gc_pool);
 	struct tbuf *err = tbuf_alloc(fiber->gc_pool);
 	int cs;
@@ -207,21 +208,21 @@ admin_dispatch(struct coio *coio, lua_State *L)
 	char *strstart, *strend;
 	bool state;
 
-	while ((pe = memchr(fiber->rbuf.data, '\n', fiber->rbuf.size)) == NULL) {
-		if (coio_bread(coio, &fiber->rbuf, 1) <= 0)
+	while ((pe = memchr(in->pos, '\n', in->end - in->pos)) == NULL) {
+		if (coio_bread(coio, in, 1) <= 0)
 			return -1;
 	}
 
 	pe++;
-	p = fiber->rbuf.data;
+	p = in->pos;
 
 	
-#line 220 "src/admin.m"
+#line 221 "src/admin.m"
 	{
 	cs = admin_start;
 	}
 
-#line 225 "src/admin.m"
+#line 226 "src/admin.m"
 	{
 	if ( p == pe )
 		goto _test_eof;
@@ -284,15 +285,15 @@ case 6:
 	}
 	goto st0;
 tr13:
-#line 319 "src/admin.rl"
+#line 320 "src/admin.rl"
 	{slab_validate(); ok(out);}
 	goto st135;
 tr20:
-#line 307 "src/admin.rl"
-	{return 0;}
+#line 308 "src/admin.rl"
+	{return -1;}
 	goto st135;
 tr25:
-#line 234 "src/admin.rl"
+#line 235 "src/admin.rl"
 	{
 			start(out);
 			tbuf_append(out, help, strlen(help));
@@ -300,9 +301,9 @@ tr25:
 		}
 	goto st135;
 tr36:
-#line 293 "src/admin.rl"
+#line 294 "src/admin.rl"
 	{strend = p;}
-#line 240 "src/admin.rl"
+#line 241 "src/admin.rl"
 	{
 			strstart[strend-strstart]='\0';
 			start(out);
@@ -311,7 +312,7 @@ tr36:
 		}
 	goto st135;
 tr43:
-#line 247 "src/admin.rl"
+#line 248 "src/admin.rl"
 	{
 			if (reload_cfg(err))
 				fail(out, err);
@@ -320,11 +321,11 @@ tr43:
 		}
 	goto st135;
 tr67:
-#line 317 "src/admin.rl"
+#line 318 "src/admin.rl"
 	{coredump(60); ok(out);}
 	goto st135;
 tr76:
-#line 254 "src/admin.rl"
+#line 255 "src/admin.rl"
 	{
 			int ret = snapshot(NULL, 0);
 
@@ -339,9 +340,9 @@ tr76:
 		}
 	goto st135;
 tr98:
-#line 303 "src/admin.rl"
+#line 304 "src/admin.rl"
 	{ state = false; }
-#line 267 "src/admin.rl"
+#line 268 "src/admin.rl"
 	{
 			strstart[strend-strstart] = '\0';
 			if (errinj_set_byname(strstart, state)) {
@@ -353,9 +354,9 @@ tr98:
 		}
 	goto st135;
 tr101:
-#line 302 "src/admin.rl"
+#line 303 "src/admin.rl"
 	{ state = true; }
-#line 267 "src/admin.rl"
+#line 268 "src/admin.rl"
 	{
 			strstart[strend-strstart] = '\0';
 			if (errinj_set_byname(strstart, state)) {
@@ -367,7 +368,7 @@ tr101:
 		}
 	goto st135;
 tr117:
-#line 210 "src/admin.rl"
+#line 211 "src/admin.rl"
 	{
 			tarantool_cfg_iterator_t *i;
 			char *key, *value;
@@ -387,15 +388,15 @@ tr117:
 		}
 	goto st135;
 tr131:
-#line 310 "src/admin.rl"
+#line 311 "src/admin.rl"
 	{start(out); fiber_info(out); end(out);}
 	goto st135;
 tr137:
-#line 309 "src/admin.rl"
+#line 310 "src/admin.rl"
 	{start(out); tarantool_info(out); end(out);}
 	goto st135;
 tr146:
-#line 228 "src/admin.rl"
+#line 229 "src/admin.rl"
 	{
 			start(out);
 			errinj_info(out);
@@ -403,33 +404,33 @@ tr146:
 		}
 	goto st135;
 tr152:
-#line 313 "src/admin.rl"
+#line 314 "src/admin.rl"
 	{start(out); palloc_stat(out); end(out);}
 	goto st135;
 tr160:
-#line 312 "src/admin.rl"
+#line 313 "src/admin.rl"
 	{start(out); show_slab(out); end(out);}
 	goto st135;
 tr164:
-#line 314 "src/admin.rl"
+#line 315 "src/admin.rl"
 	{start(out); show_stat(out);end(out);}
 	goto st135;
 st135:
 	if ( ++p == pe )
 		goto _test_eof135;
 case 135:
-#line 422 "src/admin.m"
+#line 423 "src/admin.m"
 	goto st0;
 tr14:
-#line 319 "src/admin.rl"
+#line 320 "src/admin.rl"
 	{slab_validate(); ok(out);}
 	goto st7;
 tr21:
-#line 307 "src/admin.rl"
-	{return 0;}
+#line 308 "src/admin.rl"
+	{return -1;}
 	goto st7;
 tr26:
-#line 234 "src/admin.rl"
+#line 235 "src/admin.rl"
 	{
 			start(out);
 			tbuf_append(out, help, strlen(help));
@@ -437,9 +438,9 @@ tr26:
 		}
 	goto st7;
 tr37:
-#line 293 "src/admin.rl"
+#line 294 "src/admin.rl"
 	{strend = p;}
-#line 240 "src/admin.rl"
+#line 241 "src/admin.rl"
 	{
 			strstart[strend-strstart]='\0';
 			start(out);
@@ -448,7 +449,7 @@ tr37:
 		}
 	goto st7;
 tr44:
-#line 247 "src/admin.rl"
+#line 248 "src/admin.rl"
 	{
 			if (reload_cfg(err))
 				fail(out, err);
@@ -457,11 +458,11 @@ tr44:
 		}
 	goto st7;
 tr68:
-#line 317 "src/admin.rl"
+#line 318 "src/admin.rl"
 	{coredump(60); ok(out);}
 	goto st7;
 tr77:
-#line 254 "src/admin.rl"
+#line 255 "src/admin.rl"
 	{
 			int ret = snapshot(NULL, 0);
 
@@ -476,9 +477,9 @@ tr77:
 		}
 	goto st7;
 tr99:
-#line 303 "src/admin.rl"
+#line 304 "src/admin.rl"
 	{ state = false; }
-#line 267 "src/admin.rl"
+#line 268 "src/admin.rl"
 	{
 			strstart[strend-strstart] = '\0';
 			if (errinj_set_byname(strstart, state)) {
@@ -490,9 +491,9 @@ tr99:
 		}
 	goto st7;
 tr102:
-#line 302 "src/admin.rl"
+#line 303 "src/admin.rl"
 	{ state = true; }
-#line 267 "src/admin.rl"
+#line 268 "src/admin.rl"
 	{
 			strstart[strend-strstart] = '\0';
 			if (errinj_set_byname(strstart, state)) {
@@ -504,7 +505,7 @@ tr102:
 		}
 	goto st7;
 tr118:
-#line 210 "src/admin.rl"
+#line 211 "src/admin.rl"
 	{
 			tarantool_cfg_iterator_t *i;
 			char *key, *value;
@@ -524,15 +525,15 @@ tr118:
 		}
 	goto st7;
 tr132:
-#line 310 "src/admin.rl"
+#line 311 "src/admin.rl"
 	{start(out); fiber_info(out); end(out);}
 	goto st7;
 tr138:
-#line 309 "src/admin.rl"
+#line 310 "src/admin.rl"
 	{start(out); tarantool_info(out); end(out);}
 	goto st7;
 tr147:
-#line 228 "src/admin.rl"
+#line 229 "src/admin.rl"
 	{
 			start(out);
 			errinj_info(out);
@@ -540,22 +541,22 @@ tr147:
 		}
 	goto st7;
 tr153:
-#line 313 "src/admin.rl"
+#line 314 "src/admin.rl"
 	{start(out); palloc_stat(out); end(out);}
 	goto st7;
 tr161:
-#line 312 "src/admin.rl"
+#line 313 "src/admin.rl"
 	{start(out); show_slab(out); end(out);}
 	goto st7;
 tr165:
-#line 314 "src/admin.rl"
+#line 315 "src/admin.rl"
 	{start(out); show_stat(out);end(out);}
 	goto st7;
 st7:
 	if ( ++p == pe )
 		goto _test_eof7;
 case 7:
-#line 559 "src/admin.m"
+#line 560 "src/admin.m"
 	if ( (*p) == 10 )
 		goto st135;
 	goto st0;
@@ -708,28 +709,28 @@ case 23:
 	}
 	goto tr33;
 tr33:
-#line 293 "src/admin.rl"
+#line 294 "src/admin.rl"
 	{strstart = p;}
 	goto st24;
 st24:
 	if ( ++p == pe )
 		goto _test_eof24;
 case 24:
-#line 719 "src/admin.m"
+#line 720 "src/admin.m"
 	switch( (*p) ) {
 		case 10: goto tr36;
 		case 13: goto tr37;
 	}
 	goto st24;
 tr34:
-#line 293 "src/admin.rl"
+#line 294 "src/admin.rl"
 	{strstart = p;}
 	goto st25;
 st25:
 	if ( ++p == pe )
 		goto _test_eof25;
 case 25:
-#line 733 "src/admin.m"
+#line 734 "src/admin.m"
 	switch( (*p) ) {
 		case 10: goto tr36;
 		case 13: goto tr37;
@@ -1179,28 +1180,28 @@ case 73:
 		goto tr91;
 	goto st0;
 tr91:
-#line 301 "src/admin.rl"
+#line 302 "src/admin.rl"
 	{ strstart = p; }
 	goto st74;
 st74:
 	if ( ++p == pe )
 		goto _test_eof74;
 case 74:
-#line 1190 "src/admin.m"
+#line 1191 "src/admin.m"
 	if ( (*p) == 32 )
 		goto tr92;
 	if ( 33 <= (*p) && (*p) <= 126 )
 		goto st74;
 	goto st0;
 tr92:
-#line 301 "src/admin.rl"
+#line 302 "src/admin.rl"
 	{ strend = p; }
 	goto st75;
 st75:
 	if ( ++p == pe )
 		goto _test_eof75;
 case 75:
-#line 1204 "src/admin.m"
+#line 1205 "src/admin.m"
 	switch( (*p) ) {
 		case 32: goto st75;
 		case 111: goto st76;
@@ -1892,10 +1893,10 @@ case 134:
 	_out: {}
 	}
 
-#line 325 "src/admin.rl"
+#line 326 "src/admin.rl"
 
 
-	tbuf_ltrim(&fiber->rbuf, (void *)pe - (void *)fiber->rbuf.data);
+	in->pos = pe;
 
 	if (p != pe) {
 		start(out);
@@ -1911,17 +1912,20 @@ static void
 admin_handler(va_list ap)
 {
 	struct coio coio = va_arg(ap, struct coio);
+	struct iobuf *iobuf = va_arg(ap, struct iobuf *);
 	lua_State *L = lua_newthread(tarantool_L);
 	int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
 	@try {
 		for (;;) {
-			if (admin_dispatch(&coio, L) < 0)
+			if (admin_dispatch(&coio, iobuf, L) < 0)
 				return;
+			iobuf_gc(iobuf);
 			fiber_gc();
 		}
 	} @finally {
 		luaL_unref(tarantool_L, LUA_REGISTRYINDEX, coro_ref);
 		coio_close(&coio);
+		iobuf_destroy(iobuf);
 	}
 }
 
diff --git a/src/admin.rl b/src/admin.rl
index f1deecde6a..08871e189f 100644
--- a/src/admin.rl
+++ b/src/admin.rl
@@ -189,8 +189,9 @@ show_stat(struct tbuf *buf)
 }
 
 static int
-admin_dispatch(struct coio *coio, lua_State *L)
+admin_dispatch(struct coio *coio, struct iobuf *iobuf, lua_State *L)
 {
+	struct ibuf *in = &iobuf->in;
 	struct tbuf *out = tbuf_alloc(fiber->gc_pool);
 	struct tbuf *err = tbuf_alloc(fiber->gc_pool);
 	int cs;
@@ -198,13 +199,13 @@ admin_dispatch(struct coio *coio, lua_State *L)
 	char *strstart, *strend;
 	bool state;
 
-	while ((pe = memchr(fiber->rbuf.data, '\n', fiber->rbuf.size)) == NULL) {
-		if (coio_bread(coio, &fiber->rbuf, 1) <= 0)
+	while ((pe = memchr(in->pos, '\n', in->end - in->pos)) == NULL) {
+		if (coio_bread(coio, in, 1) <= 0)
 			return -1;
 	}
 
 	pe++;
-	p = fiber->rbuf.data;
+	p = in->pos;
 
 	%%{
 		action show_configuration {
@@ -304,7 +305,7 @@ admin_dispatch(struct coio *coio, lua_State *L)
 		state = state_on | state_off;
 
 		commands = (help			%help						|
-			    exit			%{return 0;}					|
+			    exit			%{return -1;}					|
 			    lua  " "+ string		%lua						|
 			    show " "+ info		%{start(out); tarantool_info(out); end(out);}	|
 			    show " "+ fiber		%{start(out); fiber_info(out); end(out);}	|
@@ -324,7 +325,7 @@ admin_dispatch(struct coio *coio, lua_State *L)
 		write exec;
 	}%%
 
-	tbuf_ltrim(&fiber->rbuf, (void *)pe - (void *)fiber->rbuf.data);
+	in->pos = pe;
 
 	if (p != pe) {
 		start(out);
@@ -340,17 +341,20 @@ static void
 admin_handler(va_list ap)
 {
 	struct coio coio = va_arg(ap, struct coio);
+	struct iobuf *iobuf = va_arg(ap, struct iobuf *);
 	lua_State *L = lua_newthread(tarantool_L);
 	int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
 	@try {
 		for (;;) {
-			if (admin_dispatch(&coio, L) < 0)
+			if (admin_dispatch(&coio, iobuf, L) < 0)
 				return;
+			iobuf_gc(iobuf);
 			fiber_gc();
 		}
 	} @finally {
 		luaL_unref(tarantool_L, LUA_REGISTRYINDEX, coro_ref);
 		coio_close(&coio);
+		iobuf_destroy(iobuf);
 	}
 }
 
diff --git a/src/coio.m b/src/coio.m
index 3ed3d79717..ae88460e7a 100644
--- a/src/coio.m
+++ b/src/coio.m
@@ -32,6 +32,7 @@
 #include <stdio.h>
 
 #include "fiber.h"
+#include "iobuf.h"
 #include "sio.h"
 
 void
@@ -222,7 +223,7 @@ coio_write(struct coio *coio, const void *buf, size_t sz)
 }
 
 ssize_t
-coio_writev(struct coio *coio, struct iovec *iov, int iovcnt)
+coio_writev(struct coio *coio, struct iovec *iov, int iovcnt, size_t size)
 {
 	ssize_t total = 0;
 	@try {
@@ -232,6 +233,11 @@ coio_writev(struct coio *coio, struct iovec *iov, int iovcnt)
 			ssize_t nwr = sio_writev(coio->ev.fd, iov, iovcnt);
 			if (nwr >= 0) {
 				total += nwr;
+				/* If there was a hint for the total size
+				 * of the vector, use it.
+				 */
+				if (size > 0 && size == total)
+					return total;
 				iov = sio_advance_iov(iov, &iovcnt, nwr);
 				if (iovcnt == 0)
 					break;
@@ -260,18 +266,24 @@ coio_service_on_accept(struct evio_service *evio_service,
 	coio_init(&coio, fd);
 
 	/* Set connection name. */
-	char name[SERVICE_NAME_MAXLEN];
-	snprintf(name, sizeof(name),
+	char fiber_name[SERVICE_NAME_MAXLEN];
+	char iobuf_name[SERVICE_NAME_MAXLEN];
+	snprintf(fiber_name, sizeof(fiber_name),
 		 "%s/%s", evio_service->name, sio_strfaddr(addr));
+	snprintf(iobuf_name, sizeof(iobuf_name), "%s/%s", "iobuf", sio_strfaddr(addr));
 
 	/* Create the worker fiber. */
+	struct iobuf *iobuf = NULL;
 	struct fiber *f;
 
 	@try {
-		f = fiber_create(name, service->handler);
+		iobuf = iobuf_create(iobuf_name);
+		f = fiber_create(fiber_name, service->handler);
 	} @catch (tnt_Exception *e) {
 		say_error("can't create a handler fiber, dropping client connection");
 		coio_close(&coio);
+		if (iobuf)
+			iobuf_destroy(iobuf);
 		@throw;
 	}
 	/*
@@ -283,7 +295,7 @@ coio_service_on_accept(struct evio_service *evio_service,
 	 * Start the created fiber. It becomes the coio object owner
 	 * and will have to close it and free before termination.
 	 */
-	fiber_call(f, coio, service->handler_param);
+	fiber_call(f, coio, iobuf, service->handler_param);
 }
 
 void
diff --git a/src/coio_buf.m b/src/coio_buf.m
index ac0e5d5c73..3030ded3b3 100644
--- a/src/coio_buf.m
+++ b/src/coio_buf.m
@@ -28,4 +28,3 @@
  */
 #include "coio_buf.h"
 
-int coio_readahead;
diff --git a/src/fiber.m b/src/fiber.m
index 6f806f38d4..bbd5f68420 100644
--- a/src/fiber.m
+++ b/src/fiber.m
@@ -28,35 +28,19 @@
  */
 #include "fiber.h"
 #include "config.h"
-#include <arpa/inet.h>
 #include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <sys/types.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/mman.h>
-#include <sys/socket.h>
-#include <unistd.h>
-#include <sysexits.h>
-#include <third_party/queue.h>
 #include <assoc.h>
 
-#include <palloc.h>
-#include <salloc.h>
 #include <say.h>
 #include <tarantool.h>
 #include TARANTOOL_CONFIG
-#include <tarantool_ev.h>
 #include <tbuf.h>
-#include <util.h>
 #include <stat.h>
 #include <pickle.h>
-#include "coio_buf.h"
+#include "iobuf.h"
 
 @implementation FiberCancelException
 @end
@@ -68,22 +52,9 @@ __thread struct fiber *fiber = &sched;
 static __thread struct fiber *call_stack[FIBER_CALL_STACK];
 static __thread struct fiber **sp;
 static __thread uint32_t last_used_fid;
-static __thread struct palloc_pool *ex_pool;
 static __thread struct mh_i32ptr_t *fibers_registry;
 __thread SLIST_HEAD(, fiber) fibers, zombie_fibers;
 
-struct fiber_cleanup {
-	void (*handler) (void *data);
-	void *data;
-};
-
-struct fiber_server {
-	int port;
-	void *data;
-	void (*handler) (va_list ap);
-	void (*on_bind) (void *data);
-};
-
 static void
 update_last_stack_frame(struct fiber *fiber)
 {
@@ -180,7 +151,6 @@ fiber_cancel(struct fiber *f)
 	 * at least that we can't get scheduled ourselves
 	 * unless asynchronously woken up is somewhat a relief.
 	 */
-
 	fiber_testcancel(); /* Check if we're ourselves cancelled. */
 }
 
@@ -194,7 +164,6 @@ fiber_is_cancelled()
  * cancelled, and raise an exception (FiberCancelException) if
  * that's the case.
  */
-
 void
 fiber_testcancel(void)
 {
@@ -258,7 +227,6 @@ fiber_sleep(ev_tstamp delay)
 /** Wait for a forked child to complete.
  * @note: this is a cancellation point (@sa fiber_testcancel()).
 */
-
 void
 wait_for_child(pid_t pid)
 {
@@ -306,59 +274,15 @@ static void
 fiber_alloc(struct fiber *fiber)
 {
 	prelease(fiber->gc_pool);
-	tbuf_init(&fiber->rbuf, fiber->gc_pool);
-	tbuf_init(&fiber->iov, fiber->gc_pool);
-	tbuf_init(&fiber->cleanup, fiber->gc_pool);
-	fiber->iov_cnt = 0;
-}
-
-void
-fiber_register_cleanup(fiber_cleanup_handler handler, void *data)
-{
-	struct fiber_cleanup i;
-	i.handler = handler;
-	i.data = data;
-	tbuf_append(&fiber->cleanup, &i, sizeof(struct fiber_cleanup));
-}
-
-void
-fiber_cleanup(void)
-{
-	struct fiber_cleanup *cleanup = fiber->cleanup.data;
-	int i = fiber->cleanup.size / sizeof(struct fiber_cleanup);
-
-	while (i-- > 0) {
-		cleanup->handler(cleanup->data);
-		cleanup++;
-	}
-	tbuf_reset(&fiber->cleanup);
 }
 
 void
 fiber_gc(void)
 {
-	struct palloc_pool *tmp;
-
-	fiber_cleanup();
-
 	if (palloc_allocated(fiber->gc_pool) < 128 * 1024)
 		return;
 
-	tmp = fiber->gc_pool;
-	fiber->gc_pool = ex_pool;
-	ex_pool = tmp;
-	palloc_set_name(fiber->gc_pool, fiber->name);
-	palloc_set_name(ex_pool, "ex_pool");
-
-	struct tbuf tmp_rbuf = fiber->rbuf;
-	tbuf_init(&fiber->rbuf, fiber->gc_pool);
-	tbuf_append(&fiber->rbuf, tmp_rbuf.data, tmp_rbuf.size);
-
-	assert(fiber->iov_cnt == 0);
-	tbuf_init(&fiber->iov, fiber->gc_pool);
-	tbuf_init(&fiber->cleanup, fiber->gc_pool);
-
-	prelease(ex_pool);
+	prelease(fiber->gc_pool);
 }
 
 
@@ -397,7 +321,6 @@ fiber_loop(void *data __attribute__((unused)))
 			say_error("fiber `%s': exception `%s'", fiber->name, object_getClassName(e));
 			panic("fiber `%s': exiting", fiber->name);
 		}
-		tbuf_reset(&fiber->rbuf);
 		fiber_zombificate();
 		fiber_yield();	/* give control back to scheduler */
 	}
@@ -492,28 +415,9 @@ fiber_destroy_all()
 		fiber_destroy(f);
 }
 
-void
-iov_reset()
-{
-	fiber->iov_cnt = 0;	/* discard anything unwritten */
-	tbuf_reset(&fiber->iov);
-}
-
 /**
  * @note: this is a cancellation point.
  */
-
-ssize_t
-iov_flush(struct coio *coio)
-{
-	struct iovec *iov = iovec(&fiber->iov);
-	size_t iov_cnt = fiber->iov_cnt;
-
-	ssize_t nwr = coio_writev(coio, iov, iov_cnt);
-	iov_reset();
-	return nwr;
-}
-
 void
 fiber_info(struct tbuf *out)
 {
@@ -541,8 +445,6 @@ fiber_init(void)
 	SLIST_INIT(&fibers);
 	fibers_registry = mh_i32ptr_init();
 
-	ex_pool = palloc_create_pool("ex_pool");
-
 	memset(&sched, 0, sizeof(sched));
 	sched.fid = 1;
 	sched.gc_pool = palloc_create_pool("");
@@ -552,7 +454,7 @@ fiber_init(void)
 	fiber = &sched;
 	last_used_fid = 100;
 
-	coio_binit(cfg.readahead);
+	iobuf_init_readahead(cfg.readahead);
 }
 
 void
diff --git a/src/iobuf.m b/src/iobuf.m
new file mode 100644
index 0000000000..0c414f7c01
--- /dev/null
+++ b/src/iobuf.m
@@ -0,0 +1,339 @@
+/*
+ * 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 "iobuf.h"
+#include "coio_buf.h"
+#include "palloc.h"
+#include <stdio.h>
+
+/* {{{ struct ibuf */
+
+/** Initialize an input buffer. */
+static void
+ibuf_init(struct ibuf *ibuf, struct palloc_pool *pool)
+{
+	ibuf->pool = pool;
+	ibuf->capacity = 0;
+	ibuf->buf = ibuf->pos = ibuf->end = NULL;
+	/* Don't allocate the buffer yet. */
+}
+
+/** Forget all cached input. */
+static void
+ibuf_reset(struct ibuf *ibuf)
+{
+	ibuf->pos = ibuf->end = ibuf->buf;
+}
+
+/**
+ * Ensure the buffer has sufficient capacity
+ * to store size bytes.
+ */
+void
+ibuf_reserve(struct ibuf *ibuf, size_t size)
+{
+	if (size <= ibuf_unused(ibuf))
+		return;
+	size_t current_size = ibuf_size(ibuf);
+	/*
+	 * Check if we have enough space in the
+	 * current buffer. In this case de-fragment it
+	 * by moving existing data to the beginning.
+	 * Otherwise, get a bigger buffer.
+	 */
+	if (size + current_size <= ibuf->capacity) {
+		memmove(ibuf->buf, ibuf->pos, current_size);
+	} else {
+		/* Use cfg_readahead as allocation factor. */
+		size_t new_capacity = MAX(ibuf->capacity * 2, cfg_readahead);
+		while (new_capacity < current_size + size)
+			new_capacity *= 2;
+
+		ibuf->buf = palloc(ibuf->pool, new_capacity);
+		memcpy(ibuf->buf, ibuf->pos, current_size);
+		ibuf->capacity = new_capacity;
+	}
+	ibuf->pos = ibuf->buf;
+	ibuf->end = ibuf->pos + current_size;
+}
+
+/* }}} */
+
+/* {{{ struct obuf */
+
+/**
+ * Initialize the next slot in iovec array. The buffer
+ * always has at least one empty slot.
+ */
+static inline void
+obuf_init_pos(struct obuf *buf, size_t pos)
+{
+	if (pos >= IOBUF_IOV_MAX) {
+		tnt_raise(LoggedError, :ER_MEMORY_ISSUE, buf->pos,
+			  "obuf_init_pos", "iovec");
+	}
+	buf->iov[pos].iov_base = NULL;
+	buf->iov[pos].iov_len = 0;
+	buf->capacity[pos] = 0;
+}
+
+/** Allocate memory for a single iovec buffer. */
+static inline void
+obuf_alloc_pos(struct obuf *buf, size_t pos, size_t size)
+{
+	size_t capacity = pos > 0 ?  buf->capacity[pos-1] * 2 : cfg_readahead;
+	while (capacity < size) {
+		capacity *=2;
+	}
+
+	buf->iov[pos].iov_base = palloc(buf->pool, capacity);
+	buf->capacity[buf->pos] = capacity;
+	assert(buf->iov[pos].iov_len == 0);
+}
+
+/** Initialize an output buffer instance. Don't allocate memory
+ * yet -- it may never be needed.
+ */
+void
+obuf_init(struct obuf *buf, struct palloc_pool *pool)
+{
+	buf->pool = pool;
+	buf->pos = 0;
+	buf->size = 0;
+	obuf_init_pos(buf, buf->pos);
+}
+
+/** Mark an output buffer as empty. */
+static void
+obuf_reset(struct obuf *buf)
+{
+	buf->pos = 0;
+	buf->size = 0;
+	for (struct iovec *iov = buf->iov; iov->iov_len != 0; iov++) {
+		assert(iov < buf->iov + IOBUF_IOV_MAX);
+		iov->iov_len = 0;
+	}
+}
+
+/** Add data to the output buffer. Copies the data. */
+void
+obuf_dup(struct obuf *buf, void *data, size_t size)
+{
+	struct iovec *iov = &buf->iov[buf->pos];
+	size_t capacity = buf->capacity[buf->pos];
+	/**
+	 * @pre buf->pos points at an array of allocated buffers.
+	 * The array ends with a zero-initialized buffer.
+         */
+	if (iov->iov_len + size > capacity) {
+		/*
+		 * The data doesn't fit into this buffer.
+		 * It could be because the buffer is not
+		 * allocated, is partially or completely full.
+		 * Copy as much as possible into already
+		 * allocated buffers.
+		 */
+		while (iov->iov_len < capacity) {
+			/*
+			 * This buffer is allocated, but can't
+			 * fit all the data. Copy as much data as
+			 * possible.
+			 */
+			size_t fill = capacity - iov->iov_len;
+			memcpy(iov->iov_base + iov->iov_len, data, fill);
+
+			iov->iov_len += fill;
+			buf->size += fill;
+			data += fill;
+			size -= fill;
+
+			if (size == 0)          /* Nothing more to do. */
+				return;
+			buf->pos++;
+			iov = &buf->iov[buf->pos];
+			capacity = buf->capacity[buf->pos];
+		}
+		assert(capacity == 0);
+		/**
+		 * Still some data to copy. We have to get a new
+		 * buffer. Before we allocate a buffer for this
+		 * position, ensure there is an unallocated buffer
+		 * in the next one, since it works as an end marker
+		 * for the loop above.
+		 */
+		obuf_init_pos(buf, buf->pos + 1);
+		obuf_alloc_pos(buf, buf->pos, size);
+	}
+	memcpy(iov->iov_base + iov->iov_len, data, size);
+	iov->iov_len += size;
+	buf->size += size;
+	assert(iov->iov_len <= buf->capacity[buf->pos]);
+}
+
+/** Book a few bytes in the output buffer. */
+void *
+obuf_book(struct obuf *buf, size_t size)
+{
+	struct iovec *iov = &buf->iov[buf->pos];
+	size_t capacity = buf->capacity[buf->pos];
+	if (iov->iov_len + size > capacity) {
+		if (iov->iov_len > 0) {
+			/* Move to the next buffer. */
+			buf->pos++;
+			iov = &buf->iov[buf->pos];
+			capacity = buf->capacity[buf->pos];
+		}
+		/* Make sure the next buffer can store size.  */
+		if (capacity == 0) {
+			obuf_init_pos(buf, buf->pos + 1);
+			obuf_alloc_pos(buf, buf->pos, size);
+		} else if (size > capacity) {
+			/* Simply realloc. */
+			obuf_alloc_pos(buf, buf->pos, size);
+		}
+	}
+	void *booking = iov->iov_base + iov->iov_len;
+	iov->iov_len += size;
+	buf->size += size;
+	assert(iov->iov_len <= buf->capacity[buf->pos]);
+	return booking;
+}
+
+/** Forget about data in the output buffer beyond the savepoint. */
+void
+obuf_rollback_to_svp(struct obuf *buf, struct obuf_svp *svp)
+{
+	bool is_last_pos = buf->pos == svp->pos;
+
+	buf->pos = svp->pos;
+	buf->iov[buf->pos].iov_len = svp->iov_len;
+	buf->size = svp->size;
+	/**
+	 * We need this check to ensure the following
+	 * loop doesn't run away.
+	 */
+	if (is_last_pos)
+		return;
+	for (struct iovec *iov = buf->iov + buf->pos + 1; iov->iov_len != 0; iov++) {
+		assert(iov < buf->iov + IOBUF_IOV_MAX);
+		iov->iov_len = 0;
+	}
+}
+
+/* struct obuf }}} */
+
+/* {{{ struct iobuf */
+
+/**
+ * How big is a buffer which needs to be shrunk before it is put
+ * back into buffer cache.
+ */
+static int iobuf_max_pool_size()
+{
+	return 18 * cfg_readahead;
+}
+
+SLIST_HEAD(iobuf_cache, iobuf) iobuf_cache;
+
+/** Create an instance of input/output buffer or take one from cache. */
+struct iobuf *
+iobuf_create(const char *name)
+{
+	struct iobuf *iobuf;
+	if (SLIST_EMPTY(&iobuf_cache)) {
+		iobuf = palloc(eter_pool, sizeof(struct iobuf));
+		struct palloc_pool *pool = palloc_create_pool("");
+		/* Note: do not allocate memory upfront. */
+		ibuf_init(&iobuf->in, pool);
+		obuf_init(&iobuf->out, pool);
+	} else {
+		iobuf = SLIST_FIRST(&iobuf_cache);
+		SLIST_REMOVE_HEAD(&iobuf_cache, next);
+	}
+	/* When releasing the buffer, we trim it to iobuf_max_pool_size. */
+	assert(palloc_allocated(iobuf->in.pool) <= iobuf_max_pool_size());
+	palloc_set_name(iobuf->in.pool, name);
+	return iobuf;
+}
+
+/** Put an instance back to the iobuf_cache. */
+void
+iobuf_destroy(struct iobuf *iobuf)
+{
+	struct palloc_pool *pool = iobuf->in.pool;
+	if (palloc_allocated(pool) < iobuf_max_pool_size()) {
+		ibuf_reset(&iobuf->in);
+		obuf_reset(&iobuf->out);
+	} else {
+		prelease(pool);
+		ibuf_init(&iobuf->in, pool);
+		obuf_init(&iobuf->out, pool);
+	}
+	palloc_set_name(pool, "iobuf_cache");
+	SLIST_INSERT_HEAD(&iobuf_cache, iobuf, next);
+}
+
+/** Send all data in the output buffer and garbage collect. */
+ssize_t
+iobuf_flush(struct iobuf *iobuf, struct coio *coio)
+{
+	int iovcnt = iobuf->out.pos;
+	if (iobuf->out.iov[iovcnt].iov_len > 0)
+		iovcnt++;
+	ssize_t total = coio_writev(coio, iobuf->out.iov, iovcnt,
+				    obuf_size(&iobuf->out));
+	iobuf_gc(iobuf);
+	return total;
+}
+
+void
+iobuf_gc(struct iobuf *iobuf)
+{
+	/*
+	 * If we happen to have fully processed the input,
+	 * move the pos to the start of the input buffer.
+	 *
+	 * If there is some residue, move it as well,
+	 * but only in case if we don't have cfg_readahead
+	 * bytes available for the next round: it's more efficient
+	 * to move any residue now, when it's likely to be small,
+	 * rather than when we have read a bunch more data, and only
+	 * then discovered we don't have enough space to read a
+	 * full request.
+	 */
+	if (ibuf_size(&iobuf->in) == 0)
+		ibuf_reset(&iobuf->in);
+	else
+		ibuf_reserve(&iobuf->in, cfg_readahead);
+	/* Cheap to do even if already done. */
+	obuf_reset(&iobuf->out);
+}
+
+int cfg_readahead;
+
+/* struct iobuf }}} */
diff --git a/src/iproto.m b/src/iproto.m
index cb6ea44cfd..b892725d86 100644
--- a/src/iproto.m
+++ b/src/iproto.m
@@ -28,33 +28,37 @@
  */
 #include "iproto.h"
 #include "exception.h"
-
-#include <stdio.h>
 #include <string.h>
 
 #include <errcode.h>
-#include <palloc.h>
 #include <fiber.h>
-#include <tbuf.h>
 #include <say.h>
 #include "coio_buf.h"
+#include "tbuf.h"
 
 const uint32_t msg_ping = 0xff00;
 
-static void iproto_reply(iproto_callback callback, struct tbuf *request);
+static inline struct iproto_header *
+iproto(const void *pos)
+{
+	return (struct iproto_header *) pos;
+}
+
+static void
+iproto_reply(iproto_callback callback, struct obuf *out, void *req);
 
 static void
 iproto_validate_header(struct iproto_header *header);
 
 inline static void
-iproto_flush(struct coio *coio, ssize_t to_read)
+iproto_flush(struct coio *coio, struct iobuf *iobuf, ssize_t to_read)
 {
 	/*
 	 * Flush output and garbage collect before reading
 	 * next header.
 	 */
 	if (to_read > 0) {
-		iov_flush(coio);
+		iobuf_flush(iobuf, coio);
 		fiber_gc();
 	}
 }
@@ -63,8 +67,9 @@ void
 iproto_interact(va_list ap)
 {
 	struct coio coio = va_arg(ap, struct coio);
+	struct iobuf *iobuf = va_arg(ap, struct iobuf *);
 	iproto_callback callback = va_arg(ap, iproto_callback);
-	struct tbuf *in = &fiber->rbuf;
+	struct ibuf *in = &iobuf->in;
 	ssize_t to_read = sizeof(struct iproto_header);
 	@try {
 		for (;;) {
@@ -72,63 +77,66 @@ iproto_interact(va_list ap)
 				break;
 
 			/* validating iproto package header */
-			iproto_validate_header(iproto(in));
+			iproto_validate_header(iproto(in->pos));
 
 			ssize_t request_len = sizeof(struct iproto_header)
-				+ iproto(in)->len;
-			to_read = request_len - in->size;
+				+ iproto(in->pos)->len;
+			to_read = request_len - ibuf_size(in);
 
-			iproto_flush(&coio, to_read);
+			iproto_flush(&coio, iobuf, to_read);
 
 			if (to_read > 0 && coio_bread(&coio, in, to_read) <= 0)
 				break;
 
-			struct tbuf *request = tbuf_split(in, request_len);
-			iproto_reply(callback, request);
+			iproto_reply(callback, &iobuf->out, in->pos);
+			in->pos += request_len;
 
-			to_read = sizeof(struct iproto_header) - in->size;
-			iproto_flush(&coio, to_read);
+			to_read = sizeof(struct iproto_header) - ibuf_size(in);
+			iproto_flush(&coio, iobuf, to_read);
 		}
 	} @finally {
 		coio_close(&coio);
+		iobuf_destroy(iobuf);
 	}
 }
 
 /** Stack a reply to a single request to the fiber's io vector. */
 
-static void iproto_reply(iproto_callback callback, struct tbuf *request)
+static void
+iproto_reply(iproto_callback callback, struct obuf *out, void *req)
 {
-	struct iproto_header_retcode *reply;
+	struct iproto_header_retcode reply;
 
-	reply = palloc(fiber->gc_pool, sizeof(*reply));
-	reply->msg_code = iproto(request)->msg_code;
-	reply->sync = iproto(request)->sync;
+	reply.msg_code = iproto(req)->msg_code;
+	reply.sync = iproto(req)->sync;
 
-	if (unlikely(reply->msg_code == msg_ping)) {
-		reply->len = 0;
-		iov_add(reply, sizeof(struct iproto_header));
+	if (unlikely(reply.msg_code == msg_ping)) {
+		reply.len = 0;
+		obuf_dup(out, &reply, sizeof(struct iproto_header));
 		return;
 	}
+	reply.len = sizeof(uint32_t); /* ret_code */
+
+	void *p_reply = obuf_book(out, sizeof(struct iproto_header_retcode));
+	struct obuf_svp svp = obuf_create_svp(out);
 
-	reply->len = sizeof(uint32_t); /* ret_code */
-	iov_add(reply, sizeof(struct iproto_header_retcode));
-	size_t saved_iov_cnt = fiber->iov_cnt;
 	/* make request point to iproto data */
-	request->size = iproto(request)->len;
-	request->data = iproto(request)->data;
+	struct tbuf request = {
+		.size = iproto(req)->len, .capacity = iproto(req)->len,
+		.data = iproto(req)->data, .pool = fiber->gc_pool
+	};
 
 	@try {
-		callback(reply->msg_code, request);
-		reply->ret_code = 0;
+		callback(out, reply.msg_code, &request);
+		reply.ret_code = 0;
 	}
 	@catch (ClientError *e) {
-		fiber->iov.size -= (fiber->iov_cnt - saved_iov_cnt) * sizeof(struct iovec);
-		fiber->iov_cnt = saved_iov_cnt;
-		reply->ret_code = tnt_errcode_val(e->errcode);
-		iov_dup(e->errmsg, strlen(e->errmsg)+1);
+		obuf_rollback_to_svp(out, &svp);
+		reply.ret_code = tnt_errcode_val(e->errcode);
+		obuf_dup(out, 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;
+	reply.len += obuf_size(out) - svp.size;
+	memcpy(p_reply, &reply, sizeof(struct iproto_header_retcode));
 }
 
 static void
diff --git a/src/palloc.m b/src/palloc.m
index d2063c3b97..9b71ead523 100644
--- a/src/palloc.m
+++ b/src/palloc.m
@@ -42,7 +42,7 @@
 #include <tbuf.h>
 #include "exception.h"
 
-#define PALLOC_POOL_NAME_MAXLEN 16
+#define PALLOC_POOL_NAME_MAXLEN 30
 
 struct chunk {
 	uint32_t magic;
@@ -69,9 +69,9 @@ TAILQ_HEAD(class_tailq_head, chunk_class) classes;
 
 struct palloc_pool {
 	struct chunk_list_head chunks;
-	 SLIST_ENTRY(palloc_pool) link;
+	SLIST_ENTRY(palloc_pool) link;
 	size_t allocated;
-	const char *name;
+	char name[PALLOC_POOL_NAME_MAXLEN];
 };
 
 SLIST_HEAD(palloc_pool_head, palloc_pool) pools;
@@ -464,7 +464,7 @@ palloc_stat(struct tbuf *buf)
 void
 palloc_set_name(struct palloc_pool *pool, const char *name)
 {
-	pool->name = name;
+	snprintf(pool->name, sizeof(pool->name), "%s", name);
 }
 
 size_t
diff --git a/src/replica.m b/src/replica.m
index 21b9c6ac3a..296452736a 100644
--- a/src/replica.m
+++ b/src/replica.m
@@ -40,21 +40,27 @@
 static void
 remote_apply_row(struct recovery_state *r, struct tbuf *row);
 
-static struct tbuf *
-remote_read_row(struct coio *coio)
+static struct tbuf
+remote_read_row(struct coio *coio, struct iobuf *iobuf)
 {
-	ssize_t to_read = sizeof(struct header_v11) - fiber->rbuf.size;
+	struct ibuf *in = &iobuf->in;
+	ssize_t to_read = sizeof(struct header_v11) - ibuf_size(in);
 
 	if (to_read > 0)
-		coio_breadn(coio, &fiber->rbuf, to_read);
+		coio_breadn(coio, in, to_read);
 
-	ssize_t request_len = header_v11(&fiber->rbuf)->len + sizeof(struct header_v11);
-	to_read = request_len - fiber->rbuf.size;
+	ssize_t request_len = ((struct header_v11 *)in->pos)->len + sizeof(struct header_v11);
+	to_read = request_len - ibuf_size(in);
 
 	if (to_read > 0)
-		coio_breadn(coio, &fiber->rbuf, to_read);
-
-	return tbuf_split(&fiber->rbuf, request_len);
+		coio_breadn(coio, in, to_read);
+
+	struct tbuf row = {
+		.size = request_len, .capacity = request_len,
+		.data = in->pos, .pool = fiber->gc_pool
+	};
+	in->pos += request_len;
+	return row;
 }
 
 static void
@@ -83,8 +89,10 @@ pull_from_remote(va_list ap)
 {
 	struct recovery_state *r = va_arg(ap, struct recovery_state *);
 	struct coio coio;
+	struct iobuf *iobuf = NULL;
 	bool warning_said = false;
 	const int reconnect_delay = 1;
+
 	coio_clear(&coio);
 
 	for (;;) {
@@ -92,22 +100,26 @@ pull_from_remote(va_list ap)
 		@try {
 			fiber_setcancelstate(true);
 			if (! coio_is_connected(&coio)) {
+				if (iobuf == NULL)
+					iobuf = iobuf_create(fiber->name);
 				remote_connect(&coio, &r->remote->addr,
 					       r->confirmed_lsn + 1, &err);
 				warning_said = false;
 			}
 			err = "can't read row";
-			struct tbuf *row = remote_read_row(&coio);
+			struct tbuf row = remote_read_row(&coio, iobuf);
 			fiber_setcancelstate(false);
 			err = NULL;
 
-			r->remote->recovery_lag = ev_now() - header_v11(row)->tm;
+			r->remote->recovery_lag = ev_now() - header_v11(&row)->tm;
 			r->remote->recovery_last_update_tstamp = ev_now();
 
-			remote_apply_row(r, row);
+			remote_apply_row(r, &row);
 
+			iobuf_gc(iobuf);
 			fiber_gc();
 		} @catch (FiberCancelException *e) {
+			iobuf_destroy(iobuf);
 			coio_close(&coio);
 			@throw;
 		} @catch (tnt_Exception *e) {
diff --git a/src/tarantool_lua.m b/src/tarantool_lua.m
index 73ba2f328e..50b0143692 100644
--- a/src/tarantool_lua.m
+++ b/src/tarantool_lua.m
@@ -28,6 +28,7 @@
  */
 #include <tarantool_lua.h>
 #include <tarantool.h>
+#include "tbuf.h"
 
 #include "lua.h"
 #include "lauxlib.h"
diff --git a/test/box/admin.result b/test/box/admin.result
index fb318d2c34..02d33af295 100644
--- a/test/box/admin.result
+++ b/test/box/admin.result
@@ -1,3 +1,4 @@
+exit
 show stat
 ---
 statistics:
diff --git a/test/box/admin.test b/test/box/admin.test
index 986b4cb56f..b1877799ff 100644
--- a/test/box/admin.test
+++ b/test/box/admin.test
@@ -4,6 +4,7 @@ import sys
 # clear statistics:
 server.stop()
 server.deploy()
+exec admin "exit"
 exec admin "show stat"
 exec admin "help"
 exec admin "show configuration"
diff --git a/test/lib/admin_connection.py b/test/lib/admin_connection.py
index 7dd17528b5..cdbbfecf1d 100644
--- a/test/lib/admin_connection.py
+++ b/test/lib/admin_connection.py
@@ -42,7 +42,7 @@ class AdminConnection(TarantoolConnection):
             buf = self.socket.recv(bufsiz)
             if not buf:
                 break
-            res = res + buf;
+            res = res + buf
             if (res.rfind("\n...\n") >= 0 or res.rfind("\r\n...\r\n") >= 0):
                 break
 
-- 
GitLab