Skip to content

Commit 241947f

Browse files
authored
Corrections to timedwait methods (#35103)
* Refactor methods of sleep, Timer, and timedwait Made argument names consistent with documentation where reasonable. * Support pollint keyword in timedwait using Periods * Allow timedwait to take Real * Add tests for timedwait * Fix bug with sub-millisecond pollint * Test timedwait with Period parameters * Rethrow exceptions raised by timedwait callback
1 parent 48b6e08 commit 241947f

File tree

4 files changed

+79
-25
lines changed

4 files changed

+79
-25
lines changed

base/asyncevent.jl

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -255,39 +255,45 @@ function Timer(cb::Function, timeout::Real; interval::Real=0.0)
255255
end
256256

257257
"""
258-
timedwait(testcb::Function, secs::Float64; pollint::Float64=0.1)
258+
timedwait(testcb::Function, timeout::Real; pollint::Real=0.1)
259259
260-
Waits until `testcb` returns `true` or for `secs` seconds, whichever is earlier.
261-
`testcb` is polled every `pollint` seconds.
260+
Waits until `testcb` returns `true` or for `timeout` seconds, whichever is earlier.
261+
`testcb` is polled every `pollint` seconds. The minimum duration for `timeout` and `pollint`
262+
is 1 millisecond or `0.001`.
262263
263-
Returns :ok, :timed_out, or :error
264+
Returns :ok or :timed_out
264265
"""
265-
function timedwait(testcb::Function, secs::Float64; pollint::Float64=0.1)
266-
pollint > 0 || throw(ArgumentError("cannot set pollint to $pollint seconds"))
266+
function timedwait(testcb::Function, timeout::Real; pollint::Real=0.1)
267+
pollint >= 1e-3 || throw(ArgumentError("pollint must be ≥ 1 millisecond"))
267268
start = time_ns()
268-
nsecs = 1e9 * secs
269+
ns_timeout = 1e9 * timeout
269270
done = Channel(1)
270271
function timercb(aw)
271272
try
272273
if testcb()
273-
put!(done, :ok)
274-
elseif (time_ns() - start) > nsecs
275-
put!(done, :timed_out)
274+
put!(done, (:ok, nothing))
275+
elseif (time_ns() - start) > ns_timeout
276+
put!(done, (:timed_out, nothing))
276277
end
277278
catch e
278-
put!(done, :error)
279+
put!(done, (:error, CapturedException(e, catch_backtrace())))
279280
finally
280281
isready(done) && close(aw)
281282
end
282283
nothing
283284
end
284285

285-
if !testcb()
286-
t = Timer(timercb, pollint, interval = pollint)
287-
ret = fetch(done)::Symbol
288-
close(t)
289-
else
290-
ret = :ok
286+
try
287+
testcb() && return :ok
288+
catch e
289+
throw(CapturedException(e, catch_backtrace()))
291290
end
291+
292+
t = Timer(timercb, pollint, interval = pollint)
293+
ret, e = fetch(done)
294+
close(t)
295+
296+
ret === :error && throw(e)
297+
292298
return ret
293299
end

stdlib/Dates/src/types.jl

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -420,11 +420,15 @@ Base.hash(x::Time, h::UInt) =
420420
hash(hour(x), hash(minute(x), hash(second(x),
421421
hash(millisecond(x), hash(microsecond(x), hash(nanosecond(x), h))))))
422422

423-
import Base: sleep, Timer, timedwait
424-
sleep(time::Period) = sleep(toms(time) / 1000)
425-
Timer(time::Period; interval::Period = Second(0)) =
426-
Timer(toms(time) / 1000, interval = toms(interval) / 1000)
427-
timedwait(testcb::Function, time::Period) = timedwait(testcb, toms(time) / 1000)
423+
Base.sleep(duration::Period) = sleep(toms(duration) / 1000)
424+
425+
function Base.Timer(delay::Period; interval::Period=Second(0))
426+
Timer(toms(delay) / 1000, interval=toms(interval) / 1000)
427+
end
428+
429+
function Base.timedwait(testcb::Function, timeout::Period; pollint::Period=Millisecond(100))
430+
timedwait(testcb, toms(timeout) / 1000, pollint=toms(pollint) / 1000)
431+
end
428432

429433
Base.OrderStyle(::Type{<:AbstractTime}) = Base.Ordered()
430434
Base.ArithmeticStyle(::Type{<:AbstractTime}) = Base.ArithmeticWraps()

stdlib/Dates/test/types.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,4 +266,8 @@ end
266266

267267
end
268268

269+
@testset "timedwait" begin
270+
@test timedwait(() -> false, Second(0); pollint=Millisecond(1)) === :timed_out
271+
end
272+
269273
end

test/channels.jl

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,47 @@ using Distributed
252252
end
253253
end
254254

255-
using Dates
255+
@testset "timedwait" begin
256+
@test timedwait(() -> true, 0) === :ok
257+
@test timedwait(() -> false, 0) === :timed_out
258+
@test_throws ArgumentError timedwait(() -> true, 0; pollint=0)
259+
260+
# Allowing a smaller positive `pollint` results in `timewait` hanging
261+
@test_throws ArgumentError timedwait(() -> true, 0, pollint=1e-4)
262+
263+
# Callback passed in raises an exception
264+
failure_cb = function (fail_on_call=1)
265+
i = 0
266+
function ()
267+
i += 1
268+
i >= fail_on_call && error("callback failed")
269+
return false
270+
end
271+
end
272+
273+
try
274+
timedwait(failure_cb(1), 0)
275+
@test false
276+
catch e
277+
@test e isa CapturedException
278+
@test e.ex isa ErrorException
279+
end
280+
281+
try
282+
timedwait(failure_cb(2), 0)
283+
@test false
284+
catch e
285+
@test e isa CapturedException
286+
@test e.ex isa ErrorException
287+
end
288+
289+
duration = @elapsed timedwait(() -> false, 1) # Using default pollint of 0.1
290+
@test duration 1 atol=0.4
291+
292+
duration = @elapsed timedwait(() -> false, 0; pollint=1)
293+
@test duration 1 atol=0.4
294+
end
295+
256296
@testset "timedwait on multiple channels" begin
257297
@Experimental.sync begin
258298
rr1 = Channel(1)
@@ -262,13 +302,13 @@ using Dates
262302
callback() = all(map(isready, [rr1, rr2, rr3]))
263303
# precompile functions which will be tested for execution time
264304
@test !callback()
265-
@test timedwait(callback, 0.0) === :timed_out
305+
@test timedwait(callback, 0) === :timed_out
266306

267307
@async begin sleep(0.5); put!(rr1, :ok) end
268308
@async begin sleep(1.0); put!(rr2, :ok) end
269309
@async begin sleep(2.0); put!(rr3, :ok) end
270310

271-
et = @elapsed timedwait(callback, Dates.Second(1))
311+
et = @elapsed timedwait(callback, 1)
272312

273313
# assuming that 0.5 seconds is a good enough buffer on a typical modern CPU
274314
try

0 commit comments

Comments
 (0)