Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Timer args to struct and add show method #57081

Merged
merged 1 commit into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ New library features
certain compiler plugin workflows ([#56660]).
* `sort` now supports `NTuple`s ([#54494])
* `map!(f, A)` now stores the results in `A`, like `map!(f, A, A)`. or `A .= f.(A)` ([#40632]).
* `Timer` now has readable `timeout` and `interval` properties, and a more descriptive show method ([#57081])

Standard library changes
------------------------
Expand Down
44 changes: 42 additions & 2 deletions base/asyncevent.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,24 @@ Create a timer that wakes up tasks waiting for it (by calling [`wait`](@ref) on
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.
[`isopen`](@ref) to check whether a timer is still active. Use `t.timeout` and `t.interval` to read
the setup conditions of a `Timer` `t`.

```julia-repl
julia> t = Timer(1.0; interval=0.5)
Timer (open, timeout: 1.0 s, interval: 0.5 s) @0x000000010f4e6e90

julia> isopen(t)
true

julia> t.timeout
1.0

julia> close(t)

julia> isopen(t)
false
```

!!! note
`interval` is subject to accumulating time skew. If you need precise events at a particular
Expand All @@ -84,12 +101,17 @@ once. When the timer is closed (by [`close`](@ref)) waiting tasks are woken with
A `Timer` requires yield points to update its state. For instance, `isopen(t::Timer)` cannot be
used to timeout a non-yielding while loop.

!!! compat "Julia 1.12
The `timeout` and `interval` readable properties were added in Julia 1.12.

"""
mutable struct Timer
@atomic handle::Ptr{Cvoid}
cond::ThreadSynchronizer
@atomic isopen::Bool
@atomic set::Bool
timeout_ms::UInt64
interval_ms::UInt64

function Timer(timeout::Real; interval::Real = 0.0)
timeout ≥ 0 || throw(ArgumentError("timer cannot have negative timeout of $timeout seconds"))
Expand All @@ -99,7 +121,7 @@ mutable struct Timer
intervalms = ceil(UInt64, interval * 1000)
loop = eventloop()

this = new(Libc.malloc(_sizeof_uv_timer), ThreadSynchronizer(), true, false)
this = new(Libc.malloc(_sizeof_uv_timer), ThreadSynchronizer(), true, false, timeoutms, intervalms)
associate_julia_struct(this.handle, this)
iolock_begin()
err = ccall(:uv_timer_init, Cint, (Ptr{Cvoid}, Ptr{Cvoid}), loop, this)
Expand All @@ -114,6 +136,24 @@ mutable struct Timer
return this
end
end
function getproperty(t::Timer, f::Symbol)
if f == :timeout
t.timeout_ms == 0 && return 0.0
return (t.timeout_ms - 1) / 1000 # remove the +1ms compensation from the constructor
elseif f == :interval
return t.interval_ms / 1000
else
return getfield(t, f)
end
end
propertynames(::Timer) = (:handle, :cond, :isopen, :set, :timeout, :timeout_ms, :interval, :interval_ms)

function show(io::IO, t::Timer)
state = isopen(t) ? "open" : "closed"
interval = t.interval
interval_str = interval > 0 ? ", interval: $(t.interval) s" : ""
print(io, "Timer ($state, timeout: $(t.timeout) s$interval_str) @0x$(string(convert(UInt, pointer_from_objref(t)), base = 16, pad = Sys.WORD_SIZE>>2))")
end

unsafe_convert(::Type{Ptr{Cvoid}}, t::Timer) = t.handle
unsafe_convert(::Type{Ptr{Cvoid}}, async::AsyncCondition) = async.handle
Expand Down
10 changes: 10 additions & 0 deletions test/channels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,16 @@ let a = Ref(0)
@test a[] == 1
end

@testset "Timer properties" begin
t = Timer(1.0, interval = 0.5)
@test t.timeout == 1.0
@test t.interval == 0.5
close(t)
@test !isopen(t)
@test t.timeout == 1.0
@test t.interval == 0.5
end

# trying to `schedule` a finished task
let t = @async nothing
wait(t)
Expand Down
Loading