Javier Guerra wrote:
> hi everybody
>
> finally, i think i have it working! it's a library of helper threads, meant to
> make it easy to write nonblocking libraries in C.
>
I have implemented a pure lua version of an event iterator based on Mike
Pall / Diego Nehab model of 2004.
It is working, but i'm still tinkering with parameter order etc.
I am using it as a prototype for co-operative multiplexing; it should end up
in c when the design is frozen.
The companion thread despatcher (coplex) should be functional in a few days.
I personally am hooked on the idea of 2 seperate layers - one to provide
events / event notifications, and ont to despatch event handlers.
How do we go about stimulating discussion / idea swapping in this area ?
This list ? A new wiki page ?
Adrian (draft event spec attached)
LuaEvents: Event support for the Lua language
Event
The event namespace offers a generic mechanism for event handling.
This is primarily used whenever you need to watch for multiple sources of
events (e.g. multiple sockets) and cannot afford to block waiting on any
single one to become ready.
The event handling mechanism in this module was designed and implemented
by Mike Pall and Diego Nehab for Luasocket as a better alternative to the
abominable socket.select() API.
Modified by A. Sietsma to match prototype Lua implementation.
modified/deleted functions are shown thus
new/modified functions are shown thus
—Adrian
The main features of this API are:
- A unified event model for various kinds of event types and event sources
(such as sockets or timers).
- Triggered events can be fetched with a passive iterator. Any kind
of dispatching mechanism can be adapted to work on top of this API.
- A common internal API for the OS-specific backends.
To obtain the event namespace, run:
-- Loads the event module and everything it requires.
local event = require("event")
Concepts
An event is any occurence which may happen at some future time (or may have
already happened).
An event has the following properties:
- The event id is a unique number that is assigned to an event
when it is added to an event container.
- The event type specifies the conditions under which an event
is triggered.
- The event source specifies a system resource or a virtual
object that is able to trigger an event.
- Zero, one or more associated objects can be
associated with an event. These can be any Lua object and the application is
free to use them for any purpose.
- A context can be associated with
an event. This can be any Lua object and the application is free to use
them for any purpose.
- A timeout can be specified for any event. The event
will be returned after timeout seconds, if not already triggered.
An event container allows for adding, deleting and triggering events
and provides an iterator to fetch triggered events.
Functions provided by the Event namespace
- new Creates and initializes a new event container.
- time Returns the system time.
Event container (meta) methods
- :add Adds an event to the container.
- :once Add an immediate one-shot event.
- :del Deletes an event from the container.
- :set Modifies a previously added event.
- :trigger Trigger a virtual event.
- :stop Stops any running iterator (on this container).
- :clear Stops any running iterator, and deletes all
events from container.
- __call The event container can be called and returns an
iterator. Calling the iterator returns triggered events from the container.
Here are the steps you have to do in order to use the event handling
mechanism:
- Create a new event container.
- Add all events you are interested in to the container.
- Create an iterator object from the container and use it in a loop to
fetch the triggered events.
- Act on the triggered events in the body of the loop. E.g. read some data,
run a callback function or resume a thread.
- You can add new events to the container or delete events from it anytime
during processing.
Here is a simple example:
-- Create a new event container with 1 associated object per event.
local ev = event.newset(1)-- Create a new event container.
-- Add some events to the container.
ev:add("t", 2.5,"One-shot timer expired")
local tid = ev:add("T", 5.0, "Periodic timer triggered")
local vid = ev:add("v", nil, "Virtual event triggered")
-- Process all triggered events in an infinite loop.
for id, typ, src, str in ev(true) do
for id, why, src, str in ev(true) do
-- Print the current time and the associated object.
print(socket.gettime(), str)
if id == tid then
ev:trigger(vid)
elseif id == vid then
ev:add("t", 1.0, "One second later ...")
end
end
Strings are used as associated objects for the sake of keeping this example
simple. The body of the loop does nothing spectacular either. Better examples
that show various event dispatching strategies will follow in the next
sections.
Event types
The following table lists all supported event types:
event type |
event source |
trigger condition |
trigger return |
POSIX |
Windows |
"v", "V" |
Virtual event |
has been triggered |
"triggered" |
X |
X |
"t", "T" |
Timer |
has expired |
"timeout" |
X |
X |
"r", "R" |
POSIX file |
is readable |
"readable" |
X |
|
"w", "W" |
POSIX file |
is writable |
"writable" |
X |
|
"r", "R" |
Lua Socket |
is readable |
"readable" |
X | X |
|
"w", "W" |
Lua Socket |
is writable |
"writable" |
X | X |
|
"x", "X" |
POSIX file |
has an exception pending |
X |
|
"s", "S" |
POSIX signal |
has been caught |
X |
|
"h", "H" |
Windows handle |
state is signaled |
|
X |
"m", "M" |
Windows message |
is in the input queue |
|
X |
Event types are passed to the API as one-character strings. Lowercase
letters indicate temporary events; these are deleted from the event container
once the event has been triggered. Uppercase letters indicate persistent
events; you have to explicitly delete them from the container.
There are other event sources imaginable. E.g. virtual events
that work across threads or processes or virtual events with a counter.
Extending the list with events triggered by other system resources
is difficult, because you need a single call in the backend that checks
for all events at once. Otherwise you are doomed to use polling. Ugh.
More input is welcome.
— Mike
Event sources
Virtual events
A virtual event is a simple condition that can be either in the
untriggered state or in the triggered state.
The event source must be set to
nil when adding a virtual event with
ev:add.
The event source will be treated as the initial triggered state when
adding a virtual event with
ev:add.
eg. a source of true will cause the event to be immediately triggered.
Virtual events can only be
triggered with
ev:trigger.
One-shot virtual events are deleted when they are returned from the
iterator; persistent virtual events can be triggered multiple times
and have to be deleted manually, unless they
have a timeout.
A virtual event is not retriggerable
until it is returned by the iterator. It will be returned only once
by the iterator, no matter how many times it has been triggered in-between.
Virtual events are tested prior to the internal select() call, and again if
the select() times out; they will
not interrupt the select.
POSIX and Windows notes removed :
I have no idea how much of it is still true—Adrian
Timers
A Timer is specified by a number that gives the time interval after which
the timer expires. The time unit is one second; fractional values are
allowed. The time interval is relative to the time the event was added.
Temporary timers trigger an event only once; Persistent timers periodically
trigger the event until you delete them from the event container.
Note: Timers never expire before the requested time,
but may expire shortly after the requested time depending on
system timer resolution and event queueing delays. In general you cannot
rely on 100% accurate timing in a non-realtime operating system anyway.
POSIX and Windows events section hidden (not supported).
Socket/File handles
A socket (and file on unix) is identified by a file descriptor.
More specifically, it must return a file descriptor via getfd for the
LuaSocket select() function.
This is restricted to LuaSocket objects on Windows, but can be a disk file,
a pipe,a socket or any other file resource your OS provides.
An associated event will be triggered when a file is readable
or writable.
, writable or has an exception pending,
depending on the event type.
A file descriptor obtained from an external Lua module can be passed in
as an object that has a getfd() function.
Note: You have to make sure that the container only holds valid
file descriptors at all times. You have to delete all untriggered events and
all persistent events before closing the file descriptor.
Note: The results of adding the same file descriptor with the same
event type to more than one event container are undefined.
Functions provided by the Event namespace
ev = event.new([numobj],)
Creates and initializes a new event container.
The numobj parameter gives the maximum number of objects that
can be associated with each event. The default is zero.
The function returns the newly created event container. See the section on
Event container methods below.
Associated objects
An associated object
can be used to pass context information along with
events. They are It is
passed in when the event is added to the container
and will be returned by the iterator when the event has been triggered.
Associated objects are typically used by a dispatcher to tie an event
to a callback, a state or a thread.
The event container needs to create numobj tables to store
the associated object. Adding, deleting or iterating over events incurs
additional overhead depending on the number of associated objects.
Note: Usually only a single event container is needed in a single process.
However different event containers are independent of each other and can
be used e.g. in different native threads in a shared Lua universe.
It is not safe to access the same event container from different native
threads without proper locking.
Note: The effects of adding the same system event
source to different event containers are unpredictable. In general the set
of event sources should not overlap between any two event containers in
a single process.
Example:
-- Create a new event container
with 1 associated object per event.
local ev = event.newset(1)
Event container methods
id = ev:add(type [, source [, obj*]])
id = ev:add(type [, source [,context [, timeout]]])
Adds an event to the event container with the given event type, event source
, timeout, and context object.
and associated objects
Depending on the event type a previous event with
the same type and/or the same source may be replaced.
For details about the valid event types and sources see the sections on
Event types and Event sources.
See below for the timeout parameter.
Returns the event id for the newly created event as a
LIGHTUSERDATA object
negative number
Timeout
A timeout can be specified when adding an event. The event will be
returned with a reason of "timeout" after timeout seconds, if not
triggered before then. A non-numeric timeout value means no timeout.
Note: For persistent events, the timeout is re-applied when the event
is returned (triggered).
The timeout parameter is ignored for timer events.
The following examples show different ways to use associated objects.
Each one of them needs an appropriate dispatcher that knows what to do
with the associated object(s) returned from the iterator. I.e. call it
or do some other processing.
-- Create a one-shot timer with a callback function.
ev:add("t", 10.0, function() print("Timer expired!") end)
-- Create a persistent timer with a table that holds a complex object.
local obj = { notify = "acceptor", rehash = true }
obj.id = ev:add("T", 60.0, obj)
-- Typical event mode socket reader.
local function reader(ev, obj)
local s, err, part, id = obj.sock:receive()
while s do
obj:receiver(obj.part .. s)
obj.part = ""
s, err, part, id = obj.sock:receive()
end
if err == "event" then
if part then obj.part = obj.part .. part end
ev:set(id, reader, obj) -- Set associated objects for the created event.
return
end
obj:onerror(err) -- Finalize the object and pass the error.
sock:close()
end
-- (Some initialization omitted for brevity)
obj.part = "" -- Initialize partial receive buffer.
obj.sock:seteventmode(ev) -- Put the socket into event mode.
ev:once(reader, obj) -- Run the reader once to setup the transfer.
POSIX and Windows events examples hidden—Adrian
id = ev:set(id [, obj*])
id = ev:set(id [, obj, [timeout]])
Sets or overrides the associated objects
and timeout
for an existing event.
The event id specifies the event that is to be associated with
the given objects.
The timeout parameter (if non-nil) specifies the new timeout.
Returns the modified event id as a convenience. Returns nil
if the event id is invalid or has been deleted.
Example:
-- Add a persistent timer with a callback function.
local tid = ev:add("T", 2.5, function() print("Timer expired!") end)
-- Later, somewhere else, maybe in a callback: Modify the callback.
ev:set(tid, function() print("Oh no, the timer expired again!") end)
id = ev:trigger(id)
Trigger a virtual event. The event will be delivered synchronously by the
iterator.
The event id specifies the event to trigger.
Returns the id of the triggered event as a convenience. Returns nil
if the event id is invalid, has been deleted or does not refer to
a virtual event.
The C API only requires the specification of the event id. Neither
the event container object nor the Lua state is required because
event ids are unique across all event containers of a single process.
Example:
-- Add a persistent virtual event.
local vid = ev:add("V", nil, function() print("Triggered!") end)
-- Later, somewhere else, maybe in a callback: Trigger it (once).
ev:trigger(vid) -- Event will be returned in dispatcher loop.
-- Add a virtual event with a lifetime of 1 minute
local vid = ev:add("v", nil, function() print("Triggered!") end,60.0)
id = ev:once([obj*])
Generate an immediate one-shot event. This is implemented by adding
a virtual event and triggering it. The event will be delivered synchronously
by the iterator.
The event is added with the specified associated objects.
Returns the id of the added event.
Example:
-- Arrange for calling two functions from the dispatcher loop.
ev:once(function() print("Hello world!") end)
ev:once(function() print("The current time is:", os.date()) end)
id = ev:del(id)
Deletes an event from the event container.
The event to be deleted is specified by the event id.
Returns the id of the deleted event as a convenience. Returns nil
if the event id is invalid or has already been deleted.
Example:
-- Add a persistent timer that prints a counter every second.
local cnt = 0
local tid = ev:add("T", 1.0, function() cnt = cnt + 1; print(cnt) end)
-- Later, somewhere else, maybe in a callback: Delete the timer to stop it.
ev:del("t", tid)
ev:stop()
Stops any running iterator.
This method can be used anywhere inside the iteration loop, e.g. in callback
functions. It forces any running iterator to return nil on the
next invocation which terminates the loop. Note that any pending triggered
events are not deleted. Creating a new iterator and restarting the loop
will just continue returning them.
ev:clear()
Deletes all events from an event container.
This method can be used from anywhere, e.g. inside the iteration loop or
in callback functions. Pending triggered events are deleted, too.
A surrounding iteration loop will abort since the iterator returns
nil when the container is empty.
The container can be reused and new events can be added immediately
after calling ev:clear(). A surrounding iteration loop will
still abort if non-blocking behaviour is selected since a new event
collection cycle has to be started.
iterator, ev, nil = ev([block])
id, trigger, source, context = iterator()
id, type, source [, obj*] = iterator(ev, id)
The event container object can be called and returns an iterator.
Calling the iterator returns triggered events from the container.
The block parameter specifies the blocking behaviour of the iterator.
true = block. false = never block, number = block for n seconds (fractional).
nil (default) = block only if the container holds at least one timer event.
Three values are returned by calling the event container object:
The iterator function, the event container object and nil.
The iterator function returns the event id, the reason it was triggered (
one of "read", "write", "triggered", "timeout"), the event
source and the context object.
Note that any event type may be returned with a reason of "timeout".
The iterator function returns the event id, the event type, the event
source and any associated objects.
Each call to the iterator function returns a single event that has
been triggered. The iterator returns nil if a) the container is
empty or b) non-blocking behaviour was selected and no triggered events
from the current event collection cycle are remaining.
The iterator is destructive because it removes triggered events and
may also delete events from the container as a result. Do not use multiple
iterators over the same container in parallel. Aborting the loop and
creating a new iterator is allowed though. It is safe to call other methods
even while using an iterator over the same event container.
Iterators are typically used in for loops and that takes care
of managing the iterator object.
-- Careful: Using 'type' as a variable name will override the global
-- function of the same name. Using 'etype' is a better choice.
-- Process all triggered events in an infinite loop.
for id, etype, src, obj in ev(true) do
for id, trigger, src, obj in ev(true) do
...
end
-- One-shot loop through all events that have been triggered (non-blocking).
-- ( Do not wrap this up into another loop unless the other loop blocks
-- for some time. Otherwise you get polling behaviour.)
for id, etype, src, obj1 , obj2 in ev(false) do
for id, trigger, src, context in ev(false) do
...
end
-- Process all triggered events, waiting for up to 1 second for select.
for id, trigger, etype, src, obj in ev(1.0) do
...
end
Dispatching strategies section omitted—Adrian