Skip to content

unix,win: fix busy loop with zero timeout timers#4250

Merged
bnoordhuis merged 3 commits intolibuv:v1.xfrom
mizvekov:GH4245_fix_zero_timeout_timers
Dec 22, 2023
Merged

unix,win: fix busy loop with zero timeout timers#4250
bnoordhuis merged 3 commits intolibuv:v1.xfrom
mizvekov:GH4245_fix_zero_timeout_timers

Conversation

@mizvekov
Copy link
Copy Markdown
Contributor

Calling uv_timer_start(h, cb, 0, 0) from a timer callback resulted in
the timer running immediately because it was inserted at the front of
the timer heap.

If the callback did that every time, libuv would effectively busy-loop
in uv__run_timers() and never make forward progress.

Work around that by collecting all expired timers into a queue and only
running their callback afterwards.

Fixes: #4245

Copy link
Copy Markdown
Member

@vtjnash vtjnash left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without fully reviewing the implementation, this approach SGTM

Copy link
Copy Markdown
Member

@bnoordhuis bnoordhuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check how this affects the million_timers benchmark?

As a possible improvement: instead of a flag, always uv__queue_init() when the handle is stopped. That simplifies the uv_timer_stop() logic to:

if (uv__is_active(handle)) {
  heap_remove(timer_heap(handle->loop), &handle->u.heap_node, timer_less_than);
  uv__handle_stop(handle);
} else {
  uv__queue_remove(&handle->u.queue_node);
}
uv__queue_init(&handle->u.queue_node);

With complementary logic in uv_timer_start().

mizvekov and others added 2 commits December 16, 2023 17:07
Calling `uv_timer_start(h, cb, 0, 0)` from a timer callback resulted in
the timer running immediately because it was inserted at the front of
the timer heap.

If the callback did that every time, libuv would effectively busy-loop
in `uv__run_timers()` and never make forward progress.

Work around that by collecting all expired timers into a queue and only
running their callback afterwards.

Fixes: libuv#4245
@mizvekov mizvekov force-pushed the GH4245_fix_zero_timeout_timers branch from 7336e87 to 3a7cb0d Compare December 16, 2023 20:09
@mizvekov
Copy link
Copy Markdown
Contributor Author

Can you check how this affects the million_timers benchmark?

For that benchmark, there is essentially no difference.

Follows sample output for one run, and average of 10 runs computed with hyperfine:
hyperfine --warmup 1 'uv_run_benchmarks_a million_timers million_timers

Old:

10.21 seconds total
0.44 seconds init
9.56 seconds dispatch
0.21 seconds cleanup

Time (mean ± σ):     10.299 s ±  0.010 s    [User: 1.175 s, System: 0.053 s]
Range (min … max):   10.282 s … 10.315 s    10 runs

New:

10.26 seconds total
0.47 seconds init
9.54 seconds dispatch
0.25 seconds cleanup

Time (mean ± σ):     10.299 s ±  0.009 s    [User: 1.081 s, System: 0.042 s]
Range (min … max):   10.285 s … 10.312 s    10 runs

Tests run on Windows 11 system with Core i9-12900K CPU.

Copy link
Copy Markdown
Member

@bnoordhuis bnoordhuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for the PR.

@bnoordhuis bnoordhuis merged commit 51a22f6 into libuv:v1.x Dec 22, 2023
@bnoordhuis
Copy link
Copy Markdown
Member

Thanks Matheus, merged.

@vtjnash vtjnash mentioned this pull request Feb 6, 2024
santigimeno added a commit to santigimeno/libuv that referenced this pull request Feb 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

uv_timer_start behavior with timeout == 0 does not match documentation and can lead to starvation

3 participants