[Tarantool-patches] [PATCH v2 1/3] fiber: introduce schedule_task() internal function

Igor Munkin imun at tarantool.org
Thu Mar 19 17:52:57 MSK 2020


Vlad,

Thanks for the patch! Please consider several minor comments I left.

On 03.03.20, Vladislav Shpilevoy wrote:
> fiber._internal.schedule_task() is an API for a singleton fiber
> worker object. It serves for not urgent delayed execution of
> functions. Main purpose - schedule execution of a function, which
> is going to yield, from a context, where a yield is not allowed.
> Such as an FFI object's GC callback.
> 
> It will be used by SWIM and by fio, whose destruction yields, but
> they need to use ffi.gc hook, where a yield is not allowed.

Minor: AFAIR __gc metamethod[1] also obligues the one to avoid
coroutine/fiber switch[2]. Thereby GC finalizer looks to be more
accurate term here.

> 
> Part of #4727
> ---
>  src/lua/fiber.c         | 15 +++++++++
>  src/lua/fiber.lua       | 60 ++++++++++++++++++++++++++++++++++
>  test/app/fiber.result   | 72 +++++++++++++++++++++++++++++++++++++++++
>  test/app/fiber.test.lua | 41 +++++++++++++++++++++++
>  4 files changed, 188 insertions(+)
> 
> diff --git a/src/lua/fiber.c b/src/lua/fiber.c
> index 575a020d0..ddf827ab6 100644
> --- a/src/lua/fiber.c
> +++ b/src/lua/fiber.c
> @@ -828,6 +828,19 @@ lbox_fiber_set_joinable(struct lua_State *L)
>  	return 0;
>  }
>  
> +/**
> + * Alternative to fiber.sleep(infinite) which does not participate
> + * in an event loop at all until an explicit wakeup. This is less
> + * overhead. Useful for fibers sleeping most of the time.
> + */

Minor: I guess <lbox_fiber_stall> and <fiber.stall> are good names for
this API. Feel free to ignore.

> +static int
> +lbox_fiber_sleep_infinite(struct lua_State *L)
> +{
> +	(void) L;
> +	fiber_yield();
> +	return 0;
> +}
> +
>  static const struct luaL_Reg lbox_fiber_meta [] = {
>  	{"id", lbox_fiber_id},
>  	{"name", lbox_fiber_name},

<snipped>

> diff --git a/src/lua/fiber.lua b/src/lua/fiber.lua
> index 8712ee0d6..d0b765b60 100644
> --- a/src/lua/fiber.lua
> +++ b/src/lua/fiber.lua
> @@ -34,4 +34,64 @@ fiber.time = fiber_time
>  fiber.time64 = fiber_time64
>  fiber.clock = fiber_clock
>  fiber.clock64 = fiber_clock64
> +
> +local sleep_infinite = fiber.sleep_infinite
> +fiber.sleep_infinite = nil
> +
> +local worker_next_task = nil
> +local worker_last_task = nil
> +local worker_fiber = nil
> +
> +--
> +-- Worker is a singleton fiber for not urgent delayed execution of
> +-- functions. Main purpose - schedule execution of a function,
> +-- which is going to yield, from a context, where a yield is not
> +-- allowed. Such as an FFI object's GC callback.
> +--
> +local function worker_f()
> +    local task
> +    while true do
> +        while true do
> +            task = worker_next_task
> +            if task then
> +                break
> +            end
> +            sleep_infinite()
> +        end
> +        worker_next_task = task.next
> +        task.f(task.arg)
> +        fiber.sleep(0)
> +    end
> +end
> +
> +local function worker_safe_f()
> +    pcall(worker_f)
> +    -- This fiber is probably canceled and now is not able to
> +    -- sleep, create a new one.

Minor: I guess it worth mentioning the fact that <worker_f> doesn't
return. It would definitely make the comment clearer.

> +    worker_fiber = fiber.new(worker_safe_f)
> +end
> +
> +worker_fiber = fiber.new(worker_safe_f)
> +
> +local function worker_schedule_task(f, arg)
> +    local task = {f = f, arg = arg}
> +    if not worker_next_task then
> +        worker_next_task = task
> +    else

Minor: It would be great to add an assert here, since nothing except the
current implementation prevents us from indexing a nil value. Feel free
to ignore.

> +        worker_last_task.next = task
> +    end
> +    worker_last_task = task
> +    worker_fiber:wakeup()
> +end
> +
> +-- Start from '_' to hide it from auto completion.
> +fiber._internal = fiber._internal or {}
> +fiber._internal.schedule_task = worker_schedule_task
> +
> +setmetatable(fiber, {__serialize = function(self)
> +    local res = table.copy(self)
> +    res._internal = nil
> +    return setmetatable(res, {})
> +end})
> +
>  return fiber

<snipped>

> diff --git a/test/app/fiber.test.lua b/test/app/fiber.test.lua
> index 6df210d9c..f10782d1f 100644
> --- a/test/app/fiber.test.lua
> +++ b/test/app/fiber.test.lua
> @@ -688,6 +688,47 @@ tbl.time > 0
>  fiber.top_disable()
>  fiber.top()
>  
> +--
> +-- fiber._internal.schedule_task() - API for internal usage for
> +-- delayed execution of a function.
> +--
> +glob_arg = {}
> +count = 0
> +function task_f(arg)                                                            \
> +    count = count + 1                                                           \
> +    table.insert(glob_arg, arg)                                                 \
> +    arg = arg + 1                                                               \
> +    if arg <= 3 then                                                            \
> +        fiber._internal.schedule_task(task_f, arg)                              \
> +    else                                                                        \
> +        fiber.self():cancel()                                                   \
> +        error('Worker is broken')                                               \
> +    end                                                                         \
> +end
> +for i = 1, 3 do                                                                 \
> +    local csw1 = fiber.info()[fiber.id()].csw                                   \
> +    fiber._internal.schedule_task(task_f, i)                                    \
> +    local csw2 = fiber.info()[fiber.id()].csw                                   \
> +    assert(csw1 == csw2 and csw1 ~= nil)                                        \

Minor: IMHO it's better to check that csw1 is a 'number'. Feel free to
ignore.

> +end
> +old_count = count
> +test_run:wait_cond(function()                                                   \
> +    fiber.yield()                                                               \
> +    if count == old_count then                                                  \
> +        return true                                                             \
> +    end                                                                         \
> +    old_count = count                                                           \
> +end)
> +glob_arg
> +count
> +
> +-- Ensure, that after all tasks are finished, the worker didn't
> +-- stuck somewhere.
> +glob_arg = nil
> +fiber._internal.schedule_task(function(arg) glob_arg = arg end, 100)
> +fiber.yield()
> +glob_arg
> +
>  -- cleanup
>  test_run:cmd("clear filter")
>  
> -- 
> 2.21.1 (Apple Git-122.3)
> 

[1]: https://github.com/tarantool/luajit/blob/tarantool/src/lj_gc.c#L495
[2]: https://github.com/tarantool/luajit/blob/tarantool/src/lj_api.c#L1204

-- 
Best regards,
IM


More information about the Tarantool-patches mailing list