This module is is a replacement to the standard coroutine
module that adds tagged coroutines. Functions create
and wrap now receive a tag and a function, instead
of just a function. Function yield now also needs a
tag as the first argument. The tag can be any Lua value.
A yield with a specific tag yields to the dynamically
closest resume of a coroutine with that tag (making
an analogy with exceptions: the coroutine is like
a try/catch block, yield is like a throw,
and the tag is analogous with the type of the exception).
If there is no coroutine with the tag an error is thrown
at the point of the yield.
On a successful yield, any coroutine that has been passed
through in the search for the coroutine that handled that
yield becomes stacked, a new status string returned
by the status function. Attempting to directly resume a
stacked coroutine is an error. Resuming the coroutine that
handled the yield rewinds the whole stack, resuming the
stacked coroutines along the way until reaching and finally
continuing from the point of the original yield.
A failed yield can be an expensive operation, so if you are
unsure if you can yield you can use the extended isyieldable
function, which now expects a tag and will return true
only if yielding with this tag will succeed.
The function coroutine.yield is an untagged yield. A tagged
coroutine passes an untagged yield along, unless its parent
is the main coroutine: in this case, the yield is supressed, and
the source resumes from the untagged yield with the call to yield
returning nil and "untagged coroutine not found". Unfortunately
there is no way to make the untagged yield fail as if it had tried
to yield outside a coroutine.
When an untagged yield reaches an untagged parent, the parent will suspend as if the yield was intended for it; when the parent resumes the whole stack will be resumed, ultimately resuming from the point of the untagged yield. This way you can have a stack of tagged coroutines on top of an untagged coroutine (allowing the use of existing coroutine schedulers, for example).
A tagged yield that reaches an untagged coroutine fails at the
point of the call to yield as if it had reached the main coroutine.
A new function call resumes a coroutine as if it had been
wrapped by wrap: any uncaught errors while running the
coroutine will be propagated. But the stack is not unwound:
you can still get a traceback of the full stack of the dead coroutine
(including all of the coroutines that were stacked above it) using
the new traceback function. It is similar to debug.traceback,
except that it includes a full traceback, following source to
reach the source of the error and tracing parent back to the main
thread.
A new tag function returns the tag of a coroutine. A parent
function returns the coroutine that last resumed a coroutine.
A source function returns, for a given coroutine,
either the coroutine where the last yield came from,
in case of a suspended coroutine, or
where an error originated, in case of a dead coroutine. You can
use these two functions to walk a dead stack of coroutines
with the debug functions in case traceback is not enough.
Finally, the function fortag receives a tag and returns a
set of tagged coroutine functions specialized for that tag.
For compatibility with lua-coronest
there is also a make function that is like fortag except it
generates a fresh tag if none is given.
There is both a C and a pure Lua implementation. The C
implementation is more efficient, and produces better
stacktraces, but requires stock Lua 5.2 or higher (it
will not work with LuaJIT 2). The pure Lua implementation
should work on LuaJIT 2, Lua 5.2, or Lua 5.3,
but the isyieldable might give a false positive if
there are pending unyieldable C calls in the stack on any
Lua version except Lua 5.3.
Install it by running luarocks make on one of the provided
rockspec files (taggedcoro for the C version, taggedcoro-purelua
for the Lua version). The contrib folder has sample libraries
that implement some abstractions on top of coroutines that
can be freely composed with tagged coroutines. The
samples folder has sample scripts that exercise
these higher-level libraries. Some of them depend on
the thread
library and on a branch of Cosmo
that requires tagged coroutines.