Yielding in __close metamethod goes across a C-call boundary

56 views
Skip to first unread message

Artem Popov

unread,
Dec 5, 2025, 9:26:48 AM (11 days ago) Dec 5
to lua-l
Hi,

I'm hitting the "attempt to yield across a C-call boundary" error when trying to yield in __close metamethod in the following code:

----------

local function try(try_block, finally_block)
    local _ <close> = setmetatable({}, {
        __close = function(_, err)
            finally_block(err)
        end
    })

    return try_block()
end

local co = coroutine.create(function()
    try(function()
        print('try1')
        error('error1')
    end,
    function(err)
        print('finally1', err)
        coroutine.yield()
        print('finally2')
    end)

end)

coroutine.resume(co)
coroutine.close(co)

----------

It terminates with "attempt to yield across a C-call boundary" error and never prints "finally2"

Lua code, specifically lua/lstate.h, hints that resuming __close should be possible if it's invoked because of an error:


I am probably missing something, but should the interpreter allow resuming `co` in the above example if __close yielded or is this the intended behavior? I'm observing it with both Lua 5.4 and 5.5-rc2.

Cheers,
Artem

Xmilia Hermit

unread,
Dec 5, 2025, 9:58:52 AM (11 days ago) Dec 5
to lu...@googlegroups.com

> Lua code, specifically lua/lstate.h, hints that resuming __close should
> be possible if it's invoked because of an error:

You are calling the __close method through coroutine.close and not due
to the error. The error is never catched and so the __close methods
never called during the coroutine.resume call. Try running your
coroutine function through a pcall and it will work.

local co = coroutine.create(function() pcall(function()
try(function()
print('try1')
error('error1')
end,
function(err)
print('finally1', err)
coroutine.yield()
print('finally2')
end)

end) end)

coroutine.resume(co)
coroutine.resume(co)

Regards,
Xmilia

Artem Popov

unread,
Dec 5, 2025, 10:19:15 AM (11 days ago) Dec 5
to lu...@googlegroups.com
пт, 5 дек. 2025 г. в 15:58, Xmilia Hermit <xmilia...@gmail.com>:
>
>
> > Lua code, specifically lua/lstate.h, hints that resuming __close should
> > be possible if it's invoked because of an error:
>
> You are calling the __close method through coroutine.close and not due
> to the error. The error is never catched and so the __close methods
> never called during the coroutine.resume call. Try running your
> coroutine function through a pcall and it will work.

Thanks a lot, that worked! Is that completely impossible to yield in
__close, if it's a result of coroutine.close? I'm trying to implement
a try/finally mechanism providing a cleanup function for coroutines
that raised an error, but it would be great to get it to work for
manually cancelled coroutines too.

Cheers,
Artem

Roberto Ierusalimschy

unread,
Dec 5, 2025, 10:27:58 AM (11 days ago) Dec 5
to lu...@googlegroups.com
> [...] Is that completely impossible to yield in
> __close, if it's a result of coroutine.close? [...]

If that was possible, what would be the meaning of an yield inside
'coroutine.close'? It would stop closing the coroutine? Would there be
some way to resume the closing again?

-- Roberto

Artem Popov

unread,
Dec 5, 2025, 11:15:51 AM (11 days ago) Dec 5
to lu...@googlegroups.com
пт, 5 дек. 2025 г. в 16:27, Roberto Ierusalimschy <rob...@inf.puc-rio.br>:
Good question, yes, pausing the closing and waiting for an
asynchronous operation to complete. I was thinking of something
similar to what Python does, allowing awaits inside try/except here,
and in this case the closing would be resumed by the event loop.

Cheers,
Artem

Sainan

unread,
Dec 5, 2025, 11:23:05 AM (11 days ago) Dec 5
to lu...@googlegroups.com
May I suggest an alternative implementation?

function try(f, finally)
local t = table.pack(pcall(f))
if t[1] then
finally()
return table.unpack(t, 2, t.n)
end
finally(t[2])
end

local co = coroutine.create(function()
try(function()
error("Oh no")
end, function(err)
print("Finally: "..tostring(err))
coroutine.yield()
print("Finally done")
end)
end)
coroutine.resume(co)
coroutine.resume(co)

-- Sainan

Artem Popov

unread,
Dec 5, 2025, 6:30:18 PM (11 days ago) Dec 5
to lu...@googlegroups.com
пт, 5 дек. 2025 г. в 17:23, 'Sainan' via lua-l <lu...@googlegroups.com>:
Thanks a lot, that works great too. I am just starting with Lua and
examples like yours and Xmilia's earlier in the thread keep reminding
me that pcall is something I should learn to use whenever error
handling is involved.

May I ask why have you used t.n with the packed arguments table
instead of #t? I've read about this field in Programming in Lua book
earlier, but didn't understand what's the difference with the #
operator.

Cheers,
Artem

Sainan

unread,
Dec 6, 2025, 3:37:10 AM (11 days ago) Dec 6
to lu...@googlegroups.com
> May I ask why have you used t.n with the packed arguments table instead of #t?

It's a really minor thing, but # does not always count nil whereas 'n' is exactly the number of values that were passed.

-- Sainan
Reply all
Reply to author
Forward
0 new messages