Swift 6.0.3 repairs actor queue lifetime assumptions
Two runtime corrections account for work that can execute immediately after enqueueing. The job and the actor can each disappear before the enqueueing function returns.
Putting work on a concurrent queue can transfer control sooner than the enqueueing function expects. Another thread may run and destroy the job before that function reaches its next instruction. Swift 6.0.3 includes actor-runtime corrections for code that continued to rely on objects after publishing work to another thread.
The Swift release archive records the maintenance release on December 11. One included correction moves task-executor preference lookup before enqueueing. Another retains the actor around the enqueue operation when necessary. They address related lifetime hazards involving different objects.
I value Swift's prompt reclamation through reference counting. That property also makes ownership transitions consequential in the runtime itself. A pointer that was valid before publishing a job is not enough to establish that its object remains alive afterward.
Publishing a job changes who can use it
The first correction concerns DefaultActorImpl::enqueue and its lookup of a job's task-executor preference. Before the correction, scheduling code could perform that lookup after the atomic operation that enqueued the job. The commit explains that another thread could already have run and destroyed the job.
The correction computes the needed executor preference before publishing the work. Scheduling then uses the saved preference, without reading the job again. The added source comment makes the rule explicit at the successful enqueue point, because the job is off limits once another thread can execute it.
That ordering matters independently of how little code appears between enqueueing and the later read. A fast path on another thread can complete while the original thread continues through its function. The publication point creates the possibility, so source proximity supplies no lifetime guarantee.
The implementation also avoids looking up the preference unconditionally. The commit describes that lookup as potentially expensive because it searches task status records. It computes the preference only when the observed actor states suggest scheduling or related priority work will need it.
This is a useful example of preserving a performance condition while fixing correctness. The lookup moves before the unsafe boundary, but the runtime still avoids it on paths that do not require the information. The scheduling decision uses data obtained while the job remains available to the enqueueing code.
The job can hold the actor's last reference
The second correction concerns the actor itself. The commit explains that the newly enqueued job may hold the last strong reference to that actor. If another thread executes the job and releases its reference, the actor can disappear while the original enqueue invocation is still active.
Some paths need the actor after the enqueue, particularly when priorities differ. The fix retains the actor before publishing the job on those paths. That provides a separate ownership claim for the remainder of the enqueueing function, independent of the reference held by the job.
The code uses a defensive-retain helper that performs at most one retain even if the surrounding compare-and-swap loop retries. Its destructor balances that retain when the scope exits. Keeping the lifetime management in the helper reduces the need to duplicate release handling across exits from the loop.
Another path needs to keep a reference for a scheduled processing job. The helper can transfer its retained reference to that longer-lived use. This avoids a redundant retain and release while preserving the ownership required by the scheduled work.
Atomic publication does not retain an object
The enqueue operation uses an atomic compare-and-swap to update the actor's queue state. Its source comment explains the release ordering that publishes the new job's contents. That establishes visibility for the consuming thread. It does not automatically keep either the job or the actor alive for later reads on the producer's thread.
These are separate responsibilities. Atomic ordering governs when other threads can observe state. Reference ownership governs when an object may be destroyed. The defects arose because successful publication allowed the consumer to make progress while the producer still assumed the objects were usable.
The corrected code makes that boundary harder to misuse. It clears the local job pointer after successful enqueueing and asserts the defensive actor retain on paths that still need the actor. Those implementation checks document the intended relationship between publication and subsequent access.
Runtime fixes support the checked language model
The Swift 6 release introduced the opt-in language mode that diagnoses potential data races as errors. These runtime corrections sit underneath that source-level checking. An application can follow actor isolation rules while depending on the scheduler to maintain its own internal lifetime invariants.
My interest in the patch is therefore the exact transfer point. Enqueueing grants another thread permission to consume work, and that work can release its final references immediately. Swift 6.0.3 saves job information before that point and retains the actor where later processing still needs it, giving the producer an explicit basis for every access that remains after publication.