Tarantool中的fiber.yield()和fiber.testcancel()的行为

我在基于fibers构建Tarantool应用时遇到了意外的行为。

我的代码的简单重现看起来像这样:

local log = require('log')
local fiber = require('fiber')

box.cfg{}

local func = function()
    for i = 1, 100000 do
        if pcall(fiber.testcancel) ~= true then
            return 1
        end

        fiber.yield()
    end

    return 0
end

local wrapfunc = function()
    local ok, resp = pcall(func)
    log.info(ok)
    log.info(resp)
end

for _ = 1, 100 do
    local myfiber = fiber.create(wrapfunc)
    fiber.sleep(0.02)
    fiber.kill(myfiber)
end

它会在日志中打印 false,fiber is cancelled。此外,如果我使用以下func

local func = function()
    for i = 1, 100000 do
        if pcall(fiber.testcancel) ~= true then
            return 1
        end

        pcall(fiber.yield)
    end

    return 0
end

它会在日志中打印 true,1,如果我使用

local func = function()
    for i = 1, 100000 do
        if pcall(fiber.testcancel) ~= true then
            return 1
        end

        if pcall(fiber.yield) ~= true then
            return 2
        end
    end

    return 0
end

它会在日志中打印 true,2

我期望在从运行myfiber中yield之后,如果控制权返回到外部fiber并调用fiber.kill(myfiber),那么下一次控制权返回到被取消的myfiber时,我们将处于循环迭代的末尾,并且在下一次迭代时代码将成功返回1。然而,func的工作以抛出错误fiber is cancelled而不是return结束。那么yielding fiber的实际生命周期是如何工作的呢?

点赞
用户1881632
用户1881632

实际上这里没有意外行为。我认为这主要是文档问题。让我解释一下。我将你的例子简化了一下:

#!/usr/bin/env tarantool

local fiber = require('fiber')

local f1 = function() fiber.yield() end
local f2 = function() pcall(fiber.yield) end

local func = function(fn)
    fn()
    if not pcall(fiber.testcancel) then
        return 'fiber.testcancel() failed'
    end
end

local fiber1 = fiber.create(function() print(pcall(func, f1)) end)
fiber.kill(fiber1)
local fiber2 = fiber.create(function() print(pcall(func, f2)) end)
fiber.kill(fiber2)

输出结果将会是:

false   fiber is cancelled
true    fiber.testcancel() failed

当你调用 fiber.killfiber.yield()fiber.sleep() 时,它会产生错误,所以你的 fiber 无法到达 fiber.testcancel,直接死亡。当你使用 pcall(fiber.yield) 时,基本上抑制了这个错误并继续执行。然后 fiber.testcancel 检查其 fiber 状态并重新引发异常。但这只是一个愚蠢的例子。

现在,当有大块的代码,涉及到许多函数调用时,你通常希望在 yield 期间捕获这些错误,做一些最终工作并调用 fiber.testcancel() 将错误向上升级(想象一下不同部分的大堆栈跟踪中的多个这种检查)。我认为这是 fiber.testcancel 的基本用例,除了讨论它的设计是否可用。

P.s. 是的,时不时这种 yield 调用的异常没有被记录。至少我在 fiber 页面中没有找到相关内容。

2020-06-08 14:07:03