diff --git a/src/lib/swim/swim.c b/src/lib/swim/swim.c
index 6053abd529cb2fe715e81920c1f5105747199c48..707b52a20749cf0849f0bace88e5ea4aac698f5f 100644
--- a/src/lib/swim/swim.c
+++ b/src/lib/swim/swim.c
@@ -544,6 +544,8 @@ struct swim {
 	 * lines.
 	 */
 	struct swim_task round_step_task;
+	/* Whether event handler should free swim on exit. */
+	bool free_in_handler;
 };
 
 /** Put the member into a list of ACK waiters. */
@@ -628,7 +630,8 @@ swim_on_member_update(struct swim *swim, struct swim_member *member,
 			swim_member_ref(member);
 			stailq_add_tail_entry(&swim->event_queue, member,
 					      in_event_queue);
-			fiber_wakeup(swim->event_handler);
+			if (swim->event_handler != NULL)
+				fiber_wakeup(swim->event_handler);
 		}
 		member->events |= events;
 	}
@@ -1888,6 +1891,9 @@ swim_event_handler_f(va_list va)
 		}
 		swim_member_unref(m);
 	}
+	s->event_handler = NULL;
+	if (s->free_in_handler)
+		swim_delete(s);
 	return 0;
 }
 
@@ -1928,7 +1934,9 @@ swim_new(uint64_t generation)
 		return NULL;
 	}
 	fiber_set_joinable(swim->event_handler, true);
+	fiber_set_managed_shutdown(swim->event_handler);
 	fiber_start(swim->event_handler, swim);
+	swim->free_in_handler = false;
 	return swim;
 }
 
@@ -2203,11 +2211,8 @@ static inline void
 swim_kill_event_handler(struct swim *swim)
 {
 	struct fiber *f = swim->event_handler;
-	/*
-	 * Nullify so as not to keep pointer at a fiber when it is
-	 * reused.
-	 */
-	swim->event_handler = NULL;
+	if (f == NULL)
+		return;
 	fiber_cancel(f);
 	fiber_join(f);
 }
@@ -2215,8 +2220,7 @@ swim_kill_event_handler(struct swim *swim)
 void
 swim_delete(struct swim *swim)
 {
-	if (swim->event_handler != NULL)
-		swim_kill_event_handler(swim);
+	swim_kill_event_handler(swim);
 	struct ev_loop *l = swim_loop();
 	swim_scheduler_destroy(&swim->scheduler);
 	swim_ev_timer_stop(l, &swim->round_tick);
@@ -2245,6 +2249,14 @@ swim_delete(struct swim *swim)
 	free(swim);
 }
 
+void
+swim_gc(struct swim *swim)
+{
+	swim->free_in_handler = true;
+	fiber_set_joinable(swim->event_handler, false);
+	fiber_cancel(swim->event_handler);
+}
+
 /**
  * Quit message is broadcasted in the same way as round messages,
  * step by step, with the only difference that quit round steps
diff --git a/src/lib/swim/swim.h b/src/lib/swim/swim.h
index 4565eb976519d5ee2b3bf1f9c7985057f0d594b8..24fc3ea8fe4447c489316d4d6f03718c45f3d021 100644
--- a/src/lib/swim/swim.h
+++ b/src/lib/swim/swim.h
@@ -124,6 +124,13 @@ int
 swim_set_codec(struct swim *swim, enum crypto_algo algo, enum crypto_mode mode,
 	       const char *key, int key_size);
 
+/**
+ * Delete asynchronously. Does not yield. Object is invalid after return
+ * from this function and should not be used.
+ */
+void
+swim_gc(struct swim *swim);
+
 /**
  * Stop listening and broadcasting messages, cleanup all internal
  * structures, free memory. The function yields. Actual deletion
diff --git a/src/lua/swim.c b/src/lua/swim.c
index 7583815f28e07d5c81f2ce5bb4f30050ca2887c8..f2c182e22e83e23e0dc5e8917aab8662b6c4abc0 100644
--- a/src/lua/swim.c
+++ b/src/lua/swim.c
@@ -153,6 +153,20 @@ lua_swim_quit(struct lua_State *L)
 	return 0;
 }
 
+/**
+ * Delete asynchronously. Does not yield. Object is invalid after return
+ * from this function and should not be used.
+ */
+static int
+lua_swim_gc(struct lua_State *L)
+{
+	uint32_t ctypeid;
+	struct swim *s = *(struct swim **)luaL_checkcdata(L, 1, &ctypeid);
+	assert(ctypeid == ctid_swim_ptr);
+	swim_gc(s);
+	return 0;
+}
+
 void
 tarantool_lua_swim_init(struct lua_State *L)
 {
@@ -163,6 +177,7 @@ tarantool_lua_swim_init(struct lua_State *L)
 	static const struct luaL_Reg lua_swim_internal_methods [] = {
 		{"swim_new", lua_swim_new},
 		{"swim_delete", lua_swim_delete},
+		{"swim_gc", lua_swim_gc},
 		{"swim_quit", lua_swim_quit},
 		{"swim_on_member_event", lua_swim_on_member_event},
 		{"swim_on_member_event_normalize_arguments",
diff --git a/src/lua/swim.lua b/src/lua/swim.lua
index f72734d7b49fc1a49f377fc06ad009588e812a82..4462f75fde69503bf034a92b2771a8b43b540612 100644
--- a/src/lua/swim.lua
+++ b/src/lua/swim.lua
@@ -5,7 +5,6 @@ local msgpack = require('msgpack')
 local crypto = require('crypto')
 local fiber = require('fiber')
 local internal = require('swim.lib')
-local schedule_task = fiber._internal.schedule_task
 local cord_ibuf_take = buffer.internal.cord_ibuf_take
 local cord_ibuf_put = buffer.internal.cord_ibuf_put
 
@@ -979,19 +978,6 @@ swim_cfg_not_configured_mt.__call = swim_cfg_first_call
 -- removed members erasure - GC drops them automatically.
 local cache_table_mt = { __mode = 'v' }
 
---
--- SWIM garbage collection function. It can't delete the SWIM
--- instance immediately, because it is invoked by Lua GC. Firstly,
--- it is not safe to yield in FFI - Jit can't survive a yield.
--- Secondly, it is not safe to yield in any GC function, because
--- it stops garbage collection. Instead, here GC is delayed, works
--- at the end of the event loop, and deletes the instance
--- asynchronously.
---
-local function swim_gc(ptr)
-    schedule_task(internal.swim_delete, ptr)
-end
-
 --
 -- Create a new SWIM instance, and configure if @a cfg is
 -- provided.
@@ -1020,7 +1006,7 @@ local function swim_new(cfg)
     if ptr == nil then
         return nil, box.error.last()
     end
-    ffi.gc(ptr, swim_gc)
+    ffi.gc(ptr, internal.swim_gc)
     local s = setmetatable({
         ptr = ptr,
         cfg = setmetatable({index = {}}, swim_cfg_not_configured_mt),
diff --git a/test/app-luatest/shutdown_test.lua b/test/app-luatest/shutdown_test.lua
index d582b28264dd8d5c9fc93866c591fef71a742510..5e3a4feb33f61a86c3f4c8de4bfc72a0b6176405 100644
--- a/test/app-luatest/shutdown_test.lua
+++ b/test/app-luatest/shutdown_test.lua
@@ -228,3 +228,26 @@ g_netbox.test_netbox_shutdown = function(cg)
     end, {cg.peer.net_box_uri})
     test_no_hang_on_shutdown(cg.server)
 end
+
+local g_swim = t.group('swim')
+
+g_swim.before_each(function(cg)
+    cg.server = server:new()
+    cg.server:start()
+end)
+
+g_swim.after_each(function(cg)
+    if cg.server ~= nil then
+        cg.server:drop()
+    end
+end)
+
+-- Test shutdown with swim instance.
+g_swim.test_swim_shutdown = function(cg)
+    cg.server:exec(function()
+        local swim = require('swim')
+        local s = swim.new({generation = 0})
+        rawset(_G, 'test_swim', s)
+    end)
+    test_no_hang_on_shutdown(cg.server)
+end