alternate approach to timer inaccuracy due to cached times

Denis Bilenko denis.bilenko at gmail.com
Sat Oct 15 08:40:25 CEST 2011


On Fri, Oct 14, 2011 at 8:27 PM, Marc Lehmann <schmorp at schmorp.de> wrote:
>> Here's how the problem manifests itself in gevent:
>> time.sleep(7)  # system call sleep
>> gevent.sleep(10)  # this uses ev_timer to wake up the coroutine. it
>> sleeps for 3 seconds instead.
>
> Well, you can always call ev_now_update after such a long blocking
> operation. Or take timestamps more often, to get better accuracy. Or use
> proper timeouts.

I know about ev_now_update(). In fact, I've suggested to Shaun on
gevent mailing list
to call ev_now_update() periodically via setitimer. This only helps
with timeouts bigger than
the setitimer period though.

Note, that inserting ev_now_update() manually is possible with gevent
but is not a good suggestion
in general, because it's hard to predict which operation is long and
blocking. For example, GC can
kick in at any moment.

What does "take timestamps more often" means?

What are proper timeouts?

> Thats a pity - since there have been elaborate explanations given, what
> exactly do you not understand?

In the following scenario,

1) The computation takes place for 200ms.
   (We don't know that in advance, so simply inserting ev_now_update()
call is not an option.)
2) A new non-blocking connect() is made. An I/O watcher is set up.
3) A timeout of 100ms is set up.

what would be a good implementation of timeouts in 3) ?

Apparently using ev_timer is naive and does not work. That's, in fact,
what we do in gevent.

How do we fix it?

> (1) This can be fixed the good way - by a better design for example: your timeout
> relies on some other event _not_ occuring. if this is an I/O event, give it
> higher priority. You can always add your own events by using an ev_check
> watcher too.

Giving the IO watcher higher priority than ev_timer's would not work
here, would it?
IIRC, priorities only matter within the same loop iteration, here the
issue occurs when
the timer has got an event but the IO hasn't yet.


> (2) It can be fixed the lazy but correct way - if you don't whne *when* an
> event occured, you have to ask the OS. If gettimefday (usually a fast
> userspace function) is good enough for you, you can just call it when the
> other event occurs and base your timeout on that.

Can you elaborate on how to do that, provided it is applicable to the
scenario above?


> (3) And lastly, if you can't fix the design, and the lazy fix is too much
> overhead, you can create a simple list and just start yxour timers in the
> ev_prepare watcher.

That seems like it would give us the kind of timers we need. I still wonder
how (1) "the good way" or (2) "the correct way" can be applied to the
example above.
If it can not, then I wonder why don't we just start all timers in
ev_prepare callback.


>> Your Perl's Coro library probably has sleep() function that uses
>> ev_timer as well? If that's the case, it's susceptible to this issue the same way.
>
> No, becauase Coro doesn't do I/O the same way, it uses design (2) from my
> previous mails for actual I/O events.

I was talking about sleep() without I/O (for simplicity). I installed
Coro and it seems
to do the same thing gevent does - it implements sleep() with bare ev_timer.

Here's the gevent example ported to Perl/Coro:

/home/denis$ cat corotest.pl
use Coro::AnyEvent;
Coro::AnyEvent::sleep 0.001;
sleep 7;
Coro::AnyEvent::sleep 10;

/home/denis$ time perl corotest.pl
real	0m10.025s
user	0m0.016s
sys	0m0.004s

Sleep() is documented as "block the current thread for **at least**
the given number of seconds".

But it sleeps, like gevent.sleep(), for only 3 seconds instead of 10.

In my opinion, Coro::AnyEvent::sleep is clearly broken according to
its own documentation, would you agree with that?



More information about the libev mailing list