From 180c8f1d08cc0f200cd4fb20f912b6f606618063 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Mon, 8 Nov 2021 14:49:17 -0500 Subject: [PATCH] Timer: handle timeout correctly (#42854) I am not sure why we ever used round+1 instead of ceil+1, as this is simply strictly more correct. (cherry picked from commit d6f59fa1826eb2db1ac1980af71cb6fcc4c7a978) --- base/asyncevent.jl | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/base/asyncevent.jl b/base/asyncevent.jl index c8d2c404b0a09..ca0e5da512147 100644 --- a/base/asyncevent.jl +++ b/base/asyncevent.jl @@ -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} @@ -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) @@ -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 @@ -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