> I would like this to work, but for that to work, I would probably need the ability to resume to the
"outermost" coroutine, as they're being nested here.
You want to resume to the "outermost" coroutine to avoid breaking
other coroutines running in the stack, right? Consider the following
code as an illustration of the issue. The `system.listdir` plays the
role of your yielding I/O function `do_io_operation`, and the
`system.run` is the Lua equivalent of your `run_my_event_loop`.
```lua
local system = require "coutil.system"
local function combine_files_from(dir1, dir2)
return coroutine.wrap( -- coroutine C2
function () -- function F2
for path1 in system.listdir(dir1) do
for path2 in system.listdir(dir2) do
coroutine.yield(path1, path2)
end
end
end
)
end
coroutine.resume(
coroutine.create( -- coroutine C1
function() -- function F1
for path1, path2 in combine_files_from("/tmp/dir1", "/tmp/dir2") do
print(path1, path2)
end
end)
)
system.run()
```
If we execute this code, when the first `system.listdir` is called we
would have a stack with something more or less like:
```
[main thread]
- call function 'coroutine.resume' (or 'lua_resume' underneath)
[coroutine C1]
- call function F1
- call function by 'coroutine.wrap' (or 'lua_resume' underneath)
[coroutine C2]
- call function F2
- call function 'system.listdir' (or 'lua_yield' underneath)
```
Since `system.listdir` simply yields the current coroutine without any
values, this will break the "yield signature" of the iterator
coroutine C2 created by `combine_files_from`, which should yield two
path values until the end of its iterations. Thus you would like to
skip C2 and instead resume C1 that does not yield values to the main
thread, so it would be compatible with the yield from
`system.listdir`.
Unlike your original post that guarantees that `usercode.lua` always
runs on a coroutine with a yield signature that you can honor from
`do_io_operation`, it is easy to see in the example above that C1
could be modified to yield values, or even not be created altogether
if we move the `for` to the script's main chunk. So there might not
always be a coroutine in the slack that you can yield to without
breaking its "yield signature".
Anyway, even if you can make sure in your particular code that all
coroutines are rooted in the main thread by a `lua_resume` that you
control and therefore can safely yield to that point, I don't believe
it is possible in Lua right now to skip some coroutines when yielding.
But there might be a solution in some future versions of Lua as hinted
[here](
https://groups.google.com/g/lua-l/c/TmsDXBslnxk/m/ZMRAeUFZBgAJ).
> Please note that I'm on LuaJIT 2.1 (Lua 5.1), so no `lua_yieldk` and the likes.
What I would do to deal with this, which you might be able to
reproduce with old Lua, is to avoid using "yield signatures"
altogether. Or more precisely, design your coroutines and functions
implementing multithreading to rely on the pattern described
[here](
https://github.com/renatomaia/coutil/blob/master/doc/manual.md#await-function).
It is fine for coroutines to rely on yielding values to its immediate
resumer (like the ones from the chapter about coroutines in the PiL
book), as long as they don't call functions like `system.listdir` or
your `do_io_operation`, which should be documented that might yield
and thus break their "yield signature". For instance, the script above
will work as expected if `system.listdir` executes in blocking-mode
and does not yield (which can be done by passing `"~"` as second
argument, as described
[here](
https://github.com/renatomaia/coutil/blob/master/doc/manual.md#systemlistdir-path--mode)).
But for coroutines that we want to be able to yield at "more
arbitrary" points (for this non-blocking I/O support for instance),
stick to the "yields no values" signature and avoid using the standard
coroutine resume/yield. Instead, use other mechanisms to switch
execution and exchange data between coroutines. For instance, the same
functionality of the example above could be rewritten as follows:
```lua
local event = require "coutil.event"
local system = require "coutil.system"
local function combine_files_from(dir1, dir2) -- factory of coroutines cB
e = {} -- unique value as the event (could be coroutine C2 below as well)
coroutine.resume(
coroutine.create( -- coroutine C2
function () -- function F2
for path1 in system.listdir(dir1) do
for path2 in system.listdir(dir2) do
event.emitone(e, path1, path2)
end
end
event.emitone(e, nil) -- to signal iterator's end
end
)
)
return function () return select(2, event.await(e)) end
end
coroutine.resume(
coroutine.create( -- coroutine C1
function() -- function F1
for path1, path2 in combine_files_from("/tmp/dir1", "/tmp/dir2") do
print(path1, path2)
end
end
)
)
system.run()
```
Here the iterator coroutine using resume/yield is replaced by a
"coroutine thread" that sometimes suspends on calls to the I/O
function and other times produces values as an event for the iterator
to consume. The iterator by `combine_files_from` now just continuously
suspends waiting for the events.
The implementation of `event.*` functions is a Lua script
[here](
https://github.com/renatomaia/coutil/blob/master/lua/coutil/event.lua),
and I believe it should work in Lua 5.1 with very little changes. Its
documentation is
[here](
https://github.com/renatomaia/coutil/blob/master/doc/manual.md#events).
Also, if you're interested, see
[here](
https://groups.google.com/g/lua-l/c/TmsDXBslnxk/m/a-eCIIdsAQAJ)
for another example of this solution approach for this issue.
--
Renato Maia