Event - Event loop processing |
Event - Event loop processing
use Event qw(loop unloop);
# initialize application Event->flavor(attribute => value, ...);
my $ret = loop();
# and some callback will call unloop('ok');
The Event module provide a central facility to watch for various types of events and invoke a callback when these events occur. The idea is to delay the handling of events so that they may be dispatched in priority order when it is safe for callbacks to execute.
Events (the occurrence of such) are noticed and queued by 'event watchers'. The creation and configuration of event watchers is the primary topic of the rest of this document.
The following functions control or interrogate the event loop as a whole:
loop([$timeout])
one_event()
until unloop()
is called.
The argument passed to unloop()
is the return value of loop(). Loops
can be nested.
unloop($result)
loop()
return with $result.
unloop_all($result)
die
. It is works as if unloop($result)
were
called for all nested loops.
sweep([$max_prio])
max
to undef
or stop()
it.)
one_event([$timeout])
StarvePrio is the priority level for which events are dispatched during step 2. It cannot be changed without a recompile. In the rare case that an event is always pending at step 2 then I/O watchers will starve. However, this is highly unlikely since async watchers should never queue events so rapidly.
all_watchers()
all_running()
all_idle()
max
attribute set then it will queue a normal event when
its max
wait time is exceeded.
All watchers are constructed in one of the following ways:
$w = Event->flavor( [attr1 => $value,]... );
$w = Event::flavor($Class, [attr1 => $value,]...);
$w = Event::flavor->new([attr1 => $value,]...);
Where flavor is substituted with the kind of watcher. Built-in types include idle, io, signal, timer, and var.
New watchers (hopefully) have reasonable defaults and can also be
customized by passing extra attributes to the constructor. When
created, watcher objects are ``started'' and are waiting for events
(see $event->start
below).
NetServer::Portal can display watchers in real-time, formatted
similarly to the popular top
program. You may find this a useful
aide for debugging.
Watchers are configured with attributes (also known as properties). For example:
$watcher->cb(\&some_code); # set callback
warn $event->w->desc.": ".$event->hits." events happened; Wow!";
All watchers support the following attributes: cb, cbtime, debug, desc, prio, max_cb_tm, reentrant, and repeat. Watcher constructors accept the preceding and additionally: async and nice. All events support: hits, prio, and w. Moreover, watchers also offer extra attributes according to their specialty.
The following methods are available for all watchers:
start()
without
sufficient configuration information to generate events. Constructors
always invoke start()
unless the parked=
1> option is requested.
You will need to set the parked option if you preallocate unconfigured
watchers.
start
except if a watcher has special
repeat behavior. For example, repeating timers recalculate their alarm
time using the interval
parameter.
$w->cb->($w);
start
or again
methods.
Watchers are stopped implicitly if their new configuration deprives them of the ability to generate events. For instance:
my $io_watcher = Event->io(timeout => 1); # started $io_watcher->timeout(undef); # stopped implicitly $io_watcher->timeout(1); # still stopped $io_watcher->start; # restarted
$watcher
. Running events are allowed to complete
but pending events are cancelled. Cancelled watchers are no longer
valid except for read-only operations. For example, prio()
can
return the watcher's priority, but start()
will fail.
$watcher
has been cancelled.
$watcher
has been started. The return value is
not affected by suspend.
reentrant
callback invokes loop
(or sweep
, with lesser
probability).
$watcher
is suspended.
The callback is invoked only when no events are pending. If there is
never a chance to idle, an event will be generated at least every
max
seconds and not more often than min
seconds.
Var watchers generate events when the given variable is read from or written to. As perl is a concise language, it is often difficult to predict when a variable will be read. For this reason, variable watchers should poll for writes unless you know what you are doing.
The $time
and $sec
are in seconds. Fractional seconds may be
used if Time::HiRes is available. The constructor also accepts an
after
attribute for easier initialization. It might be useful to
know the time at the start of today. You can find it with:
use Time::Local; my $TodaySeconds = int timelocal(0,0,0,(localtime)[3,4,5]);
If interval
is set then the watcher will automatically repeat. Be
aware that due to lags in the event loop, the interval
timeout may
already be in the past. If the hard
flag is set, the event will be
queued for execution relative to the last time the callback was
invoke. However, if hard
is false the new timeout will be
calculated relative to the current time (this is the default).
The callback is invoked when the file descriptor, fd
, has data to
be read, written, or pending exceptions. fd
can be a GLOB, an
IO::Handle object, or a file number (file descriptor).
Note that it is your option whether to have multiple watchers per file handle or to use a single watcher for all event conditions.
If timeout_cb is set then timeouts use this alternate callback instead of the main callback.
=head2 PRIORITY
Priority is used to sort the event queue. Meaningful priorities range from -1 to 6 inclusive. Lower numbers mean higher priority (-1 is the highest priority and 6 is the lowest). If multiple events get queued, the ones with the highest priority are serviced first. Events with equal priority are serviced in first-in-first-out order.
use Event qw(PRIO_HIGH PRIO_NORMAL); # some constants
LEVELS: -1 0 1 2 3 4 5 6 ----------------------+-------------+--------------- PRIO_HIGH PRIO_NORMAL
A negative priority causes the callback to be invoked immediately upon event occurrence. Use this with caution. While it may seem advantageous to use negative priorities, they bypass the whole point of having an event queue.
Each watcher has a default priority, assigned by its constructor:
io PRIO_NORMAL signal PRIO_HIGH timer PRIO_NORMAL var PRIO_NORMAL
Default priorities are stored in ${``Event::${type}::DefaultPriority''}.
If the default priority is not satisfactory for your purposes, the
constructor options nice
, async
, or prio
can be used to
adjust it. nice
specifies an offset from the default priority;
async
forces the priority to -1; and prio
assigns a given
priority of your choice. If more than one of these options are given
then prio
overrides async
overrides nice
.
These options are only supported as constructor arguments.
start()
method. If you don't want the watcher started then request
parked=
1>.
at
will usually be in the future.
$event
as its only argument.
Perhaps you are wondering what happens if something goes wrong and an
untrapped die
occurs within your callback? $Event::DIED
is just
for this purpose. See the full description of DIED
below.
data()
method associates arbitrary data with a watcher.
This method is not intended for implementers of watchers. If you are
subclassing or implementing a watcher, consider the private()
method.
at
attribute is
recalculated using interval
upon callback return.
timeout
.
string constant description ------ -------- --------------- 'r' R read 'w' W write 'e' E exception 't' T timeout
Thus, both of these statements enable interest in read:
$w->poll($w->poll . 'r'); $w->poll($w->poll | R);
A given type of watcher may support all or a subset of the available events.
private()
method to associate arbitrary data with a
watcher. This method is intended for implementers of watchers or
watcher subclasses. Each caller's package accesses its own private
attribute.
sweep
or loop
which
in turn may invoke the same callback again recursively. This can be
useful but can also be confusing. Moreover, if you keep reentering
callbacks you will quickly run out of stack space. Disable this
feature per watcher by setting reentrant to false. This will cause
the watcher to be suspended during recursive calls to sweep
or
loop
.
%SIG
. A given signal can be handled by %SIG
or Event, but not both.
Suspend is for debugging. If you suspend all watchers in an
application then you can examine the complete state unchanged for as
long as you like without worrying about timer expirations. If you
actually wish to stop a watcher then use the stop()
method.
cb
.
got
is available in the callback of watchers with poll
.
got
is in the same format as poll
except that it gives what
kind of event actually happened. In contrast, poll
is just an
indication of interest.
hits
every time it registers an event.
Signals in quick succession can be clumped into a single event.
debug
.
loop
or sweep
is called, an exception context is
established for the duration of event processing. If an exception is
detected then $Event::DIED
is invoked. The default hook uses
warn
to output the exception. After the DIED handler completes,
event processing continues as if nothing happened.
If you'd like more detailed output you can install the verbose handler:
$Event::DIED = \&Event::verbose_exception_handler;
Or you can write your own. The handler is invoked like this:
$Event::DIED->($event, $@);
If you do not want to continue looping after an error, you can do something like this:
$Event::DIED = sub { Event::verbose_exception_handler(@_); Event::unloop_all(); };
add_hooks
method allows insertion of perl code at key points in
the optimized event processing core. While flexible, this can hurt
performance *significantly*. If you want customization *and*
performance, please see the C API.
Currently support hooks are detailed as follows:
hook purpose ------------- ---------------------------------------------- prepare returns minimum time to block (timeable) check assess state after normal return from select/poll asynccheck check for signals, etc callback invoked before each event callback
Event also has a direct API for callbacks written exclusively in C. See Event::MakeMaker.
Event loops and threads are two different solutions to the same problem: asynchronous processing. Event loops have been around since the beginning of computing. They are well understood and proven to be a good solution for many applications.
While event loops make use of basic operating system services, the bulk of their implementation is usually outside the kernel. While an event loop may appear to do many things in parallel, it does not, even on multiprocessor hardware. Actions are always dispatched sequentially. This implies that long running callbacks must be avoided because otherwise event processing is halted.
Event loops work well when actions are short and to the point. Long-running tasks must be broken into short steps and scheduled for execution. Some sort of a state machine is usually required. While a big, complex application server is usually simpler to implement in a multithreaded fashion, a web browser can easily get by without threads. Consider a JPEG file download and render. When some new bytes are available they are sorted to the right place on the screen. Only a little state must be kept to keep track of how much has been rendered and to process subsequent incoming bytes.
Threads can either substitute for an event loop or complement it. Threads are similar to processes in that the operating system manages task switching for you. However, the difference is that all threads share the same address space. This is good and bad. Higher performance can be achieved but since data is shared between threads, extreme care must be taken to access or modify global data. The operating system can switch threads at any moment or can execute multiple threads simultaneously. I hope this sounds dangerous! It is! Threads can introduce maddeningly complicated and hard to debug synchronization problems.
Threads are like rocket fuel. They are essential when you really need them but most applications would be better off with a simple event loop. Even if threads are genuinely needed, consider confining them to the parts of an application where truly scalable performance is really worth the difficulty of a multithreaded implementation. For example, most GUIs applications do not need threads and most scientific compute intensive problems can be isolated from event dispatching. On the other hand, high performance transaction servers generally do mandate a truly multithreaded approach.
Another consideration is that threads are not quite as widely available as event loops. While a few forward-thinking operating systems have offered threads since the beginning, their addition to many popular operating systems is much more recent and some still offer no threads support. If portability is a requirement, one must check that threads support is available and also carefully test a particular threads implementation to see whether it supports the features you need. It is likely that all platforms will have a solid implementation soon but at this point in history it is best to double check.
Many suggestions by Mark Mielke <Mark.Mielke.markm@nt.com>
The Java language is oriented to use non-preemptive threads, yet even Java uses an event-loop for Swing (AFAIK). That is one of the reasons I don't use Java for network-centric applications. My belief is that the benefit of multi-threading is the gain in performance on SMP hardware. In my view, non-preemptive threads (java green-threads) are usually poor design. I find them harder to work with, harder to debug, and slower for a rather marginal gain in readability. I really like working with a state machine. I find it leads to more stable and better code. It also has the benefit of abstracting away how concurrency is achieved.
Contributed by artur@vogon-solutions.com, 12 Jul 1999.
The meaning of $io->timeout(0)
might change. Use undef
to unset
the timeout.
There seems to be some sort of bug in the global destruction phase:
Attempt to free unreferenced scalar during global destruction. Use of uninitialized value during global destruction. Explicit blessing to '' (assuming package main) during global destruction.
This section is concerned with advanced kernel architecture.
A scalable and explicit event delivery mechanism for UNIX
Abstract: UNIX applications not wishing to block when doing I/O often
use the select()
system call, to wait for events on multiple file
descriptors. The select()
mechanism works well for small-scale
applications, but scales poorly as the number of file descriptors
increases. Many modern applications, such as Internet servers, use
hundreds or thousands of file descriptors, and suffer greatly from the
poor scalability of select(). Previous work has shown that while the
traditional implementation of select()
can be improved, the poor
scalability is inherent in the design. We present a new event-delivery
mechanism, which allows the application to register interest in one or
more sources of events, and to efficiently dequeue new events. We show
that this mechanism, which requires only minor changes to
applications, performs independently of the number of file
descriptors.
http://www.usenix.org/events/usenix99/full_papers/banga/banga_html/index.html
Even if this module does not end up being the One and True Event Loop, the author will insure that it is source compatible with its successor, or arrange for gradual migration. Back in the early days, the Event programming API was changing at every release. Care was taken to allow the old API to continue to work, and the transition was eased by printing out lots of warnings about the new usage. So you shouldn't sit on your hands in anticipation of the One and True Event Loop. Just start coding!
If you have insights or complaints then please subscribe to the mailing list! Send email to:
perl-loop-subscribe@perl.org
Joshua N. Pritikin <jpritikin@pobox.com>
For my motivation, see http://why-compete.org
Initial 0.01 implementation by Graham Barr <gbarr@pobox.com>. Other contributors include at least those lists below and folks mentioned in the ChangeLog.
Gisle Aas <gisle@aas.no> Uri Guttman <uri@sysarch.com> Nick Ing-Simmons <nick@ni-s.u-net.com> (Tk) Sarathy <gsar@engin.umich.edu> Jochen Stenzel <perl@jochen-stenzel.de>
Copyright © 1997 Joshua Nathaniel Pritikin & Graham Barr
Copyright © 1998, 1999, 2000, 2001, 2002 Joshua Nathaniel Pritikin
All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
Event - Event loop processing |