--- libev/ev.pod 2008/10/23 06:30:48 1.198 +++ libev/ev.pod 2008/10/23 07:18:21 1.199 @@ -1290,20 +1290,20 @@ =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 @@ ev_timer_init (timer, callback, 60., 0.); ev_timer_start (loop, timer); -Then, each time there is some activity, C the timer, -initialise it again, and start it: +Then, each time there is some activity, C 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 inactivity. This is the easiest way, and involves using C instead of C. -For this, configure an C with a C value of C<60> and -then call C 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 the timer, and -C will automatically restart it if need be. - -That means you can ignore the C value and C -altogether and only ever use the C value and C. +To implement this, configure an C with a C value +of C<60> and then call C 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 +the timer, and C will automatically restart it if need be. + +That means you can ignore both the C function and the +C argument to C, and only ever use the C +member and C. 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 alone, but remember the time of last activity, and check for a real timeout only @@ -1370,10 +1376,10 @@ 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 @@ 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, in fact, time out. Otherwise +the callback was invoked too early (C 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 is used, taking advantage of the C 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. - -To start the timer, simply intiialise the watcher and C, -then call the callback: +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 initialise the watcher and set C +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: +And when there is some activity, simply store the current time in +C, 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 of the list. + +Then use an C to fire when the timeout at the I 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 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