lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


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)

Title: 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:

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:

An event container allows for adding, deleting and triggering events and provides an iterator to fetch triggered events.

Functions provided by the Event namespace

Event container (meta) methods

Here are the steps you have to do in order to use the event handling mechanism:

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" XX  
"w", "W" Lua Socket is writable "writable" XX  
"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.

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