-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
Better handling of racy @spawn
in @sync
#41927
Conversation
base/task.jl
Outdated
close(c) | ||
if @isdefined(c_ex) | ||
# Racy `@spawn`/`@async` many manage to `put!` tasks in the channel `c`. | ||
# Let's synchronize them as well, to report possible errors: | ||
for r in c | ||
c_ex = _sync1!(c_ex, r) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, maybe it's better to always throw an error when we find something is scheduled/@sync_add
ed after close
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we could somehow cheaply prevent the use of the channel from any task that is not (transitively) part of the @sync
group. Probably not though (perhaps similar cost to determining it this causes a deadlock?) and so throwing an error in detected races sounds good to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cheaply prevent the use of the channel from any task that is not (transitively) part of the
@sync
group
I guess we can lower @sync $body
to
task_local_storage(SYNC_KEY, Channel(Inf)) do
$body
end
and @spawn $body
to
let ch = get(task_local_storage(), SYNC_KEY, nothing)
function wrapper()
task_local_storage(SYNC_KEY, ch)
$body
end
t = Task(wrapper)
t.sticky = false
if ch !== nothing
put!(ch, t)
end
schedule(t)
end
?
Not sure if you'd consider task_local_storage
reasonably cheap, though :)
But maybe more importantly, it's a breaking change since I think it's reasonable to write something like
@sync begin
Threads.foreach(xs) do x
y = cpu_intensive_work(x)
if rare_condition(y)
@spawn io_intensive_work(y)
end
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tweaked sync_end
in 19a946f. Now it won't wait for the racy tasks and it always throws when a racy task is found.
Co-authored-by: Jameson Nash <[email protected]>
Co-authored-by: Jameson Nash <[email protected]>
Co-authored-by: Jameson Nash <[email protected]>
This "racy" use of
isready
julia/base/task.jl
Lines 344 to 347 in 543386d
can introduce a confusing behavior when
@spawn
or@async
that are lexically in scope of@sync
can be scheduled in a racy manner:I think it's reasonable to expect that the task
Threads.@spawn y[] = :completed
is either not scheduled or completed by theend
of@sync
block. I wouldn't recommend using a pattern like this. But, it's still nice to report an error from the lexically scoped tasks (if any) reliably via@sync
as much as possible.But, in current Julia, I can check that
sync_end_race()
can return:notset
by runningin the REPL. (Getting a similar behavior in a script requires a bit more code. See the test included in the PR.)