Browse Source

*** empty log message ***

master
Marc Alexander Lehmann 13 years ago
parent
commit
7087666c12
  1. 4
      Changes
  2. 126
      ev.pod

4
Changes

@ -7,6 +7,10 @@ WISH? monotonic clocks times/GetTickCount for coarse corrections?
ADD fails with EEXIST.
- use memset to initialise most arrays now and do away with the
init functions.
- expand time-out strategies into a "Be smart about timeouts" section.
- drop the "struct" from all ev_watcher declarations in the
documentation (yeah, it was a mistake to have a function called
ev_loop).
3.45 Tue Oct 21 21:59:26 CEST 2008
- disable inotify usage on linux <2.6.25, as it is broken

126
ev.pod

@ -1290,20 +1290,20 @@ then order of execution is undefined.
=head3 Be smart about timeouts
Many real-world problems invole some kind of time-out, usually for error
Many real-world problems involve some kind of timeout, usually for error
recovery. A typical example is an HTTP request - if the other side hangs,
you want to raise some error after a while.
Here are some ways on how to handle this problem, from simple and
inefficient to very efficient.
What follows are some ways to handle this problem, from obvious and
inefficient to smart and efficient.
In the following examples a 60 second activity timeout is assumed - a
timeout that gets reset to 60 seconds each time some data ("a lifesign")
was received.
In the following, a 60 second activity timeout is assumed - a timeout that
gets reset to 60 seconds each time there is activity (e.g. each time some
data or other life sign was received).
=over 4
=item 1. Use a timer and stop, reinitialise, start it on activity.
=item 1. Use a timer and stop, reinitialise and start it on activity.
This is the most obvious, but not the most simple way: In the beginning,
start the watcher:
@ -1311,55 +1311,61 @@ start the watcher:
ev_timer_init (timer, callback, 60., 0.);
ev_timer_start (loop, timer);
Then, each time there is some activity, C<ev_timer_stop> the timer,
initialise it again, and start it:
Then, each time there is some activity, C<ev_timer_stop> it, initialise it
and start it again:
ev_timer_stop (loop, timer);
ev_timer_set (timer, 60., 0.);
ev_timer_start (loop, timer);
This is relatively simple to implement, but means that each time there
is some activity, libev will first have to remove the timer from it's
internal data strcuture and then add it again.
This is relatively simple to implement, but means that each time there is
some activity, libev will first have to remove the timer from its internal
data structure and then add it again. Libev tries to be fast, but it's
still not a constant-time operation.
=item 2. Use a timer and re-start it with C<ev_timer_again> inactivity.
This is the easiest way, and involves using C<ev_timer_again> instead of
C<ev_timer_start>.
For this, configure an C<ev_timer> with a C<repeat> value of C<60> and
then call C<ev_timer_again> at start and each time you successfully read
or write some data. If you go into an idle state where you do not expect
data to travel on the socket, you can C<ev_timer_stop> the timer, and
C<ev_timer_again> will automatically restart it if need be.
To implement this, configure an C<ev_timer> with a C<repeat> value
of C<60> and then call C<ev_timer_again> at start and each time you
successfully read or write some data. If you go into an idle state where
you do not expect data to travel on the socket, you can C<ev_timer_stop>
the timer, and C<ev_timer_again> will automatically restart it if need be.
That means you can ignore the C<after> value and C<ev_timer_start>
altogether and only ever use the C<repeat> value and C<ev_timer_again>.
That means you can ignore both the C<ev_timer_start> function and the
C<after> argument to C<ev_timer_set>, and only ever use the C<repeat>
member and C<ev_timer_again>.
At start:
ev_timer_init (timer, callback, 0., 60.);
ev_timer_init (timer, callback);
timer->repeat = 60.;
ev_timer_again (loop, timer);
Each time you receive some data:
Each time there is some activity:
ev_timer_again (loop, timer);
It is even possible to change the time-out on the fly:
It is even possible to change the time-out on the fly, regardless of
whether the watcher is active or not:
timer->repeat = 30.;
ev_timer_again (loop, timer);
This is slightly more efficient then stopping/starting the timer each time
you want to modify its timeout value, as libev does not have to completely
remove and re-insert the timer from/into it's internal data structure.
remove and re-insert the timer from/into its internal data structure.
It is, however, even simpler than the "obvious" way to do it.
=item 3. Let the timer time out, but then re-arm it as required.
This method is more tricky, but usually most efficient: Most timeouts are
relatively long compared to the loop iteration time - in our example,
within 60 seconds, there are usually many I/O events with associated
activity resets.
relatively long compared to the intervals between other activity - in
our example, within 60 seconds, there are usually many I/O events with
associated activity resets.
In this case, it would be more efficient to leave the C<ev_timer> alone,
but remember the time of last activity, and check for a real timeout only
@ -1370,10 +1376,10 @@ within the callback:
static void
callback (EV_P_ ev_timer *w, int revents)
{
ev_tstamp now = ev_now (EV_A);
ev_tstamp now = ev_now (EV_A);
ev_tstamp timeout = last_activity + 60.;
// if last_activity is older than now - timeout, we did time out
// if last_activity + 60. is older than now, we did time out
if (timeout < now)
{
// timeout occured, take action
@ -1381,41 +1387,81 @@ within the callback:
else
{
// callback was invoked, but there was some activity, re-arm
// to fire in last_activity + 60.
// the watcher to fire in last_activity + 60, which is
// guaranteed to be in the future, so "again" is positive:
w->again = timeout - now;
ev_timer_again (EV_A_ w);
}
}
To summarise the callback: first calculate the real time-out (defined as
"60 seconds after the last activity"), then check if that time has been
reached, which means there was a real timeout. Otherwise the callback was
invoked too early (timeout is in the future), so re-schedule the timer to
fire at that future time.
To summarise the callback: first calculate the real timeout (defined
as "60 seconds after the last activity"), then check if that time has
been reached, which means something I<did>, in fact, time out. Otherwise
the callback was invoked too early (C<timeout> is in the future), so
re-schedule the timer to fire at that future time, to see if maybe we have
a timeout then.
Note how C<ev_timer_again> is used, taking advantage of the
C<ev_timer_again> optimisation when the timer is already running.
This scheme causes more callback invocations (about one every 60 seconds),
but virtually no calls to libev to change the timeout.
This scheme causes more callback invocations (about one every 60 seconds
minus half the average time between activity), but virtually no calls to
libev to change the timeout.
To start the timer, simply intiialise the watcher and C<last_activity>,
then call the callback:
To start the timer, simply initialise the watcher and set C<last_activity>
to the current time (meaning we just have some activity :), then call the
callback, which will "do the right thing" and start the timer:
ev_timer_init (timer, callback);
last_activity = ev_now (loop);
callback (loop, timer, EV_TIMEOUT);
And when there is some activity, simply remember the time in
C<last_activity>:
And when there is some activity, simply store the current time in
C<last_activity>, no libev calls at all:
last_actiivty = ev_now (loop);
This technique is slightly more complex, but in most cases where the
time-out is unlikely to be triggered, much more efficient.
Changing the timeout is trivial as well (if it isn't hard-coded in the
callback :) - just change the timeout and invoke the callback, which will
fix things for you.
=item 4. Whee, use a double-linked list for your timeouts.
If there is not one request, but many thousands, all employing some kind
of timeout with the same timeout value, then one can do even better:
When starting the timeout, calculate the timeout value and put the timeout
at the I<end> of the list.
Then use an C<ev_timer> to fire when the timeout at the I<beginning> of
the list is expected to fire (for example, using the technique #3).
When there is some activity, remove the timer from the list, recalculate
the timeout, append it to the end of the list again, and make sure to
update the C<ev_timer> if it was taken from the beginning of the list.
This way, one can manage an unlimited number of timeouts in O(1) time for
starting, stopping and updating the timers, at the expense of a major
complication, and having to use a constant timeout. The constant timeout
ensures that the list stays sorted.
=back
So what method is the best?
The method #2 is a simple no-brain-required solution that is adequate in
most situations. Method #3 requires a bit more thinking, but handles many
cases better, and isn't very complicated either. In most case, choosing
either one is fine.
Method #1 is almost always a bad idea, and buys you nothing. Method #4 is
rather complicated, but extremely efficient, something that really pays
off after the first or so million of active timers, i.e. it's usually
overkill :)
=head3 The special problem of time updates
Establishing the current time is a costly operation (it usually takes at

Loading…
Cancel
Save