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