diff --git a/NEWS.md b/NEWS.md index 8f02045a213d6..1708866609427 100644 --- a/NEWS.md +++ b/NEWS.md @@ -112,6 +112,8 @@ New library features * New `ltruncate`, `rtruncate` and `ctruncate` functions for truncating strings to text width, accounting for char widths ([#55351]) * `isless` (and thus `cmp`, sorting, etc.) is now supported for zero-dimensional `AbstractArray`s ([#55772]) * `invoke` now supports passing a Method instead of a type signature making this interface somewhat more flexible for certain uncommon use cases ([#56692]). +* `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]) * `invoke` now supports passing a CodeInstance instead of a type, which can enable certain compiler plugin workflows ([#56660]). * `sort` now supports `NTuple`s ([#54494]) diff --git a/base/asyncevent.jl b/base/asyncevent.jl index c6cb3d3fa73bb..8c708455976e2 100644 --- a/base/asyncevent.jl +++ b/base/asyncevent.jl @@ -275,7 +275,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. @@ -285,6 +285,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 @@ -304,7 +311,8 @@ julia> begin 3 ``` """ -function Timer(cb::Function, timeout; kwargs...) +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) @@ -319,6 +327,7 @@ function Timer(cb::Function, timeout; kwargs...) 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 diff --git a/test/channels.jl b/test/channels.jl index eed7a7ecc0566..f3e5381a62e94 100644 --- a/test/channels.jl +++ b/test/channels.jl @@ -550,8 +550,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