Overview
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.
Installation
Get the latest release of the C module from LuaRocks:
luarocks install taggedcoro
Or if you want the pure Lua version:
luarocks install taggedcoro-purelua
If you want to install from HEAD, download a tarball/zip of
this repository or clone it, then run luarocks make
on one of
the provided rockspecs.
The C module is compatible with both Lua 5.2 and Lua 5.3. The Lua module is compatible with LuaJIT 2, Lua 5.2, and Lua 5.3.
Module reference
All the functions below are exported by the module.
create(tag, f)
Creates a new tagged coroutine, with tag tag
(any Lua value except nil
) and body f
(must be a function).
Returns this new coroutine. Like the standard coroutine
library, a coroutine is an object with type "thread".
isyieldable(tag)
Returns true
when a yield with tag tag
will not fail, otherwise it will return false.
resume(co, ...)
Starts or continues the execution of the tagged coroutine co
. It works just like coroutine.resume
.
call(co, ...)
Starts or continues the execution of the tagged coroutine co
, but any errors are propagated, and the
initial boolean is not returned in case of success (either because the coroutine yielded or it finished
running). The source of the error is still recorded (see source
below) so the stack can be inspected
later.
running()
Returns the running coroutine plus a boolean, true when the running coroutine is the main one.
status(co)
Returns the status of the (tagged or not) coroutine co
, as a string. Returns the same status strings
as coroutine.status
, plus a new one
for tagged coroutines: "stacked". A coroutine is "stacked" if it has been suspended by a yield but is
not the coroutine that handled that yield (because of a different tag). A "stacked" coroutine cannot
be resumed directly.
wrap(co) or wrap(tag, f)
If called with a tagged coroutine, returns a function that calls call
on this coroutine
plus any arguments to the function. If called with a tag tag
and a function f
, creates
a new coroutine and then returns both a function that calls call
on this coroutine
and the coroutine itself.
yield(tag, ...)
Suspends the execution of the nearest coroutine with the tag tag
(tag equality is checked with ==
, not rawequal
).
Any arguments to yield are returned by the resume
or call
call that last resumed this coroutine. Any interving
coroutines are stacked (see status
, above).
If there is no coroutine with a matching tag yield
throws an error at the point of its call. It
will also throw an error at the point of the call if yielding failed because of non-yieldable
C functions.
tag(co)
Returns the tag associated with the tagged coroutine co
, or nil
if co
is an untagged coroutine.
parent(co)
Returns the coroutine that last resumed the tagged coroutine co
(through either resume
or call
).
Returns nil
if co
is an untagged coroutine, or if the coroutine has been created but not started yet.
source(co)
If the tagged coroutine co
is suspended, returns the tagged coroutine that called yield
. If co
is dead due to an error, returns the tagged coroutine where the error originated. If co
is running
but caught an error in another coroutine (through resume
, pcall
, or xpcall
), source
also
returns the tagged coroutine where the error originated. Returns nil
in
all other cases (including if co
is an untagged coroutine).
You can use source
and parent
to walk the stack (using the debug
functions) after an error occurs.
The traceback
function below uses this to construct a full traceback string analogous to the traceback returned
by debug.traceback
but for the full coroutine stack, starting from the coroutine that originated the error.
traceback([co,] [message [, level]])
If the first argument is a coroutine, uses the source
(see above) of this coroutine as the start
of the traceback, otherwise uses the source
of the running coroutine as the start. The traceback
ends after tracing the main coroutine (or after tracing an untagged coroutine; in this case there will
be a message indicating this in the traceback). If a message is present it is appended to the beginning
of the traceback. An optional level number tells at which level to start the traceback, on the starting
coroutine (default is 1, the top of the call stack). Returns a string with a traceback of the call stack
of all coroutines from the starting one, using parent
to trace back from it.
fortag(tag)
Returns a table containing all of the functions above (except fortag
itself), with the four functions
that expect a tag (create
, wrap
, yield
, and isyieldable
) specialized for the tag tag
(the signatures
of these functions do not have a tag
parameter).
make([tag])
Like fortag
, but generates a fresh tag (an new empty table) if none is given.
install() - Lua 5.2 and Lua 5.3 only
Installs a metatable for all coroutines (tagged and untagged, because all coroutines must share a single metatable).
After installing this metatable every coroutine has resume
, call
, status
, tag
, parent
, and source
as methods.
Every coroutine can also be called as if it were a function, with the same effect as its call
method. Returns the
module (so you can both require the module and install the metatable in one step: local tc = require("taggedcoro").install()
).
Extras
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.
Contact
Please open an issue on github if you would like to report any bugs, or suggest improvements.