Skip to content

Commit 7db4de7

Browse files
nshylocker
authored andcommitted
fiber: fix leak on dead joinable fiber search
When fiber is accessed from Lua we create a userdata object and keep the reference for future accesses. The reference is cleared when fiber is stopped. But if fiber is joinable is still can be found with `fiber.find`. In this case we create userdata object again. Unfortunately as fiber is already stopped we fail to clear the reference. The trigger memory that clear the reference is also leaked. As well as fiber storage if it is accessed after fiber is stopped. Let's add `on_destroy` trigger to fiber and clear the references there. Note that with current set of LSAN suppressions the trigger memory leak of the issue is not reported. Closes tarantool#10187 NO_DOC=bugfix
1 parent ef716a3 commit 7db4de7

File tree

5 files changed

+64
-15
lines changed

5 files changed

+64
-15
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## bugfix/core
2+
3+
* Fixed memory leaks on using dead fiber (gh-10187).

src/lib/core/fiber.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,7 @@ fiber_reset(struct fiber *fiber)
10341034
{
10351035
rlist_create(&fiber->on_yield);
10361036
rlist_create(&fiber->on_stop);
1037+
rlist_create(&fiber->on_destroy);
10371038
clock_stat_reset(&fiber->clock_stat);
10381039
}
10391040

@@ -1046,6 +1047,11 @@ fiber_recycle(struct fiber *fiber)
10461047
assert(diag_is_empty(&fiber->diag));
10471048
/* no pending wakeup */
10481049
assert(rlist_empty(&fiber->state));
1050+
assert(rlist_empty(&fiber->on_stop));
1051+
if (trigger_run(&fiber->on_destroy, fiber) != 0)
1052+
panic("on_destroy triggers can't fail");
1053+
/* On destroy triggers are expected to remove themselves. */
1054+
assert(rlist_empty(&fiber->on_destroy));
10491055
fiber_stack_recycle(fiber);
10501056
fiber_reset(fiber);
10511057
fiber->name[0] = '\0';
@@ -1642,6 +1648,7 @@ fiber_destroy(struct cord *cord, struct fiber *f)
16421648
assert(f != cord->fiber);
16431649
trigger_destroy(&f->on_yield);
16441650
trigger_destroy(&f->on_stop);
1651+
trigger_destroy(&f->on_destroy);
16451652
rlist_del(&f->state);
16461653
rlist_del(&f->link);
16471654
rlist_del(&f->wake);

src/lib/core/fiber.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,8 @@ struct fiber {
683683
* request, even if they are never destroyed.
684684
*/
685685
struct rlist on_stop;
686+
/** Triggers invoked before fiber is recycled. */
687+
struct rlist on_destroy;
686688
/**
687689
* The list of fibers awaiting for this fiber's timely
688690
* (or untimely) death.

src/lua/fiber.c

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,21 +88,29 @@ luaL_testcancel(struct lua_State *L)
8888
static const char *fiberlib_name = "fiber";
8989

9090
/**
91-
* Trigger invoked when the fiber has stopped execution of its
92-
* current request. Only purpose - delete storage.lua.fid_ref and
93-
* storage.lua.storage_ref keeping a reference of Lua
94-
* fiber and fiber.storage objects. Unlike Lua stack,
95-
* Lua fiber storage may be created not only for fibers born from
96-
* Lua land. For example, an IProto request may execute a Lua
97-
* function, which can create the storage. Trigger guarantees,
98-
* that even for non-Lua fibers the Lua storage is destroyed.
91+
* Trigger invoked when the fiber is stopped. Only purpose - delete
92+
* storage.lua.storage_ref keeping a reference of Lua fiber.storage object.
93+
* Unlike Lua stack, Lua fiber storage may be created not only for fibers born
94+
* from Lua land. For example, an IProto request may execute a Lua function,
95+
* which can create the storage. Trigger guarantees, that even for non-Lua
96+
* fibers the Lua storage is destroyed.
9997
*/
10098
static int
10199
lbox_fiber_on_stop(struct trigger *trigger, void *event)
102100
{
103101
struct fiber *f = event;
104102
luaL_unref(tarantool_L, LUA_REGISTRYINDEX, f->storage.lua.storage_ref);
105103
f->storage.lua.storage_ref = FIBER_LUA_NOREF;
104+
trigger_clear(trigger);
105+
free(trigger);
106+
return 0;
107+
}
108+
109+
/** Cleanup Lua fiber data when fiber is destroyed. */
110+
static int
111+
lbox_fiber_on_destroy(struct trigger *trigger, void *event)
112+
{
113+
struct fiber *f = event;
106114
luaL_unref(tarantool_L, LUA_REGISTRYINDEX, f->storage.lua.fid_ref);
107115
f->storage.lua.fid_ref = FIBER_LUA_NOREF;
108116
trigger_clear(trigger);
@@ -118,13 +126,10 @@ lbox_pushfiber(struct lua_State *L, struct fiber *f)
118126
{
119127
int fid_ref = f->storage.lua.fid_ref;
120128
if (fid_ref == FIBER_LUA_NOREF) {
121-
struct trigger *t = malloc(sizeof(*t));
122-
if (t == NULL) {
123-
diag_set(OutOfMemory, sizeof(*t), "malloc", "t");
124-
luaT_error(L);
125-
}
126-
trigger_create(t, lbox_fiber_on_stop, NULL, (trigger_f0)free);
127-
trigger_add(&f->on_stop, t);
129+
struct trigger *on_destroy = xmalloc(sizeof(*on_destroy));
130+
trigger_create(on_destroy, lbox_fiber_on_destroy, NULL,
131+
(trigger_f0)free);
132+
trigger_add(&f->on_destroy, on_destroy);
128133

129134
uint64_t fid = f->fid;
130135
/* create a new userdata */
@@ -653,6 +658,14 @@ lbox_fiber_storage(struct lua_State *L)
653658
struct fiber *f = lbox_checkfiber(L, 1);
654659
int storage_ref = f->storage.lua.storage_ref;
655660
if (storage_ref == FIBER_LUA_NOREF) {
661+
if (f->flags & FIBER_IS_DEAD) {
662+
diag_set(IllegalParams, "the fiber is dead");
663+
luaT_error(L);
664+
}
665+
struct trigger *on_stop = xmalloc(sizeof(*on_stop));
666+
trigger_create(on_stop, lbox_fiber_on_stop, NULL,
667+
(trigger_f0)free);
668+
trigger_add(&f->on_stop, on_stop);
656669
lua_newtable(L); /* create local storage on demand */
657670
storage_ref = luaL_ref(L, LUA_REGISTRYINDEX);
658671
f->storage.lua.storage_ref = storage_ref;

test/app-luatest/fiber_test.lua

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,27 @@ g.test_gh_9406_shutdown_with_lingering_fiber_join = function()
5757
local cmd = string.format('%s -e "%s"', tarantool_bin, script)
5858
t.assert(os.execute(cmd) == 0)
5959
end
60+
61+
g.test_gh_10187_no_memory_leak_on_dead_fiber_search = function()
62+
local f = fiber.new(function() end)
63+
f:set_joinable(true)
64+
f:wakeup()
65+
fiber.yield()
66+
local x = fiber.find(f:id())
67+
-- Check found the fiber object is the same as the one created after the
68+
-- fiber became dead.
69+
t.assert_equals(x, f)
70+
-- Check we cannot access dead fiber storage.
71+
t.assert_error_covers({
72+
type = 'IllegalParams',
73+
message = 'the fiber is dead',
74+
}, x.__index, x, 'storage')
75+
-- Check fiber object is GC after fiber is joined.
76+
local weak_table = setmetatable({}, {__mode = 'v'})
77+
weak_table.fiber = f
78+
x:join()
79+
f = nil -- luacheck: no unused
80+
x = nil -- luacheck: no unused
81+
collectgarbage()
82+
t.assert_equals(weak_table.fiber, nil)
83+
end

0 commit comments

Comments
 (0)