diff --git a/.gitignore b/.gitignore
index 965a7d9fb40013600f53ec4bbba3f53bfa5b8d1d..1c7d39299fdb1c51e2502e9a420be7b47de2519b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,8 +48,7 @@ src/box/lua/*.lua.c
 src/tarantool
 src/trivia/tarantool.h
 tarantool-*.tar.gz
-test/lib/*.pyc
-test/lib/*/*.pyc
+test/lib/
 test/unit/*.test
 test/unit/fiob
 test/var
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5a4d3a4fbf9608b246f18275a98608e13ece6fe0..abb1fc0992dcdd3383d2d19b2eb47d829d7cec4f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -53,6 +53,7 @@ include(cmake/profile.cmake)
 include(cmake/FindReadline.cmake)
 include(cmake/FindSphinx.cmake)
 include(cmake/systemd.cmake)
+include(cmake/module.cmake)
 
 if (NOT READLINE_FOUND)
     message(FATAL_ERROR "readline library not found.")
diff --git a/cmake/module.cmake b/cmake/module.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..c8015a0417483c0bd65484edc643b8abfecec50e
--- /dev/null
+++ b/cmake/module.cmake
@@ -0,0 +1,29 @@
+# A helper function to extract public API
+function(rebuild_module_api)
+    set (dstfile "${CMAKE_CURRENT_BINARY_DIR}/tarantool.h")
+    set (tmpfile "${dstfile}.new")
+    set (headers)
+    # Get absolute path for header files (required of out-of-source build)
+    foreach (header ${ARGN})
+        if (IS_ABSOLUTE ${header})
+            list(APPEND headers ${header})
+        else()
+            list(APPEND headers ${CMAKE_CURRENT_SOURCE_DIR}/${header})
+        endif()
+    endforeach()
+
+    add_custom_command(OUTPUT ${dstfile}
+        COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_header.h > ${tmpfile}
+        COMMAND cat ${headers} | ${CMAKE_SOURCE_DIR}/extra/apigen >> ${tmpfile}
+        COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_footer.h >> ${tmpfile}
+        COMMAND ${CMAKE_COMMAND} -E copy_if_different ${tmpfile} ${dstfile}
+        COMMAND ${CMAKE_COMMAND} -E remove ${tmpfile}
+        DEPENDS ${srcfiles}
+                ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_header.h
+                ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_footer.h
+        )
+
+    add_custom_target(rebuild_module_api ALL DEPENDS ${srcfiles} ${dstfile})
+    install(FILES ${dstfile} DESTINATION ${MODULE_INCLUDEDIR})
+endfunction()
+set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/tarantool.h" PROPERTIES GENERATED HEADER_FILE_ONLY)
diff --git a/cmake/utils.cmake b/cmake/utils.cmake
index c03c0878a19d151263fbb864b2bf69b03bf3da2a..d7cbc4a58d515ddded4971d937bf1c5f6fa81dda 100644
--- a/cmake/utils.cmake
+++ b/cmake/utils.cmake
@@ -80,31 +80,3 @@ function(bin_source varname srcfile dstfile)
 
 endfunction()
 
-# A helper function to extract public API
-function(apigen)
-    set (dstfile "${CMAKE_CURRENT_BINARY_DIR}/tarantool.h")
-    set (tmpfile "${dstfile}.new")
-    set (headers)
-    # Get absolute path for header files (required of out-of-source build)
-    foreach (header ${ARGN})
-        if (IS_ABSOLUTE ${header})
-            list(APPEND headers ${header})
-        else()
-            list(APPEND headers ${CMAKE_CURRENT_SOURCE_DIR}/${header})
-        endif()
-    endforeach()
-
-    add_custom_command(OUTPUT ${dstfile}
-        COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_header.h > ${tmpfile}
-        COMMAND cat ${headers} | ${CMAKE_SOURCE_DIR}/extra/apigen >> ${tmpfile}
-        COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_footer.h >> ${tmpfile}
-        COMMAND ${CMAKE_COMMAND} -E copy_if_different ${tmpfile} ${dstfile}
-        COMMAND ${CMAKE_COMMAND} -E remove ${tmpfile}
-        DEPENDS ${srcfiles}
-                ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_header.h
-                ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_footer.h
-        )
-
-    add_custom_target(generate_module_api ALL DEPENDS ${srcfiles} ${dstfile})
-    install(FILES ${dstfile} DESTINATION ${MODULE_INCLUDEDIR})
-endfunction()
diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 7ba096bdd009d0a2258f9d90c1555701ef437dfd..20464e854c4ec5097d1f66ddaa55fc113e3d45a8 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -40,10 +40,11 @@ add_library(box
     sophia_engine.cc
     sophia_index.cc
     space.cc
+    func.cc
     alter.cc
     schema.cc
     session.cc
-    port.cc
+    port.c
     request.cc
     txn.cc
     box.cc
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 88b6e52e6e5c37aa0ff425dffc8b02b793e395ae..6fe1c0fa2393bb2e154a395cd381711656d3ac64 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -31,6 +31,7 @@
 #include "user_def.h"
 #include "user.h"
 #include "space.h"
+#include "func.h"
 #include "txn.h"
 #include "tuple.h"
 #include "fiber.h" /* for gc_pool */
@@ -71,6 +72,8 @@ struct latch schema_lock = LATCH_INITIALIZER(schema_lock);
 
 /** _func columns */
 #define FUNC_SETUID      3
+/** _func columns */
+#define FUNC_LANGUAGE    4
 
 /* {{{ Auxiliary functions and methods. */
 
@@ -1306,34 +1309,32 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 
 /** Create a function definition from tuple. */
 static void
-func_def_create_from_tuple(struct func_def *func, struct tuple *tuple)
+func_def_create_from_tuple(struct func_def *def, struct tuple *tuple)
 {
-	func->fid = tuple_field_u32(tuple, ID);
-	func->uid = tuple_field_u32(tuple, UID);
-	func->setuid = false;
-	/*
-	 * Do not initialize the privilege cache right away since
-	 * when loading up a function definition during recovery,
-	 * user cache may not be filled up yet (space _user is
-	 * recovered after space _func), so no user cache entry
-	 * may exist yet for such user.  The cache will be filled
-	 * up on demand upon first access.
-	 *
-	 * Later on consistency of the cache is ensured by DDL
-	 * checks (see user_has_data()).
-	 */
-	func->owner_credentials.auth_token = BOX_USER_MAX; /* invalid value */
+	def->fid = tuple_field_u32(tuple, ID);
+	def->uid = tuple_field_u32(tuple, UID);
 	const char *name = tuple_field_cstr(tuple, NAME);
 	uint32_t len = strlen(name);
-	if (len >= sizeof(func->name)) {
+	if (len >= sizeof(def->name)) {
 		tnt_raise(ClientError, ER_CREATE_FUNCTION,
 			  name, "function name is too long");
 	}
-	snprintf(func->name, sizeof(func->name), "%s", name);
-	/** Nobody has access to the function but the owner. */
-	memset(func->access, 0, sizeof(func->access));
+	snprintf(def->name, sizeof(def->name), "%s", name);
 	if (tuple_field_count(tuple) > FUNC_SETUID)
-		func->setuid = tuple_field_u32(tuple, FUNC_SETUID);
+		def->setuid = tuple_field_u32(tuple, FUNC_SETUID);
+	else
+		def->setuid = false;
+	if (tuple_field_count(tuple) > FUNC_LANGUAGE) {
+		const char *language = tuple_field_cstr(tuple, FUNC_LANGUAGE);
+		def->language = STR2ENUM(func_language, language);
+		if (def->language == func_language_MAX) {
+			tnt_raise(ClientError, ER_FUNCTION_LANGUAGE,
+				  name, language);
+		}
+	} else {
+		/* Lua is the default. */
+		def->language = FUNC_LANGUAGE_LUA;
+	}
 }
 
 /** Remove a function from function cache */
@@ -1352,9 +1353,9 @@ static void
 func_cache_replace_func(struct trigger * /* trigger */, void *event)
 {
 	struct txn_stmt *stmt = txn_stmt((struct txn*) event);
-	struct func_def func;
-	func_def_create_from_tuple(&func, stmt->new_tuple);
-	func_cache_replace(&func);
+	struct func_def def;
+	func_def_create_from_tuple(&def, stmt->new_tuple);
+	func_cache_replace(&def);
 }
 
 /**
@@ -1369,36 +1370,36 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 	struct txn_stmt *stmt = txn_stmt(txn);
 	struct tuple *old_tuple = stmt->old_tuple;
 	struct tuple *new_tuple = stmt->new_tuple;
-	struct func_def func;
+	struct func_def def;
 
 	uint32_t fid = tuple_field_u32(old_tuple ?
 				       old_tuple : new_tuple, ID);
-	struct func_def *old_func = func_by_id(fid);
+	struct func *old_func = func_by_id(fid);
 	if (new_tuple != NULL && old_func == NULL) { /* INSERT */
-		func_def_create_from_tuple(&func, new_tuple);
-		func_cache_replace(&func);
+		func_def_create_from_tuple(&def, new_tuple);
+		func_cache_replace(&def);
 		struct trigger *on_rollback =
 			txn_alter_trigger_new(func_cache_remove_func, NULL);
 		trigger_add(&txn->on_rollback, on_rollback);
 	} else if (new_tuple == NULL) {         /* DELETE */
-		func_def_create_from_tuple(&func, old_tuple);
+		func_def_create_from_tuple(&def, old_tuple);
 		/*
 		 * Can only delete func if you're the one
 		 * who created it or a superuser.
 		 */
-		access_check_ddl(func.uid);
+		access_check_ddl(def.uid);
 		/* Can only delete func if it has no grants. */
-		if (schema_find_grants("function", old_func->fid)) {
+		if (schema_find_grants("function", old_func->def.fid)) {
 			tnt_raise(ClientError, ER_DROP_FUNCTION,
-				  (unsigned) func.uid,
+				  (unsigned) old_func->def.uid,
 				  "function has grants");
 		}
 		struct trigger *on_commit =
 			txn_alter_trigger_new(func_cache_remove_func, NULL);
 		trigger_add(&txn->on_commit, on_commit);
 	} else {                                /* UPDATE, REPLACE */
-		func_def_create_from_tuple(&func, new_tuple);
-		access_check_ddl(func.uid);
+		func_def_create_from_tuple(&def, new_tuple);
+		access_check_ddl(def.uid);
 		struct trigger *on_commit =
 			txn_alter_trigger_new(func_cache_replace_func, NULL);
 		trigger_add(&txn->on_commit, on_commit);
@@ -1462,8 +1463,8 @@ priv_def_check(struct priv_def *priv)
 	}
 	case SC_FUNCTION:
 	{
-		struct func_def *func = func_cache_find(priv->object_id);
-		if (func->uid != grantor->uid && grantor->uid != ADMIN) {
+		struct func *func = func_cache_find(priv->object_id);
+		if (func->def.uid != grantor->uid && grantor->uid != ADMIN) {
 			tnt_raise(ClientError, ER_ACCESS_DENIED,
 				  priv_name(priv->access), grantor->name);
 		}
diff --git a/src/box/errcode.h b/src/box/errcode.h
index 68f770db0715b47ceec4e50ba4909223b34a2bd3..24226031a1103e2ee76ffb38c55af1f75adb15cc 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -150,6 +150,8 @@ struct errcode_record {
 	/* 96 */_(ER_GUEST_USER_PASSWORD,       2, "Setting password for guest user has no effect") \
 	/* 97 */_(ER_TRANSACTION_CONFLICT,      2, "Transaction has been aborted by conflict") \
 	/* 98 */_(ER_UNSUPPORTED_ROLE_PRIV,     2, "Unsupported role privilege '%s'") \
+	/* 98 */_(ER_LOAD_FUNCTION,		2, "Failed to dynamically load function '%s': %s") \
+	/* 98 */_(ER_FUNCTION_LANGUAGE,		2, "Unsupported language '%s' specified for function '%s'") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/func.cc b/src/box/func.cc
new file mode 100644
index 0000000000000000000000000000000000000000..21708cb6a0b2945c188ae2e63b0a4714ac43a6d7
--- /dev/null
+++ b/src/box/func.cc
@@ -0,0 +1,142 @@
+/*
+ * 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 "func.h"
+
+#include <dlfcn.h>
+
+#include "lua/utils.h"
+#include "scoped_guard.h"
+
+struct func *
+func_new(struct func_def *def)
+{
+	struct func *func = (struct func *) malloc(sizeof(struct func));
+	if (func == NULL)
+		return NULL;
+	func->def = *def;
+	/** Nobody has access to the function but the owner. */
+	memset(func->access, 0, sizeof(func->access));
+	/*
+	 * Do not initialize the privilege cache right away since
+	 * when loading up a function definition during recovery,
+	 * user cache may not be filled up yet (space _user is
+	 * recovered after space _func), so no user cache entry
+	 * may exist yet for such user.  The cache will be filled
+	 * up on demand upon first access.
+	 *
+	 * Later on consistency of the cache is ensured by DDL
+	 * checks (see user_has_data()).
+	 */
+	func->owner_credentials.auth_token = BOX_USER_MAX; /* invalid value */
+	func->func = NULL;
+	func->dlhandle = NULL;
+	return func;
+}
+
+static void
+func_unload(struct func *func)
+{
+	if (func->dlhandle)
+		dlclose(func->dlhandle);
+	func->dlhandle = NULL;
+	func->func = NULL;
+}
+
+void
+func_load(struct func *func)
+{
+	func_unload(func);
+
+	struct lua_State *L = tarantool_L;
+	int n = lua_gettop(L);
+
+	auto l_guard = make_scoped_guard([=]{
+		lua_settop(L, n);
+	});
+	/*
+	 * Call package.searchpath(name, package.cpath) and use
+	 * the path to the function in dlopen().
+	 */
+	lua_getglobal(L, "package");
+	lua_getfield(L, -1, "searchpath");
+
+	/*
+	 * Extract package name from function name.
+	 * E.g. name = foo.bar.baz, function = baz, package = foo.bar
+	 */
+	const char *package = func->def.name;
+	const char *package_end = NULL;
+	const char *sym = package;
+	while ((sym = strchr(sym, '.')))
+		package_end = sym;
+	if (package_end == NULL) {
+		sym = package;
+		package_end = package + strlen(package);
+	} else {
+		sym = package_end + 1;
+	}
+
+	/* First argument of searchpath: name */
+	lua_pushlstring(L, package, package_end - package);
+	/* Fetch  cpath from 'package' as the second argument */
+	lua_getfield(L, -3, "cpath");
+
+	if (lua_pcall(L, 2, 1, 0)) {
+		tnt_raise(ClientError, ER_LOAD_FUNCTION, func->def.name,
+			  lua_tostring(L, -1));
+	}
+	if (lua_isnil(L, -1)) {
+		tnt_raise(ClientError, ER_LOAD_FUNCTION, func->def.name,
+			  "shared library not found in the search path");
+	}
+	func->dlhandle = dlopen(lua_tostring(L, -1), RTLD_NOW | RTLD_LOCAL);
+	if (func->dlhandle == NULL) {
+		tnt_raise(ClientError, ER_LOAD_FUNCTION, func->def.name,
+			  dlerror());
+	}
+	func->func = (box_function_f) dlsym(func->dlhandle, sym);
+	if (func->func == NULL) {
+		tnt_raise(ClientError, ER_LOAD_FUNCTION, func->def.name,
+			  dlerror());
+	}
+}
+
+void
+func_update(struct func *func, struct func_def *def)
+{
+	func_unload(func);
+	func->def = *def;
+}
+
+void
+func_delete(struct func *func)
+{
+	func_unload(func);
+	free(func);
+}
diff --git a/src/box/func.h b/src/box/func.h
new file mode 100644
index 0000000000000000000000000000000000000000..cd402d5ec9ef64342d830a871e59b5383720be8d
--- /dev/null
+++ b/src/box/func.h
@@ -0,0 +1,90 @@
+#ifndef TARANTOOL_BOX_FUNC_H_INCLUDED
+#define TARANTOOL_BOX_FUNC_H_INCLUDED
+/*
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "key_def.h"
+
+extern "C" {
+
+/** \cond public */
+struct port;
+struct request;
+
+/**
+ * API of C stored function.
+ */
+typedef int (*box_function_f)(struct request *request,
+			      struct port *port);
+
+/** \endcond public */
+} /* extern "C" */
+
+
+/**
+ * Stored function.
+ */
+struct func {
+	struct func_def def;
+	/**
+	 * For C functions, the body of the function.
+	 */
+	box_function_f func;
+	/**
+	 * Each stored function keeps a handle to the
+	 * dynamic library for the C callback.
+	 */
+	void *dlhandle;
+	/**
+	 * Authentication id of the owner of the function,
+	 * used for set-user-id functions.
+	 */
+	struct credentials owner_credentials;
+	/**
+	 * Cached runtime access information.
+	 */
+	struct access access[BOX_USER_MAX];
+};
+
+struct func *
+func_new(struct func_def *def);
+
+void
+func_update(struct func *func, struct func_def *def);
+
+void
+func_delete(struct func *func);
+
+/**
+ * Resolve func->func (find the respective DLL and fetch the
+ * symbol from it).
+ */
+void
+func_load(struct func *func);
+
+#endif /* TARANTOOL_BOX_FUNC_H_INCLUDED */
diff --git a/src/box/iproto_port.cc b/src/box/iproto_port.cc
index 06110d6b8bcce5bbd6cadff0d27bf62f235fe2e4..a789457df5c0343622efc7d2e2488cd01d348e8b 100644
--- a/src/box/iproto_port.cc
+++ b/src/box/iproto_port.cc
@@ -108,7 +108,7 @@ iproto_port(struct port *port)
 
 enum { SVP_SIZE = sizeof(iproto_header_bin) + sizeof(iproto_body_bin) };
 
-static inline void
+extern "C" void
 iproto_port_eof(struct port *ptr)
 {
 	struct iproto_port *port = iproto_port(ptr);
@@ -144,7 +144,7 @@ iproto_reply_select(struct obuf *buf, struct obuf_svp *svp, uint64_t sync,
 	memcpy(pos + sizeof(header), &body, sizeof(body));
 }
 
-static inline void
+extern "C" void
 iproto_port_add_tuple(struct port *ptr, struct tuple *tuple)
 {
 	struct iproto_port *port = iproto_port(ptr);
diff --git a/src/box/key_def.cc b/src/box/key_def.cc
index 2f7fff6bee4aed9a70e5a1e1b0c49e411ece57cf..138479d2e47b501d67455d90323a17722c56bed2 100644
--- a/src/box/key_def.cc
+++ b/src/box/key_def.cc
@@ -35,6 +35,8 @@
 const char *field_type_strs[] = {"UNKNOWN", "NUM", "STR", "ARRAY", "NUMBER", ""};
 STRS(index_type, ENUM_INDEX_TYPE);
 
+const char *func_language_strs[] = {"LUA", "C"};
+
 const uint32_t key_mp_type[] = {
 	/* [UNKNOWN] = */ UINT32_MAX,
 	/* [NUM]     = */  1U << MP_UINT,
diff --git a/src/box/key_def.h b/src/box/key_def.h
index 574b2651c000ae5c131cc2d83535a60a9b01c0f2..901ff6506ccd2b95e81882e6acffabc52ba8981c 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -311,6 +311,16 @@ struct credentials {
 	uint32_t uid;
 };
 
+/**
+ * The supported language of the stored function.
+ */
+enum func_language {
+	FUNC_LANGUAGE_LUA,
+	FUNC_LANGUAGE_C,
+	func_language_MAX,
+};
+extern const char *func_language_strs[];
+
 /**
  * Definition of a function. Function body is not stored
  * or replicated (yet).
@@ -326,18 +336,11 @@ struct func_def {
 	 */
 	bool setuid;
 	/**
-	 * Authentication id of the owner of the function,
-	 * used for set-user-id functions.
+	 * The language of the stored function.
 	 */
-	struct credentials owner_credentials;
+	enum func_language language;
 	/** Function name. */
 	char name[BOX_NAME_MAX + 1];
-	/**
-	 * Strictly speaking, this doesn't belong
-	 * to func def but belongs to func cache entry.
-	 * Kept here for simplicity.
-	 */
-	struct access access[BOX_USER_MAX];
 };
 
 /**
diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc
index 1211519a4c0465c5f0db0db41ef234c975d4a2a0..3fce6f259af60611cc6d409121668ef1c15714e7 100644
--- a/src/box/lua/call.cc
+++ b/src/box/lua/call.cc
@@ -50,6 +50,7 @@
 #include "box/txn.h"
 #include "box/user_def.h"
 #include "box/user.h"
+#include "box/func.h"
 #include "box/schema.h"
 #include "box/session.h"
 #include "box/iproto_constants.h"
@@ -95,7 +96,7 @@ port_lua(struct port *port) { return (struct port_lua *) port; }
  * @sa port_add_lua_multret
  */
 
-static void
+extern "C" void
 port_lua_add_tuple(struct port *port, struct tuple *tuple)
 {
 	lua_State *L = port_lua(port)->L;
@@ -439,7 +440,6 @@ box_lua_find(lua_State *L, const char *name, const char *name_end)
 	return 1 + objstack;
 }
 
-
 /**
  * A helper to find lua stored procedures for box.call.
  * box.call iteslf is pure Lua, to avoid issues
@@ -468,12 +468,12 @@ struct SetuidGuard
 	struct credentials *orig_credentials;
 
 	inline SetuidGuard(const char *name, uint32_t name_len,
-			   uint8_t access);
+			   uint8_t access, struct func *func);
 	inline ~SetuidGuard();
 };
 
 SetuidGuard::SetuidGuard(const char *name, uint32_t name_len,
-			 uint8_t access)
+			 uint8_t access, struct func *func)
 	:setuid(false)
 	,orig_credentials(current_user())
 {
@@ -486,22 +486,14 @@ SetuidGuard::SetuidGuard(const char *name, uint32_t name_len,
 	if ((orig_credentials->universal_access & PRIV_ALL) == PRIV_ALL)
 		return;
 	access &= ~orig_credentials->universal_access;
-	/*
-	 * We need to look up the function by name even if
-	 * the user has access to it, since it could require
-	 * a set of other user id.
-	 */
-	struct func_def *func = func_by_name(name, name_len);
 	if (func == NULL && access == 0) {
 		/**
 		 * Well, the function is not explicitly defined,
-		 * so it's obviously not a setuid one. Wasted
-		 * cycles on look up while the user had universal
-		 * access :(
+		 * so it's obviously not a setuid one.
 		 */
 		return;
 	}
-	if (func == NULL || (func->uid != orig_credentials->uid &&
+	if (func == NULL || (func->def.uid != orig_credentials->uid &&
 	     access & ~func->access[orig_credentials->auth_token].effective)) {
 		/* Access violation, report error. */
 		char name_buf[BOX_NAME_MAX + 1];
@@ -511,16 +503,16 @@ SetuidGuard::SetuidGuard(const char *name, uint32_t name_len,
 		tnt_raise(ClientError, ER_FUNCTION_ACCESS_DENIED,
 			  priv_name(access), user->name, name_buf);
 	}
-	if (func->setuid) {
+	if (func->def.setuid) {
 		/** Remember and change the current user id. */
 		if (func->owner_credentials.auth_token >= BOX_USER_MAX) {
 			/*
 			 * Fill the cache upon first access, since
-			 * when func_def is created, no user may
+			 * when func is created, no user may
 			 * be around to fill it (recovery of
 			 * system spaces from a snapshot).
 			 */
-			struct user *owner = user_cache_find(func->uid);
+			struct user *owner = user_cache_find(func->def.uid);
 			credentials_init(&func->owner_credentials, owner);
 		}
 		setuid = true;
@@ -564,9 +556,19 @@ execute_call(lua_State *L, struct request *request, struct obuf *out)
 {
 	const char *name = request->key;
 	uint32_t name_len = mp_decode_strl(&name);
+	int oc = 0; /* how many objects are on stack after box_lua_find */
 
-	/* Try to find a function by name */
-	int oc = box_lua_find(L, name, name + name_len);
+	struct func *func = func_by_name(name, name_len);
+	/*
+	 * func == NULL means that perhaps the user has a global
+	 * "EXECUTE" privilege, so no specific grant to a function.
+	 */
+	if (func == NULL || func->def.language == FUNC_LANGUAGE_LUA) {
+		/* Try to find a function by name in Lua */
+		oc = box_lua_find(L, name, name + name_len);
+	} else if (func->func == NULL) {
+		func_load(func);
+	}
 	/**
 	 * Check access to the function and optionally change
 	 * execution time user id (set user id). Sic: the order
@@ -574,17 +576,24 @@ execute_call(lua_State *L, struct request *request, struct obuf *out)
 	 * https://github.com/tarantool/tarantool/issues/300
 	 * - if a function does not exist, say it first.
 	 */
-	SetuidGuard setuid(name, name_len, PRIV_X);
+	SetuidGuard setuid(name, name_len, PRIV_X, func);
 	/* Push the rest of args (a tuple). */
 	const char *args = request->tuple;
-	uint32_t arg_count = mp_decode_array(&args);
-	luaL_checkstack(L, arg_count, "call: out of stack");
 
-	for (uint32_t i = 0; i < arg_count; i++) {
-		luamp_decode(L, luaL_msgpack_default, &args);
-	}
-	lua_call(L, arg_count + oc - 1, LUA_MULTRET);
+	if (func == NULL || func->def.language == FUNC_LANGUAGE_LUA) {
+		uint32_t arg_count = mp_decode_array(&args);
+		luaL_checkstack(L, arg_count, "call: out of stack");
 
+		for (uint32_t i = 0; i < arg_count; i++) {
+			luamp_decode(L, luaL_msgpack_default, &args);
+		}
+		lua_call(L, arg_count + oc - 1, LUA_MULTRET);
+	} else {
+		struct port_lua port;
+		port_lua_create(&port, L);
+
+		func->func(request, (struct port *) &port);
+	}
 	/**
 	 * Add all elements from Lua stack to iproto.
 	 *
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 5461756fb9f412737173b95b52793611e77aae36..d5eb948d49eed7b6e3b970878d9940b78ecaaeb5 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -976,7 +976,9 @@ end
 box.schema.func = {}
 box.schema.func.create = function(name, opts)
     opts = opts or {}
-    check_param_table(opts, { setuid = 'boolean', if_not_exists = 'boolean' })
+    check_param_table(opts, { setuid = 'boolean',
+                              if_not_exists = 'boolean',
+                              language = 'string'})
     local _func = box.space[box.schema.FUNC_ID]
     local func = _func.index.name:get{name}
     if func then
@@ -985,9 +987,9 @@ box.schema.func.create = function(name, opts)
         end
         return
     end
-    opts = update_param_table(opts, { setuid = false })
+    opts = update_param_table(opts, { setuid = false, type = 'lua'})
     opts.setuid = opts.setuid and 1 or 0
-    _func:auto_increment{session.uid(), name, opts.setuid}
+    _func:auto_increment{session.uid(), name, opts.setuid, opts.language}
 end
 
 box.schema.func.drop = function(name, opts)
diff --git a/src/box/port.cc b/src/box/port.c
similarity index 100%
rename from src/box/port.cc
rename to src/box/port.c
diff --git a/src/box/port.h b/src/box/port.h
index 776b35d06a6415d3330bb708f7e328f957d112ab..e839114876127ea8ea9796e112d7e741c884d4ca 100644
--- a/src/box/port.h
+++ b/src/box/port.h
@@ -30,6 +30,10 @@
  */
 #include "trivia/util.h"
 
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
 struct tuple;
 struct port;
 
@@ -77,14 +81,18 @@ port_add_tuple(struct port *port, struct tuple *tuple)
 	(port->vtab->add_tuple)(port, tuple);
 }
 
-/** Reused in port_lua */
-void
-null_port_eof(struct port *port __attribute__((unused)));
-
 /**
  * This one does not have state currently, thus a single
  * instance is sufficient.
  */
 extern struct port null_port;
 
+/** Reused in port_lua */
+void
+null_port_eof(struct port *port __attribute__((unused)));
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
 #endif /* INCLUDES_TARANTOOL_BOX_PORT_H */
diff --git a/src/box/schema.cc b/src/box/schema.cc
index ed5f07283865f0d7e570e9520be734dde78463c3..04203b546676f190b4bf34858aa536d9173472e2 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -30,6 +30,7 @@
 #include "user_def.h"
 #include "engine.h"
 #include "space.h"
+#include "func.h"
 #include "tuple.h"
 #include "assoc.h"
 #include "lua/utils.h"
@@ -313,35 +314,35 @@ schema_free(void)
 	while (mh_size(funcs) > 0) {
 		mh_int_t i = mh_first(funcs);
 
-		struct func_def *func = (struct func_def *)
-				mh_i32ptr_node(funcs, i)->val;
-		func_cache_delete(func->fid);
+		struct func *func = ((struct func *)
+				     mh_i32ptr_node(funcs, i)->val);
+		func_cache_delete(func->def.fid);
 	}
 	mh_i32ptr_delete(funcs);
 }
 
 void
-func_cache_replace(struct func_def *func)
+func_cache_replace(struct func_def *def)
 {
-	struct func_def *old = func_by_id(func->fid);
+	struct func *old = func_by_id(def->fid);
 	if (old) {
-		*old = *func;
+		func_update(old, def);
 		return;
 	}
 	if (mh_size(funcs) >= BOX_FUNCTION_MAX)
 		tnt_raise(ClientError, ER_FUNCTION_MAX, BOX_FUNCTION_MAX);
-	void *ptr = malloc(sizeof(*func));
-	if (ptr == NULL) {
+	struct func *func = func_new(def);
+	if (func == NULL) {
 error:
 		panic_syserror("Out of memory for the data "
-			       "dictionary cache.");
+			       "dictionary cache (stored function).");
 	}
-	memcpy(ptr, func, sizeof(*func));
-	func = (struct func_def *) ptr;
-	const struct mh_i32ptr_node_t node = { func->fid, func };
+	const struct mh_i32ptr_node_t node = { def->fid, func };
 	mh_int_t k = mh_i32ptr_put(funcs, &node, NULL, NULL);
-	if (k == mh_end(funcs))
+	if (k == mh_end(funcs)) {
+		func_delete(func);
 		goto error;
+	}
 }
 
 void
@@ -350,19 +351,19 @@ func_cache_delete(uint32_t fid)
 	mh_int_t k = mh_i32ptr_find(funcs, fid, NULL);
 	if (k == mh_end(funcs))
 		return;
-	struct func_def *func = (struct func_def *)
+	struct func *func = (struct func *)
 		mh_i32ptr_node(funcs, k)->val;
 	mh_i32ptr_del(funcs, k, NULL);
-	free(func);
+	func_delete(func);
 }
 
-struct func_def *
+struct func *
 func_by_id(uint32_t fid)
 {
 	mh_int_t func = mh_i32ptr_find(funcs, fid, NULL);
 	if (func == mh_end(funcs))
 		return NULL;
-	return (struct func_def *) mh_i32ptr_node(funcs, func)->val;
+	return (struct func *) mh_i32ptr_node(funcs, func)->val;
 }
 
 bool
diff --git a/src/box/schema.h b/src/box/schema.h
index e321deb518cf8e0562a536b1cd0d4ad4db65c6d2..93d6076d036c2a4269e287eeefebe6d8ca5b22fb 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -120,30 +120,33 @@ schema_find_id(uint32_t system_space_id, uint32_t index_id,
 	       const char *name, uint32_t len);
 
 void
-func_cache_replace(struct func_def *func);
+func_cache_replace(struct func_def *def);
 
 void
 func_cache_delete(uint32_t fid);
 
-struct func_def *
+struct func;
+
+struct func *
 func_by_id(uint32_t fid);
 
-static inline struct func_def *
+static inline struct func *
 func_cache_find(uint32_t fid)
 {
-	struct func_def *func = func_by_id(fid);
+	struct func *func = func_by_id(fid);
 	if (func == NULL)
 		tnt_raise(ClientError, ER_NO_SUCH_FUNCTION, int2str(fid));
 	return func;
 }
 
-static inline struct func_def *
+static inline struct func *
 func_by_name(const char *name, uint32_t name_len)
 {
 	uint32_t fid = schema_find_id(SC_FUNC_ID, 2, name, name_len);
 	return func_by_id(fid);
 }
 
+
 /**
  * Check whether or not an object has grants on it (restrict
  * constraint in drop object).
diff --git a/src/box/sysview_index.cc b/src/box/sysview_index.cc
index 111ef64bef89ef2e6c23fc3a26841c9f6e10645e..6d70bc1ba6567bd6fd5ad3ff98e78645cd4765cd 100644
--- a/src/box/sysview_index.cc
+++ b/src/box/sysview_index.cc
@@ -29,6 +29,7 @@
 #include "sysview_index.h"
 #include "schema.h"
 #include "space.h"
+#include "func.h"
 #include "tuple.h"
 #include "session.h"
 
@@ -224,10 +225,10 @@ vfunc_filter(struct space *source, struct tuple *tuple)
 
 	const char *name = tuple_field_cstr(tuple, 2);
 	uint32_t name_len = strlen(name);
-	struct func_def *func = func_by_name(name, name_len);
+	struct func *func = func_by_name(name, name_len);
 	assert(func != NULL);
 	uint8_t effective = func->access[cr->auth_token].effective;
-	if (func->uid == cr->uid || (PRIV_X & effective))
+	if (func->def.uid == cr->uid || (PRIV_X & effective))
 		return true;
 	return false;
 }
diff --git a/src/box/tuple.h b/src/box/tuple.h
index c16d36d87d7d4d126c51c9e18728b97914ef2fed..130def0ab3115dc1dd4eff2731f17763dda358c1 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -177,11 +177,9 @@ struct tuple *
 tuple_alloc(struct tuple_format *format, size_t size);
 
 /**
- * Create a new tuple from a sequence of BER-len encoded fields.
+ * Create a new tuple from a sequence of MsgPack encoded fields.
  * tuple->refs is 0.
  *
- * @post *data is advanced to the length of tuple data
- *
  * Throws an exception if tuple format is incorrect.
  */
 struct tuple *
diff --git a/src/box/user.cc b/src/box/user.cc
index c08dced2abde7329669de6d2f0133d571f3f5053..1042c8ba14f7352b66e931b34c4454af37e245c5 100644
--- a/src/box/user.cc
+++ b/src/box/user.cc
@@ -31,6 +31,7 @@
 #include "assoc.h"
 #include "schema.h"
 #include "space.h"
+#include "func.h"
 #include "index.h"
 #include "bit/bit.h"
 #include "fiber.h"
@@ -213,7 +214,7 @@ access_find(struct priv_def *priv)
 	}
 	case SC_FUNCTION:
 	{
-		struct func_def *func = func_by_id(priv->object_id);
+		struct func *func = func_by_id(priv->object_id);
 		if (func)
 			access = func->access;
 		break;
diff --git a/src/ffisyms.cc b/src/ffisyms.cc
index ffa296180b36fa8558c9b51892f7984c14619150..068cc861e60581ecb41f498d0c1f34c15685d004 100644
--- a/src/ffisyms.cc
+++ b/src/ffisyms.cc
@@ -1,4 +1,3 @@
-
 #include <bit/bit.h>
 #include <lib/msgpuck/msgpuck.h>
 #include "scramble.h"
@@ -66,5 +65,5 @@ void *ffi_symbols[] = {
 	(void *) random_bytes,
 	(void *) fiber_time,
 	(void *) fiber_time64,
-	(void *) sophia_schedule
+	(void *) sophia_schedule,
 };
diff --git a/src/trivia/CMakeLists.txt b/src/trivia/CMakeLists.txt
index 4a50400da68c92377432a065dea36c91a16a523c..e81b4a585b886d5a0ec2958bf5e128984869262b 100644
--- a/src/trivia/CMakeLists.txt
+++ b/src/trivia/CMakeLists.txt
@@ -1,6 +1,7 @@
 set(api_headers
     ${CMAKE_CURRENT_BINARY_DIR}/config.h
-    ../say.h
-    ../coeio.h
-    ../lua/utils.h)
-apigen(${api_headers})
+    ${CMAKE_SOURCE_DIR}/src/say.h
+    ${CMAKE_SOURCE_DIR}/src/coeio.h
+    ${CMAKE_SOURCE_DIR}/src/lua/utils.h
+    ${CMAKE_SOURCE_DIR}/src/box/func.h)
+rebuild_module_api(${api_headers})
diff --git a/test-run b/test-run
index 7a9dffa16d0da05ec33fe0b131e92f6d86a87fd6..6b4dcd67126709a92e722a62c8a87a6ec90ceb0f 160000
--- a/test-run
+++ b/test-run
@@ -1 +1 @@
-Subproject commit 7a9dffa16d0da05ec33fe0b131e92f6d86a87fd6
+Subproject commit 6b4dcd67126709a92e722a62c8a87a6ec90ceb0f
diff --git a/test/app/CMakeLists.txt b/test/app/CMakeLists.txt
index 3b553fd30d5055fbdffabc5cc15543469a9c0f6d..804698325c69722700810776a13eda74cb01b7ea 100644
--- a/test/app/CMakeLists.txt
+++ b/test/app/CMakeLists.txt
@@ -1,7 +1,14 @@
 include_directories(${CMAKE_BINARY_DIR}/src/trivia)
-add_library(module_api SHARED module_api.c)
-set_target_properties(module_api PROPERTIES PREFIX "")
-if(TARGET_OS_DARWIN)
-    set_target_properties(module_api PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
-endif(TARGET_OS_DARWIN)
-add_dependencies(module_api generate_module_api)
+
+function(build_module module files)
+    add_library(${module} SHARED ${files})
+    set_target_properties(${module} PROPERTIES PREFIX "")
+    add_dependencies(${module} rebuild_module_api)
+    if(TARGET_OS_DARWIN)
+        set_target_properties(${module} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
+    endif(TARGET_OS_DARWIN)
+endfunction()
+
+
+build_module(module_api module_api.c)
+build_module(function1 function1.c)
diff --git a/test/app/function1.c b/test/app/function1.c
new file mode 100644
index 0000000000000000000000000000000000000000..b73e500823a27fd7398fd08b153020198ea630da
--- /dev/null
+++ b/test/app/function1.c
@@ -0,0 +1,8 @@
+#include "tarantool.h"
+
+int
+function1(struct request *request, struct port *port)
+{
+	say_info("-- function1 -  called --");
+	return 0;
+}
diff --git a/test/app/function1.result b/test/app/function1.result
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/app/function1.test.lua b/test/app/function1.test.lua
new file mode 100755
index 0000000000000000000000000000000000000000..f84ff151a4e5bda83b1701ce0c1a095adbb2e412
--- /dev/null
+++ b/test/app/function1.test.lua
@@ -0,0 +1,22 @@
+#!/usr/bin/env tarantool
+
+box.cfg{
+    listen              = os.getenv("LISTEN"),
+    slab_alloc_arena    = 0.1,
+    pid_file            = "tarantool.pid",
+    rows_per_wal        = 50,
+    logger = "tarantool.log"
+}
+
+package.cpath = '../app/?.so;../app/?.dylib;'..package.cpath
+
+log = require('log')
+net = require('net.box')
+
+box.schema.func.create('function1', {language = "C"})
+box.schema.user.grant('guest', 'execute', 'function', 'function1')
+
+c = net:new(os.getenv("LISTEN"))
+c:call('function1')
+
+os.exit(0)
diff --git a/test/app/init_script.result b/test/app/init_script.result
index 072e1608910b0fad8b5102310ece2fbfb4737a36..19e35675f491b547ba0f300fe4e650a0e863e979 100644
--- a/test/app/init_script.result
+++ b/test/app/init_script.result
@@ -10,7 +10,7 @@ box.cfg
 5	background:false
 6	logger:tarantool.log
 7	slab_alloc_arena:0.1
-8	listen:3314
+8	listen:port
 9	logger_nonblock:true
 10	snap_dir:.
 11	coredump:false
diff --git a/test/app/init_script.test.lua b/test/app/init_script.test.lua
index d79c24e754398471cc021ca34c5852d44cf3968a..8c14ec86ee552128c06bdb8586dfef7a3b890308 100755
--- a/test/app/init_script.test.lua
+++ b/test/app/init_script.test.lua
@@ -3,7 +3,7 @@
 -- Testing init script
 --
 box.cfg{
-    listen = 3314,
+    listen = os.getenv("LISTEN"),
     pid_file = "box.pid",
     slab_alloc_arena=0.1,
     logger="tarantool.log"
@@ -26,7 +26,14 @@ print[[
 ]]
 t = {}
 
-for k,v in pairs(box.cfg) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t,k..':'..tostring(v)) end end
+for k,v in pairs(box.cfg) do
+    if k == 'listen' then
+        v = 'port'
+    end
+    if type(v) ~= 'table' and type(v) ~= 'function' then
+        table.insert(t,k..':'..tostring(v))
+    end
+end
 
 print('box.cfg')
 for k,v in pairs(t) do print(k, v) end
diff --git a/test/app/snapshot.test.lua b/test/app/snapshot.test.lua
index 228fa058d3079ccd784c6fbefd32e252d8ccc468..c98cbf9a16b381852d202c34039bb2e8359bb3c0 100755
--- a/test/app/snapshot.test.lua
+++ b/test/app/snapshot.test.lua
@@ -27,6 +27,8 @@ function purge()
     end
 end
 
+continue_snapshoting = true
+
 function snapshot(lsn)
     fiber.name('snapshot')
     while true do
@@ -35,8 +37,12 @@ function snapshot(lsn)
             lsn = new_lsn;
             box.snapshot()
         end
+        if not continue_snapshoting then
+            break
+        end
         fiber.sleep(0.001)
     end
+    snap_chan:put("!")
 end
 box.cfg{logger="tarantool.log", slab_alloc_arena=0.1, rows_per_wal=5000}
 
@@ -55,8 +61,11 @@ fiber.create(noise)
 fiber.create(purge)
 fiber.create(noise)
 fiber.create(purge)
-fiber.create(snapshot, box.info.server.lsn)
+snap_chan = fiber.channel()
+snap_fib = fiber.create(snapshot, box.info.server.lsn)
 
 fiber.sleep(0.3)
+continue_snapshoting = false
+snap_chan:get()
 print('ok')
 os.exit(0)
diff --git a/test/box/misc.result b/test/box/misc.result
index b937e97b0df73669d1c7ad86f677cf1373d02baa..2dd1329d52d3bdf025a153863ce47d15b26837d7 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -252,6 +252,8 @@ t;
   - 'box.error.TUPLE_NOT_FOUND : 4'
   - 'box.error.DROP_USER : 44'
   - 'box.error.CROSS_ENGINE_TRANSACTION : 81'
+  - 'box.error.injection : table: <address>
+  - 'box.error.FUNCTION_LANGUAGE : 100'
   - 'box.error.MODIFY_INDEX : 14'
   - 'box.error.TUPLE_FOUND : 3'
   - 'box.error.FIELD_TYPE : 23'
@@ -279,7 +281,7 @@ t;
   - 'box.error.ROLE_EXISTS : 83'
   - 'box.error.NO_SUCH_ROLE : 82'
   - 'box.error.NO_ACTIVE_TRANSACTION : 80'
-  - 'box.error.injection : table: <address>
+  - 'box.error.LOAD_FUNCTION : 99'
   - 'box.error.FIELD_TYPE_MISMATCH : 24'
   - 'box.error.UNSUPPORTED : 5'
   - 'box.error.INVALID_MSGPACK : 20'
diff --git a/test/module/box.lua b/test/module/box.lua
deleted file mode 100644
index c17a78000894e2eb4d1da36d0fb507a0c5a793f6..0000000000000000000000000000000000000000
--- a/test/module/box.lua
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env tarantool
-
-box.cfg{
-    listen              = os.getenv("LISTEN"),
-    slab_alloc_arena    = 0.1,
-    pid_file            = "tarantool.pid",
-    rows_per_wal        = 50
-}
-
-require('console').listen(os.getenv('ADMIN'))
diff --git a/test/module/suite.ini b/test/module/suite.ini
deleted file mode 100644
index afca8175f19d31be093c3346cc078cac507472ba..0000000000000000000000000000000000000000
--- a/test/module/suite.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[default]
-script = box.lua
-disabled = 
-description = tarantool/box, optional lua modules
diff --git a/test/wal_off/snapshot_stress.result b/test/wal_off/snapshot_stress.result
index e136206758b7f57cedad15be7088f9c17311900e..47f90d56121bd9403d3f63aa0301ca18c0b2d45c 100644
--- a/test/wal_off/snapshot_stress.result
+++ b/test/wal_off/snapshot_stress.result
@@ -105,6 +105,15 @@ snaps = fio.glob(snap_search_wildcard);
 initial_snap_count = #snaps
 ---
 ...
+if box.space.accounts then box.space.accounts:drop() end
+---
+...
+if box.space.operations then box.space.operations:drop() end
+---
+...
+if box.space.deleting then box.space.deleting:drop() end
+---
+...
 s1 = box.schema.create_space("accounts")
 ---
 ...
@@ -117,6 +126,12 @@ s2 = box.schema.create_space("operations")
 i2 = s2:create_index('primary', { type = 'HASH', parts = {1, 'num'} })
 ---
 ...
+s3 = box.schema.create_space("deleting")
+---
+...
+i3 = s3:create_index('primary', { type = 'TREE', parts = {1, 'num'} })
+---
+...
 n_accs = 0
 ---
 ...
@@ -145,6 +160,11 @@ function get_new_space_name()
 end;
 ---
 ...
+tmp = get_new_space_name()
+if box.space[tmp] then box.space[tmp]:drop() tmp = get_new_space_name() end
+tmp = nil
+n_spaces = 0
+
 function get_rnd_acc()
     return math.floor(math.random() * n_accs)
 end;
@@ -201,10 +221,18 @@ function do_rand_op()
 end;
 ---
 ...
+function remove_smth()
+    s3:delete{i3:min()[1]}
+end;
+---
+...
 function init()
     for i = 1,accounts_start do
         add_acc()
     end
+    for i = 1,workers_count*iteration_count do
+        s3:auto_increment{"I hate dentists!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"}
+    end
 end;
 ---
 ...
@@ -214,6 +242,7 @@ function work_itr()
         fiber.sleep(0)
     end
     add_acc()
+    remove_smth()
     add_space()
 end;
 ---
diff --git a/test/wal_off/snapshot_stress.test.lua b/test/wal_off/snapshot_stress.test.lua
index 4b3c045d480b59e31f0b8c6629c6546c804247f5..8e0d4cffbdd6e39b78d8becca753967fe93c27f6 100644
--- a/test/wal_off/snapshot_stress.test.lua
+++ b/test/wal_off/snapshot_stress.test.lua
@@ -45,10 +45,16 @@ snap_search_wildcard = fio.pathjoin(box.cfg.snap_dir, '*.snap');
 snaps = fio.glob(snap_search_wildcard);
 initial_snap_count = #snaps
 
+if box.space.accounts then box.space.accounts:drop() end
+if box.space.operations then box.space.operations:drop() end
+if box.space.deleting then box.space.deleting:drop() end
+
 s1 = box.schema.create_space("accounts")
 i1 = s1:create_index('primary', { type = 'HASH', parts = {1, 'num'} })
 s2 = box.schema.create_space("operations")
 i2 = s2:create_index('primary', { type = 'HASH', parts = {1, 'num'} })
+s3 = box.schema.create_space("deleting")
+i3 = s3:create_index('primary', { type = 'TREE', parts = {1, 'num'} })
 
 n_accs = 0
 n_ops = 0
@@ -67,6 +73,11 @@ function get_new_space_name()
     return "test" .. tostring(n_spaces - 1)
 end;
 
+tmp = get_new_space_name()
+if box.space[tmp] then box.space[tmp]:drop() tmp = get_new_space_name() end
+tmp = nil
+n_spaces = 0
+
 function get_rnd_acc()
     return math.floor(math.random() * n_accs)
 end;
@@ -113,10 +124,17 @@ function do_rand_op()
     do_op(get_rnd_acc(), get_rnd_acc(), get_rnd_val())
 end;
 
+function remove_smth()
+    s3:delete{i3:min()[1]}
+end;
+
 function init()
     for i = 1,accounts_start do
         add_acc()
     end
+    for i = 1,workers_count*iteration_count do
+        s3:auto_increment{"I hate dentists!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"}
+    end
 end;
 
 function work_itr()
@@ -125,6 +143,7 @@ function work_itr()
         fiber.sleep(0)
     end
     add_acc()
+    remove_smth()
     add_space()
 end;
 
diff --git a/third_party/libev/ev.c b/third_party/libev/ev.c
index 34a30b6f5018fa1b6f613754abb477ece135b373..083c9f6c10c2a3357319b424c68755c0044e9beb 100644
--- a/third_party/libev/ev.c
+++ b/third_party/libev/ev.c
@@ -2225,7 +2225,7 @@ ev_recommended_backends (void) EV_THROW
 {
   unsigned int flags = ev_supported_backends ();
 
-#ifndef __NetBSD__
+#if !defined(__NetBSD__) && !defined(__FreeBSD__)
   /* kqueue is borked on everything but netbsd apparently */
   /* it usually doesn't work correctly on anything but sockets and pipes */
   flags &= ~EVBACKEND_KQUEUE;