diff --git a/client/tarantool_checksum/CMakeLists.txt b/client/tarantool_checksum/CMakeLists.txt
index e24280ad6376a39326fba1143b34e237c9ed3676..fadc74533c73788082dfd0338d88dd091e12839c 100644
--- a/client/tarantool_checksum/CMakeLists.txt
+++ b/client/tarantool_checksum/CMakeLists.txt
@@ -16,6 +16,9 @@ list(APPEND util_checksum_sources
     ${CMAKE_SOURCE_DIR}/cfg/tarantool_box_cfg.c
     ${CMAKE_SOURCE_DIR}/cfg/prscfg.c)
 
+list(APPEND util_checksum_sources
+    ${CMAKE_SOURCE_DIR}/third_party/PMurHash.c)
+
 set_source_files_compile_flags(
     ${util_checksum_sources})
 add_executable(${util_checksum} ${util_checksum_sources})
diff --git a/client/tarantool_checksum/tc_generate.c b/client/tarantool_checksum/tc_generate.c
index 3ee329393be449b2d7f100677b7a4e9410a91c0b..2683a217922305815fdb299ebd74ba6ab997afaa 100644
--- a/client/tarantool_checksum/tc_generate.c
+++ b/client/tarantool_checksum/tc_generate.c
@@ -43,7 +43,7 @@
 #include <cfg/prscfg.h>
 #include <cfg/tarantool_box_cfg.h>
 
-#include <third_party/murmur_hash2.c>
+#include <third_party/PMurHash.h>
 #include <third_party/crc32.h>
 
 #include "tc_key.h"
@@ -74,7 +74,7 @@ search_hash(const struct tc_key *k, struct tc_space *s)
 			break;
 		}
 		case TC_SPACE_KEY_STRING:
-			 h = MurmurHash2(TC_KEY_DATA(k, i), TC_KEY_SIZE(k, i), h);
+			 h = PMurHash32(h, TC_KEY_DATA(k, i), TC_KEY_SIZE(k, i));
 			break;
 		case TC_SPACE_KEY_UNKNOWN:
 			assert(1);
diff --git a/client/tarantool_checksum/tc_verify.c b/client/tarantool_checksum/tc_verify.c
index 18321a55a61464e957ad0a1859dc811bd3559c9b..aac9ba84a65c476287bed0ca5d10e64c3f70dab5 100644
--- a/client/tarantool_checksum/tc_verify.c
+++ b/client/tarantool_checksum/tc_verify.c
@@ -44,7 +44,6 @@
 #include <cfg/prscfg.h>
 #include <cfg/tarantool_box_cfg.h>
 
-#include <third_party/murmur_hash2.c>
 #include <third_party/crc32.h>
 
 #include "tc_key.h"
diff --git a/cmake/BuildLibEIO.cmake b/cmake/BuildLibEIO.cmake
index 71ec083fe89afd7156b1e8422415538a00028a2a..3d93759effda0998bbcc7e9a5fad75768aa5a6f5 100644
--- a/cmake/BuildLibEIO.cmake
+++ b/cmake/BuildLibEIO.cmake
@@ -6,6 +6,7 @@ macro(libeio_build)
     set(eio_compile_flags "${eio_compile_flags} -Wno-unused-value")
     set(eio_compile_flags "${eio_compile_flags} -Wno-dangling-else")
     set(eio_compile_flags "${eio_compile_flags} -DENABLE_BUNDLED_LIBEIO=1")
+    set(eio_compile_flags "${eio_compile_flags} -DEIO_STACKSIZE=0")
 
     set(eio_src
         ${PROJECT_SOURCE_DIR}/third_party/tarantool_eio.c
diff --git a/cmake/BuildMisc.cmake b/cmake/BuildMisc.cmake
index 8f90c771b67959f7e1802529e5ae4383c45ccd05..e8975a5021977408b31ff9de8445b63e513472c5 100644
--- a/cmake/BuildMisc.cmake
+++ b/cmake/BuildMisc.cmake
@@ -5,6 +5,7 @@ macro(libmisc_build)
         ${PROJECT_SOURCE_DIR}/third_party/crc32.c
         ${PROJECT_SOURCE_DIR}/third_party/proctitle.c
         ${PROJECT_SOURCE_DIR}/third_party/qsort_arg.c
+        ${PROJECT_SOURCE_DIR}/third_party/PMurHash.c
     )
 
     if (NOT HAVE_MEMMEM)
diff --git a/doc/www-data.in/download.cmake b/doc/www-data.in/download.cmake
index 5fe64d3a38f70e80f6e0a1713a5932497fe18303..7516b678eed77c99a012ae322a54e5e265d97fec 100644
--- a/doc/www-data.in/download.cmake
+++ b/doc/www-data.in/download.cmake
@@ -115,7 +115,7 @@ that production systems use the builds provided on this site.
 
 We maintain an always up-to-date Debian GNU/Linux and Ubuntu package
 repository at <a
-href="http://tarantool.org/dist/debian">http://tarantoo.org/dist/debian</a> and <a
+href="http://tarantool.org/dist/debian">http://tarantool.org/dist/debian</a> and <a
 href="http://tarantool.org/dist/ubuntu">http://tarantool.org/dist/ubuntu</a> respectively.
 
 At the moment the repository contains builds for Debian "Sid", "Squeeze",
@@ -129,6 +129,20 @@ At the moment the repository contains builds for Debian "Sid", "Squeeze",
     sudo apt-get update
     sudo apt-get install tarantool tarantool-client
 
+### CentOS 5-6 GNU/Linux
+
+CentOS GNU/Linux repository is available at
+<a href="http://tarantool.org/dist/centos">http://tarantool.org/dist/centos</a>.
+
+Add the following section to your yum repository list (/etc/yum.repos.d/tarantool.repo)
+to enable it:
+
+    [tarantool]
+    name=CentOS-$releasever - Tarantool
+    baseurl=http://tarantool.org/dist/centos/$releasever/os/$basearch/
+    enabled=1
+    gpgcheck=0
+
 # Development branch
 
 In the same manner as for [the stable
@@ -262,7 +276,7 @@ Tarantool/Box использует стандартную схему нумер
 Мы поддерживаем собственные репозитории .deb пакетов, который
 содержит последние версии сервера и клиентских библиотек для
 *Debian GNU/Linux* и  *Ubuntu*:
-<a href="http://tarantool.org/dist/debian">http://tarantoo.org/dist/debian</a>,
+<a href="http://tarantool.org/dist/debian">http://tarantool.org/dist/debian</a>,
 <a href="http://tarantool.org/dist/ubuntu">http://tarantool.org/dist/ubuntu</a>.
 
 Репозитории содержат пакеты для Debian "Sid", "Squeeze",
@@ -277,6 +291,18 @@ Tarantool/Box использует стандартную схему нумер
     sudo apt-get update
     sudo apt-get install tarantool tarantool-client
 
+CentOS GNU/Linux репозиторий доступен по адресу:
+<a href="http://tarantool.org/dist/centos">http://tarantool.org/dist/centos</a>.
+
+Добавьте следующую секцию в Ваш список репозиториев
+(/etc/yum.repos.d/tarantool.repo) чтобы сделать его доступным для Yum:
+
+    [tarantool]
+    name=CentOS-$releasever - Tarantool
+    baseurl=http://tarantool.org/dist/centos/$releasever/os/$basearch/
+    enabled=1
+    gpgcheck=0
+
 ### Архив старых релизов
 
 Архив старых релизов доступен по адресу <a
diff --git a/include/assoc.h b/include/assoc.h
index 49db6f4b14ecf2084a0ee33875974d00dfe4b194..497e12ecfc436494988f013ea2c175b1253823ad 100644
--- a/include/assoc.h
+++ b/include/assoc.h
@@ -82,7 +82,7 @@ static inline int lstrcmp(const void *a, const void *b)
 		return bl - al;
 	return memcmp(a, b, al);
 }
-#include <third_party/murmur_hash2.c>
+#include <third_party/PMurHash.h>
 #define mh_name _lstrptr
 struct mh_lstrptr_node_t {
 	const void *key;
@@ -97,7 +97,7 @@ mh_strptr_hash(const mh_node_t *a, mh_hash_arg_t arg) {
 	(void) arg;
 	const void *_k = (a->key);
 	const u32 l = load_varint32(&_k);
-	return (u32) MurmurHash2(_k, l, 13);
+	return PMurHash32(13, _k, l);
 }
 #define mh_hash(a, arg) mh_strptr_hash(a, arg)
 #define mh_eq_arg_t void *
diff --git a/include/coeio.h b/include/coeio.h
index 0eac1b09834e90b36f497a2af299760a03c373f0..4ddd514015805acd56e1374c3a36f66f3cfd4295 100644
--- a/include/coeio.h
+++ b/include/coeio.h
@@ -47,22 +47,7 @@
  * Yield the current fiber until a created task is complete.
  */
 
-/**
- * A single task' context.
- */
-struct coeio_req {
-	struct eio_req *req;
-	struct fiber *f;
-	bool complete;
-	bool wait;
-	void *f_data;
-	void *result;
-	struct rlist link;
-};
-
 void coeio_init(void);
-void coeio_free(void);
-struct coeio_req *coeio_custom(void (*f)(eio_req*), void *arg);
-void *coeio_wait(struct coeio_req *r);
+ssize_t coeio_custom(ssize_t (*f)(va_list ap), ev_tstamp timeout, ...);
 
 #endif /* TARANTOOL_COEIO_H_INCLUDED */
diff --git a/include/coio.h b/include/coio.h
index 5b5bb95cb6dba591082066c042d5f4e9f04bee59..da9814f2fed72faa8527cb3468538d4c53933ff5 100644
--- a/include/coio.h
+++ b/include/coio.h
@@ -29,11 +29,11 @@
  * SUCH DAMAGE.
  */
 #include "evio.h"
-/*
+
+/**
  * Co-operative I/O
  * Yield the current fiber until IO is ready.
  */
-
 struct coio_service
 {
 	struct evio_service evio_service;
@@ -46,11 +46,39 @@ struct coio_service
 void
 coio_connect(struct ev_io *coio, struct sockaddr_in *addr);
 
+bool
+coio_connect_timeout(struct ev_io *coio, struct sockaddr_in *addr,
+		     socklen_t len, ev_tstamp timeout);
+
+bool
+coio_connect_addrinfo(struct ev_io *coio, struct addrinfo *ai,
+		      ev_tstamp timeout);
+
+void
+coio_bind(struct ev_io *coio, struct sockaddr_in *addr,
+	  socklen_t addrlen);
+
+int
+coio_accept(struct ev_io *coio, struct sockaddr_in *addr, socklen_t addrlen,
+	    ev_tstamp timeout);
+
 void
-coio_init(struct ev_io *coio, int fd);
+coio_init(struct ev_io *coio);
 
 ssize_t
-coio_read_ahead(struct ev_io *coio, void *buf, size_t sz, size_t bufsiz);
+coio_read_ahead_timeout(struct ev_io *coio, void *buf, size_t sz, size_t bufsiz,
+		        ev_tstamp timeout);
+
+/**
+ * Reat at least sz bytes, with readahead.
+ *
+ * Returns 0 in case of EOF.
+ */
+static inline ssize_t
+coio_read_ahead(struct ev_io *coio, void *buf, size_t sz, size_t bufsiz)
+{
+	return coio_read_ahead_timeout(coio, buf, sz, bufsiz, TIMEOUT_INFINITY);
+}
 
 ssize_t
 coio_readn_ahead(struct ev_io *coio, void *buf, size_t sz, size_t bufsiz);
@@ -61,18 +89,45 @@ coio_read(struct ev_io *coio, void *buf, size_t sz)
 	return coio_read_ahead(coio, buf, sz, sz);
 }
 
+static inline ssize_t
+coio_read_timeout(struct ev_io *coio, void *buf, size_t sz, ev_tstamp timeout)
+{
+	return coio_read_ahead_timeout(coio, buf, sz, sz, timeout);
+}
+
 static inline ssize_t
 coio_readn(struct ev_io *coio, void *buf, size_t sz)
 {
 	return coio_readn_ahead(coio, buf, sz, sz);
 }
 
-void
-coio_write(struct ev_io *coio, const void *buf, size_t sz);
+ssize_t
+coio_readn_ahead_timeout(struct ev_io *coio, void *buf, size_t sz, size_t bufsiz,
+		         ev_tstamp timeout);
+
+ssize_t
+coio_write_timeout(struct ev_io *coio, const void *buf, size_t sz,
+		   ev_tstamp timeout);
+
+static inline void
+coio_write(struct ev_io *coio, const void *buf, size_t sz)
+{
+	coio_write_timeout(coio, buf, sz, TIMEOUT_INFINITY);
+}
 
 ssize_t
 coio_writev(struct ev_io *coio, struct iovec *iov, int iovcnt, size_t size);
 
+ssize_t
+coio_sendto_timeout(struct ev_io *coio, const void *buf, size_t sz, int flags,
+		    const struct sockaddr_in *dest_addr, socklen_t addrlen,
+		    ev_tstamp timeout);
+
+ssize_t
+coio_recvfrom_timeout(struct ev_io *coio, void *buf, size_t sz, int flags,
+		      struct sockaddr_in *src_addr, socklen_t addrlen,
+		      ev_tstamp timeout);
+
 void
 coio_service_init(struct coio_service *service, const char *name,
 		  const char *host, int port,
diff --git a/include/coio_buf.h b/include/coio_buf.h
index 50a025ba88c0550e855af3335f65b53972383586..8adecf53b93eedc8ff0bfc3ebc08d368e33756e5 100644
--- a/include/coio_buf.h
+++ b/include/coio_buf.h
@@ -32,7 +32,11 @@
 #include "iobuf.h"
 /** Buffered cooperative IO */
 
-/** Read at least sz bytes, buffered. Return 0 in case of EOF. */
+/**
+ * Read at least sz bytes, buffered.
+ * Return the number of bytes read (can be less than n in case
+ * of EOF).
+ */
 static inline ssize_t
 coio_bread(struct ev_io *coio, struct ibuf *buf, size_t sz)
 {
@@ -42,6 +46,22 @@ coio_bread(struct ev_io *coio, struct ibuf *buf, size_t sz)
 	return n;
 }
 
+/**
+ * Read at least sz bytes buffered or until a timeout reached.
+ * Return the amount of bytes read (can be less than sz
+ * in case of EOF or timeout).
+ */
+static inline ssize_t
+coio_bread_timeout(struct ev_io *coio, struct ibuf *buf, size_t sz,
+		   ev_tstamp timeout)
+{
+	ibuf_reserve(buf, sz);
+	ssize_t n = coio_read_ahead_timeout(coio, buf->end, sz, ibuf_unused(buf),
+			                    timeout);
+	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 ev_io *coio, struct ibuf *buf, size_t sz)
@@ -52,4 +72,20 @@ coio_breadn(struct ev_io *coio, struct ibuf *buf, size_t sz)
 	return n;
 }
 
+/** Reat at least sz bytes, buffered. Throw an exception in case
+ * of EOF.
+ * @return the number of bytes read. Can be less than sz in
+ * case of timeout.
+ */
+static inline ssize_t
+coio_breadn_timeout(struct ev_io *coio, struct ibuf *buf, size_t sz,
+		    ev_tstamp timeout)
+{
+	ibuf_reserve(buf, sz);
+	ssize_t n = coio_readn_ahead_timeout(coio, buf->end, sz, ibuf_unused(buf),
+			                     timeout);
+	buf->end += n;
+	return n;
+}
+
 #endif /* TARANTOOL_COIO_BUF_H_INCLUDED */
diff --git a/include/evio.h b/include/evio.h
index 2e19f7d2e1093e91af3aec0313ae4f8caf8c8405..9994b06c5bc4696d177a2d61b58aadda23f8caf3 100644
--- a/include/evio.h
+++ b/include/evio.h
@@ -115,16 +115,38 @@ void
 evio_service_stop(struct evio_service *service);
 
 void
-evio_clear(struct ev_io *ev);
+evio_socket(struct ev_io *coio, int domain, int type, int protocol);
 
 void
-evio_close(struct ev_io *ev);
+evio_close(struct ev_io *evio);
 
 static inline bool
-evio_is_connected(struct ev_io *ev)
+evio_is_active(struct ev_io *ev)
 {
 	return ev->fd >= 0;
 }
 
+static inline void
+evio_timeout_init(ev_tstamp *start, ev_tstamp *delay, ev_tstamp timeout)
+{
+	*start = ev_now();
+	*delay = timeout;
+}
+
+static inline void
+evio_timeout_update(ev_tstamp start, ev_tstamp *delay)
+{
+	ev_tstamp elapsed = ev_now() - start;
+	*delay = (elapsed >= *delay) ? 0 : *delay - elapsed;
+}
+
+void
+evio_setsockopt_tcp(int fd);
+
+void
+evio_setsockopt_tcpserver(int fd);
+
+void
+evio_bind_addrinfo(struct ev_io *coio, struct addrinfo *ai);
 
 #endif /* TARANTOOL_EVIO_H_INCLUDED */
diff --git a/include/ipc.h b/include/ipc.h
index a2eccedb0fd10b826f53ab3a1a7c4664e80863f0..a2193177cf4f140a59c5a5978a8c946a123891f0 100644
--- a/include/ipc.h
+++ b/include/ipc.h
@@ -175,6 +175,4 @@ ipc_channel_has_readers(struct ipc_channel *ch);
 bool
 ipc_channel_has_writers(struct ipc_channel *ch);
 
-extern const ev_tstamp IPC_TIMEOUT_INFINITY;
-
 #endif /* TARANTOOL_IPC_H_INCLUDED */
diff --git a/include/palloc.h b/include/palloc.h
index 2f8935b043a4610d220bca4ec7781a75cc97845a..d945aa5552116b4f81de27ee1e261a895667a8bb 100644
--- a/include/palloc.h
+++ b/include/palloc.h
@@ -32,6 +32,8 @@
 #include <stdint.h>
 #include "util.h"
 
+#define PALLOC_POOL_NAME_MAXLEN 30
+
 struct tbuf;
 
 struct palloc_pool;
diff --git a/include/sio.h b/include/sio.h
index 7b3909bce2b3773d4ba87cb0d4b4bdde52494b58..1b9536a6da3d1d8c9f2f1a0a4c804fd0d3e40396 100644
--- a/include/sio.h
+++ b/include/sio.h
@@ -36,16 +36,20 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
+#include <netdb.h>
 #include <fcntl.h>
 #include "exception.h"
 
 enum { SERVICE_NAME_MAXLEN = 32 };
 
 @interface SocketError: SystemError
+- (id) init: (int) fd in: (const char *) format: (va_list) ap;
 - (id) init: (int) fd in: (const char *) format, ...;
 @end
 
-int sio_socket(void);
+int sio_socket(int domain, int type, int protocol);
+
+int sio_shutdown(int fd, int how);
 
 int sio_getfl(int fd);
 int sio_setfl(int fd, int flag, int on);
@@ -60,12 +64,23 @@ sio_getsockopt(int fd, int level, int optname,
 int sio_connect(int fd, struct sockaddr_in *addr, socklen_t addrlen);
 int sio_bind(int fd, struct sockaddr_in *addr, socklen_t addrlen);
 int sio_listen(int fd);
+int sio_listen_backlog();
 int sio_accept(int fd, struct sockaddr_in *addr, socklen_t *addrlen);
 
 ssize_t sio_read(int fd, void *buf, size_t count);
+ssize_t sio_read_total(int fd, void *buf, size_t count, size_t total);
+
 ssize_t sio_write(int fd, const void *buf, size_t count);
 ssize_t sio_writev(int fd, const struct iovec *iov, int iovcnt);
 
+ssize_t sio_write_total(int fd, const void *buf, size_t count, size_t total);
+
+ssize_t sio_sendto(int fd, const void *buf, size_t len, int flags,
+		   const struct sockaddr_in *dest_addr, socklen_t addrlen);
+
+ssize_t sio_recvfrom(int fd, void *buf, size_t len, int flags,
+		     struct sockaddr_in *src_addr, socklen_t *addrlen);
+
 int sio_getpeername(int fd, struct sockaddr_in *addr);
 const char *sio_strfaddr(struct sockaddr_in *addr);
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6d4a02ce73c1eada33d14ed67336b59b7ad8bb74..6dbba7e4953e31a0ab8f4e86ca6a02da5b6957f0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -96,6 +96,7 @@ set (common_sources
      lua/slab.m
      lua/uuid.m
      lua/lua_ipc.m
+     lua/lua_socket.m
      lua/session.m
 )
 
diff --git a/src/asio.m b/src/asio.m
deleted file mode 100644
index f06d9ed85acb2ccec3b3b3a1a5f24621ff7ccce9..0000000000000000000000000000000000000000
--- a/src/asio.m
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * 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 "asio.h"
-#include "fiber.h"
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <rlist.h>
-
-/*
- * Asynchronous IO Tasks (libeio wrapper).
- * ---
- *
- * Libeio request processing is designed in edge-trigger
- * manner, when libeio is ready to process some requests it
- * calls asio_poller callback.
- *
- * Due to libeio design, asio_poller is called while locks
- * are being held, so it's unable to call any libeio function
- * inside this callback.
- *
- * asio_poller triggers asio_watcher to start the polling process.
- * In case if none of the requests are complete by that time, it
- * starts idle_watcher, which would periodically invoke eio_poll
- * until any of requests are complete.
- *
- * See for details:
- * http://pod.tst.eu/http://cvs.schmorp.de/libeio/eio.pod
-*/
-struct asio_manager {
-	ev_idle asio_repeat_watcher;
-	ev_async asio_watcher;
-	struct rlist active;
-	struct rlist complete;
-	struct rlist ready;
-};
-
-static struct asio_manager asio_manager;
-
-inline static void
-asio_put(struct asio *a, struct rlist *list)
-{
-	rlist_del_entry(a, link);
-	rlist_add_tail_entry(list, a, link);
-}
-
-static void
-asio_schedule_repeat(struct ev_idle *w,
-		     int events __attribute__((unused)))
-{
-	if (eio_poll() != -1)
-		ev_idle_stop(w);
-}
-
-static void
-asio_schedule(struct ev_async *w __attribute__((unused)),
-	      int events __attribute__((unused)))
-{
-	if (eio_poll() == -1)
-		ev_idle_start(&asio_manager.asio_repeat_watcher);
-}
-
-static void asio_poller(void)
-{
-	ev_async_send(&asio_manager.asio_watcher);
-}
-
-/**
- * Init asio subsystem.
- *
- * Create idle and async watchers, init eio.
- */
-void
-asio_init(void)
-{
-	memset(&asio_manager, 0, sizeof(struct asio_manager));
-
-	rlist_init(&asio_manager.active);
-	rlist_init(&asio_manager.complete);
-	rlist_init(&asio_manager.ready);
-
-	ev_idle_init(&asio_manager.asio_repeat_watcher, asio_schedule_repeat);
-	ev_async_init(&asio_manager.asio_watcher, asio_schedule);
-	ev_async_start(&asio_manager.asio_watcher);
-
-	eio_init(asio_poller, NULL);
-}
-
-static inline void
-asio_free_list(struct rlist *list)
-{
-	struct asio *a;
-	struct asio *a_next;
-	a = rlist_first_entry(list, struct asio, link);
-	while (a != rlist_last_entry(list, struct asio, link)) {
-		a_next = rlist_next_entry(a, link);
-		free(a);
-		a = a_next;
-	}
-}
-
-/**
- * Cancel active tasks and free memory.
- */
-void
-asio_free(void)
-{
-	struct asio *a;
-	/* cancel active requests */
-	rlist_foreach_entry(a, &asio_manager.active, link)
-		asio_cancel(a);
-	/* free all complete and ready requests */
-	asio_free_list(&asio_manager.complete);
-	asio_free_list(&asio_manager.ready);
-}
-
-inline static struct asio*
-asio_alloc(void)
-{
-	struct asio *a = malloc(sizeof(struct asio));
-	if (a == NULL)
-		return NULL;
-	memset(a, 0, sizeof(struct asio));
-	rlist_init(&a->link);
-	return a;
-}
-
-static int
-asio_on_complete(eio_req *req)
-{
-	struct asio *a = req->data;
-	struct fiber *f = a->f;
-	a->complete = true;
-	asio_put(a, &asio_manager.complete);
-	if (a->wakeup)
-		fiber_wakeup(f);
-	return 0;
-}
-
-/**
- * Create new eio task with specified libeio function and
- * argument.
- *
- * @return asio object pointer, or NULL on error.
- *
- * @code
- *	static void request(eio_req *req) {
- *		(void)req->data; // "arg"
- *
- *		req->result = "result";
- *	}
- *
- *      struct asio *a = asio_create(request, "arg");
- *      assert(a != NULL);
- *
- */
-struct asio*
-asio_create(void (*f)(eio_req*), void *arg)
-{
-	struct asio *a;
-	if (!rlist_empty(&asio_manager.complete)) {
-		a = rlist_first_entry(&asio_manager.complete, struct asio, link);
-	} else {
-		a = asio_alloc();
-		if (a == NULL)
-			return NULL;
-	}
-	a->wakeup = false;
-	a->complete = false;
-	a->f = fiber;
-	a->f_data = arg;
-	a->result = NULL;
-	a->req = eio_custom(f, 0, asio_on_complete, a);
-	if (a->req == NULL)
-		return NULL;
-	asio_put(a, &asio_manager.active);
-	return a;
-}
-
-/**
- * Yield and wait for a request completion.
- *
- * @return true if timeout exceeded
- */
-bool
-asio_wait(struct asio *a, ev_tstamp timeout)
-{
-	if (a->complete)
-		return 0;
-	a->wakeup = true;
-	bool rc = fiber_yield_timeout(timeout);
-	fiber_testcancel();
-	return rc;
-}
-
-/**
- * Cancel eio request.
- */
-void
-asio_cancel(struct asio *a)
-{
-	eio_cancel(a->req);
-	asio_put(a, &asio_manager.ready);
-}
-
-/**
- * Finish with request interaction.
- */
-void
-asio_finish(struct asio *a)
-{
-	asio_put(a, &asio_manager.ready);
-}
diff --git a/src/coeio.m b/src/coeio.m
index 83bbed9bfffd7bc9c5503fbd50bbdae30af0e8ec..0e200fc48ee398e72ea0cf915127ae20e1757a05 100644
--- a/src/coeio.m
+++ b/src/coeio.m
@@ -37,51 +37,57 @@
 
 /*
  * Asynchronous IO Tasks (libeio wrapper).
- * ---
+ * ---------------------------------------
  *
- * Libeio request processing is designed in edge-trigger
+ * libeio request processing is designed in edge-trigger
  * manner, when libeio is ready to process some requests it
  * calls coeio_poller callback.
  *
- * Due to libeio design, coeio_poller is called while locks
- * are being held, so it's unable to call any libeio function
- * inside this callback.
+ * Due to libeio design, want_pall callback is called while
+ * locks are being held, so it's not possible to call any libeio
+ * function inside this callback. Thus coeio_want_poll raises an
+ * async event which will be dealt with normally as part of the
+ * main Tarantool/Box event loop.
  *
- * coeio_poller triggers coeio_watcher to start the polling process.
- * In case if none of the requests are complete by that time, it
- * starts idle_watcher, which would periodically invoke eio_poll
- * until any of requests are complete.
+ * The async event handler, in turn, performs eio_poll(), which
+ * will run on_complete callback for all ready eio tasks.
+ * In case if some of the requests are not complete by the time
+ * eio_poll() has been called, coeio_idle watcher is started, which
+ * would periodically invoke eio_poll() until all requests are
+ * complete.
  *
  * See for details:
  * http://pod.tst.eu/http://cvs.schmorp.de/libeio/eio.pod
 */
-struct coeio_manager {
-	ev_idle coeio_repeat_watcher;
-	ev_async coeio_watcher;
-	struct rlist active;
-};
 
-static struct coeio_manager coeio_manager;
+struct coeio_manager {
+	ev_idle coeio_idle;
+	ev_async coeio_async;
+} coeio_manager;
 
 static void
-coeio_schedule_repeat(struct ev_idle *w,
-		     int events __attribute__((unused)))
+coeio_idle_cb(struct ev_idle *w, int events __attribute__((unused)))
 {
-	if (eio_poll() != -1)
+	if (eio_poll() != -1) {
+		/* nothing to do */
 		ev_idle_stop(w);
+	}
 }
 
 static void
-coeio_schedule(struct ev_async *w __attribute__((unused)),
-	      int events __attribute__((unused)))
+coeio_async_cb(struct ev_async *w __attribute__((unused)),
+	       int events __attribute__((unused)))
 {
-	if (eio_poll() == -1)
-		ev_idle_start(&coeio_manager.coeio_repeat_watcher);
+	if (eio_poll() == -1) {
+		/* not all tasks are complete. */
+		ev_idle_start(&coeio_manager.coeio_idle);
+	}
 }
 
-static void coeio_poller(void)
+static void
+coeio_want_poll_cb(void)
 {
-	ev_async_send(&coeio_manager.coeio_watcher);
+	ev_async_send(&coeio_manager.coeio_async);
 }
 
 /**
@@ -92,127 +98,110 @@ static void coeio_poller(void)
 void
 coeio_init(void)
 {
-	memset(&coeio_manager, 0, sizeof(struct coeio_manager));
+	eio_init(coeio_want_poll_cb, NULL);
 
-	rlist_init(&coeio_manager.active);
+	ev_idle_init(&coeio_manager.coeio_idle, coeio_idle_cb);
+	ev_async_init(&coeio_manager.coeio_async, coeio_async_cb);
 
-	ev_idle_init(&coeio_manager.coeio_repeat_watcher, coeio_schedule_repeat);
-	ev_async_init(&coeio_manager.coeio_watcher, coeio_schedule);
-	ev_async_start(&coeio_manager.coeio_watcher);
-
-	eio_init(coeio_poller, NULL);
+	ev_async_start(&coeio_manager.coeio_async);
 }
 
 /**
- * Cancel active tasks and free memory.
+ * A single task context.
  */
-void
-coeio_free(void)
-{
-	struct coeio_req *r;
-	struct coeio_req *r_next;
-
-	/* cancel active requests */
-	r = rlist_first_entry(&coeio_manager.active, struct coeio_req, link);
-	while (1) {
-		if (r == rlist_last_entry(&coeio_manager.active,
-					  struct coeio_req, link))
-			break;
-		r_next = rlist_next_entry(r, link);
-		/* eio_cancel sets task as cancelled, this guarantees
-		 * that coeio_on_complete would never be called for
-		 * this request, thus we are allowed to free memory here. */
-		eio_cancel(r->req);
-		free(r);
-		r = r_next;
-	}
-}
+struct coeio_task {
+	/** The calling fiber. */
+	struct fiber *fiber;
+	/** The callback. */
+	ssize_t (*func)(va_list ap);
+	/**
+	 * If the callback sets errno, it's preserved across the
+	 * call.
+	 */
+	/** Callback arguments. */
+	va_list ap;
+	/** Callback results. */
+	ssize_t result;
+	int errorno;
+};
 
-inline static struct coeio_req*
-coeio_alloc(void)
+static void
+coeio_custom_cb(eio_req *req)
 {
-	struct coeio_req *r = calloc(1, sizeof(struct coeio_req));
-	if (r == NULL) {
-		tnt_raise(LoggedError, :ER_MEMORY_ISSUE,
-			  sizeof(struct coeio_req), "coeio_alloc",
-			  "coeio_req");
-	}
-	rlist_init(&r->link);
-	return r;
+	struct coeio_task *task = req->data;
+	req->result = task->func(task->ap);
 }
 
+/**
+ * A callback invoked by eio_poll when associated
+ * eio_request is complete.
+ */
 static int
 coeio_on_complete(eio_req *req)
 {
-	struct coeio_req *r = req->data;
-	struct fiber *f = r->f;
-	r->complete = true;
-	rlist_del_entry(r, link);
-	if (r->wait)
-		fiber_wakeup(f);
+	/*
+	 * Don't touch the task if the request is cancelled:
+	 * the task is allocated on the caller's stack and
+	 * may be already gone. Don't wakeup the caller
+	 * if the task is cancelled: in this case the caller
+	 * is already woken up, avoid double wake-up.
+	 */
+	if (! EIO_CANCELLED(req)) {
+		struct coeio_task *task = req->data;
+		task->result = req->result;
+		task->errorno = req->errorno;
+		fiber_wakeup(task->fiber);
+	}
 	return 0;
 }
 
 /**
- * Create new eio task with specified libeio function and
- * argument.
+ * Create new eio task with specified function and
+ * arguments. Yield and wait until the task is complete
+ * or a timeout occurs.
  *
- * @throws ER_MEMORY_ISSUE
+ * This function doesn't throw exceptions to avoid double error
+ * checking: in most cases it's also necessary to check the return
+ * value of the called function and perform necessary actions. If
+ * func sets errno, the errno is preserved across the call.
  *
- * @return coeio object pointer.
+ * @retval -1 and errno = ENOMEM if failed to create a task
+ * @retval -1 and errno = ETIMEDOUT if timed out
+ * @retval the function return (errno is preserved).
  *
  * @code
- *	static void request(eio_req *req) {
- *		(void)req->data; // "arg"
- *
- *		req->result = "result";
+ *	static ssize_t openfile_cb(va_list ap)
+ *	{
+ *	         const char *filename = va_arg(ap);
+ *	         int flags = va_arg(ap);
+ *	         return open(filename, flags);
  *	}
  *
- *      struct coeio_req *r = coeio_custom(request, "arg");
- *
- */
-struct coeio_req*
-coeio_custom(void (*f)(eio_req*), void *arg)
-{
-	struct coeio_req *r = coeio_alloc();
-	r->f = fiber;
-	r->f_data = arg;
-	r->req = eio_custom(f, 0, coeio_on_complete, r);
-	if (r->req == NULL) {
-		tnt_raise(LoggedError, :ER_MEMORY_ISSUE,
-			  sizeof(struct eio_req), "coeio_custom",
-			  "eio_req");
-	}
-	rlist_add_tail_entry(&coeio_manager.active, r, link);
-	return r;
-}
-
-/**
- * Yield and wait for a request completion.
- *
- * @throws FiberCancelException
- *
- * @return request result pointer.
- *
- * @code
- *      struct coeio_req *r = coeio_custom(callback, NULL);
- *
- *      // wait for result and free request object
- *      void *result = coeio_wait(r);
- *
- *      // continue with result
+ *	 if (coeio_custom(openfile_cb, 0.10, "/tmp/file", 0) == -1)
+ *		// handle errors.
+ *	...
  */
-void *coeio_wait(struct coeio_req *r)
+ssize_t
+coeio_custom(ssize_t (*func)(va_list ap), ev_tstamp timeout, ...)
 {
-	if (r->complete) {
-		void *result = r->result;
-		free(r);
-		return result;
+	struct coeio_task task;
+	task.fiber = fiber;
+	task.func = func;
+	task.result = -1;
+	va_start(task.ap, timeout);
+	struct eio_req *req = eio_custom(coeio_custom_cb, 0,
+					 coeio_on_complete, &task);
+	if (req == NULL) {
+		errno = ENOMEM;
+	} else if (fiber_yield_timeout(timeout)) {
+		/* timeout. */
+		errno = ETIMEDOUT;
+		task.result = -1;
+		eio_cancel(req);
+	} else {
+		/* the task is complete. */
+		errno = task.errorno;
 	}
-	r->wait = true;
-	fiber_yield();
-	void *result = r->result;
-	free(r);
-	fiber_testcancel();
-	return result;
+	va_end(task.ap);
+	return task.result;
 }
diff --git a/src/coio.m b/src/coio.m
index c1c8a2c746f40edf95c0f503f28d73bc4b0a0a27..d24c67d074f961ee19830cb96bb5952005f41d94 100644
--- a/src/coio.m
+++ b/src/coio.m
@@ -35,84 +35,159 @@
 #include "iobuf.h"
 #include "sio.h"
 
+
 /** Note: this function does not throw */
 void
-coio_init(struct ev_io *coio, int fd)
+coio_init(struct ev_io *coio)
 {
-	assert(fd >= 0);
-
 	/* Prepare for ev events. */
 	coio->data = fiber;
 	ev_init(coio, (void *) fiber_schedule);
-	coio->fd = fd;
+	coio->fd = -1;
 }
 
 /**
- * Connect to a host and initialize coio with connected
- * socket.
+ * Connect to a host.
  */
 void
 coio_connect(struct ev_io *coio, struct sockaddr_in *addr)
 {
-	int fd = sio_socket();
-	@try {
-		coio_init(coio, fd);
+	coio_connect_timeout(coio, addr, sizeof(*addr),
+			     TIMEOUT_INFINITY);
+}
 
-                int on = 1;
-                /* libev is non-blocking */
-                sio_setfl(fd, O_NONBLOCK, on);
+/**
+ * Connect to a host with a specified timeout.
+ * @retval true timeout
+ * @retval false connected
+ */
+bool
+coio_connect_timeout(struct ev_io *coio, struct sockaddr_in *addr,
+		     socklen_t len, ev_tstamp timeout)
+{
+	if (sio_connect(coio->fd, addr, len) == 0)
+		return false;
+	assert(errno == EINPROGRESS);
+	/*
+	 * Wait until socket is ready for writing or
+	 * timed out.
+	 */
+	ev_io_set(coio, coio->fd, EV_WRITE);
+	ev_io_start(coio);
+	bool is_timedout = fiber_yield_timeout(timeout);
+	ev_io_stop(coio);
+	fiber_testcancel();
+	if (is_timedout) {
+		errno = ETIMEDOUT;
+		return true;
+	}
+	int error = EINPROGRESS;
+	socklen_t sz = sizeof(error);
+	sio_getsockopt(coio->fd, SOL_SOCKET, SO_ERROR,
+		       &error, &sz);
+	if (error != 0) {
+		errno = error;
+		tnt_raise(SocketError, :coio->fd in:"connect");
+	}
+	return false;
+}
 
-                /*
-		 * SO_KEEPALIVE to ensure connections don't hang
-                 * around for too long when a link goes away
-                 */
-                sio_setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
-                               &on, sizeof(on));
-                /*
-                 * Lower latency is more important than higher
-                 * bandwidth, and we usually write entire
-                 * request/response in a single syscall.
-                 */
-                sio_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
-                               &on, sizeof(on));
+/**
+ * Connect to a first address in addrinfo list and initialize coio
+ * with connected socket.
+ *
+ * If coio is already initialized, socket family,
+ * type and protocol must match the remote address.
+ *
+ * @retval true  timeout
+ * @retval false sucess
+ */
+bool
+coio_connect_addrinfo(struct ev_io *coio, struct addrinfo *ai,
+		      ev_tstamp timeout)
+{
+	ev_tstamp start, delay;
+	evio_timeout_init(&start, &delay, timeout);
+	assert(! evio_is_active(coio));
+	bool res = true;
+	while (ai) {
+		struct sockaddr_in *addr = (struct sockaddr_in *)ai->ai_addr;
+		@try {
+			evio_socket(coio, ai->ai_family,
+				    ai->ai_socktype,
+				    ai->ai_protocol);
+			res = coio_connect_timeout(coio, addr, ai->ai_addrlen,
+						   delay);
+			return res;
+		} @catch (SocketError *e) {
+			if (ai->ai_next == NULL)
+				@throw;
+			ev_now_update();
+			evio_timeout_update(start, &delay);
+		} @finally {
+			if (res)
+				evio_close(coio);
+		}
+		ai = ai->ai_next;
+	}
+	/* unreachable. */
+	tnt_raise(SocketError, :coio->fd in: "connect_addrinfo()");
+}
 
-		if (sio_connect(fd, addr, sizeof(*addr)) < 0) {
-			assert(errno == EINPROGRESS);
-			/* Wait until socket is ready for writing. */
-			ev_io_set(coio, fd, EV_WRITE);
-			ev_io_start(coio);
-			fiber_yield();
-			ev_io_stop(coio);
-			fiber_testcancel();
+/**
+ * Wait a client connection on a server socket until
+ * timedout.
+ */
+int
+coio_accept(struct ev_io *coio, struct sockaddr_in *addr,
+	    socklen_t addrlen, ev_tstamp timeout)
+{
+	ev_tstamp start, delay;
+	evio_timeout_init(&start, &delay, timeout);
 
-			int error = EINPROGRESS;
-			socklen_t sz = sizeof(error);
-			sio_getsockopt(fd, SOL_SOCKET, SO_ERROR,
-				       &error, &sz);
-			if (error != 0) {
-				errno = error;
-				tnt_raise(SocketError, :fd in:"connect");
-			}
+	while (true) {
+		/* Assume that there are waiting clients
+		 * available */
+		int fd = sio_accept(coio->fd, addr, &addrlen);
+		if (fd >= 0) {
+			evio_setsockopt_tcp(fd);
+			return fd;
 		}
-	} @catch (tnt_Exception *e) {
-		evio_close(coio);
-		@throw;
+		/* The socket is not ready, yield */
+		if (! ev_is_active(coio)) {
+			ev_io_set(coio, coio->fd, EV_READ);
+			ev_io_start(coio);
+		}
+		/* Yield control to other fibers until the timeout
+		 * is being reached. */
+		bool is_timedout = fiber_yield_timeout(delay);
+		fiber_testcancel();
+		if (is_timedout) {
+			errno = ETIMEDOUT;
+			tnt_raise(SocketError, :coio->fd in:"accept");
+		}
+		evio_timeout_update(start, &delay);
 	}
 }
 
 /**
  * Read at least sz bytes from socket with readahead.
  *
- * In case of EOF returns 0.
+ * In case of EOF returns the amount read until eof (possibly 0),
+ * and sets errno to 0.
  * Can read up to bufsiz bytes.
  *
- * Returns the number of bytes read.
+ * @retval the number of bytes read, sets the errno to ETIMEDOUT or 0.
  */
 ssize_t
-coio_read_ahead(struct ev_io *coio, void *buf, size_t sz, size_t bufsiz)
+coio_read_ahead_timeout(struct ev_io *coio, void *buf, size_t sz,
+			size_t bufsiz, ev_tstamp timeout)
 {
 	assert(sz <= bufsiz);
 
+	ev_tstamp start, delay;
+	evio_timeout_init(&start, &delay, timeout);
+
 	ssize_t to_read = (ssize_t) sz;
 	@try {
 		while (true) {
@@ -129,15 +204,25 @@ coio_read_ahead(struct ev_io *coio, void *buf, size_t sz, size_t bufsiz)
 				buf += nrd;
 				bufsiz -= nrd;
 			} else if (nrd == 0) {
-				return 0;
+				errno = 0;
+				return sz - to_read;
 			}
 			/* The socket is not ready, yield */
 			if (! ev_is_active(coio)) {
 				ev_io_set(coio, coio->fd, EV_READ);
 				ev_io_start(coio);
 			}
-			fiber_yield();
+			/*
+			 * Yield control to other fibers until the
+			 * timeout is being reached.
+			 */
+			bool is_timedout = fiber_yield_timeout(delay);
 			fiber_testcancel();
+			if (is_timedout) {
+				errno = ETIMEDOUT;
+				return sz - to_read;
+			}
+			evio_timeout_update(start, &delay);
 		}
 	} @finally {
 		ev_io_stop(coio);
@@ -163,6 +248,25 @@ coio_readn_ahead(struct ev_io *coio, void *buf, size_t sz, size_t bufsiz)
 	return nrd;
 }
 
+/**
+ * Read at least sz bytes, with readahead and timeout.
+ *
+ * Treats EOF as an error, and throws an exception.
+ *
+ * @retval the number of bytes read, > 0.
+ */
+ssize_t
+coio_readn_ahead_timeout(struct ev_io *coio, void *buf, size_t sz, size_t bufsiz,
+		         ev_tstamp timeout)
+{
+	ssize_t nrd = coio_read_ahead_timeout(coio, buf, sz, bufsiz, timeout);
+	if (nrd < sz && errno == 0) { /* EOF. */
+		errno = EPIPE;
+		tnt_raise(SocketError, :coio->fd in:"unexpected EOF when reading "
+			  "from socket");
+	}
+	return nrd;
+}
 
 /** Write sz bytes to socket.
  *
@@ -170,22 +274,29 @@ coio_readn_ahead(struct ev_io *coio, void *buf, size_t sz, size_t bufsiz)
  * the socket is not ready, yields the current
  * fiber until the socket becomes ready, until
  * all data is written.
+ *
+ * @retval the number of bytes written. Can be less than
+ * requested only in case of timeout.
  */
-void
-coio_write(struct ev_io *coio, const void *buf, size_t sz)
+ssize_t
+coio_write_timeout(struct ev_io *coio, const void *buf, size_t sz,
+	   ev_tstamp timeout)
 {
+	size_t towrite = sz;
+	ev_tstamp start, delay;
+	evio_timeout_init(&start, &delay, timeout);
 	@try {
 		while (true) {
 			/*
 			 * Sic: write as much data as possible,
 			 * assuming the socket is ready.
 		         */
-			ssize_t nwr = sio_write(coio->fd, buf, sz);
+			ssize_t nwr = sio_write(coio->fd, buf, towrite);
 			if (nwr > 0) {
 				/* Go past the data just written. */
-				if (nwr >= sz)
-					return;
-				sz -= nwr;
+				if (nwr >= towrite)
+					return sz;
+				towrite -= nwr;
 				buf += nwr;
 			}
 			if (! ev_is_active(coio)) {
@@ -195,6 +306,19 @@ coio_write(struct ev_io *coio, const void *buf, size_t sz)
 			/* Yield control to other fibers. */
 			fiber_yield();
 			fiber_testcancel();
+			/*
+			 * Yield control to other fibers until the
+			 * timeout is reached or the socket is
+			 * ready.
+			 */
+			bool is_timedout = fiber_yield_timeout(delay);
+			fiber_testcancel();
+
+			if (is_timedout) {
+				errno = ETIMEDOUT;
+				return sz - towrite;
+			}
+			evio_timeout_update(start, &delay);
 		}
 	} @finally {
 		ev_io_stop(coio);
@@ -219,7 +343,8 @@ coio_flush(int fd, struct iovec *iov, ssize_t offset, int iovcnt)
 }
 
 ssize_t
-coio_writev(struct ev_io *coio, struct iovec *iov, int iovcnt, size_t size_hint)
+coio_writev(struct ev_io *coio, struct iovec *iov, int iovcnt,
+	    size_t size_hint)
 {
 	ssize_t total = 0;
 	size_t iov_len = 0;
@@ -259,6 +384,100 @@ coio_writev(struct ev_io *coio, struct iovec *iov, int iovcnt, size_t size_hint)
 	return total;
 }
 
+/**
+ * Send up to sz bytes to a UDP socket.
+ * Return the number of bytes sent.
+ *
+ * @retval  0, errno = ETIMEDOUT timeout
+ * @retval  n  the number of bytes written
+ */
+ssize_t
+coio_sendto_timeout(struct ev_io *coio, const void *buf, size_t sz, int flags,
+		    const struct sockaddr_in *dest_addr, socklen_t addrlen,
+		    ev_tstamp timeout)
+{
+	ev_tstamp start, delay;
+	evio_timeout_init(&start, &delay, timeout);
+	@try {
+		while (true) {
+			/*
+			 * Sic: write as much data as possible,
+			 * assuming the socket is ready.
+		         */
+			ssize_t nwr = sio_sendto(coio->fd, buf, sz,
+						 flags, dest_addr, addrlen);
+			if (nwr > 0)
+				return nwr;
+			if (! ev_is_active(coio)) {
+				ev_io_set(coio, coio->fd, EV_WRITE);
+				ev_io_start(coio);
+			}
+			/*
+			 * Yield control to other fibers until
+			 * timeout is reached or the socket is
+			 * ready.
+			 */
+			bool is_timedout = fiber_yield_timeout(delay);
+			fiber_testcancel();
+			if (is_timedout) {
+				errno = ETIMEDOUT;
+				return 0;
+			}
+			evio_timeout_update(start, &delay);
+		}
+	} @finally {
+		ev_io_stop(coio);
+	}
+}
+
+/**
+ * Read a datagram up to sz bytes from a socket, with a timeout.
+ *
+ * @retval   0, errno = 0   eof
+ * @retval   0, errno = ETIMEDOUT timeout
+ * @retvl    n              number of bytes read
+ */
+ssize_t
+coio_recvfrom_timeout(struct ev_io *coio, void *buf, size_t sz, int flags,
+		      struct sockaddr_in *src_addr, socklen_t addrlen,
+		      ev_tstamp timeout)
+{
+	ev_tstamp start, delay;
+	evio_timeout_init(&start, &delay, timeout);
+
+	@try {
+		while (true) {
+			/*
+			 * Read as much data as possible,
+			 * assuming the socket is ready.
+		         */
+			ssize_t nrd = sio_recvfrom(coio->fd, buf, sz, flags,
+						   src_addr, &addrlen);
+			if (nrd >= 0)
+				return nrd;
+
+			if (! ev_is_active(coio)) {
+				ev_io_set(coio, coio->fd, EV_WRITE);
+				ev_io_start(coio);
+			}
+			/*
+			 * Yield control to other fibers until
+			 * timeout is reached or the socket is
+			 * ready.
+			 */
+			bool is_timedout = fiber_yield_timeout(delay);
+			fiber_testcancel();
+			if (is_timedout) {
+				errno = ETIMEDOUT;
+				return 0;
+			}
+			evio_timeout_update(start, &delay);
+		}
+	} @finally {
+		ev_io_stop(coio);
+	}
+}
+
 void
 coio_service_on_accept(struct evio_service *evio_service,
 		       int fd, struct sockaddr_in *addr)
@@ -266,7 +485,8 @@ coio_service_on_accept(struct evio_service *evio_service,
 	struct coio_service *service = evio_service->on_accept_param;
 	struct ev_io coio;
 
-	coio_init(&coio, fd);
+	coio_init(&coio);
+	coio.fd = fd;
 
 	/* Set connection name. */
 	char fiber_name[SERVICE_NAME_MAXLEN];
@@ -311,4 +531,3 @@ coio_service_init(struct coio_service *service, const char *name,
 	service->handler = handler;
 	service->handler_param = handler_param;
 }
-
diff --git a/src/evio.m b/src/evio.m
index 83a2080e97e0824b04750175663adcc06475b00b..fddf5ec2403933b0ad8ac4a6fc6308fd0f87b6de 100644
--- a/src/evio.m
+++ b/src/evio.m
@@ -38,25 +38,108 @@
 
 #define BIND_RETRY_DELAY 0.1
 
+/** Note: this function does not throw. */
 void
-evio_clear(struct ev_io *ev)
+evio_close(struct ev_io *evio)
 {
-	ev_init(ev, NULL);
-	ev->data = NULL;
-	ev->fd = -1;
+	/* Stop I/O events. Safe to do even if not started. */
+	ev_io_stop(evio);
+	/* Close the socket. */
+	close(evio->fd);
+	/* Make sure evio_is_active() returns a proper value. */
+	evio->fd = -1;
 }
 
-/** Note: this function does not throw. */
+/**
+ * Create an endpoint for communication.
+ * Set socket as non-block and apply protocol specific options.
+ */
 void
-evio_close(struct ev_io *ev)
+evio_socket(struct ev_io *coio, int domain, int type, int protocol)
 {
-	/* Stop I/O events. Safe to do even if not started. */
-	ev_io_stop(ev);
+	assert(coio->fd == -1);
+	/* Don't leak fd if setsockopt fails. */
+	coio->fd = sio_socket(domain, type, protocol);
+	if (type == SOCK_STREAM) {
+		evio_setsockopt_tcp(coio->fd);
+	} else {
+		sio_setfl(coio->fd, O_NONBLOCK, 1);
+	}
+}
 
-	/* Close the socket. */
-	close(ev->fd);
-	/* Make sure evio_is_connected() returns a proper value. */
-	ev->fd = -1;
+
+/** Set common tcp socket client options. */
+void
+evio_setsockopt_tcp(int fd)
+{
+	int on = 1;
+	/* In case this throws, the socket is not leaked. */
+	sio_setfl(fd, O_NONBLOCK, on);
+	/* SO_KEEPALIVE to ensure connections don't hang
+	 * around for too long when a link goes away.
+	 */
+	sio_setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
+		       &on, sizeof(on));
+	/*
+	 * Lower latency is more important than higher
+	 * bandwidth, and we usually write entire
+	 * request/response in a single syscall.
+	 */
+	sio_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
+		       &on, sizeof(on));
+}
+
+/** Set tcp options for server sockets. */
+void
+evio_setsockopt_tcpserver(int fd)
+{
+	int on = 1;
+	/* In case this throws, the socket is not leaked. */
+	sio_setfl(fd, O_NONBLOCK, on);
+	/* Allow reuse local adresses. */
+	sio_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+		       &on, sizeof(on));
+	sio_setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
+		       &on, sizeof(on));
+
+	/* Send all buffered messages on socket before take
+	 * control out from close(2) or shutdown(2). */
+	struct linger linger = { 0, 0 };
+
+	sio_setsockopt(fd, SOL_SOCKET, SO_LINGER,
+		       &linger, sizeof(linger));
+}
+
+/**
+ * Bind to a first address in addrinfo list and initialize coio
+ * with bound socket.
+ */
+void
+evio_bind_addrinfo(struct ev_io *evio, struct addrinfo *ai)
+{
+	assert(! evio_is_active(evio));
+	int fd = -1;
+	while (ai) {
+		struct sockaddr_in *addr = (struct sockaddr_in *)ai->ai_addr;
+		@try {
+			fd = sio_socket(ai->ai_family, ai->ai_socktype,
+					ai->ai_protocol);
+			evio_setsockopt_tcpserver(fd);
+			if (sio_bind(fd, addr, ai->ai_addrlen) == 0) {
+				evio->fd = fd;
+				return; /* success. */
+			}
+			assert(errno == EADDRINUSE);
+		} @catch (SocketError *e) {
+			if (ai->ai_next == NULL) {
+				close(fd);
+				@throw;
+			}
+		}
+		close(fd);
+		ai = ai->ai_next;
+	}
+	tnt_raise(SocketError, :evio->fd in:"evio_bind_addrinfo()");
 }
 
 static inline int
@@ -84,22 +167,8 @@ evio_service_accept_cb(ev_io *watcher,
 
 		if (fd < 0) /* EAGAIN, EWOULDLOCK, EINTR */
 			return;
-
-		int on = 1;
-		/* libev is non-blocking */
-		sio_setfl(fd, O_NONBLOCK, on);
-		/* SO_KEEPALIVE to ensure connections don't hang
-		 * around for too long when a link goes away
-		 */
-		sio_setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
-			       &on, sizeof(on));
-		/*
-		 * Lower latency is more important than higher
-		 * bandwidth, and we usually write entire
-		 * request/response in a single syscall.
-		 */
-		sio_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
-			       &on, sizeof(on));
+		/* set common tcp options */
+		evio_setsockopt_tcp(fd);
 		/*
 		 * Invoke the callback and pass it the accepted
 		 * socket.
@@ -123,22 +192,10 @@ static int
 evio_service_bind_and_listen(struct evio_service *service)
 {
 	/* Create a socket. */
-	int fd = sio_socket();
+	int fd = sio_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 
 	@try {
-		int on = 1;
-		/* Set appropriate options. */
-		sio_setfl(fd, O_NONBLOCK, on);
-
-		sio_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
-			       &on, sizeof(on));
-		sio_setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
-			       &on, sizeof(on));
-
-		struct linger linger = { 0, 0 };
-
-		sio_setsockopt(fd, SOL_SOCKET, SO_LINGER,
-			       &linger, sizeof(linger));
+		evio_setsockopt_tcpserver(fd);
 
 		if (sio_bind(fd, &service->addr, sizeof(service->addr)) ||
 		    sio_listen(fd)) {
diff --git a/src/ipc.m b/src/ipc.m
index aed588e3528bcec1d00dea3e5c477cbaf2eef7f6..db588c6d3ce20368e1ad081c9a741a26723cefb6 100644
--- a/src/ipc.m
+++ b/src/ipc.m
@@ -31,8 +31,6 @@
 #include <stdlib.h>
 #include <rlist.h>
 
-const ev_tstamp IPC_TIMEOUT_INFINITY = 365*86400*100.0;
-
 struct ipc_channel {
 	struct rlist readers, writers, bcast;
 	unsigned size;
@@ -147,7 +145,7 @@ ipc_channel_get_timeout(struct ipc_channel *ch, ev_tstamp timeout)
 void *
 ipc_channel_get(struct ipc_channel *ch)
 {
-	return ipc_channel_get_timeout(ch, IPC_TIMEOUT_INFINITY);
+	return ipc_channel_get_timeout(ch, TIMEOUT_INFINITY);
 }
 
 int
@@ -200,7 +198,7 @@ ipc_channel_put_timeout(struct ipc_channel *ch, void *data,
 void
 ipc_channel_put(struct ipc_channel *ch, void *data)
 {
-	ipc_channel_put_timeout(ch, data, IPC_TIMEOUT_INFINITY);
+	ipc_channel_put_timeout(ch, data, TIMEOUT_INFINITY);
 }
 
 bool
diff --git a/src/iproto.m b/src/iproto.m
index 75642eb81ecbbb0b6857790c912be05b2d6255e6..e5aabfd7f7c0bfd2e7c9e1b1a3d9a77fd9f7857c 100644
--- a/src/iproto.m
+++ b/src/iproto.m
@@ -401,7 +401,7 @@ SLIST_HEAD(, iproto_session) iproto_session_cache =
 static inline bool
 iproto_session_is_idle(struct iproto_session *session)
 {
-	return !evio_is_connected(&session->input) &&
+	return !evio_is_active(&session->input) &&
 		ibuf_size(&session->iobuf[0]->in) == 0 &&
 		ibuf_size(&session->iobuf[1]->in) == 0;
 }
@@ -751,14 +751,14 @@ iproto_process_request(struct iproto_request *request)
 	struct iobuf *iobuf = request->iobuf;
 	struct port_iproto port;
 	@try {
-		if (unlikely(! evio_is_connected(&session->output)))
+		if (unlikely(! evio_is_active(&session->output)))
 			return;
 
 		fiber_set_sid(fiber, session->sid);
 		iproto_reply(&port, *session->handler,
 			     &iobuf->out, header);
 
-		if (unlikely(! evio_is_connected(&session->output)))
+		if (unlikely(! evio_is_active(&session->output)))
 			return;
 		if (! ev_is_active(&session->output))
 			ev_feed_event(&session->output, EV_WRITE);
diff --git a/src/lua/init.m b/src/lua/init.m
index f1ad96ca8136fb75ec541ff4651b46231687dfbc..8eca308a1dbd78759009c02bd4fe48dfc6f0e0ee 100644
--- a/src/lua/init.m
+++ b/src/lua/init.m
@@ -45,6 +45,7 @@
 #include "pickle.h"
 #include "fiber.h"
 #include "lua_ipc.h"
+#include "lua_socket.h"
 #include "lua/info.h"
 #include "lua/slab.h"
 #include "lua/stat.h"
@@ -1367,6 +1368,7 @@ tarantool_lua_init()
 	tarantool_lua_stat_init(L);
 	tarantool_lua_ipc_init(L);
 	tarantool_lua_uuid_init(L);
+	tarantool_lua_socket_init(L);
 	tarantool_lua_session_init(L);
 
 	mod_lua_init(L);
diff --git a/src/lua/lua_ipc.m b/src/lua/lua_ipc.m
index 802fe2fb13d3670e79386004f7cb55337b3c303a..596b3a7d104c7cd1da6e966caf24f32d6f106943 100644
--- a/src/lua/lua_ipc.m
+++ b/src/lua/lua_ipc.m
@@ -117,7 +117,7 @@ lbox_ipc_channel_put(struct lua_State *L)
 
 	switch (top) {
 	case 2:
-		timeout = IPC_TIMEOUT_INFINITY;
+		timeout = TIMEOUT_INFINITY;
 		break;
 	case 3:
 		if (!lua_isnumber(L, -1))
@@ -175,7 +175,7 @@ lbox_ipc_channel_get(struct lua_State *L)
 		if (timeout < 0)
 			luaL_error(L, "wrong timeout");
 	} else {
-		timeout = IPC_TIMEOUT_INFINITY;
+		timeout = TIMEOUT_INFINITY;
 	}
 
 	struct ipc_channel *ch = lbox_check_channel(L, 1);
diff --git a/include/asio.h b/src/lua/lua_socket.h
similarity index 61%
rename from include/asio.h
rename to src/lua/lua_socket.h
index 57f404b7e7838495d6f4b7f81221fb1870d8638a..663d5e3b5091162c73b950e98824cf0c7f914219 100644
--- a/include/asio.h
+++ b/src/lua/lua_socket.h
@@ -1,5 +1,5 @@
-#ifndef TARANTOOL_ASIO_H_INCLUDED
-#define TARANTOOL_ASIO_H_INCLUDED
+#ifndef TARANTOOL_LUA_SOCKET_H_INCLUDED
+#define TARANTOOL_LUA_SOCKET_H_INCLUDED
 /*
  * Redistribution and use in source and binary forms, with or
  * without modification, are permitted provided that the following
@@ -29,43 +29,7 @@
  * SUCH DAMAGE.
  */
 
-#include "config.h"
+struct lua_State;
+void tarantool_lua_socket_init(struct lua_State *L);
 
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <tarantool_ev.h>
-#include <tarantool_eio.h>
-#include <coro.h>
-#include <util.h>
-#include <rlist.h>
-
-/**
- * Asynchronous IO Tasks (libeio wrapper)
- *
- * Yield the current fiber until a created task is complete.
- */
-
-
-/**
- * A single task' context.
- */
-struct asio {
-	struct eio_req *req;
-	bool complete;
-	bool wakeup;
-	struct fiber *f;
-	void *f_data;
-	void *result;
-	struct rlist link;
-};
-
-void asio_init(void);
-void asio_free(void);
-struct asio *asio_create(void (*f)(eio_req*), void *arg);
-bool asio_wait(struct asio *a, ev_tstamp timeout);
-void asio_cancel(struct asio *a);
-void asio_finish(struct asio *a);
-
-#endif /* TARANTOOL_ASIO_H_INCLUDED */
+#endif /* TARANTOOL_LUA_SOCKET_H_INCLUDED */
diff --git a/src/lua/lua_socket.m b/src/lua/lua_socket.m
new file mode 100644
index 0000000000000000000000000000000000000000..b7bdfc82424fa3675cae66bfda57d4e4393013f3
--- /dev/null
+++ b/src/lua/lua_socket.m
@@ -0,0 +1,920 @@
+/*
+ * 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 "lua_socket.h"
+#include "lua.h"
+#include "lauxlib.h"
+#include "lualib.h"
+
+#include "sio.h"
+#include "evio.h"
+#include "coio.h"
+#include "coeio.h"
+#include "iobuf.h"
+#include "coio_buf.h"
+
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include "fiber.h"
+#include "tbuf.h"
+#include <lua/init.h>
+#include <stdlib.h>
+
+static const char socketlib_name[] = "box.socket";
+
+/**
+ * gethostbyname(), getaddrinfo() and friends do not use
+ * errno or errno.h errors for their error returns.
+ * Here we map all failures of name resolution to a single
+ * socket error number.
+ */
+enum bio_error {
+	ERESOLVE = -1
+};
+
+/** last operation status */
+enum bio_status {
+	BIO_ERROR,
+	BIO_TIMEOUT,
+	BIO_EOF,
+	BIO_LIMIT
+};
+
+struct bio_socket {
+	struct ev_io coio;
+	struct iobuf *iob;
+	/** SOCK_DGRAM or SOCK_STREAM */
+	int socktype;
+	int error;
+};
+
+static int
+bio_pushsocket(struct lua_State *L, int socktype)
+{
+	struct bio_socket *s = lua_newuserdata(L, sizeof(struct bio_socket));
+	luaL_getmetatable(L, socketlib_name);
+	lua_setmetatable(L, -2);
+	coio_init(&s->coio);
+	s->socktype = socktype;
+	s->iob = NULL;
+	s->error = 0;
+	/*
+	 * Do not create a file descriptor yet. Thanks to ipv6,
+	 * socket family is not known until host name is resolved.
+	 * Socket type is saved in s->socktype.
+	 */
+	return 1;
+}
+
+static inline struct bio_socket *
+bio_checksocket(struct lua_State *L, int narg)
+{
+	/* avoiding unnecessary luajit assert */
+	if (lua_gettop(L) < narg)
+		luaL_error(L, "box.socket: incorrect method call");
+	return luaL_checkudata(L, narg, socketlib_name);
+}
+
+static inline struct bio_socket *
+bio_checkactivesocket(struct lua_State *L, int narg)
+{
+	struct bio_socket *s = bio_checksocket(L, narg);
+	if (! evio_is_active(&s->coio))
+		luaL_error(L, "box.socket: socket is not initialized");
+	return s;
+}
+
+/**
+ * The last error is saved in socket. It can be pretty harmless,
+ * for example a timeout. Clear the last error before the next
+ * call. For now clear any error, even a persistent one: it's not
+ * clear how being any smarter can benefit the library user.
+ */
+static inline void
+bio_clearerr(struct bio_socket *s)
+{
+	s->error = false;
+}
+
+static void
+bio_initbuf(struct bio_socket *s)
+{
+	assert(s->iob == NULL);
+	char name[PALLOC_POOL_NAME_MAXLEN];
+	const char *type = s->socktype == SOCK_STREAM ? "tcp" : "udp";
+	snprintf(name, sizeof(name), "box.io.%s(%d)",
+		 type, s->coio.fd);
+	s->iob = iobuf_create(name);
+}
+
+static inline int
+bio_pushstatus(struct lua_State *L, enum bio_status s)
+{
+	static char *status_strs[] = {"error", "timeout", "eof", "limit"};
+	lua_pushstring(L, status_strs[s]);
+	return 1;
+}
+
+static int
+bio_pusherrorcode(struct lua_State *L, struct bio_socket *s)
+{
+	lua_pushinteger(L, s->error);
+	if (s->error >= 0)
+		lua_pushstring(L, strerror(s->error));
+	else if (s->error == ERESOLVE)
+		lua_pushstring(L, "Host name resolution failed");
+	return 2;
+}
+
+/** Error from accept, connect, bind, etc. */
+static int
+bio_pusherror(struct lua_State *L, struct bio_socket *s, int errorno)
+{
+	s->error = errorno;
+	bio_pushstatus(L, errorno == ETIMEDOUT ? BIO_TIMEOUT : BIO_ERROR);
+	return 1 + bio_pusherrorcode(L, s);
+}
+
+static int
+bio_pushsockerror(struct lua_State *L, struct bio_socket *s, int errorno)
+{
+	lua_pushnil(L); /* no socket. */
+	return 1 + bio_pusherror(L, s, errorno);
+}
+
+/** Error from send */
+static int
+bio_pushsenderror(struct lua_State *L, struct bio_socket *s, size_t sz, int errorno)
+{
+	lua_pushinteger(L, sz); /* sent zero bytes. */
+	return 1 + bio_pusherror(L, s, errorno);
+}
+
+/** Error from recv */
+static int
+bio_pushrecverror(struct lua_State *L, struct bio_socket *s, int errorno)
+{
+	lua_pushstring(L, ""); /* received no data. */
+	return 1 + bio_pusherror(L, s, errorno);
+}
+
+static inline int
+bio_pusheof(struct lua_State *L, struct bio_socket *s)
+{
+	struct ibuf *in = &s->iob->in;
+	lua_pushlstring(L, in->pos, ibuf_size(in));
+	in->pos += ibuf_size(in);
+	bio_pushstatus(L, BIO_EOF);
+	return 2;
+}
+
+/*
+ * Resolver function, run in separate thread by
+ * coeio (libeio).
+*/
+static ssize_t
+bio_getaddrinfo_cb(va_list ap)
+{
+	const char *host = va_arg(ap, const char *);
+	const char *port = va_arg(ap, const char *);
+	const struct addrinfo *hints = va_arg(ap, const struct addrinfo *);
+	struct addrinfo **res = va_arg(ap, struct addrinfo **);
+	if (getaddrinfo(host, port, hints, res)) {
+		errno = ERESOLVE;
+		return -1;
+	}
+	return 0;
+}
+
+static struct addrinfo *
+bio_resolve(int socktype, const char *host, const char *port,
+            ev_tstamp timeout)
+{
+	/* Fill hinting information for use by connect(2) or bind(2). */
+	struct addrinfo *result = NULL;
+	struct addrinfo hints;
+	memset(&hints, 0, sizeof(struct addrinfo));
+	hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+	hints.ai_socktype = socktype;
+	hints.ai_flags = AI_ADDRCONFIG|AI_NUMERICSERV|AI_PASSIVE;
+	hints.ai_protocol = 0;
+	/* do resolving */
+	if (coeio_custom(bio_getaddrinfo_cb, timeout, host, port,
+			 &hints, &result))
+		return NULL;
+	return result;
+}
+
+static int
+lbox_socket_tostring(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checksocket(L, -1);
+	lua_pushfstring(L, "%d", s->coio.fd);
+	return 1;
+}
+
+/**
+ * box.io.tcp()
+ *
+ * Create SOCK_STREAM socket object.
+ */
+static int
+lbox_socket_tcp(struct lua_State *L)
+{
+	return bio_pushsocket(L, SOCK_STREAM);
+}
+
+/**
+ * box.io.udp()
+ *
+ * Create SOCK_DGRAM socket object.
+ */
+static int
+lbox_socket_udp(struct lua_State *L)
+{
+	return bio_pushsocket(L, SOCK_DGRAM);
+}
+
+/**
+ * socket:close()
+ *
+ * Close the socket. A closed socket should not be used
+ * any more.
+ */
+static int
+lbox_socket_close(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checksocket(L, -1);
+	if (! evio_is_active(&s->coio))
+		return 0;
+	if (s->iob) {
+		iobuf_destroy(s->iob);
+		s->iob = NULL;
+	}
+	evio_close(&s->coio);
+	bio_clearerr(s);
+	return 0;
+}
+
+/**
+ * socket:shutdown(how)
+ *
+ * Shut down part of a full-duplex connection.
+ *
+ * @retval self                                 success
+ * @retval nil, status = "error", eno, estr     error
+ */
+static int
+lbox_socket_shutdown(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checkactivesocket(L, -1);
+	int how = luaL_checkint(L, 2);
+	bio_clearerr(s);
+	if (shutdown(s->coio.fd, how))
+		return bio_pushsockerror(L, s, errno);
+	/* case #1: Success */
+	lua_settop(L, 1);
+	return 1;
+}
+
+/**
+ * socket:error()
+ *
+ * @return error code and error description of the last error.
+ */
+static int
+lbox_socket_error(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checksocket(L, -1);
+	return bio_pusherrorcode(L, s);
+}
+
+/**
+ * socket:connect(host, port [, timeout])
+ *
+ * Connect socket to a host.
+ *
+ * @retval self                                 success
+ * @retval nil, status = "error", eno, estr     error
+ * @retval nil, status = "timeout", eno, estr   timeout
+ */
+static int
+lbox_socket_connect(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checksocket(L, 1);
+	const char *host = luaL_checkstring(L, 2);
+	const char *port = luaL_checkstring(L, 3);
+	double timeout = TIMEOUT_INFINITY;
+	if (lua_gettop(L) == 4)
+		timeout = luaL_checknumber(L, 4);
+	if (evio_is_active(&s->coio))
+		return bio_pushsockerror(L, s, EALREADY);
+	bio_clearerr(s);
+
+	/* try to resolve a hostname */
+	ev_tstamp start, delay;
+	evio_timeout_init(&start, &delay, timeout);
+	struct addrinfo *ai = bio_resolve(s->socktype, host, port, delay);
+	if (ai == NULL)
+		return bio_pushsockerror(L, s, errno);
+
+	evio_timeout_update(start, &delay);
+	@try {
+		/* connect to a first available host */
+		if (coio_connect_addrinfo(&s->coio, ai, delay))
+			return bio_pushsockerror(L, s, ETIMEDOUT);
+	} @catch (SocketError *e) {
+		return bio_pushsockerror(L, s, errno);
+	} @finally {
+		freeaddrinfo(ai);
+	}
+	bio_initbuf(s);
+	/* Success */
+	lua_settop(L, 1);
+	return 1;
+}
+
+/**
+ * socket:send(data [, timeout])
+ *
+ * Send data to a socket.
+ *
+ * In case of socket an error or timeout send() returns
+ * the number of bytes written before the error occurred.
+ *
+ *
+ * @retval size                                 success
+ * @retval size, status = "timeout", eno, estr  timeout
+ * @retval size, status = "error", eno, estr    error
+ *
+ */
+static int
+lbox_socket_send(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checkactivesocket(L, 1);
+	size_t buf_size = 0;
+	const char *buf = luaL_checklstring(L, 2, &buf_size);
+	double timeout = TIMEOUT_INFINITY;
+	if (lua_gettop(L) == 3)
+		timeout = luaL_checknumber(L, 3);
+	if (s->iob == NULL)
+		return bio_pushsenderror(L, s, 0, ENOTCONN);
+	bio_clearerr(s);
+	@try {
+		ssize_t nwr = coio_write_timeout(&s->coio, buf, buf_size,
+						 timeout);
+		if (nwr < buf_size)
+			return bio_pushsenderror(L, s, nwr, ETIMEDOUT);
+	} @catch (SocketError *e) {
+		return bio_pushsenderror(L, s, 0, errno);
+	}
+	/* case #1: Success */
+	lua_pushinteger(L, buf_size);
+	return 1;
+}
+
+/**
+ * socket:recv(size [, timeout])
+ *
+ * Try to read size bytes from a socket.
+ *
+ * In case of timeout all read data will be available on next
+ * read. In case of read error the data is thrown away.
+ *
+ * @retval data                                         success
+ * @retval data = "", status = "error", eno, estr       socket error
+ * @retval data = "", status = "timeout", eno, estr     read timeout
+ * @retval data = chunk, status = "eof"                 eof
+ */
+static int
+lbox_socket_recv(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checkactivesocket(L, 1);
+	int sz = luaL_checkint(L, 2);
+	double timeout = TIMEOUT_INFINITY;
+	if (lua_gettop(L) >= 3)
+		timeout = luaL_checknumber(L, 3);
+	if (s->iob == NULL)
+		return bio_pushrecverror(L, s, ENOTCONN);
+	/* Clear possible old timeout status. */
+	bio_clearerr(s);
+	/*
+	 * Readahead buffer can contain sufficient amount of
+	 * data from the previous call to cover the required read
+	 * size.
+	 *
+	 * If not, try to read as much as possible to readahead
+	 * until it's more or equal to the required size.
+	 */
+	struct ibuf *in = &s->iob->in;
+	ssize_t to_read = sz - ibuf_size(in);
+
+	if (to_read > 0) {
+		ssize_t nrd;
+		@try {
+			nrd = coio_bread_timeout(&s->coio, in, to_read,
+						 timeout);
+		} @catch (SocketError *e) {
+			return bio_pushrecverror(L, s, errno);
+		}
+		if (nrd < to_read) {
+			/*  timeout or EOF. */
+			if (errno == ETIMEDOUT)
+				return bio_pushrecverror(L, s, ETIMEDOUT);
+			return bio_pusheof(L, s);
+		}
+	}
+	lua_pushlstring(L, in->pos, sz);
+	in->pos += sz;
+	return 1;
+}
+
+struct readline_state {
+	int pos;
+	const char *sep;
+	size_t sep_size;
+};
+
+static void
+readline_state_init(struct lua_State *L, struct readline_state *rs, int idx)
+{
+	int i = 0;
+	lua_pushnil(L);
+	while (lua_next(L, idx) != 0) {
+		rs[i].pos = 0;
+		rs[i].sep = luaL_checklstring(L, -1, &rs[i].sep_size);
+		if (rs[i].sep_size == 0)
+			luaL_error(L, "box.io.readline: bad separator");
+		lua_pop(L, 1);
+		i++;
+	}
+}
+
+static inline int
+readline_state_try(struct readline_state *rs, int i, char chr)
+{
+	if (unlikely(rs[i].sep[ rs[i].pos ] == chr)) {
+		if (unlikely(rs[i].sep_size == rs[i].pos + 1))
+			return i;
+		rs[i].pos++;
+	} else {
+		rs[i].pos = 0;
+	}
+	return -1;
+}
+
+static int
+readline_state_next(struct readline_state *rs, int size, char chr)
+{
+	int i;
+	for (i = 0; i < size; i++) {
+		/* we have to repeat state check to ensure that we
+		 * don't miss new separator state after previous
+		 * reset. */
+		if (unlikely(rs[i].sep[ rs[i].pos ] == chr)) {
+first_matched:		if (unlikely(rs[i].sep_size == rs[i].pos + 1))
+				return i;
+			rs[i].pos++;
+			continue;
+		}
+		rs[i].pos = 0;
+		if (unlikely(rs[i].sep[ rs[i].pos ] == chr))
+			goto first_matched;
+	}
+	return -1;
+}
+
+static void
+lbox_socket_readline_cr(struct lua_State *L)
+{
+	/* emulate user passed {'\n'} as the separate table */
+	lua_newtable(L);
+	lua_pushnumber(L, 1);
+	lua_pushstring(L, "\n");
+	lua_rawset(L, -3);
+}
+
+static int
+lbox_socket_readline_opts(struct lua_State *L, unsigned int *limit,
+		      double *timeout)
+{
+	int seplist = 2;
+	switch (lua_gettop(L)) {
+	case 1:
+		/* readline() */
+		lbox_socket_readline_cr(L);
+		break;
+	case 2:
+		 /* readline(limit)
+		    readline({seplist}) */
+		if (lua_isnumber(L, 2)) {
+			*limit = luaL_checkint(L, 2);
+			lbox_socket_readline_cr(L);
+			seplist = 3;
+		} else if (! lua_istable(L, 2))
+			luaL_error(L, "box.io.readline: bad argument");
+		break;
+	case 3:
+		/* readline(limit, timeout)
+		 * readline(limit, {seplist})
+		 * readline({seplist}, timeout) */
+		if (lua_isnumber(L, 2)) {
+			*limit = luaL_checkint(L, 2);
+			if (lua_isnumber(L, 3)) {
+				*timeout = luaL_checknumber(L, 3);
+				lbox_socket_readline_cr(L);
+				seplist = 4;
+				break;
+			} else if (! lua_istable(L, 3))
+				luaL_error(L, "box.io.readline: bad argument");
+			seplist = 3;
+			break;
+		} else if (! lua_istable(L, 2))
+			luaL_error(L, "box.io.readline: bad argument");
+		*timeout = luaL_checknumber(L, 3);
+		seplist = 2;
+		break;
+	case 4:
+		/* readline(limit, {seplist}, timeout) */
+		*limit = luaL_checkint(L, 2);
+		if (! lua_istable(L, 3))
+			luaL_error(L, "box.io.readline: bad argument");
+		seplist = 3;
+		*timeout = luaL_checknumber(L, 4);
+		break;
+	default:
+		luaL_error(L, "box.io.readline: bad argument");
+		break;
+	}
+	return seplist;
+}
+
+/**
+ * socket:readline(limit, seplist, timeout)
+ *
+ * Possible usage:
+ *
+ * readline() == readline(limit == inf, seplist == {'\n'}, timeout == inf)
+ * readline(limit)
+ * readline(limit, timeout)
+ * readline({seplist})
+ * readline(limit, {seplist})
+ * readline({seplist}, timeout)
+ * readline(limit, {seplist}, timeout)
+ *
+ * In case of socket error and timeout all read data will be
+ * available on next read.
+ *
+ * @retval data, nil, sep = str                     success
+ * @retval data = "", status = "timeout", eno, estr timeout
+ * @retval data = "", status = "error", eno, estr   error
+ * @retval data = chunk, status = "limit"           limit
+ * @retval data = chunk, status = "eof"             eof
+ */
+static int
+lbox_socket_readline(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checkactivesocket(L, 1);
+	if (s->iob == NULL)
+		return bio_pushrecverror(L, s, ENOTCONN);
+	bio_clearerr(s);
+
+	unsigned int limit = UINT_MAX;
+	double timeout = TIMEOUT_INFINITY;
+	int seplist = lbox_socket_readline_opts(L, &limit, &timeout);
+
+	int rs_size = lua_objlen(L, seplist);
+	if (rs_size == 0)
+		luaL_error(L, "box.io.readline: bad separator table");
+
+	size_t bottom = 0;
+	int match;
+	struct ibuf *in = &s->iob->in;
+
+	@try {
+		/* readline implementation uses a simple state machine
+		 * to determine current position of a possible
+		 * separator. */
+		struct readline_state *rs =
+			palloc(in->pool, sizeof(struct readline_state) * rs_size);
+		readline_state_init(L, rs, seplist);
+
+		ev_tstamp start, delay;
+		evio_timeout_init(&start, &delay, timeout);
+
+		while (1) {
+
+			/* case #4: user limit reached */
+			if (bottom == limit) {
+				lua_pushlstring(L, in->pos, bottom);
+				s->iob->in.pos += bottom;
+				bio_pushstatus(L, BIO_LIMIT);
+				return 2;
+			}
+
+			/* if current read position (bottom) equals to
+			 * the readahead size, then read new data. */
+			if (bottom == ibuf_size(in)) {
+
+				ssize_t nrd = coio_bread_timeout(&s->coio, &s->iob->in, 1,
+						                 delay);
+				/* case #5: eof (step 1)*/
+				if (nrd == 0) {
+					if (errno == ETIMEDOUT)
+						return bio_pushrecverror(L, s, ETIMEDOUT);
+					return bio_pusheof(L, s);
+				}
+			}
+
+			match = readline_state_next(rs, rs_size, in->pos[bottom]);
+			bottom++;
+			if (match >= 0)
+				break;
+
+			evio_timeout_update(start, &delay);
+		}
+	} @catch (SocketError *e) {
+		return bio_pushrecverror(L, s, errno);
+	}
+
+	/* case #1: success, separator matched */
+	lua_pushlstring(L, in->pos, bottom);
+	in->pos += bottom;
+	lua_pushnil(L);
+	lua_pushinteger(L, match + 1);
+	return 3;
+}
+
+/**
+ * socket:bind(host, port [, timeout])
+ *
+ * Bind a socket to the given host.
+ *
+ * @retval socket                             success
+ * @retval nil, status = "error", eno, estr   error
+ * @retval nil, status = "timeout", eno, estr timeout
+ */
+static int
+lbox_socket_bind(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checksocket(L, 1);
+	const char *host = luaL_checkstring(L, 2);
+	const char *port = luaL_checkstring(L, 3);
+	double timeout = TIMEOUT_INFINITY;
+	if (lua_gettop(L) == 4)
+		timeout = luaL_checknumber(L, 4);
+	if (evio_is_active(&s->coio))
+		return bio_pusherror(L, s, EALREADY);
+	bio_clearerr(s);
+	/* try to resolve a hostname */
+	struct addrinfo *ai = bio_resolve(s->socktype, host, port, timeout);
+	if (ai == NULL)
+		return bio_pusherror(L, s, errno);
+	@try {
+		evio_bind_addrinfo(&s->coio, ai);
+	} @catch (SocketError *e) {
+		/* case #2: error */
+		return bio_pusherror(L, s, errno);
+	} @finally {
+		freeaddrinfo(ai);
+	}
+	/* case #1: Success */
+	lua_settop(L, 1);
+	return 1;
+}
+
+/**
+ * socket:listen()
+ *
+ * Marks the socket that it will be used to accept incoming
+ * connection requests using socket:accept().
+ *
+ * @retval socket (self) on success
+ * @retval nil, status = "error", errno, error string
+ */
+static int
+lbox_socket_listen(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checkactivesocket(L, 1);
+	bio_clearerr(s);
+	if (listen(s->coio.fd, sio_listen_backlog()))
+		return bio_pusherror(L, s, errno);
+	lua_settop(L, 1);
+	return 1;
+}
+
+/**
+ * socket:accept([timeout])
+ *
+ * Wait for a new client connection and create connected
+ * socket.
+ *
+ * @retval socket (client), nil, address, port        success
+ * @retval nil, status = "error", eno, estr           error
+ * @retval nil, status = "timeout", eno, estr         timeout
+ */
+static int
+lbox_socket_accept(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checkactivesocket(L, 1);
+	double timeout = TIMEOUT_INFINITY;
+	if (lua_gettop(L) == 2)
+		timeout = luaL_checknumber(L, 2);
+	bio_clearerr(s);
+
+	struct sockaddr_in addr;
+	/* push client socket */
+	bio_pushsocket(L, SOCK_STREAM);
+	struct bio_socket *client = lua_touserdata(L, -1);
+	@try {
+		client->coio.fd = coio_accept(&s->coio, &addr, sizeof(addr),
+					      timeout);
+	} @catch (SocketError *e) {
+		return bio_pusherror(L, s, errno);
+	}
+	/* get user host and port */
+	char hbuf[NI_MAXHOST];
+	char sbuf[NI_MAXSERV];
+	int rc = getnameinfo((struct sockaddr*)&addr, sizeof(addr),
+			     hbuf, sizeof(hbuf),
+			     sbuf, sizeof(sbuf), NI_NUMERICHOST|NI_NUMERICSERV);
+	if (rc != 0)
+		return bio_pusherror(L, s, ERESOLVE);
+
+	bio_initbuf(client);
+	lua_pushnil(L); /* status */
+	/* push host and port */
+	lua_pushstring(L, hbuf);
+	lua_pushstring(L, sbuf);
+	return 4;
+}
+
+/**
+ * socket:sendto(buf, host, port [, timeout])
+ *
+ * Send a message on a UDP socket to a specified host.
+ *
+ * @retval  size success
+ * @retval  0, status = "error", eno, estr    error
+ * @retval  0, status = "timeout", eno, estr  timeout
+ */
+static int
+lbox_socket_sendto(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checksocket(L, 1);
+	size_t buf_size = 0;
+	const char *buf = luaL_checklstring(L, 2, &buf_size);
+	const char *host = luaL_checkstring(L, 3);
+	const char *port = luaL_checkstring(L, 4);
+	double timeout = TIMEOUT_INFINITY;
+	if (lua_gettop(L) == 5)
+		timeout = luaL_checknumber(L, 5);
+	bio_clearerr(s);
+
+	/* try to resolve a hostname */
+	ev_tstamp start, delay;
+	evio_timeout_init(&start, &delay, timeout);
+	struct addrinfo *a = bio_resolve(s->socktype, host, port, delay);
+	if (a == NULL)
+		return bio_pushsenderror(L, s, 0, errno);
+
+	evio_timeout_update(start, &delay);
+	size_t nwr;
+	@try {
+		/* maybe init the socket */
+		struct sockaddr_in *addr = (struct sockaddr_in *) a->ai_addr;
+		if (! evio_is_active(&s->coio))
+			evio_socket(&s->coio, addr->sin_family, s->socktype, 0);
+
+		nwr = coio_sendto_timeout(&s->coio, buf, buf_size, 0,
+					  addr, a->ai_addrlen, delay);
+	} @catch (SocketError *e) {
+		/* case #2-3: error or timeout */
+		return bio_pushsenderror(L, s, 0, errno);
+	} @finally {
+		freeaddrinfo(a);
+	}
+	if (nwr == 0) {
+		assert(errno == ETIMEDOUT);
+		return bio_pushsenderror(L, s, 0, ETIMEDOUT);
+	}
+	lua_pushinteger(L, nwr);
+	return 1;
+}
+
+/**
+ * socket:recvfrom(limit [, timeout])
+ *
+ * Receive a message on a UDP socket.
+ *
+ * @retval data, nil, client_addr, client_port      success
+ * @retval data = "", status = "error",  eno, estr  error
+ * @retval data = "", status = "timeout", eno, estr timeout
+ * @retval data, status = "eof"                     eof
+ */
+static int
+lbox_socket_recvfrom(struct lua_State *L)
+{
+	struct bio_socket *s = bio_checkactivesocket(L, 1);
+	int buf_size = luaL_checkint(L, 2);
+	double timeout = TIMEOUT_INFINITY;
+	if (lua_gettop(L) == 3)
+		timeout = luaL_checknumber(L, 3);
+	bio_clearerr(s);
+
+	/* Maybe initialize the buffer, can throw ER_MEMORY_ISSUE. */
+	if (s->iob == NULL)
+		bio_initbuf(s);
+
+	struct sockaddr_in addr;
+	struct ibuf *in = &s->iob->in;
+	size_t nrd;
+	@try {
+		ibuf_reserve(in, buf_size);
+		nrd = coio_recvfrom_timeout(&s->coio, in->pos, buf_size, 0, &addr,
+				            sizeof(addr), timeout);
+	} @catch (SocketError *e) {
+		return bio_pushrecverror(L, s, errno);
+	}
+	if (nrd == 0) {
+		if (errno == ETIMEDOUT)
+			return bio_pushrecverror(L, s, errno);
+		return bio_pusheof(L, s);
+	}
+
+	/* push host and port */
+	char hbuf[NI_MAXHOST];
+	char sbuf[NI_MAXSERV];
+	int rc = getnameinfo((struct sockaddr*)&addr, sizeof(addr),
+			     hbuf, sizeof(hbuf),
+			     sbuf, sizeof(sbuf), NI_NUMERICHOST|NI_NUMERICSERV);
+	if (rc != 0) {
+		/* case #2: error */
+		return bio_pushrecverror(L, s, ERESOLVE);
+	}
+
+	/* case #1: push received data */
+	lua_pushlstring(L, in->pos, nrd);
+	lua_pushnil(L);
+	lua_pushstring(L, hbuf);
+	lua_pushstring(L, sbuf);
+	return 4;
+}
+
+void
+tarantool_lua_socket_init(struct lua_State *L)
+{
+	static const struct luaL_reg lbox_socket_meta[] = {
+		{"__gc", lbox_socket_close},
+		{"__tostring", lbox_socket_tostring},
+		{"error", lbox_socket_error},
+		{"close", lbox_socket_close},
+		{"shutdown", lbox_socket_shutdown},
+		{"connect", lbox_socket_connect},
+		{"send", lbox_socket_send},
+		{"recv", lbox_socket_recv},
+		{"readline", lbox_socket_readline},
+		{"bind", lbox_socket_bind},
+		{"listen", lbox_socket_listen},
+		{"accept", lbox_socket_accept},
+		{"sendto", lbox_socket_sendto},
+		{"recvfrom", lbox_socket_recvfrom},
+		{NULL, NULL}
+	};
+	static const struct luaL_reg socketlib[] = {
+		{"tcp", lbox_socket_tcp},
+		{"udp", lbox_socket_udp},
+		{NULL, NULL}
+	};
+	tarantool_lua_register_type(L, socketlib_name, lbox_socket_meta);
+	luaL_register(L, socketlib_name, socketlib);
+	lua_pop(L, 1);
+}
diff --git a/src/memcached-grammar.m b/src/memcached-grammar.m
index e0f341415b7fb920eb45bcd7cfef68ea4e81a4b2..b5da180f3ae20fc144276d2cae4adcf7ee7cc160 100644
--- a/src/memcached-grammar.m
+++ b/src/memcached-grammar.m
@@ -256,14 +256,15 @@ case 12:
 tr26:
 #line 247 "src/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -280,7 +281,7 @@ tr26:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -297,14 +298,15 @@ tr26:
 		}
 	goto st197;
 tr30:
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -321,7 +323,7 @@ tr30:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -338,16 +340,17 @@ tr30:
 		}
 	goto st197;
 tr39:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -364,7 +367,7 @@ tr39:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -383,14 +386,15 @@ tr39:
 tr58:
 #line 247 "src/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -407,7 +411,7 @@ tr58:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -442,14 +446,15 @@ tr58:
 		}
 	goto st197;
 tr62:
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -466,7 +471,7 @@ tr62:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -501,16 +506,17 @@ tr62:
 		}
 	goto st197;
 tr71:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -527,7 +533,7 @@ tr71:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -564,14 +570,15 @@ tr71:
 tr91:
 #line 248 "src/memcached-grammar.rl"
 	{cas = natoq(fstart, p);}
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -588,7 +595,7 @@ tr91:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -607,14 +614,15 @@ tr91:
 		}
 	goto st197;
 tr95:
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -631,7 +639,7 @@ tr95:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -650,16 +658,17 @@ tr95:
 		}
 	goto st197;
 tr105:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -676,7 +685,7 @@ tr105:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -697,9 +706,9 @@ tr105:
 tr118:
 #line 249 "src/memcached-grammar.rl"
 	{incr = natoq(fstart, p);}
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -762,9 +771,9 @@ tr118:
 		}
 	goto st197;
 tr122:
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -827,11 +836,11 @@ tr122:
 		}
 	goto st197;
 tr132:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -894,9 +903,9 @@ tr132:
 		}
 	goto st197;
 tr141:
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -928,9 +937,9 @@ tr146:
 			if (exptime > 0 && exptime <= 60*60*24*30)
 				exptime = exptime + ev_now();
 		}
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -956,11 +965,11 @@ tr146:
 		}
 	goto st197;
 tr157:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -986,9 +995,9 @@ tr157:
 		}
 	goto st197;
 tr169:
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -1004,9 +1013,9 @@ tr169:
 tr174:
 #line 250 "src/memcached-grammar.rl"
 	{flush_delay = natoq(fstart, p);}
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -1020,11 +1029,11 @@ tr174:
 		}
 	goto st197;
 tr185:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -1038,9 +1047,9 @@ tr185:
 		}
 	goto st197;
 tr195:
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -1059,9 +1068,9 @@ tr195:
 		}
 	goto st197;
 tr213:
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -1075,14 +1084,15 @@ tr213:
 tr233:
 #line 247 "src/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -1099,7 +1109,7 @@ tr233:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -1116,14 +1126,15 @@ tr233:
 		}
 	goto st197;
 tr237:
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -1140,7 +1151,7 @@ tr237:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -1157,16 +1168,17 @@ tr237:
 		}
 	goto st197;
 tr246:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -1183,7 +1195,7 @@ tr246:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -1202,14 +1214,15 @@ tr246:
 tr263:
 #line 247 "src/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -1226,7 +1239,7 @@ tr263:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -1239,14 +1252,15 @@ tr263:
 		}
 	goto st197;
 tr267:
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -1263,7 +1277,7 @@ tr267:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -1276,16 +1290,17 @@ tr267:
 		}
 	goto st197;
 tr276:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
 #line 252 "src/memcached-grammar.rl"
 	{
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
@@ -1302,7 +1317,7 @@ tr276:
 				goto exit;
 			}
 		}
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -1315,9 +1330,9 @@ tr276:
 		}
 	goto st197;
 tr281:
-#line 280 "src/memcached-grammar.rl"
+#line 281 "src/memcached-grammar.rl"
 	{ p++; }
-#line 274 "src/memcached-grammar.rl"
+#line 275 "src/memcached-grammar.rl"
 	{
 			done = true;
 			stats.bytes_read += p - in->pos;
@@ -1332,21 +1347,21 @@ st197:
 	if ( ++p == pe )
 		goto _test_eof197;
 case 197:
-#line 1336 "src/memcached-grammar.m"
+#line 1351 "src/memcached-grammar.m"
 	goto st0;
 tr27:
 #line 247 "src/memcached-grammar.rl"
 	{bytes = natoq(fstart, p);}
 	goto st13;
 tr40:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st13;
 st13:
 	if ( ++p == pe )
 		goto _test_eof13;
 case 13:
-#line 1350 "src/memcached-grammar.m"
+#line 1365 "src/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr30;
 	goto st0;
@@ -1358,7 +1373,7 @@ st14:
 	if ( ++p == pe )
 		goto _test_eof14;
 case 14:
-#line 1362 "src/memcached-grammar.m"
+#line 1377 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 32: goto st14;
 		case 78: goto st15;
@@ -1472,18 +1487,18 @@ case 26:
 		goto tr45;
 	goto st0;
 tr45:
-#line 288 "src/memcached-grammar.rl"
+#line 289 "src/memcached-grammar.rl"
 	{append = true; }
 	goto st27;
 tr209:
-#line 289 "src/memcached-grammar.rl"
+#line 290 "src/memcached-grammar.rl"
 	{append = false;}
 	goto st27;
 st27:
 	if ( ++p == pe )
 		goto _test_eof27;
 case 27:
-#line 1487 "src/memcached-grammar.m"
+#line 1502 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 13: goto st0;
 		case 32: goto st27;
@@ -1509,7 +1524,7 @@ st28:
 	if ( ++p == pe )
 		goto _test_eof28;
 case 28:
-#line 1513 "src/memcached-grammar.m"
+#line 1528 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st29;
 	goto st0;
@@ -1530,7 +1545,7 @@ st30:
 	if ( ++p == pe )
 		goto _test_eof30;
 case 30:
-#line 1534 "src/memcached-grammar.m"
+#line 1549 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr50;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -1544,7 +1559,7 @@ st31:
 	if ( ++p == pe )
 		goto _test_eof31;
 case 31:
-#line 1548 "src/memcached-grammar.m"
+#line 1563 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st31;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -1558,7 +1573,7 @@ st32:
 	if ( ++p == pe )
 		goto _test_eof32;
 case 32:
-#line 1562 "src/memcached-grammar.m"
+#line 1577 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr54;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -1576,7 +1591,7 @@ st33:
 	if ( ++p == pe )
 		goto _test_eof33;
 case 33:
-#line 1580 "src/memcached-grammar.m"
+#line 1595 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st33;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -1590,7 +1605,7 @@ st34:
 	if ( ++p == pe )
 		goto _test_eof34;
 case 34:
-#line 1594 "src/memcached-grammar.m"
+#line 1609 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr58;
 		case 13: goto tr59;
@@ -1604,14 +1619,14 @@ tr59:
 	{bytes = natoq(fstart, p);}
 	goto st35;
 tr72:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st35;
 st35:
 	if ( ++p == pe )
 		goto _test_eof35;
 case 35:
-#line 1615 "src/memcached-grammar.m"
+#line 1630 "src/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr62;
 	goto st0;
@@ -1623,7 +1638,7 @@ st36:
 	if ( ++p == pe )
 		goto _test_eof36;
 case 36:
-#line 1627 "src/memcached-grammar.m"
+#line 1642 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 32: goto st36;
 		case 78: goto st37;
@@ -1747,7 +1762,7 @@ st48:
 	if ( ++p == pe )
 		goto _test_eof48;
 case 48:
-#line 1751 "src/memcached-grammar.m"
+#line 1766 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st49;
 	goto st0;
@@ -1768,7 +1783,7 @@ st50:
 	if ( ++p == pe )
 		goto _test_eof50;
 case 50:
-#line 1772 "src/memcached-grammar.m"
+#line 1787 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr79;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -1782,7 +1797,7 @@ st51:
 	if ( ++p == pe )
 		goto _test_eof51;
 case 51:
-#line 1786 "src/memcached-grammar.m"
+#line 1801 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st51;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -1796,7 +1811,7 @@ st52:
 	if ( ++p == pe )
 		goto _test_eof52;
 case 52:
-#line 1800 "src/memcached-grammar.m"
+#line 1815 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr83;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -1814,7 +1829,7 @@ st53:
 	if ( ++p == pe )
 		goto _test_eof53;
 case 53:
-#line 1818 "src/memcached-grammar.m"
+#line 1833 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st53;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -1828,7 +1843,7 @@ st54:
 	if ( ++p == pe )
 		goto _test_eof54;
 case 54:
-#line 1832 "src/memcached-grammar.m"
+#line 1847 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr87;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -1842,7 +1857,7 @@ st55:
 	if ( ++p == pe )
 		goto _test_eof55;
 case 55:
-#line 1846 "src/memcached-grammar.m"
+#line 1861 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st55;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -1856,7 +1871,7 @@ st56:
 	if ( ++p == pe )
 		goto _test_eof56;
 case 56:
-#line 1860 "src/memcached-grammar.m"
+#line 1875 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr91;
 		case 13: goto tr92;
@@ -1866,7 +1881,7 @@ case 56:
 		goto st56;
 	goto st0;
 tr106:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st57;
 tr92:
@@ -1877,7 +1892,7 @@ st57:
 	if ( ++p == pe )
 		goto _test_eof57;
 case 57:
-#line 1881 "src/memcached-grammar.m"
+#line 1896 "src/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr95;
 	goto st0;
@@ -1889,7 +1904,7 @@ st58:
 	if ( ++p == pe )
 		goto _test_eof58;
 case 58:
-#line 1893 "src/memcached-grammar.m"
+#line 1908 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr95;
 		case 13: goto st57;
@@ -1963,14 +1978,14 @@ case 65:
 	}
 	goto st0;
 tr107:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st66;
 st66:
 	if ( ++p == pe )
 		goto _test_eof66;
 case 66:
-#line 1974 "src/memcached-grammar.m"
+#line 1989 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr95;
 		case 13: goto st57;
@@ -2014,18 +2029,18 @@ case 70:
 		goto tr113;
 	goto st0;
 tr113:
-#line 297 "src/memcached-grammar.rl"
+#line 298 "src/memcached-grammar.rl"
 	{incr_sign = -1;}
 	goto st71;
 tr202:
-#line 296 "src/memcached-grammar.rl"
+#line 297 "src/memcached-grammar.rl"
 	{incr_sign = 1; }
 	goto st71;
 st71:
 	if ( ++p == pe )
 		goto _test_eof71;
 case 71:
-#line 2029 "src/memcached-grammar.m"
+#line 2044 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 13: goto st0;
 		case 32: goto st71;
@@ -2051,7 +2066,7 @@ st72:
 	if ( ++p == pe )
 		goto _test_eof72;
 case 72:
-#line 2055 "src/memcached-grammar.m"
+#line 2070 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st73;
 	goto st0;
@@ -2072,7 +2087,7 @@ st74:
 	if ( ++p == pe )
 		goto _test_eof74;
 case 74:
-#line 2076 "src/memcached-grammar.m"
+#line 2091 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr118;
 		case 13: goto tr119;
@@ -2082,7 +2097,7 @@ case 74:
 		goto st74;
 	goto st0;
 tr133:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st75;
 tr119:
@@ -2093,7 +2108,7 @@ st75:
 	if ( ++p == pe )
 		goto _test_eof75;
 case 75:
-#line 2097 "src/memcached-grammar.m"
+#line 2112 "src/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr122;
 	goto st0;
@@ -2105,7 +2120,7 @@ st76:
 	if ( ++p == pe )
 		goto _test_eof76;
 case 76:
-#line 2109 "src/memcached-grammar.m"
+#line 2124 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr122;
 		case 13: goto st75;
@@ -2179,14 +2194,14 @@ case 83:
 	}
 	goto st0;
 tr134:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st84;
 st84:
 	if ( ++p == pe )
 		goto _test_eof84;
 case 84:
-#line 2190 "src/memcached-grammar.m"
+#line 2205 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr122;
 		case 13: goto st75;
@@ -2256,7 +2271,7 @@ st90:
 	if ( ++p == pe )
 		goto _test_eof90;
 case 90:
-#line 2260 "src/memcached-grammar.m"
+#line 2275 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr141;
 		case 13: goto st91;
@@ -2272,14 +2287,14 @@ tr147:
 		}
 	goto st91;
 tr158:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st91;
 st91:
 	if ( ++p == pe )
 		goto _test_eof91;
 case 91:
-#line 2283 "src/memcached-grammar.m"
+#line 2298 "src/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr141;
 	goto st0;
@@ -2305,7 +2320,7 @@ st93:
 	if ( ++p == pe )
 		goto _test_eof93;
 case 93:
-#line 2309 "src/memcached-grammar.m"
+#line 2324 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr146;
 		case 13: goto tr147;
@@ -2326,7 +2341,7 @@ st94:
 	if ( ++p == pe )
 		goto _test_eof94;
 case 94:
-#line 2330 "src/memcached-grammar.m"
+#line 2345 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr141;
 		case 13: goto st91;
@@ -2400,14 +2415,14 @@ case 101:
 	}
 	goto st0;
 tr159:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st102;
 st102:
 	if ( ++p == pe )
 		goto _test_eof102;
 case 102:
-#line 2411 "src/memcached-grammar.m"
+#line 2426 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr141;
 		case 13: goto st91;
@@ -2495,7 +2510,7 @@ case 111:
 	}
 	goto st0;
 tr186:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st112;
 tr175:
@@ -2506,7 +2521,7 @@ st112:
 	if ( ++p == pe )
 		goto _test_eof112;
 case 112:
-#line 2510 "src/memcached-grammar.m"
+#line 2525 "src/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr169;
 	goto st0;
@@ -2532,7 +2547,7 @@ st114:
 	if ( ++p == pe )
 		goto _test_eof114;
 case 114:
-#line 2536 "src/memcached-grammar.m"
+#line 2551 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr174;
 		case 13: goto tr175;
@@ -2549,7 +2564,7 @@ st115:
 	if ( ++p == pe )
 		goto _test_eof115;
 case 115:
-#line 2553 "src/memcached-grammar.m"
+#line 2568 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr169;
 		case 13: goto st112;
@@ -2623,14 +2638,14 @@ case 122:
 	}
 	goto st0;
 tr187:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st123;
 st123:
 	if ( ++p == pe )
 		goto _test_eof123;
 case 123:
-#line 2634 "src/memcached-grammar.m"
+#line 2649 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr169;
 		case 13: goto st112;
@@ -2666,18 +2681,18 @@ case 126:
 	}
 	goto st0;
 tr191:
-#line 293 "src/memcached-grammar.rl"
+#line 294 "src/memcached-grammar.rl"
 	{show_cas = false;}
 	goto st127;
 tr198:
-#line 294 "src/memcached-grammar.rl"
+#line 295 "src/memcached-grammar.rl"
 	{show_cas = true;}
 	goto st127;
 st127:
 	if ( ++p == pe )
 		goto _test_eof127;
 case 127:
-#line 2681 "src/memcached-grammar.m"
+#line 2696 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 13: goto st0;
 		case 32: goto st127;
@@ -2703,7 +2718,7 @@ st128:
 	if ( ++p == pe )
 		goto _test_eof128;
 case 128:
-#line 2707 "src/memcached-grammar.m"
+#line 2722 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr195;
 		case 13: goto st129;
@@ -2963,7 +2978,7 @@ st156:
 	if ( ++p == pe )
 		goto _test_eof156;
 case 156:
-#line 2967 "src/memcached-grammar.m"
+#line 2982 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st157;
 	goto st0;
@@ -2984,7 +2999,7 @@ st158:
 	if ( ++p == pe )
 		goto _test_eof158;
 case 158:
-#line 2988 "src/memcached-grammar.m"
+#line 3003 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr225;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -2998,7 +3013,7 @@ st159:
 	if ( ++p == pe )
 		goto _test_eof159;
 case 159:
-#line 3002 "src/memcached-grammar.m"
+#line 3017 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st159;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -3012,7 +3027,7 @@ st160:
 	if ( ++p == pe )
 		goto _test_eof160;
 case 160:
-#line 3016 "src/memcached-grammar.m"
+#line 3031 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr229;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -3030,7 +3045,7 @@ st161:
 	if ( ++p == pe )
 		goto _test_eof161;
 case 161:
-#line 3034 "src/memcached-grammar.m"
+#line 3049 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st161;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -3044,7 +3059,7 @@ st162:
 	if ( ++p == pe )
 		goto _test_eof162;
 case 162:
-#line 3048 "src/memcached-grammar.m"
+#line 3063 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr233;
 		case 13: goto tr234;
@@ -3058,14 +3073,14 @@ tr234:
 	{bytes = natoq(fstart, p);}
 	goto st163;
 tr247:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st163;
 st163:
 	if ( ++p == pe )
 		goto _test_eof163;
 case 163:
-#line 3069 "src/memcached-grammar.m"
+#line 3084 "src/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr237;
 	goto st0;
@@ -3077,7 +3092,7 @@ st164:
 	if ( ++p == pe )
 		goto _test_eof164;
 case 164:
-#line 3081 "src/memcached-grammar.m"
+#line 3096 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 32: goto st164;
 		case 78: goto st165;
@@ -3203,7 +3218,7 @@ st176:
 	if ( ++p == pe )
 		goto _test_eof176;
 case 176:
-#line 3207 "src/memcached-grammar.m"
+#line 3222 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st177;
 	goto st0;
@@ -3224,7 +3239,7 @@ st178:
 	if ( ++p == pe )
 		goto _test_eof178;
 case 178:
-#line 3228 "src/memcached-grammar.m"
+#line 3243 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr255;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -3238,7 +3253,7 @@ st179:
 	if ( ++p == pe )
 		goto _test_eof179;
 case 179:
-#line 3242 "src/memcached-grammar.m"
+#line 3257 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st179;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -3252,7 +3267,7 @@ st180:
 	if ( ++p == pe )
 		goto _test_eof180;
 case 180:
-#line 3256 "src/memcached-grammar.m"
+#line 3271 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto tr259;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -3270,7 +3285,7 @@ st181:
 	if ( ++p == pe )
 		goto _test_eof181;
 case 181:
-#line 3274 "src/memcached-grammar.m"
+#line 3289 "src/memcached-grammar.m"
 	if ( (*p) == 32 )
 		goto st181;
 	if ( 48 <= (*p) && (*p) <= 57 )
@@ -3284,7 +3299,7 @@ st182:
 	if ( ++p == pe )
 		goto _test_eof182;
 case 182:
-#line 3288 "src/memcached-grammar.m"
+#line 3303 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 10: goto tr263;
 		case 13: goto tr264;
@@ -3298,14 +3313,14 @@ tr264:
 	{bytes = natoq(fstart, p);}
 	goto st183;
 tr277:
-#line 282 "src/memcached-grammar.rl"
+#line 283 "src/memcached-grammar.rl"
 	{ noreply = true; }
 	goto st183;
 st183:
 	if ( ++p == pe )
 		goto _test_eof183;
 case 183:
-#line 3309 "src/memcached-grammar.m"
+#line 3324 "src/memcached-grammar.m"
 	if ( (*p) == 10 )
 		goto tr267;
 	goto st0;
@@ -3317,7 +3332,7 @@ st184:
 	if ( ++p == pe )
 		goto _test_eof184;
 case 184:
-#line 3321 "src/memcached-grammar.m"
+#line 3336 "src/memcached-grammar.m"
 	switch( (*p) ) {
 		case 32: goto st184;
 		case 78: goto st185;
@@ -3632,7 +3647,7 @@ case 196:
 	_out: {}
 	}
 
-#line 307 "src/memcached-grammar.rl"
+#line 308 "src/memcached-grammar.rl"
 
 
 	if (!done) {
diff --git a/src/memcached-grammar.rl b/src/memcached-grammar.rl
index cce61f3b22b74b1f4dab435f389c9740c9c04750..1c73641cb43b193e297ae4b7fa1a2d83ac450bed 100644
--- a/src/memcached-grammar.rl
+++ b/src/memcached-grammar.rl
@@ -252,8 +252,9 @@ memcached_dispatch(struct ev_io *coio, struct iobuf *iobuf)
 		action read_data {
 			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;
+				size_t to_read = bytes + 2 - (pe - p);
+				if (coio_bread(coio, in, to_read) < to_read)
+					return -1; /* premature EOF */
 			}
 			/*
 			 * Buffered read may have reallocated the
diff --git a/src/palloc.m b/src/palloc.m
index d811768c479f448cd377231002c16c5b1848c2d3..2d7831026e8968995cd313da8aee0a4caa56605c 100644
--- a/src/palloc.m
+++ b/src/palloc.m
@@ -42,8 +42,6 @@
 #include <tbuf.h>
 #include "exception.h"
 
-#define PALLOC_POOL_NAME_MAXLEN 30
-
 struct chunk {
 	uint32_t magic;
 	void *brk;
diff --git a/src/replica.m b/src/replica.m
index 208d95e9a9bd137b8ef5a9391beb7522b6604168..60b7a40bff3c8de1f379c1e77f81eac58892ecb1 100644
--- a/src/replica.m
+++ b/src/replica.m
@@ -51,7 +51,8 @@ remote_read_row(struct ev_io *coio, struct iobuf *iobuf)
 		coio_breadn(coio, in, to_read);
 	}
 
-	ssize_t request_len = ((struct header_v11 *)in->pos)->len + sizeof(struct header_v11);
+	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)
@@ -69,6 +70,8 @@ static void
 remote_connect(struct ev_io *coio, struct sockaddr_in *remote_addr,
 	       i64 initial_lsn, const char **err)
 {
+	evio_socket(coio, AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
 	*err = "can't connect to master";
 	coio_connect(coio, remote_addr);
 
@@ -95,13 +98,13 @@ pull_from_remote(va_list ap)
 	bool warning_said = false;
 	const int reconnect_delay = 1;
 
-	evio_clear(&coio);
+	coio_init(&coio);
 
 	for (;;) {
 		const char *err = NULL;
 		@try {
 			fiber_setcancellable(true);
-			if (! evio_is_connected(&coio)) {
+			if (! evio_is_active(&coio)) {
 				if (iobuf == NULL)
 					iobuf = iobuf_create(fiber->name);
 				remote_connect(&coio, &r->remote->addr,
diff --git a/src/sio.m b/src/sio.m
index 7ac6bfaf5fee70774878a75bc697c6b8118b0086..74b2189ce65ae3b52d6dd5239bba1a0ac1982b7a 100644
--- a/src/sio.m
+++ b/src/sio.m
@@ -61,21 +61,26 @@ sio_socketname(int fd)
 	return name;
 }
 
-
 @implementation SocketError
-- (id) init: (int) fd in: (const char *) format, ...
+- (id) init: (int) fd in: (const char *) format: (va_list) ap
 {
 	int save_errno = errno;
 	char buf[TNT_ERRMSG_MAX];
-	va_list ap;
-	va_start(ap, format);
 	vsnprintf(buf, sizeof(buf), format, ap);
-	va_end(ap);
 	const char *socketname = sio_socketname(fd);
 	errno = save_errno;
 	self = [self init: "%s, called on %s", buf, socketname];
 	return self;
 }
+
+- (id) init: (int) fd in: (const char *) format, ...
+{
+	va_list ap;
+	va_start(ap, format);
+	self = [self init: fd :format :ap];
+	va_end(ap);
+	return self;
+}
 @end
 
 /** Get a string representation of a socket option name,
@@ -97,12 +102,22 @@ sio_option_name(int option)
 #undef CASE_OPTION
 }
 
+/** shut down part of a full-duplex connection */
+int
+sio_shutdown(int fd, int how)
+{
+	int rc = shutdown(fd, how);
+	if (rc < 0)
+		tnt_raise(SocketError, :fd in:"shutdown");
+	return rc;
+}
+
 /** Try to automatically configure a listen backlog.
  * On Linux, use the system setting, which defaults
  * to 128. This way a system administrator can tune
  * the backlog as needed. On other systems, use SOMAXCONN.
  */
-static int
+int
 sio_listen_backlog()
 {
 #ifdef TARGET_OS_LINUX
@@ -120,9 +135,9 @@ sio_listen_backlog()
 
 /** Create a TCP socket. */
 int
-sio_socket(void)
+sio_socket(int domain, int type, int protocol)
 {
-	int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	int fd = socket(domain, type, protocol);
 	if (fd < 0)
 		tnt_raise(SocketError, :fd in:"socket");
 	return fd;
@@ -179,8 +194,9 @@ sio_connect(int fd, struct sockaddr_in *addr, socklen_t addrlen)
 {
 	/* Establish the connection. */
 	int rc = connect(fd, (struct sockaddr *) addr, addrlen);
-	if (rc < 0 && errno != EINPROGRESS)
+	if (rc < 0 && errno != EINPROGRESS) {
 		tnt_raise(SocketError, :fd in:"connect");
+	}
 	return rc;
 }
 
@@ -250,6 +266,30 @@ sio_writev(int fd, const struct iovec *iov, int iovcnt)
 	return n;
 }
 
+/** Send a message on a socket. */
+ssize_t
+sio_sendto(int fd, const void *buf, size_t len, int flags,
+	   const struct sockaddr_in *dest_addr, socklen_t addrlen)
+{
+	ssize_t n = sendto(fd, buf, len, flags, dest_addr, addrlen);
+	if (n < 0 && errno != EAGAIN &&
+	    errno != EWOULDBLOCK && errno != EINTR)
+			tnt_raise(SocketError, :fd in:"sendto(%zd)", len);
+	return n;
+}
+
+/** Receive a message on a socket. */
+ssize_t
+sio_recvfrom(int fd, void *buf, size_t len, int flags,
+	     struct sockaddr_in *src_addr, socklen_t *addrlen)
+{
+	ssize_t n = recvfrom(fd, buf, len, flags, src_addr, addrlen);
+	if (n < 0 && errno != EAGAIN &&
+	    errno != EWOULDBLOCK && errno != EINTR)
+			tnt_raise(SocketError, :fd in:"recvfrom(%zd)", len);
+	return n;
+}
+
 /** Get socket peer name. */
 int
 sio_getpeername(int fd, struct sockaddr_in *addr)
@@ -276,4 +316,3 @@ sio_strfaddr(struct sockaddr_in *addr)
 		 inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
 	return name;
 }
-
diff --git a/src/tarantool.m b/src/tarantool.m
index ea54b0dac6d27f1b0293024b4f6e1f5ef6999a57..fc1f955807ccbb6e2bc1ea21448012a669fb1668 100644
--- a/src/tarantool.m
+++ b/src/tarantool.m
@@ -594,9 +594,8 @@ tarantool_free(void)
 		unlink(cfg.pid_file);
 	destroy_tarantool_cfg(&cfg);
 
-	fiber_free();
-	coeio_free();
 	session_free();
+	fiber_free();
 	palloc_free();
 	ev_default_destroy();
 #ifdef ENABLE_GCOV
diff --git a/test/big/iterator.result b/test/big/iterator.result
index 117481a34bc27fce90e26daa497e4fd584b4481b..adb61724241eda93ceb1fcf7eb2dcc45e7740091 100644
--- a/test/big/iterator.result
+++ b/test/big/iterator.result
@@ -830,9 +830,9 @@ lua iterate(20, 4, 0, 1, box.index.GE, 'pid_001')
 ---
 sorted output
 $pid_001$
-$pid_002$
-$pid_005$
-$pid_017$
+$pid_007$
+$pid_011$
+$pid_019$
 $pid_023$
 ...
 lua iterate(20, 4, 0, 1, box.index.GE, 'pid_999')
diff --git a/test/big/sql.result b/test/big/sql.result
index 1fe0688f60a0436916546eeb9268980255b7d387..222809b43f52e4786f68133acaa5a56555725067 100644
--- a/test/big/sql.result
+++ b/test/big/sql.result
@@ -84,9 +84,9 @@ insert into t1 values ('key3', 'part1', 'part2_b')
 Insert OK, 1 row affected
 lua for k, v in box.space[1]:pairs() do print(v) end
 ---
-830039403: {'part1', 'part2'}
-863593835: {'part1', 'part2_b'}
 846816619: {'part1', 'part2_a'}
+863593835: {'part1', 'part2_b'}
+830039403: {'part1', 'part2'}
 ...
 select * from t1 where k0='key1'
 Found 1 tuple:
@@ -108,8 +108,10 @@ Found 3 tuples:
 [846816619, 'part1', 'part2_a']
 [863593835, 'part1', 'part2_b']
 call box.select_range(1, 0, 100, 'key2')
-Found 1 tuple:
+Found 3 tuples:
+[830039403, 'part1', 'part2']
 [846816619, 'part1', 'part2_a']
+[863593835, 'part1', 'part2_b']
 call box.select_range(1, 1, 100, 'part1', 'part2_a')
 Found 2 tuples:
 [846816619, 'part1', 'part2_a']
@@ -336,9 +338,9 @@ insert into t4 values(3, 'Creature ')
 Insert OK, 1 row affected
 lua for k, v in box.space[4]:pairs() do print(v) end
 ---
+2: {'Bilimbi'}
 3: {'Creature '}
 1: {'Aardvark '}
-2: {'Bilimbi'}
 ...
 lua box.space[4].index[0].idx:min()
 ---
diff --git a/test/box/lua.result b/test/box/lua.result
index b6064e5f4ebd90cc6481997bc670e5b928bd7214..77fab6ea919ef47c01d41d8690cad03b60e768f5 100644
--- a/test/box/lua.result
+++ b/test/box/lua.result
@@ -15,33 +15,34 @@ lua for n in pairs(box) do print('  - box.', n) end
   - box.fiber
   - box.space
   - box.cfg
+  - box.on_reload_configuration
   - box.time64
   - box.uuid
-  - box.select_limit
+  - box.ipc
   - box.delete
-  - box.on_reload_configuration
-  - box.replace
   - box.bless_space
+  - box.replace
   - box.counter
+  - box.auto_increment
   - box.time
   - box.select_range
   - box.insert
-  - box.auto_increment
   - box.update
+  - box.select_reverse_range
   - box.info
   - box.session
   - box.uuid_hex
-  - box.select_reverse_range
+  - box.select
   - box.slab
   - box.process
-  - box.select
   - box.dostring
+  - box.select_limit
   - box.stat
   - box.flags
   - box.unpack
-  - box.ipc
-  - box.index
   - box.pack
+  - box.index
+  - box.socket
 ...
 lua box.pack()
 ---
diff --git a/test/box/socket.result b/test/box/socket.result
new file mode 100644
index 0000000000000000000000000000000000000000..bf4be1d81b251bec09333f176ece380c0ad2c756
--- /dev/null
+++ b/test/box/socket.result
@@ -0,0 +1,914 @@
+lua s = box.socket.udp()
+---
+...
+lua type(s)
+---
+ - userdata
+...
+lua s:close()
+---
+...
+lua s = box.socket.tcp()
+---
+...
+lua type(s)
+---
+ - userdata
+...
+lua s:close()
+---
+...
+lua s:close()
+---
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:connect('localhost', '30303')
+---
+ - nil
+ - error
+ - 111
+ - Connection refused
+...
+lua s:error()
+---
+ - 111
+ - Connection refused
+...
+lua s:connect('127.0.0.1', '30303')
+---
+ - nil
+ - error
+ - 111
+ - Connection refused
+...
+lua s:error()
+---
+ - 111
+ - Connection refused
+...
+lua s:connect('::1', '30303')
+---
+ - nil
+ - error
+ - -1
+ - Host name resolution failed
+...
+lua s:error()
+---
+ - -1
+ - Host name resolution failed
+...
+lua s:connect('127.0.0.1', '30303', 0.01)
+---
+ - nil
+ - error
+ - 111
+ - Connection refused
+...
+lua s:error()
+---
+ - 111
+ - Connection refused
+...
+lua s:connect('127.0.0.1')
+---
+error: 'bad argument #3 to ''?'' (string expected, got no value)'
+...
+lua s:connect()
+---
+error: 'bad argument #2 to ''?'' (string expected, got no value)'
+...
+lua s:connect(123)
+---
+error: 'bad argument #3 to ''?'' (string expected, got no value)'
+...
+lua s:close()
+---
+...
+lua s:close()
+---
+...
+lua s:connect('somewhereelse', '30303', 0.01)
+---
+ - nil
+ - timeout
+ - 110
+ - Connection timed out
+...
+lua s:error()
+---
+ - 110
+ - Connection timed out
+...
+lua s:close()
+---
+...
+lua s:send()
+---
+error: 'box.socket: socket is not initialized'
+...
+lua s:send(1)
+---
+error: 'box.socket: socket is not initialized'
+...
+lua s:error()
+---
+ - 110
+ - Connection timed out
+...
+lua s = box.socket.tcp()
+---
+...
+lua type(s:connect('127.0.0.1', '30303'))
+---
+ - userdata
+...
+lua s:send('ping')
+---
+ - 4
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua n, status, error_code, error_str = s:send(string.rep('=', 200000), 0.0000001)
+---
+...
+lua type(n)
+---
+ - number
+...
+lua type(status)
+---
+ - string
+...
+lua type(error_code)
+---
+ - number
+...
+lua type(error_str)
+---
+ - string
+...
+lua n
+---
+ - 163840
+...
+lua status
+---
+ - timeout
+...
+lua error_code
+---
+ - 110
+...
+lua error_str
+---
+ - Connection timed out
+...
+lua s:error()
+---
+ - 110
+ - Connection timed out
+...
+connected
+lua s:send('ping')
+---
+ - 0
+ - error
+ - 104
+ - Connection reset by peer
+...
+lua s:error()
+---
+ - 104
+ - Connection reset by peer
+...
+lua s:close()
+---
+...
+lua s:recv()
+---
+error: 'box.socket: socket is not initialized'
+...
+lua type(s:connect('127.0.0.1', '30308'))
+---
+ - userdata
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+12
+lua s:recv(12)
+---
+ - Hello, World
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:close()
+---
+...
+lua type(s:connect('127.0.0.1', '30308'))
+---
+ - userdata
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+21
+lua s:recv(11)
+---
+ - Hello World
+...
+lua s:recv(5)
+---
+ -  Over
+...
+lua s:recv(5)
+---
+ - sized
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:close()
+---
+...
+lua type(s:connect('127.0.0.1', '30308'))
+---
+ - userdata
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+connected
+3
+lua s:recv(4, 0.01)
+---
+ - 
+ - timeout
+ - 110
+ - Connection timed out
+...
+lua s:error()
+---
+ - 110
+ - Connection timed out
+...
+lua s:recv(4)
+---
+ - ping
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:close()
+---
+...
+lua type(s:connect('127.0.0.1', '30309'))
+---
+ - userdata
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+connected
+4
+lua s:recv(6)
+---
+ - ping
+ - eof
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:close()
+---
+...
+lua type(s:connect('127.0.0.1', '30311'))
+---
+ - userdata
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+connected
+24
+lua s:recv(5)
+---
+ - ping 
+...
+lua s:recv(5)
+---
+ - ping 
+...
+lua s:recv(5)
+---
+ - ping 
+...
+lua s:recv(5)
+---
+ - ping 
+...
+lua s:recv(5)
+---
+ - end 
+ - eof
+...
+lua s:recv(5)
+---
+ - 
+ - eof
+...
+lua s:recv(5)
+---
+ - 
+ - eof
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:close()
+---
+...
+lua type(s:connect('127.0.0.1', '30305'))
+---
+ - userdata
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+12
+lua s:readline()
+---
+ - Hello World
+
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+12
+lua s:readline(5)
+---
+ - Hello
+ - limit
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline(5, 0.01)
+---
+ -  Worl
+ - limit
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline(6, 0.01)
+---
+ - d
+
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+9
+lua s:readline({'i', 'D'})
+---
+ - AbcD
+ - nil
+ - 2
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline({'i', 'G'})
+---
+ - efG
+ - nil
+ - 2
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline({'i'})
+---
+ - hi
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+13
+lua s:readline({'Cat', 'Cow', 'Dog', 'Star'})
+---
+ - Cat
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline({'Cat', 'Cow', 'Dog', 'Star'})
+---
+ - Cow
+ - nil
+ - 2
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline({'Cat', 'Cow', 'Dog', 'Star'})
+---
+ - Dog
+ - nil
+ - 3
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline({'Cat', 'Cow', 'Dog', 'Star'})
+---
+ - Star
+ - nil
+ - 4
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+15
+lua s:readline(3, {'Cat', 'Coow'})
+---
+ - Cat
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline(3, {'Cat', 'Coow'})
+---
+ - Coo
+ - limit
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline(3, {'Dogg', 'Star'})
+---
+ - wDo
+ - limit
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline(3, {'Dogg', 'Star'})
+---
+ - ggS
+ - limit
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline(3)
+---
+ - tar
+ - limit
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+23
+lua sl = {'Crown', 'King', 'Kong', 'Cezar'}
+---
+...
+lua s:readline(sl, 1.0)
+---
+ - KKong
+ - nil
+ - 3
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline(sl, 1.0)
+---
+ - King
+ - nil
+ - 2
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline(sl, 1.0)
+---
+ - CezaCezar
+ - nil
+ - 4
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline(sl, 1.0)
+---
+ - Crown
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+26
+lua sl = {'Agatha', 'Road', 'Corn', 'Drive', 'Pop'}
+---
+...
+lua s:readline(64, sl, 1.0)
+---
+ - RoAgatha
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline(64, sl, 1.0)
+---
+ - Pop
+ - nil
+ - 5
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline(64, sl, 1.0)
+---
+ - PoCorn
+ - nil
+ - 3
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline(64, sl, 1.0)
+---
+ - Drive
+ - nil
+ - 4
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+21
+lua s:readline({'Canada'}, 0.01)
+---
+ - 
+ - timeout
+ - 110
+ - Connection timed out
+...
+lua s:error()
+---
+ - 110
+ - Connection timed out
+...
+2
+lua s:readline({'Canada'}, 0.01)
+---
+ - RoadAfricaCubaRomaniaCanada
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+6
+lua s:readline({'Canada'}, 0.01)
+---
+ - Canada
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+19
+lua s:readline({'msg'})
+---
+ - msg
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline({'msg'})
+---
+ -  msg
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline({'msg'})
+---
+ -  msg
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline({'msg'})
+---
+ -  msg
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline({'msg'})
+---
+ -  msg
+ - nil
+ - 1
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline({'msg'})
+---
+ - 
+ - eof
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:readline({'msg'})
+---
+ - 
+ - eof
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:close()
+---
+...
+lua type(s:connect('127.0.0.1', '30307'))
+---
+ - userdata
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+29
+lua s:readline({'Z'})
+---
+ - SomelongLongStringStrinString
+ - eof
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:close()
+---
+...
+lua type(s:connect('localhost', '30303'))
+---
+ - userdata
+...
+lua s:send('ping')
+---
+ - 4
+...
+connected
+lua s:recv(4)
+---
+ - ping
+...
+lua s:send('ping')
+---
+ - 4
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua s:send('ping')
+---
+ - 0
+ - error
+ - 32
+ - Broken pipe
+...
+lua s:error()
+---
+ - 32
+ - Broken pipe
+...
+lua s:close()
+---
+...
+lua type(s:bind('localhost', '30303'))
+---
+ - userdata
+...
+lua type(s:listen())
+---
+ - userdata
+...
+lua client, status, addr = s:accept()
+---
+...
+lua addr
+---
+ - 127.0.0.1
+...
+lua data = client:recv(4)
+---
+...
+lua data
+---
+ - ping
+...
+lua client:send(data, 4)
+---
+ - 4
+...
+lua client:close()
+---
+...
+lua s:close()
+---
+...
+ping
+lua s = box.socket.udp()
+---
+...
+lua type(s:sendto('ping', 'localhost', '30302'))
+---
+ - number
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+ping
+lua s:recv(4)
+---
+ - 
+ - error
+ - 107
+ - Transport endpoint is not connected
+...
+lua s:close()
+---
+...
+lua s = box.socket.udp()
+---
+...
+lua type(s:bind('localhost', '30301'))
+---
+ - userdata
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua data, status, client, port = s:recvfrom(4)
+---
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+lua data
+---
+ - ping
+...
+lua client
+---
+ - 127.0.0.1
+...
+lua type(s:sendto(data, client, port))
+---
+ - number
+...
+lua s:error()
+---
+ - 0
+ - Success
+...
+ping
+lua s:close()
+---
+...
diff --git a/test/box/socket.test b/test/box/socket.test
new file mode 100644
index 0000000000000000000000000000000000000000..91e4e5d4f5e1980b8a5d7f1aa575baabd390bb18
--- /dev/null
+++ b/test/box/socket.test
@@ -0,0 +1,511 @@
+# encoding: tarantool
+
+import socket
+
+########################
+#                      #
+# box.socket.tcp/udp() #
+#                      #
+########################
+
+exec admin "lua s = box.socket.udp()"
+exec admin "lua type(s)"
+exec admin "lua s:close()"
+exec admin "lua s = box.socket.tcp()"
+exec admin "lua type(s)"
+
+### socket:close()
+
+# close
+exec admin "lua s:close()"
+
+# close (on closed)
+exec admin "lua s:close()"
+
+# error
+exec admin "lua s:error()"
+
+####################
+#                  #
+# socket:connect() #
+#                  #
+####################
+
+exec admin "lua s:connect('localhost', '30303')"
+# Connection refused
+exec admin "lua s:error()"
+
+exec admin "lua s:connect('127.0.0.1', '30303')"
+# Connection refused
+exec admin "lua s:error()"
+
+exec admin "lua s:connect('::1', '30303')"
+# connection refused
+exec admin "lua s:error()"
+
+exec admin "lua s:connect('127.0.0.1', '30303', 0.01)"
+# connection refused
+exec admin "lua s:error()"
+
+# bad args
+exec admin "lua s:connect('127.0.0.1')"
+exec admin "lua s:connect()"
+exec admin "lua s:connect(123)"
+exec admin "lua s:close()"
+
+exec admin "lua s:close()"
+exec admin "lua s:connect('somewhereelse', '30303', 0.01)"
+# timedout
+exec admin "lua s:error()"
+exec admin "lua s:close()"
+
+##################
+#                #
+# socket:send() #
+#                #
+##################
+
+# bad args
+exec admin "lua s:send()"
+exec admin "lua s:send(1)"
+# bad file descriptor
+exec admin "lua s:error()"
+
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+s.bind(('localhost', 30303))
+s.listen(1)
+
+exec admin "lua s = box.socket.tcp()"
+exec admin "lua type(s:connect('127.0.0.1', '30303'))"
+exec admin "lua s:send('ping')"
+exec admin "lua s:error()"
+
+# timedout
+exec admin "lua n, status, error_code, error_str = s:send(string.rep('=', 200000), 0.0000001)"
+exec admin "lua type(n)"
+exec admin "lua type(status)"
+exec admin "lua type(error_code)"
+exec admin "lua type(error_str)"
+exec admin "lua n"
+exec admin "lua status"
+exec admin "lua error_code"
+exec admin "lua error_str"
+
+exec admin "lua s:error()"
+
+conn, addr = s.accept()
+print('connected')
+
+conn.close()
+s.close()
+
+# connection reset by peer
+exec admin "lua s:send('ping')"
+exec admin "lua s:error()"
+
+exec admin "lua s:close()"
+
+#################
+#               #
+# socket:recv() #
+#               #
+#################
+
+# bad args
+exec admin "lua s:recv()"
+
+# test for case #1: successful recv
+#
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+s.bind(('localhost', 30308))
+s.listen(1)
+exec admin "lua type(s:connect('127.0.0.1', '30308'))"
+exec admin "lua s:error()"
+conn, addr = s.accept()
+print(conn.send("Hello, World"))
+exec admin "lua s:recv(12)"
+exec admin "lua s:error()"
+exec admin "lua s:close()"
+conn.close()
+s.close()
+
+# test for case #1: successful serial chunk recv (3 x chunk)
+#
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+s.bind(('localhost', 30308))
+s.listen(1)
+exec admin "lua type(s:connect('127.0.0.1', '30308'))"
+exec admin "lua s:error()"
+conn, addr = s.accept()
+print(conn.send("Hello World Oversized"))
+exec admin "lua s:recv(11)"
+exec admin "lua s:recv(5)"
+exec admin "lua s:recv(5)"
+exec admin "lua s:error()"
+exec admin "lua s:close()"
+conn.close()
+s.close()
+
+# test for case #2-3: timedout, error
+#
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+s.bind(('localhost', 30308))
+s.listen(1)
+
+exec admin "lua type(s:connect('127.0.0.1', '30308'))"
+exec admin "lua s:error()"
+
+conn, addr = s.accept()
+print('connected')
+
+print(conn.send("pin"))
+exec admin "lua s:recv(4, 0.01)"
+# timedout
+exec admin "lua s:error()"
+
+conn.send("g")
+
+# ping
+exec admin "lua s:recv(4)"
+exec admin "lua s:error()"
+
+exec admin "lua s:close()"
+conn.close()
+s.close()
+
+# test for case #4: EOF (data is less then recv size)
+#
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+s.bind(('localhost', 30309))
+s.listen(1)
+
+exec admin "lua type(s:connect('127.0.0.1', '30309'))"
+exec admin "lua s:error()"
+
+conn, addr = s.accept()
+print('connected')
+
+print(conn.send("ping"))
+conn.close()
+exec admin "lua s:recv(6)"
+exec admin "lua s:error()"
+exec admin "lua s:close()"
+s.close()
+
+# test for case #4: EOF (data is more then recv size)
+#
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+s.bind(('localhost', 30311))
+s.listen(1)
+
+exec admin "lua type(s:connect('127.0.0.1', '30311'))"
+exec admin "lua s:error()"
+
+conn, addr = s.accept()
+print('connected')
+
+print(conn.send("ping ping ping ping end "))
+conn.close()
+
+# recv should not say EOF, even if it really happened
+exec admin "lua s:recv(5)"
+exec admin "lua s:recv(5)"
+exec admin "lua s:recv(5)"
+exec admin "lua s:recv(5)"
+
+# eof
+exec admin "lua s:recv(5)"
+# eof (zero)
+exec admin "lua s:recv(5)"
+# eof (zero)
+exec admin "lua s:recv(5)"
+
+exec admin "lua s:error()"
+exec admin "lua s:close()"
+s.close()
+
+#####################
+#                   #
+# socket:readline() #
+#                   #
+#####################
+
+# possible usage:
+#
+# 1. readline() == readline(limit == inf, seplist == {'\n'}, timeout == inf)
+# 2. readline(limit)
+# 3. readline(limit, timeout)
+# 4. readline({seplist})
+# 5. readline(limit, {seplist})
+# 6. readline({seplist}, timeout)
+# 7. readline(limit, {seplist}, timeout)
+
+# test for case #1, 4: separator or limit found + different usage
+#
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+s.bind(('localhost', 30305))
+s.listen(1)
+exec admin "lua type(s:connect('127.0.0.1', '30305'))"
+exec admin "lua s:error()"
+conn, addr = s.accept()
+
+# 1. readline() == readline(limit == inf, seplist == {'\n'}, timeout == inf)
+print(conn.send("Hello World\n"))
+exec admin "lua s:readline()"
+exec admin "lua s:error()"
+
+# 2. readline(limit)
+print(conn.send("Hello World\n"))
+exec admin "lua s:readline(5)"
+exec admin "lua s:error()"
+
+# 3. readline(limit, timeout) (just api check)
+exec admin "lua s:readline(5, 0.01)"
+exec admin "lua s:error()"
+exec admin "lua s:readline(6, 0.01)"
+exec admin "lua s:error()"
+
+# 4. readline({seplist})
+print(conn.send("AbcDefGhi"))
+exec admin "lua s:readline({'i', 'D'})"
+exec admin "lua s:error()"
+exec admin "lua s:readline({'i', 'G'})"
+exec admin "lua s:error()"
+exec admin "lua s:readline({'i'})"
+exec admin "lua s:error()"
+
+print(conn.send("CatCowDogStar"))
+exec admin "lua s:readline({'Cat', 'Cow', 'Dog', 'Star'})"
+exec admin "lua s:error()"
+exec admin "lua s:readline({'Cat', 'Cow', 'Dog', 'Star'})"
+exec admin "lua s:error()"
+exec admin "lua s:readline({'Cat', 'Cow', 'Dog', 'Star'})"
+exec admin "lua s:error()"
+exec admin "lua s:readline({'Cat', 'Cow', 'Dog', 'Star'})"
+exec admin "lua s:error()"
+
+# 5. readline(limit, {seplist})
+print(conn.send("CatCoowDoggStar"))
+exec admin "lua s:readline(3, {'Cat', 'Coow'})"
+exec admin "lua s:error()"
+exec admin "lua s:readline(3, {'Cat', 'Coow'})"
+exec admin "lua s:error()"
+exec admin "lua s:readline(3, {'Dogg', 'Star'})"
+exec admin "lua s:error()"
+exec admin "lua s:readline(3, {'Dogg', 'Star'})"
+exec admin "lua s:error()"
+
+# read 'tar' part
+exec admin "lua s:readline(3)"
+exec admin "lua s:error()"
+
+# 6. readline({seplist}, timeout)
+print(conn.send("KKongKingCezaCezarCrown"))
+exec admin "lua sl = {'Crown', 'King', 'Kong', 'Cezar'}"
+exec admin "lua s:readline(sl, 1.0)"
+exec admin "lua s:error()"
+exec admin "lua s:readline(sl, 1.0)"
+exec admin "lua s:error()"
+exec admin "lua s:readline(sl, 1.0)"
+exec admin "lua s:error()"
+exec admin "lua s:readline(sl, 1.0)"
+exec admin "lua s:error()"
+
+# 7. readline(limit, {seplist}, timeout)
+print(conn.send("RoAgathaPopPoCornDriveRoad"))
+exec admin "lua sl = {'Agatha', 'Road', 'Corn', 'Drive', 'Pop'}"
+exec admin "lua s:readline(64, sl, 1.0)"
+exec admin "lua s:error()"
+exec admin "lua s:readline(64, sl, 1.0)"
+exec admin "lua s:error()"
+exec admin "lua s:readline(64, sl, 1.0)"
+exec admin "lua s:error()"
+exec admin "lua s:readline(64, sl, 1.0)"
+exec admin "lua s:error()"
+
+# test for case #2-3: timedout, errors
+#
+print(conn.send("AfricaCubaRomaniaCana"))
+exec admin "lua s:readline({'Canada'}, 0.01)"
+# timedout
+exec admin "lua s:error()"
+
+print(conn.send("da"))
+# should read whole line
+exec admin "lua s:readline({'Canada'}, 0.01)"
+exec admin "lua s:error()"
+
+# to ensure readahead pointer correctness
+print(conn.send("Canada"))
+exec admin "lua s:readline({'Canada'}, 0.01)"
+exec admin "lua s:error()"
+
+# test for case #5: eof testing
+#
+print(conn.send("msg msg msg msg msg"))
+conn.close()
+
+exec admin "lua s:readline({'msg'})"
+exec admin "lua s:error()"
+
+exec admin "lua s:readline({'msg'})"
+exec admin "lua s:error()"
+
+exec admin "lua s:readline({'msg'})"
+exec admin "lua s:error()"
+
+exec admin "lua s:readline({'msg'})"
+exec admin "lua s:error()"
+
+exec admin "lua s:readline({'msg'})"
+exec admin "lua s:error()"
+
+# eof (zero)
+exec admin "lua s:readline({'msg'})"
+exec admin "lua s:error()"
+
+# eof (zero)
+exec admin "lua s:readline({'msg'})"
+exec admin "lua s:error()"
+
+exec admin "lua s:close()"
+
+s.close()
+
+# test for case #5: eof in the middle of a separator
+#                   pending
+#
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+s.bind(('localhost', 30307))
+s.listen(1)
+exec admin "lua type(s:connect('127.0.0.1', '30307'))"
+exec admin "lua s:error()"
+conn, addr = s.accept()
+
+print(conn.send("SomelongLongStringStrinString"))
+conn.close()
+
+# should be returned with eof flag
+exec admin "lua s:readline({'Z'})"
+exec admin "lua s:error()"
+
+exec admin "lua s:close()"
+
+s.close()
+
+###########################
+# simple usage as testing #
+###########################
+
+# echo server (test is server) (TCP).
+#
+# connect from stored procedure to echo server and
+# do send/recv.
+#
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+s.bind(('localhost', 30303))
+s.listen(1)
+
+exec admin "lua type(s:connect('localhost', '30303'))"
+exec admin "lua s:send('ping')"
+
+conn, addr = s.accept()
+print('connected')
+data = conn.recv(4)
+conn.sendall(data)
+
+exec admin "lua s:recv(4)"
+conn.close()
+s.close()
+
+exec admin "lua s:send('ping')"
+exec admin "lua s:error()"
+
+# broken pipe
+exec admin "lua s:send('ping')"
+exec admin "lua s:error()"
+
+exec admin "lua s:close()"
+
+# echo server (box is server) (TCP).
+#
+# connect from test to echo server implemented in
+# stored procedure and do send/recv.
+#
+exec admin "lua type(s:bind('localhost', '30303'))"
+exec admin "lua type(s:listen())"
+
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+s.connect(('localhost', 30303))
+s.sendall('ping')
+
+exec admin "lua client, status, addr = s:accept()"
+exec admin "lua addr"
+exec admin "lua data = client:recv(4)"
+exec admin "lua data"
+exec admin "lua client:send(data, 4)"
+exec admin "lua client:close()"
+exec admin "lua s:close()"
+
+data = s.recv(4)
+s.close()
+print(data)
+
+# echo server (test is server) (UDP).
+#
+# connect from stored procedure to echo server and
+# do send/recv.
+#
+s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+s.bind(('localhost', 30302))
+
+# SOCK_DGRAM
+exec admin "lua s = box.socket.udp()"
+exec admin "lua type(s:sendto('ping', 'localhost', '30302'))"
+exec admin "lua s:error()"
+
+data, addr = s.recvfrom(4)
+print(data)
+s.sendto(data, addr)
+
+exec admin "lua s:recv(4)"
+
+s.close()
+exec admin "lua s:close()"
+
+# echo server (box is server) (UDP).
+#
+# connect from test to echo server implemented in
+# stored procedure and do send/recv.
+#
+exec admin "lua s = box.socket.udp()"
+exec admin "lua type(s:bind('localhost', '30301'))"
+exec admin "lua s:error()"
+
+s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+s.sendto('ping', ('127.0.0.1', 30301))
+
+exec admin "lua data, status, client, port = s:recvfrom(4)"
+exec admin "lua s:error()"
+exec admin "lua data"
+exec admin "lua client"
+
+exec admin "lua type(s:sendto(data, client, port))"
+exec admin "lua s:error()"
+
+data = s.recv(4)
+print(data)
+s.close()
+
+exec admin "lua s:close()"
diff --git a/test/unit/bit.c b/test/unit/bit.c
index 9b305369efd3253bc5201aaefc34d6662579397b..a7cce03672e5333f53edf8bbc3ad26a64ca4b54e 100644
--- a/test/unit/bit.c
+++ b/test/unit/bit.c
@@ -3,6 +3,7 @@
 #include <stdio.h>
 #include <inttypes.h>
 #include <assert.h>
+#include <stdlib.h>
 
 #include "unit.h"
 
diff --git a/third_party/PMurHash.c b/third_party/PMurHash.c
new file mode 100644
index 0000000000000000000000000000000000000000..017501264ddff28d8521ef841e000c456f5358ef
--- /dev/null
+++ b/third_party/PMurHash.c
@@ -0,0 +1,317 @@
+/*-----------------------------------------------------------------------------
+ * MurmurHash3 was written by Austin Appleby, and is placed in the public
+ * domain.
+ *
+ * This implementation was written by Shane Day, and is also public domain.
+ *
+ * This is a portable ANSI C implementation of MurmurHash3_x86_32 (Murmur3A)
+ * with support for progressive processing.
+ */
+
+/*-----------------------------------------------------------------------------
+ 
+If you want to understand the MurmurHash algorithm you would be much better
+off reading the original source. Just point your browser at:
+http://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp
+
+
+What this version provides?
+
+1. Progressive data feeding. Useful when the entire payload to be hashed
+does not fit in memory or when the data is streamed through the application.
+Also useful when hashing a number of strings with a common prefix. A partial
+hash of a prefix string can be generated and reused for each suffix string.
+
+2. Portability. Plain old C so that it should compile on any old compiler.
+Both CPU endian and access-alignment neutral, but avoiding inefficient code
+when possible depending on CPU capabilities.
+
+3. Drop in. I personally like nice self contained public domain code, making it
+easy to pilfer without loads of refactoring to work properly in the existing
+application code & makefile structure and mucking around with licence files.
+Just copy PMurHash.h and PMurHash.c and you're ready to go.
+
+
+How does it work?
+
+We can only process entire 32 bit chunks of input, except for the very end
+that may be shorter. So along with the partial hash we need to give back to
+the caller a carry containing up to 3 bytes that we were unable to process.
+This carry also needs to record the number of bytes the carry holds. I use
+the low 2 bits as a count (0..3) and the carry bytes are shifted into the
+high byte in stream order.
+
+To handle endianess I simply use a macro that reads a uint32_t and define
+that macro to be a direct read on little endian machines, a read and swap
+on big endian machines, or a byte-by-byte read if the endianess is unknown.
+
+-----------------------------------------------------------------------------*/
+
+
+#include "PMurHash.h"
+
+/* I used ugly type names in the header to avoid potential conflicts with
+ * application or system typedefs & defines. Since I'm not including any more
+ * headers below here I can rename these so that the code reads like C99 */
+#undef uint32_t
+#define uint32_t MH_UINT32
+#undef uint8_t
+#define uint8_t  MH_UINT8
+
+/* MSVC warnings we choose to ignore */
+#if defined(_MSC_VER)
+  #pragma warning(disable: 4127) /* conditional expression is constant */
+#endif
+
+/*-----------------------------------------------------------------------------
+ * Endianess, misalignment capabilities and util macros
+ *
+ * The following 3 macros are defined in this section. The other macros defined
+ * are only needed to help derive these 3.
+ *
+ * READ_UINT32(x)   Read a little endian unsigned 32-bit int
+ * UNALIGNED_SAFE   Defined if READ_UINT32 works on non-word boundaries
+ * ROTL32(x,r)      Rotate x left by r bits
+ */
+
+/* Convention is to define __BYTE_ORDER == to one of these values */
+#if !defined(__BIG_ENDIAN)
+  #define __BIG_ENDIAN 4321
+#endif
+#if !defined(__LITTLE_ENDIAN)
+  #define __LITTLE_ENDIAN 1234
+#endif
+
+/* I386 */
+#if defined(_M_IX86) || defined(__i386__) || defined(__i386) || defined(i386)
+  #define __BYTE_ORDER __LITTLE_ENDIAN
+  #define UNALIGNED_SAFE
+#endif
+
+/* gcc 'may' define __LITTLE_ENDIAN__ or __BIG_ENDIAN__ to 1 (Note the trailing __),
+ * or even _LITTLE_ENDIAN or _BIG_ENDIAN (Note the single _ prefix) */
+#if !defined(__BYTE_ORDER)
+  #if defined(__LITTLE_ENDIAN__) && __LITTLE_ENDIAN__==1 || defined(_LITTLE_ENDIAN) && _LITTLE_ENDIAN==1
+    #define __BYTE_ORDER __LITTLE_ENDIAN
+  #elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1 || defined(_BIG_ENDIAN) && _BIG_ENDIAN==1
+    #define __BYTE_ORDER __BIG_ENDIAN
+  #endif
+#endif
+
+/* gcc (usually) defines xEL/EB macros for ARM and MIPS endianess */
+#if !defined(__BYTE_ORDER)
+  #if defined(__ARMEL__) || defined(__MIPSEL__)
+    #define __BYTE_ORDER __LITTLE_ENDIAN
+  #endif
+  #if defined(__ARMEB__) || defined(__MIPSEB__)
+    #define __BYTE_ORDER __BIG_ENDIAN
+  #endif
+#endif
+
+/* Now find best way we can to READ_UINT32 */
+#if __BYTE_ORDER==__LITTLE_ENDIAN
+  /* CPU endian matches murmurhash algorithm, so read 32-bit word directly */
+  #define READ_UINT32(ptr)   (*((uint32_t*)(ptr)))
+#elif __BYTE_ORDER==__BIG_ENDIAN
+  /* TODO: Add additional cases below where a compiler provided bswap32 is available */
+  #if defined(__GNUC__) && (__GNUC__>4 || (__GNUC__==4 && __GNUC_MINOR__>=3))
+    #define READ_UINT32(ptr)   (__builtin_bswap32(*((uint32_t*)(ptr))))
+  #else
+    /* Without a known fast bswap32 we're just as well off doing this */
+    #define READ_UINT32(ptr)   (ptr[0]|ptr[1]<<8|ptr[2]<<16|ptr[3]<<24)
+    #define UNALIGNED_SAFE
+  #endif
+#else
+  /* Unknown endianess so last resort is to read individual bytes */
+  #define READ_UINT32(ptr)   (ptr[0]|ptr[1]<<8|ptr[2]<<16|ptr[3]<<24)
+
+  /* Since we're not doing word-reads we can skip the messing about with realignment */
+  #define UNALIGNED_SAFE
+#endif
+
+/* Find best way to ROTL32 */
+#if defined(_MSC_VER)
+  #include <stdlib.h>  /* Microsoft put _rotl declaration in here */
+  #define ROTL32(x,r)  _rotl(x,r)
+#else
+  /* gcc recognises this code and generates a rotate instruction for CPUs with one */
+  #define ROTL32(x,r)  (((uint32_t)x << r) | ((uint32_t)x >> (32 - r)))
+#endif
+
+
+/*-----------------------------------------------------------------------------
+ * Core murmurhash algorithm macros */
+
+#define C1  (0xcc9e2d51)
+#define C2  (0x1b873593)
+
+/* This is the main processing body of the algorithm. It operates
+ * on each full 32-bits of input. */
+#define DOBLOCK(h1, k1) do{ \
+        k1 *= C1; \
+        k1 = ROTL32(k1,15); \
+        k1 *= C2; \
+        \
+        h1 ^= k1; \
+        h1 = ROTL32(h1,13); \
+        h1 = h1*5+0xe6546b64; \
+    }while(0)
+
+
+/* Append unaligned bytes to carry, forcing hash churn if we have 4 bytes */
+/* cnt=bytes to process, h1=name of h1 var, c=carry, n=bytes in c, ptr/len=payload */
+#define DOBYTES(cnt, h1, c, n, ptr, len) do{ \
+    int _i = cnt; \
+    while(_i--) { \
+        c = c>>8 | *ptr++<<24; \
+        n++; len--; \
+        if(n==4) { \
+            DOBLOCK(h1, c); \
+            n = 0; \
+        } \
+    } }while(0)
+
+/*---------------------------------------------------------------------------*/
+
+/* Main hashing function. Initialise carry to 0 and h1 to 0 or an initial seed
+ * if wanted. Both ph1 and pcarry are required arguments. */
+void PMurHash32_Process(uint32_t *ph1, uint32_t *pcarry, const void *key, int len)
+{
+  uint32_t h1 = *ph1;
+  uint32_t c = *pcarry;
+
+  const uint8_t *ptr = (uint8_t*)key;
+  const uint8_t *end;
+
+  /* Extract carry count from low 2 bits of c value */
+  int n = c & 3;
+
+#if defined(UNALIGNED_SAFE)
+  /* This CPU handles unaligned word access */
+
+  /* Consume any carry bytes */
+  int i = (4-n) & 3;
+  if(i && i <= len) {
+    DOBYTES(i, h1, c, n, ptr, len);
+  }
+
+  /* Process 32-bit chunks */
+  end = ptr + len/4*4;
+  for( ; ptr < end ; ptr+=4) {
+    uint32_t k1 = READ_UINT32(ptr);
+    DOBLOCK(h1, k1);
+  }
+
+#else /*UNALIGNED_SAFE*/
+  /* This CPU does not handle unaligned word access */
+
+  /* Consume enough so that the next data byte is word aligned */
+  int i = -(long)ptr & 3;
+  if(i && i <= len) {
+      DOBYTES(i, h1, c, n, ptr, len);
+  }
+
+  /* We're now aligned. Process in aligned blocks. Specialise for each possible carry count */
+  end = ptr + len/4*4;
+  switch(n) { /* how many bytes in c */
+  case 0: /* c=[----]  w=[3210]  b=[3210]=w            c'=[----] */
+    for( ; ptr < end ; ptr+=4) {
+      uint32_t k1 = READ_UINT32(ptr);
+      DOBLOCK(h1, k1);
+    }
+    break;
+  case 1: /* c=[0---]  w=[4321]  b=[3210]=c>>24|w<<8   c'=[4---] */
+    for( ; ptr < end ; ptr+=4) {
+      uint32_t k1 = c>>24;
+      c = READ_UINT32(ptr);
+      k1 |= c<<8;
+      DOBLOCK(h1, k1);
+    }
+    break;
+  case 2: /* c=[10--]  w=[5432]  b=[3210]=c>>16|w<<16  c'=[54--] */
+    for( ; ptr < end ; ptr+=4) {
+      uint32_t k1 = c>>16;
+      c = READ_UINT32(ptr);
+      k1 |= c<<16;
+      DOBLOCK(h1, k1);
+    }
+    break;
+  case 3: /* c=[210-]  w=[6543]  b=[3210]=c>>8|w<<24   c'=[654-] */
+    for( ; ptr < end ; ptr+=4) {
+      uint32_t k1 = c>>8;
+      c = READ_UINT32(ptr);
+      k1 |= c<<24;
+      DOBLOCK(h1, k1);
+    }
+  }
+#endif /*UNALIGNED_SAFE*/
+
+  /* Advance over whole 32-bit chunks, possibly leaving 1..3 bytes */
+  len -= len/4*4;
+
+  /* Append any remaining bytes into carry */
+  DOBYTES(len, h1, c, n, ptr, len);
+
+  /* Copy out new running hash and carry */
+  *ph1 = h1;
+  *pcarry = (c & ~0xff) | n;
+} 
+
+/*---------------------------------------------------------------------------*/
+
+/* Finalize a hash. To match the original Murmur3A the total_length must be provided */
+uint32_t PMurHash32_Result(uint32_t h, uint32_t carry, uint32_t total_length)
+{
+  uint32_t k1;
+  int n = carry & 3;
+  if(n) {
+    k1 = carry >> (4-n)*8;
+    k1 *= C1; k1 = ROTL32(k1,15); k1 *= C2; h ^= k1;
+  }
+  h ^= total_length;
+
+  /* fmix */
+  h ^= h >> 16;
+  h *= 0x85ebca6b;
+  h ^= h >> 13;
+  h *= 0xc2b2ae35;
+  h ^= h >> 16;
+
+  return h;
+}
+
+/*---------------------------------------------------------------------------*/
+
+/* Murmur3A compatable all-at-once */
+uint32_t PMurHash32(uint32_t seed, const void *key, int len)
+{
+  uint32_t h1=seed, carry=0;
+  PMurHash32_Process(&h1, &carry, key, len);
+  return PMurHash32_Result(h1, carry, len);
+}
+
+/*---------------------------------------------------------------------------*/
+
+/* Provide an API suitable for smhasher */
+void PMurHash32_test(const void *key, int len, uint32_t seed, void *out)
+{
+  uint32_t h1=seed, carry=0;
+  const uint8_t *ptr = (uint8_t*)key;
+  const uint8_t *end = ptr + len;
+
+#if 0 /* Exercise the progressive processing */
+  while(ptr < end) {
+    //const uint8_t *mid = ptr + rand()%(end-ptr)+1;
+    const uint8_t *mid = ptr + (rand()&0xF);
+    mid = mid<end?mid:end;
+    PMurHash32_Process(&h1, &carry, ptr, mid-ptr);
+    ptr = mid;
+  }
+#else
+  PMurHash32_Process(&h1, &carry, ptr, (int)(end-ptr));
+#endif
+  h1 = PMurHash32_Result(h1, carry, len);
+  *(uint32_t*)out = h1;
+}
+
+/*---------------------------------------------------------------------------*/
diff --git a/third_party/PMurHash.h b/third_party/PMurHash.h
new file mode 100644
index 0000000000000000000000000000000000000000..28ead00a7de1d52231f8d2026a3de5e8d76e5ae5
--- /dev/null
+++ b/third_party/PMurHash.h
@@ -0,0 +1,64 @@
+/*-----------------------------------------------------------------------------
+ * MurmurHash3 was written by Austin Appleby, and is placed in the public
+ * domain.
+ *
+ * This implementation was written by Shane Day, and is also public domain.
+ *
+ * This is a portable ANSI C implementation of MurmurHash3_x86_32 (Murmur3A)
+ * with support for progressive processing.
+ */
+
+/* ------------------------------------------------------------------------- */
+/* Determine what native type to use for uint32_t */
+
+/* We can't use the name 'uint32_t' here because it will conflict with
+ * any version provided by the system headers or application. */
+
+/* First look for special cases */
+#if defined(_MSC_VER)
+  #define MH_UINT32 unsigned long
+#endif
+
+/* If the compiler says it's C99 then take its word for it */
+#if !defined(MH_UINT32) && ( \
+     defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L )
+  #include <stdint.h>
+  #define MH_UINT32 uint32_t
+#endif
+
+/* Otherwise try testing against max value macros from limit.h */
+#if !defined(MH_UINT32)
+  #include  <limits.h>
+  #if   (USHRT_MAX == 0xffffffffUL)
+    #define MH_UINT32 unsigned short
+  #elif (UINT_MAX == 0xffffffffUL)
+    #define MH_UINT32 unsigned int
+  #elif (ULONG_MAX == 0xffffffffUL)
+    #define MH_UINT32 unsigned long
+  #endif
+#endif
+
+#if !defined(MH_UINT32)
+  #error Unable to determine type name for unsigned 32-bit int
+#endif
+
+/* I'm yet to work on a platform where 'unsigned char' is not 8 bits */
+#define MH_UINT8  unsigned char
+
+
+/* ------------------------------------------------------------------------- */
+/* Prototypes */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void PMurHash32_Process(MH_UINT32 *ph1, MH_UINT32 *pcarry, const void *key, int len);
+MH_UINT32 PMurHash32_Result(MH_UINT32 h1, MH_UINT32 carry, MH_UINT32 total_length);
+MH_UINT32 PMurHash32(MH_UINT32 seed, const void *key, int len);
+
+void PMurHash32_test(const void *key, int len, MH_UINT32 seed, void *out);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/third_party/README b/third_party/README
index 7fb70f87e8d83ce21cb4b3fabd482480c23bda5c..abf30f28929d178fc20a98792a4e243a663aab54 100644
--- a/third_party/README
+++ b/third_party/README
@@ -34,8 +34,27 @@ How to update it:
 - merge our Makefile with the Makefile in
 the source tarball
 
+
+How to update libeio
+====================
+Note: we have two important changes to the stock libeio:
+- it's possible to change the libeio thread stack size
+  with EIO_STACKSIZE constant. In particular, EIO_STACKSIZE
+  set to 0 means the default stack size.
+  A larger stack size is necessary to make getaddrinfo()
+  work.
+
+Otherwise: cvs up
+
 How to update rb.h
 ======================
 Get the header from
 git://canonware.com/jemalloc.git
 apply rb.patch patch
+=======
+
+How to update murmur hash
+=========================
+
+wget http://smhasher.googlecode.com/svn/trunk/PMurHash.c -O PMurHash.c
+wget http://smhasher.googlecode.com/svn/trunk/PMurHash.h -O PMurHash.h
diff --git a/third_party/libeio/xthread.h b/third_party/libeio/xthread.h
index 2a89f45921fb082a89163532bef647195e7bbdef..60310d59cc024d44a930b1f5cced053c18a201f9 100644
--- a/third_party/libeio/xthread.h
+++ b/third_party/libeio/xthread.h
@@ -140,7 +140,8 @@ xthread_create (xthread_t *tid, void *(*proc)(void *), void *arg)
 
   pthread_attr_init (&attr);
   pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
-  pthread_attr_setstacksize (&attr, PTHREAD_STACK_MIN < X_STACKSIZE ? X_STACKSIZE : PTHREAD_STACK_MIN);
+  if (X_STACKSIZE != 0)
+    pthread_attr_setstacksize (&attr, PTHREAD_STACK_MIN < X_STACKSIZE ?  X_STACKSIZE : PTHREAD_STACK_MIN);
 #ifdef PTHREAD_SCOPE_PROCESS
   pthread_attr_setscope (&attr, PTHREAD_SCOPE_PROCESS);
 #endif
diff --git a/third_party/murmur_hash2.c b/third_party/murmur_hash2.c
deleted file mode 100644
index 6c64bace58d7cd19a81f0f2920cac0aaf498d9ff..0000000000000000000000000000000000000000
--- a/third_party/murmur_hash2.c
+++ /dev/null
@@ -1,64 +0,0 @@
-//-----------------------------------------------------------------------------
-// MurmurHash2, by Austin Appleby
-
-// Note - This code makes a few assumptions about how your machine behaves -
-
-// 1. We can read a 4-byte value from any address without crashing
-// 2. sizeof(int) == 4
-
-// And it has a few limitations -
-
-// 1. It will not work incrementally.
-// 2. It will not produce the same results on little-endian and big-endian
-//    machines.
-
-static inline unsigned int MurmurHash2 ( const void * key, int len, unsigned int seed )
-{
-	// 'm' and 'r' are mixing constants generated offline.
-	// They're not really 'magic', they just happen to work well.
-
-	const unsigned int m = 0x5bd1e995;
-	const int r = 24;
-
-	// Initialize the hash to a 'random' value
-
-	unsigned int h = seed ^ len;
-
-	// Mix 4 bytes at a time into the hash
-
-	const unsigned char * data = (const unsigned char *)key;
-
-	while(len >= 4)
-	{
-		unsigned int k = *(unsigned int *)data;
-
-		k *= m; 
-		k ^= k >> r; 
-		k *= m; 
-		
-		h *= m; 
-		h ^= k;
-
-		data += 4;
-		len -= 4;
-	}
-	
-	// Handle the last few bytes of the input array
-
-	switch(len)
-	{
-	case 3: h ^= data[2] << 16;
-	case 2: h ^= data[1] << 8;
-	case 1: h ^= data[0];
-	        h *= m;
-	};
-
-	// Do a few final mixes of the hash to ensure the last few
-	// bytes are well-incorporated.
-
-	h ^= h >> 13;
-	h *= m;
-	h ^= h >> 15;
-
-	return h;
-} 
diff --git a/third_party/tarantool_ev.c b/third_party/tarantool_ev.c
index f7c47bcccf99a035230cbb1929568e566356ef6f..763de0292e921f6e70db6d3b09ae1dedf25e4642 100644
--- a/third_party/tarantool_ev.c
+++ b/third_party/tarantool_ev.c
@@ -26,5 +26,8 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+
 #include "tarantool_ev.h"
 #include "third_party/libev/ev.c"
+
+const ev_tstamp TIMEOUT_INFINITY = 365*86400*100.0;
diff --git a/third_party/tarantool_ev.h b/third_party/tarantool_ev.h
index 949f535e66375b43feae9a15a2f2285c1bc86d1e..ccd8ed4d01dffabc30ede95e940ad8b0056f51d7 100644
--- a/third_party/tarantool_ev.h
+++ b/third_party/tarantool_ev.h
@@ -52,5 +52,6 @@
 #else /* !defined(ENABLE_BUNDLED_LIBEV) */
 #include <ev.h>
 #endif
+extern const ev_tstamp TIMEOUT_INFINITY;
 
 #endif /* TARANTOOL_EV_H_INCLUDED */