Skip to content

Commit

Permalink
Timer: handle timeout correctly (#42854)
Browse files Browse the repository at this point in the history
I am not sure why we ever used round+1 instead of ceil+1, as this is
simply strictly more correct.
  • Loading branch information
vtjnash authored Nov 8, 2021
1 parent 27f579f commit d6f59fa
Showing 1 changed file with 19 additions and 15 deletions.
34 changes: 19 additions & 15 deletions base/asyncevent.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,13 @@ end
Create a timer that wakes up tasks waiting for it (by calling [`wait`](@ref) on the timer object).
Waiting tasks are woken after an initial delay of `delay` seconds, and then repeating with the given
`interval` in seconds. If `interval` is equal to `0`, the timer is only triggered once. When
the timer is closed (by [`close`](@ref)) waiting tasks are woken with an error. Use [`isopen`](@ref)
to check whether a timer is still active.
Waiting tasks are woken after an initial delay of at least `delay` seconds, and then repeating after
at least `interval` seconds again elapse. If `interval` is equal to `0`, the timer is only triggered
once. When the timer is closed (by [`close`](@ref)) waiting tasks are woken with an error. Use
[`isopen`](@ref) to check whether a timer is still active.
Note: `interval` is subject to accumulating time skew. If you need precise events at a particular
absolute time, create a new timer at each expiration with the difference to the next time computed.
"""
mutable struct Timer
handle::Ptr{Cvoid}
Expand All @@ -74,8 +77,9 @@ mutable struct Timer
function Timer(timeout::Real; interval::Real = 0.0)
timeout 0 || throw(ArgumentError("timer cannot have negative timeout of $timeout seconds"))
interval 0 || throw(ArgumentError("timer cannot have negative repeat interval of $interval seconds"))
timeout = UInt64(round(timeout * 1000)) + 1
interval = UInt64(ceil(interval * 1000))
# libuv has a tendency to timeout 1 ms early, so we need +1 on the timeout (in milliseconds), unless it is zero
timeoutms = ceil(UInt64, timeout * 1000) + !iszero(timeout)
intervalms = ceil(UInt64, interval * 1000)
loop = eventloop()

this = new(Libc.malloc(_sizeof_uv_timer), ThreadSynchronizer(), true, false)
Expand All @@ -87,7 +91,7 @@ mutable struct Timer
ccall(:uv_update_time, Cvoid, (Ptr{Cvoid},), loop)
err = ccall(:uv_timer_start, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, UInt64, UInt64),
this, @cfunction(uv_timercb, Cvoid, (Ptr{Cvoid},)),
timeout, interval)
timeoutms, intervalms)
@assert err == 0
iolock_end()
return this
Expand Down Expand Up @@ -222,18 +226,18 @@ end
"""
Timer(callback::Function, delay; interval = 0)
Create a timer that wakes up tasks waiting for it (by calling [`wait`](@ref) on the timer object) and
calls the function `callback`.
Create a timer that runs the function `callback` at each timer expiration.
Waiting tasks are woken and the function `callback` is called after an initial delay of `delay` seconds,
and then repeating with the given `interval` in seconds. If `interval` is equal to `0`, the timer
is only triggered once. The function `callback` is called with a single argument, the timer itself.
When the timer is closed (by [`close`](@ref)) waiting tasks are woken with an error. Use [`isopen`](@ref)
to check whether a timer is still active.
Waiting tasks are woken and the function `callback` is called after an initial delay of `delay`
seconds, and then repeating with the given `interval` in seconds. If `interval` is equal to `0`, the
callback is only run once. The function `callback` is called with a single argument, the timer
itself. Stop a timer by calling `close`. The `cb` may still be run one final time, if the timer has
already expired.
# Examples
Here the first number is printed after a delay of two seconds, then the following numbers are printed quickly.
Here the first number is printed after a delay of two seconds, then the following numbers are
printed quickly.
```julia-repl
julia> begin
Expand Down

2 comments on commit d6f59fa

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

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

Executing the daily package evaluation, I will reply here when finished:

@nanosoldier runtests(ALL, isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

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

Your package evaluation job has completed - possible new issues were detected. A full report can be found here.

Please sign in to comment.