Skip to content
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
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ New library features
* `replace(string, pattern...)` now supports an optional `IO` argument to
write the output to a stream rather than returning a string ([#48625]).
* `startswith` now supports seekable `IO` streams ([#43055]).
* `Timer(f, ...)` will now match the stickiness of the parent task when creating timer tasks, which can be overridden
by the new `spawn` kwarg. This avoids the issue where sticky tasks i.e. `@async` make their parent sticky ([#56745])

Standard library changes
------------------------
Expand Down
15 changes: 12 additions & 3 deletions base/asyncevent.jl
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ end

# timer with repeated callback
"""
Timer(callback::Function, delay; interval = 0)
Timer(callback::Function, delay; interval = 0, spawn::Union{Nothing,Bool}=nothing)

Create a timer that runs the function `callback` at each timer expiration.

Expand All @@ -278,6 +278,13 @@ callback is only run once. The function `callback` is called with a single argum
itself. Stop a timer by calling `close`. The `callback` may still be run one final time, if the timer
has already expired.

If `spawn` is `true`, the created task will be spawned, meaning that it will be allowed
to move thread, which avoids the side-effect of forcing the parent task to get stuck to the thread
it is on. If `spawn` is `nothing` (default), the task will be spawned if the parent task isn't sticky.

!!! compat "Julia 1.12"
The `spawn` argument was introduced in Julia 1.12.

# Examples

Here the first number is printed after a delay of two seconds, then the following numbers are
Expand All @@ -297,8 +304,9 @@ julia> begin
3
```
"""
function Timer(cb::Function, timeout::Real; interval::Real=0.0)
timer = Timer(timeout, interval=interval)
function Timer(cb::Function, timeout; spawn::Union{Nothing,Bool}=nothing, kwargs...)
sticky = spawn === nothing ? current_task().sticky : !spawn
timer = Timer(timeout; kwargs...)
t = @task begin
unpreserve_handle(timer)
while _trywait(timer)
Expand All @@ -312,6 +320,7 @@ function Timer(cb::Function, timeout::Real; interval::Real=0.0)
isopen(timer) || return
end
end
t.sticky = sticky
# here we are mimicking parts of _trywait, in coordination with task `t`
preserve_handle(timer)
@lock timer.cond begin
Expand Down
7 changes: 5 additions & 2 deletions test/channels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,11 @@ end
# make sure 1-shot timers work
let a = []
Timer(t -> push!(a, 1), 0.01, interval = 0)
sleep(0.2)
@test a == [1]
@test timedwait(() -> a == [1], 10) === :ok
end
let a = []
Timer(t -> push!(a, 1), 0.01, interval = 0, spawn = true)
@test timedwait(() -> a == [1], 10) === :ok
end

# make sure that we don't accidentally create a one-shot timer
Expand Down
Loading