@@ -316,15 +316,19 @@ function _wait(t::Task; expected_cancellation = nothing)
316316 lock (donenotify)
317317 try
318318 while ! istaskdone (t) && cancellation_request () === expected_cancellation
319- wait (donenotify; waitee= t)
319+ wait (donenotify; waitee= t, expected_cancellation )
320320 end
321321 finally
322322 unlock (donenotify)
323323 end
324324 end
325325 nothing
326326end
327- cancel_wait! (waitee:: Task , waiter:: Task ) = cancel_wait! (waitee. donenotify, waiter, nothing ; waitee)
327+
328+ # We handle cancellation explicitly above - just suppress the error here
329+ cancel_wait! (waitee:: Task , @nospecialize (creq)) = nothing
330+ cancel_wait! (waitee:: Task , waiter:: Task , @nospecialize (creq)) =
331+ cancel_wait! (waitee. donenotify, waiter, creq, nothing ; waitee)
328332
329333# have `waiter` wait for `t`
330334function _wait2 (t:: Task , waiter:: Task )
@@ -367,7 +371,7 @@ Throws a `ConcurrencyViolationError` if `t` is the currently running task, to pr
367371"""
368372function wait (t:: Task ; throw= true )
369373 _wait (t)
370- cr = cancellation_request ()
374+ cr = cancellation_request_or_yield ()
371375 if cr != = nothing
372376 propagate_cancellation! (t, cr)
373377 end
@@ -611,7 +615,7 @@ function sync_end(c::Channel{Any})
611615 r = take! (c)
612616 if isa (r, Task)
613617 _wait (r)
614- cr = cancellation_request ()
618+ cr = cancellation_request_or_yield ()
615619 if cr != = nothing
616620 return sync_cancel! (c, r, cr, @isdefined (c_ex) ? c_ex : CompositeException ())
617621 end
@@ -1423,6 +1427,14 @@ end
14231427 throw (req)
14241428end
14251429
1430+ function cancellation_request_raw ()
1431+ ct = current_task ()
1432+ req = @atomic :monotonic ct. cancellation_request
1433+ req === nothing && return req
1434+ req = @atomic :acquire ct. cancellation_request
1435+ return req
1436+ end
1437+
14261438"""
14271439 cancellation_request()
14281440
@@ -1431,13 +1443,26 @@ cancellation has been requested. If a cancellation request is present, it is
14311443loaded with acquire semantics.
14321444"""
14331445function cancellation_request ()
1434- ct = current_task ()
1435- req = @atomic :monotonic ct. cancellation_request
1436- req === nothing && return req
1437- cr = @atomic :acquire ct. cancellation_request
1446+ cr = cancellation_request_raw ()
14381447 return conform_cancellation_request (cr)
14391448end
14401449
1450+ """
1451+ cancellation_request_or_yield()
1452+
1453+ Like [`cancellation_request`](@ref), but specifically handles CANCEL_REQUEST_YIELD
1454+ by calling yield internally and re-checking for cancellation requests.
1455+ """
1456+ function cancellation_request_or_yield ()
1457+ while true
1458+ _cr = cancellation_request_raw ()
1459+ cr = conform_cancellation_request (_cr)
1460+ cr != = CANCEL_REQUEST_YIELD && return cr
1461+ @atomicreplace :sequentially_consistent :monotonic current_task (). cancellation_request _cr => nothing
1462+ yield ()
1463+ end
1464+ end
1465+
14411466"""
14421467 Core.cancellation_point!()
14431468
@@ -1451,7 +1476,7 @@ Core.cancellation_point!
14511476function cancel! (t:: Task , crequest= CANCEL_REQUEST_SAFE)
14521477 # TODO : Raise task priority
14531478 @atomic :release t. cancellation_request = crequest
1454- # TODO : SYS_membarrier() ?
1479+ Threads . atomic_fence_heavy ()
14551480 # Special case: If the task hasn't started yet at this point, we want to set
14561481 # it up to cancel any waits, but we need to be a bit careful with concurrent
14571482 # starts of the task.
@@ -1465,15 +1490,29 @@ function cancel!(t::Task, crequest=CANCEL_REQUEST_SAFE)
14651490 end
14661491 return
14671492 end
1468- # Try to interrupt the task if it's at a cancellation point (has reset_ctx set)
1493+ # Try to interrupt the task. The barrier above synchronizes with the establishment
1494+ # of a wait object and guarantees that either:
1495+ # 1. We have the wait object in t.queue, or
1496+ # 2. The task saw the cancellation and called (a different method of) cancel_wait!
1497+ # itself.
1498+ # Note that it is possible for both to be true, in which case the task wins
1499+ # and our call to cancel_wait! is will no-op after acquiring the waitee lock.
1500+ #
1501+ # Additionally, if there is no wait object, either
1502+ # 1. The task is suspended, but not using our wait object protocol.
1503+ # In this case, cancellation will not succeed.
1504+ # 2. The task is running.
1505+ #
1506+ # We can't tell the difference, but we unconditionally try to send the cancellation
1507+ # signal. If a reset_ctx exists, this will cause the task to be interrupted.
14691508 tid = Threads. threadid (t)
1470- if tid != 0
1471- ccall (:jl_send_cancellation_signal , Cvoid, (Int16,), (tid - 1 ) % Int16)
1472- end
1473- while ! istaskdone (t)
1509+ if ! istaskdone (t)
14741510 waitee = t. queue
1475- waitee === nothing && (yield (); continue )
1476- invokelatest (cancel_wait!, waitee, t) && break
1511+ if waitee != = nothing
1512+ invokelatest (cancel_wait!, waitee, t, crequest)
1513+ elseif tid != 0
1514+ ccall (:jl_send_cancellation_signal , Cvoid, (Int16,), (tid - 1 ) % Int16)
1515+ end
14771516 end
14781517 if t. sticky
14791518 # If this task is sticky, it won't be able to run if the task currently
@@ -1494,7 +1533,7 @@ function cancel!(t::Task, crequest=CANCEL_REQUEST_SAFE)
14941533 end
14951534end
14961535
1497- function cancel_wait! (q:: StickyWorkqueue , t:: Task )
1536+ function cancel_wait! (q:: StickyWorkqueue , t:: Task , @nospecialize (creq) )
14981537 # Tasks in the workqueue are runnable - we do not cancel the wait,
14991538 # but we do need to check whether it's in there
15001539 lock (q. lock)
0 commit comments