Run a regularly scheduled callback on a precise interval

121 views
Skip to first unread message

William Ehlhardt

unread,
Jul 25, 2014, 5:22:09 AM7/25/14
to li...@googlegroups.com
Hi all,

uv_timer_t's run interval seems to schedule the next run for $interval ms *after* the callback completes, instead of accounting for the callback run time and scheduling the next wakeup for $callback_start_time + $interval.

For example, if I set the interval to 1000ms, and the callback takes 100ms to run, the callback will actually get run every 1100ms by libuv. Is there a way to make it run every 1000ms instead?

See attached program for a demonstration. I expect the time to print out every 5s, but it prints out every 7s because of the 2s sleep() in the callback.

-William
timertest.c

Fedor Indutny

unread,
Jul 25, 2014, 5:24:30 AM7/25/14
to li...@googlegroups.com
Hi!

What if the callback execution would take 1000ms to run? Would you like it to just constantly spin?

Cheers.


--
You received this message because you are subscribed to the Google Groups "libuv" group.
To unsubscribe from this group and stop receiving emails from it, send an email to libuv+un...@googlegroups.com.
To post to this group, send email to li...@googlegroups.com.
Visit this group at http://groups.google.com/group/libuv.
For more options, visit https://groups.google.com/d/optout.

William Ehlhardt

unread,
Jul 25, 2014, 5:25:37 AM7/25/14
to li...@googlegroups.com, fe...@indutny.com
On Friday, July 25, 2014 4:24:30 AM UTC-5, Fedor Indutny wrote:
What if the callback execution would take 1000ms to run? Would you like it to just constantly spin?

Yep. I'll deal with the consequences of that myself.
 

Saúl Ibarra Corretgé

unread,
Jul 25, 2014, 5:33:29 AM7/25/14
to li...@googlegroups.com
On 07/25/2014 11:25 AM, William Ehlhardt wrote:
> On Friday, July 25, 2014 4:24:30 AM UTC-5, Fedor Indutny wrote:
>
> What if the callback execution would take 1000ms to run? Would you
> like it to just constantly spin?
>
>
> Yep. I'll deal with the consequences of that myself.
>

There are multiple opinions about how that should go. I for one would
get rid of the timer repeat functionality and leave it on the hands of
the user so you calculate the time you want. Anyway, you can calculate
how long your callback took to execute and use uv_timer_set_repeat to
modify it, or just call uv_timer_start with the new timeout.


Cheers,

--
Saúl Ibarra Corretgé
bettercallsaghul.com


signature.asc

Fedor Indutny

unread,
Jul 25, 2014, 5:34:34 AM7/25/14
to li...@googlegroups.com
Yeah, repeating timer is just an API sugar over the regular timers.

William Ehlhardt

unread,
Aug 1, 2014, 1:22:50 AM8/1/14
to li...@googlegroups.com
On Friday, July 25, 2014 4:33:29 AM UTC-5, Saúl Ibarra Corretgé wrote:
There are multiple opinions about how that should go. I for one would
get rid of the timer repeat functionality and leave it on the hands of
the user so you calculate the time you want. Anyway, you can calculate
how long your callback took to execute and use uv_timer_set_repeat to
modify it, or just call uv_timer_start with the new timeout.

I wound up taking the latter option, but I discovered a problem: if I set the timeout to 0, UV_RUN_ONCE gets stuck running my callback forever (instead of, in my example, an async_t on the same loop that's trying to shut the loop down). See attached program, which gets stuck busylooping forever instead of terminating.
forever.c

William Ehlhardt

unread,
Aug 1, 2014, 1:34:52 AM8/1/14
to li...@googlegroups.com
Ah, so I think I found a workaround: simply guarantee that the timeout is always nonzero. Even if the callback takes enough time to run that the timer needs to run again immediately, the nonzero timeout seems to guarantee that uv_run will come up for air before running it. See attached.


--
You received this message because you are subscribed to a topic in the Google Groups "libuv" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/libuv/uaN1o4IAWW8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to libuv+un...@googlegroups.com.
workaround.c

William Ehlhardt

unread,
Aug 1, 2014, 1:44:27 AM8/1/14
to li...@googlegroups.com
uv_run getting stuck forever even with UV_RUN_ONCE still seems like a bug, though.

Saúl Ibarra Corretgé

unread,
Aug 1, 2014, 3:50:30 AM8/1/14
to li...@googlegroups.com
On 08/01/2014 07:44 AM, William Ehlhardt wrote:
> uv_run getting stuck forever even with UV_RUN_ONCE still seems like a
> bug, though.
>

It's not. The reason is buried inside how uv_run works, which I need to
document for once :-S

Basically, you are scheduling a timer for 0ms, which means it's due *now*.

Then, when you call uv_run, the first thing to do is run the due timers,
so, your timer's callback will be called.

Then, the poll timeout will be calculated. If you have that timer and a
TCP server, for example, the polling timeout would infinite, because
your timer is not active (I assume you didn't set repeat), so it doesn't
count nor there is any other condition to make the timeout be 0.

The loop will block for i/o for the aforementioned amount of time. It
will return when there is some i/o activity. i/o callbacks will be
fired, also check and close.

Then uv_run will return.

UV_RUN_ONCE doesn't imply not blocking waiting for i/o, that's what
UV_RUN_NOWAIT does.

Hope that clears it up a bit.
signature.asc

William Ehlhardt

unread,
Aug 1, 2014, 4:29:19 AM8/1/14
to li...@googlegroups.com
On Fri, Aug 1, 2014 at 2:48 AM, Saúl Ibarra Corretgé <sag...@gmail.com> wrote:
> It's not. The reason is buried inside how uv_run works, which I need to
> document for once :-S
>
> Basically, you are scheduling a timer for 0ms, which means it's due *now*.
>
> Then, when you call uv_run, the first thing to do is run the due timers,
> so, your timer's callback will be called.

Right, but I would expect UV_RUN_ONCE to only execute those timers
which were due when uv_run was started, not the ones that became due
while it was running.

Iñaki Baz Castillo

unread,
Aug 1, 2014, 4:34:13 AM8/1/14
to li...@googlegroups.com
2014-08-01 9:48 GMT+02:00 Saúl Ibarra Corretgé <sag...@gmail.com>:
> UV_RUN_ONCE doesn't imply not blocking waiting for i/o, that's what
> UV_RUN_NOWAIT does.

Any real use case for UV_RUN_ONCE having UV_RUN_NOWAIT?

I consider UV_RUN_ONCE a bit "problematic" given, exactly, the
rationale in your previous mail. This is, I consider extremely useless
blocking until a TCP connection is received, and then exit. No sense
IMHO.

If there is no real use-cases for UV_RUN_ONCE (that cannot be achieved
with UV_RUN_DEFAULT and/or UV_RUN_NOWAIT) then I suggest dropping it.


--
Iñaki Baz Castillo
<i...@aliax.net>

William Ehlhardt

unread,
Aug 1, 2014, 4:40:21 AM8/1/14
to li...@googlegroups.com
On Fri, Aug 1, 2014 at 3:33 AM, Iñaki Baz Castillo <i...@aliax.net> wrote:
> Any real use case for UV_RUN_ONCE having UV_RUN_NOWAIT?

I'm not sure, but I'd say that getting stuck on UV_RUN_NOWAIT is just
as bad, for the same reasons (try changing it in my original test
file).

Saúl Ibarra Corretgé

unread,
Aug 1, 2014, 4:41:46 AM8/1/14
to li...@googlegroups.com
Well, your timer was called when uv_run started its process, because due
timers are the first thing to be calculated. If you schedule more timers
in other callbacks, they will run on the next iteration.

If you think you've found an inconsistency, can you provide a test case?
signature.asc

William Ehlhardt

unread,
Aug 1, 2014, 4:44:38 AM8/1/14
to li...@googlegroups.com
On Fri, Aug 1, 2014 at 3:39 AM, Saúl Ibarra Corretgé <sag...@gmail.com> wrote:
> Well, your timer was called when uv_run started its process, because due
> timers are the first thing to be calculated. If you schedule more timers
> in other callbacks, they will run on the next iteration.
>
> If you think you've found an inconsistency, can you provide a test case?

Sure. I posted this before, but here it is again (with UV_RUN_NOWAIT
instead, just for grins). Expected behavior: UV_RUN_NOWAIT should
return instead of busylooping until the end of time. See attached.
forever.c

Saúl Ibarra Corretgé

unread,
Aug 1, 2014, 4:46:26 AM8/1/14
to li...@googlegroups.com
On 08/01/2014 10:33 AM, Iñaki Baz Castillo wrote:
> 2014-08-01 9:48 GMT+02:00 Saúl Ibarra Corretgé <sag...@gmail.com>:
>> UV_RUN_ONCE doesn't imply not blocking waiting for i/o, that's what
>> UV_RUN_NOWAIT does.
>
> Any real use case for UV_RUN_ONCE having UV_RUN_NOWAIT?
>
> I consider UV_RUN_ONCE a bit "problematic" given, exactly, the
> rationale in your previous mail. This is, I consider extremely useless
> blocking until a TCP connection is received, and then exit. No sense
> IMHO.
>

If you have a timer that kicks in 100ms, UV_RUN_ONCE will block for
100ms. What I showed was an example.

> If there is no real use-cases for UV_RUN_ONCE (that cannot be achieved
> with UV_RUN_DEFAULT and/or UV_RUN_NOWAIT) then I suggest dropping it.
>

Not going to happen. People embedding libuv into other event loops may
want to iterate the loop at their own pace. UV_RUN_DEFAULT doesn't help
because it will loop for ever, and UV_RUN_NOWAIT doesn't help either
because it always does a zero tiemout poll.

The fact that *you* don't need it doesn't mean other don't have a use
for it.
signature.asc

Saúl Ibarra Corretgé

unread,
Aug 1, 2014, 4:52:20 AM8/1/14
to li...@googlegroups.com
Sorry, I must have missed it.

Looks like a granularity problem. Basically, the logic in uv__run_timers
will spin forever unless there is a timer due in the future.

Please open an issue on GH and attach your test case. Extra points if
you come up with a fix :-)
signature.asc

Iñaki Baz Castillo

unread,
Aug 1, 2014, 4:58:14 AM8/1/14
to li...@googlegroups.com
2014-08-01 10:44 GMT+02:00 Saúl Ibarra Corretgé <sag...@gmail.com>:
> If you have a timer that kicks in 100ms, UV_RUN_ONCE will block for
> 100ms. What I showed was an example.

Same as if you close the timer in its first callback (and that is even
better than using UV_RUN_ONCE since you can free the timer handle and
the entire loop without having to iterate all of them and call
uv_close for later run again uv_run to execute their close callbacks).



>> If there is no real use-cases for UV_RUN_ONCE (that cannot be achieved
>> with UV_RUN_DEFAULT and/or UV_RUN_NOWAIT) then I suggest dropping it.
>>
>
> Not going to happen. People embedding libuv into other event loops may
> want to iterate the loop at their own pace. UV_RUN_DEFAULT doesn't help
> because it will loop for ever, and UV_RUN_NOWAIT doesn't help either
> because it always does a zero tiemout poll.
>
> The fact that *you* don't need it doesn't mean other don't have a use
> for it.

That's why I was asking.

Saúl Ibarra Corretgé

unread,
Aug 1, 2014, 5:12:52 AM8/1/14
to li...@googlegroups.com
On 08/01/2014 10:57 AM, Iñaki Baz Castillo wrote:
> 2014-08-01 10:44 GMT+02:00 Saúl Ibarra Corretgé <sag...@gmail.com>:
>> If you have a timer that kicks in 100ms, UV_RUN_ONCE will block for
>> 100ms. What I showed was an example.
>
> Same as if you close the timer in its first callback (and that is even
> better than using UV_RUN_ONCE since you can free the timer handle and
> the entire loop without having to iterate all of them and call
> uv_close for later run again uv_run to execute their close callbacks).
>

No sure what you are trying to explain. Close callbacks behave the same
no matter how you run the loop. Actually, if you close the timer in its
callback, it will be called on that same iteration, as the last step.
signature.asc

Iñaki Baz Castillo

unread,
Aug 1, 2014, 5:23:59 AM8/1/14
to li...@googlegroups.com
So if you run UV_RUN_ONCE having a timer then it will exit at first
execution (plus its close callback if invoked). But if you forgot that
you also have a running TCP server or UDP socket then it will block
forever. That sounds sad IMHO.

I've never needed UV_RUN_ONCE nor UV_RUN_NOWAIT. I consider them bad
ways (workarounds) for semi-closing a loop. Do you want to exit the
loop? Have a proper xxxx_close() custom function that properly closes
all the handles. If you do not have control over your running handles
then your fault so fix your code.

Saúl Ibarra Corretgé

unread,
Aug 1, 2014, 5:36:42 AM8/1/14
to li...@googlegroups.com

> So if you run UV_RUN_ONCE having a timer then it will exit at first
> execution (plus its close callback if invoked). But if you forgot that
> you also have a running TCP server or UDP socket then it will block
> forever. That sounds sad IMHO.
>

I still don't get it. That how it works *by design*. libuv can't guess
that you only wanted the timer to fire. If you use UV_RUN_ONCE you need
to know why. Do we need better docs for it? Probably.

> I've never needed UV_RUN_ONCE nor UV_RUN_NOWAIT. I consider them bad
> ways (workarounds) for semi-closing a loop. Do you want to exit the
> loop? Have a proper xxxx_close() custom function that properly closes
> all the handles. If you do not have control over your running handles
> then your fault so fix your code.
>

Again, it's *your* experience, not everyone else's. As an example, Node
uses UV_RUN_ONCE, in order to be able to emit an event just before the
loop is going to exit and thus execution will finish. You can't do that
with UV_RUN_DEFAULT.
signature.asc

Iñaki Baz Castillo

unread,
Aug 1, 2014, 5:55:26 AM8/1/14
to li...@googlegroups.com
2014-08-01 11:34 GMT+02:00 Saúl Ibarra Corretgé <sag...@gmail.com>:
> Node
> uses UV_RUN_ONCE, in order to be able to emit an event just before the
> loop is going to exit and thus execution will finish. You can't do that
> with UV_RUN_DEFAULT.

You can. Just call uv_close for that handle when its event is fired.

Saúl Ibarra Corretgé

unread,
Aug 1, 2014, 6:31:59 AM8/1/14
to li...@googlegroups.com
On 08/01/2014 11:55 AM, Iñaki Baz Castillo wrote:
> 2014-08-01 11:34 GMT+02:00 Saúl Ibarra Corretgé <sag...@gmail.com>:
>> Node
>> uses UV_RUN_ONCE, in order to be able to emit an event just before the
>> loop is going to exit and thus execution will finish. You can't do that
>> with UV_RUN_DEFAULT.
>
> You can. Just call uv_close for that handle when its event is fired.
>
>

You can't. You don't know what the loop is going to do next while
running inside it. The event to be emitted is a global one, that is,
"hey, your application is about to end", it's not about a single handle.
signature.asc

Iñaki Baz Castillo

unread,
Aug 1, 2014, 6:37:27 AM8/1/14
to li...@googlegroups.com
2014-08-01 12:29 GMT+02:00 Saúl Ibarra Corretgé <sag...@gmail.com>:
> You can't. You don't know what the loop is going to do next while
> running inside it. The event to be emitted is a global one, that is,
> "hey, your application is about to end", it's not about a single handle.


Before you can run uv_run(UV_RUN_ONCE) you must exit the same loop
(which was running with UV_RUN_DEFAULT), right? So, assuming the loop
is normally running, why don't you call a custom function
close_my_loop() which properly closes all the handles and notifies the
application or whatever?

Again, for me anything that ends the loop without closing all the
handles is not a good design. Said that I'm not talking about Node
since I do not know Node internals.

Saúl Ibarra Corretgé

unread,
Aug 1, 2014, 8:26:53 AM8/1/14
to li...@googlegroups.com
The scenario is that you want to notify the user about the process going
away and give him one last chance. IIRC he can prevent the loop from
exiting by creating new handles in this event. But all that is beyond
the point. Bottom line is: not all applications are like yours, other
people have different needs, and there is no One True Design To Rule
Them All (TM).
signature.asc

Iñaki Baz Castillo

unread,
Aug 1, 2014, 9:03:20 AM8/1/14
to li...@googlegroups.com
2014-08-01 14:24 GMT+02:00 Saúl Ibarra Corretgé <sag...@gmail.com>:
> The scenario is that you want to notify the user about the process going
> away and give him one last chance. IIRC he can prevent the loop from
> exiting by creating new handles in this event. But all that is beyond
> the point. Bottom line is: not all applications are like yours, other
> people have different needs, and there is no One True Design To Rule
> Them All (TM).

Sure. I just say that any design in which the loop is exited without
releasing its resources (handles and so on) is a Bad Design (TM).

Saúl Ibarra Corretgé

unread,
Aug 1, 2014, 9:54:17 AM8/1/14
to li...@googlegroups.com
Nobody said that is a good thing. You can exit the loop early and
continue to run it later on without any problem.
signature.asc

Iñaki Baz Castillo

unread,
Aug 1, 2014, 9:56:28 AM8/1/14
to li...@googlegroups.com

IMHO it is not good to re-join "later" a loop in which there were timers or pending TCP connections. Define "later".

--
Iñaki Baz Castillo
<i...@aliax.net>

William Ehlhardt

unread,
Aug 17, 2014, 10:00:42 PM8/17/14
to li...@googlegroups.com
On Fri, Aug 1, 2014 at 3:50 AM, Saúl Ibarra Corretgé <sag...@gmail.com> wrote:
> Please open an issue on GH and attach your test case. Extra points if
> you come up with a fix :-)

Sorry, just getting around to this. Before I do: did anyone else file
the bug already?

Saúl Ibarra Corretgé

unread,
Aug 18, 2014, 3:58:56 AM8/18/14
to li...@googlegroups.com
Not that I know of.
signature.asc

William Ehlhardt

unread,
Aug 18, 2014, 6:50:55 PM8/18/14
to li...@googlegroups.com
On Fri, Aug 1, 2014 at 3:50 AM, Saúl Ibarra Corretgé <sag...@gmail.com> wrote:
> Please open an issue on GH and attach your test case. Extra points if
> you come up with a fix :-)

Done. I don't plan to work on a fix myself, FYI.

https://github.com/joyent/libuv/issues/1427

Cheers!

Saúl Ibarra Corretgé

unread,
Aug 19, 2014, 3:27:59 AM8/19/14
to li...@googlegroups.com
Thanks!
signature.asc
Reply all
Reply to author
Forward
0 new messages