Skip to content

Commit 670944c

Browse files
committed
[5.5][Concurrency] Propagate Darwin vouchers across async tasks.
Darwin OSes support vouchers, which are key/value sets that can be adopted on a thread to influence its execution, or sent to another process. APIs like Dispatch propagate vouchers to worker threads when running async code. This change makes Swift Concurrency do the same. The change consists of a few different parts: 1. A set of shims (in VoucherShims.h) which provides declarations for the necessary calls when they're not available from the SDK, and stub implementations for non-Darwin platforms. 2. One of Job's reserved fields is now used to store the voucher associated with a job. 3. Jobs grab the current thread's voucher when they're created. 4. A VoucherManager class manages adoption of vouchers when running a Job, and replacing vouchers in suspended tasks. 5. A VoucherManager instance is maintained in ExecutionTrackingInfo, and is updated as necessary throughout a Job/Task's lifecycle. rdar://76080222 (cherry picked from commit a80a19b)
1 parent 43aaf29 commit 670944c

File tree

7 files changed

+647
-26
lines changed

7 files changed

+647
-26
lines changed

include/swift/ABI/Task.h

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "swift/ABI/Metadata.h"
2424
#include "swift/ABI/MetadataValues.h"
2525
#include "swift/Runtime/Config.h"
26+
#include "swift/Runtime/VoucherShims.h"
2627
#include "swift/Basic/STLExtras.h"
2728
#include "bitset"
2829
#include "queue" // TODO: remove and replace with our own mpsc
@@ -72,7 +73,13 @@ class alignas(2 * alignof(void*)) Job :
7273
// Derived classes can use this to store a Job Id.
7374
uint32_t Id = 0;
7475

75-
void *Reserved[2] = {};
76+
/// The voucher associated with the job. Note: this is currently unused on
77+
/// non-Darwin platforms, with stub implementations of the functions for
78+
/// consistency.
79+
voucher_t Voucher = nullptr;
80+
81+
/// Reserved for future use.
82+
void *Reserved = nullptr;
7683

7784
// We use this union to avoid having to do a second indirect branch
7885
// when resuming an asynchronous task, which we expect will be the
@@ -88,23 +95,32 @@ class alignas(2 * alignof(void*)) Job :
8895
Job(JobFlags flags, JobInvokeFunction *invoke,
8996
const HeapMetadata *metadata = &jobHeapMetadata)
9097
: HeapObject(metadata), Flags(flags), RunJob(invoke) {
98+
Voucher = voucher_copy();
9199
assert(!isAsyncTask() && "wrong constructor for a task");
92100
}
93101

94102
Job(JobFlags flags, TaskContinuationFunction *invoke,
95-
const HeapMetadata *metadata = &jobHeapMetadata)
103+
const HeapMetadata *metadata = &jobHeapMetadata,
104+
bool captureCurrentVoucher = true)
96105
: HeapObject(metadata), Flags(flags), ResumeTask(invoke) {
106+
if (captureCurrentVoucher)
107+
Voucher = voucher_copy();
97108
assert(isAsyncTask() && "wrong constructor for a non-task job");
98109
}
99110

100111
/// Create a job with "immortal" reference counts.
101112
/// Used for async let tasks.
102113
Job(JobFlags flags, TaskContinuationFunction *invoke,
103-
const HeapMetadata *metadata, InlineRefCounts::Immortal_t immortal)
114+
const HeapMetadata *metadata, InlineRefCounts::Immortal_t immortal,
115+
bool captureCurrentVoucher = true)
104116
: HeapObject(metadata, immortal), Flags(flags), ResumeTask(invoke) {
117+
if (captureCurrentVoucher)
118+
Voucher = voucher_copy();
105119
assert(isAsyncTask() && "wrong constructor for a non-task job");
106120
}
107121

122+
~Job() { swift_voucher_release(Voucher); }
123+
108124
bool isAsyncTask() const {
109125
return Flags.isAsyncTask();
110126
}
@@ -229,8 +245,9 @@ class AsyncTask : public Job {
229245
/// Private.initialize separately.
230246
AsyncTask(const HeapMetadata *metadata, JobFlags flags,
231247
TaskContinuationFunction *run,
232-
AsyncContext *initialContext)
233-
: Job(flags, run, metadata),
248+
AsyncContext *initialContext,
249+
bool captureCurrentVoucher)
250+
: Job(flags, run, metadata, captureCurrentVoucher),
234251
ResumeContext(initialContext) {
235252
assert(flags.isAsyncTask());
236253
Id = getNextTaskId();
@@ -243,8 +260,9 @@ class AsyncTask : public Job {
243260
AsyncTask(const HeapMetadata *metadata, InlineRefCounts::Immortal_t immortal,
244261
JobFlags flags,
245262
TaskContinuationFunction *run,
246-
AsyncContext *initialContext)
247-
: Job(flags, run, metadata, immortal),
263+
AsyncContext *initialContext,
264+
bool captureCurrentVoucher)
265+
: Job(flags, run, metadata, immortal, captureCurrentVoucher),
248266
ResumeContext(initialContext) {
249267
assert(flags.isAsyncTask());
250268
Id = getNextTaskId();

include/swift/Runtime/VoucherShims.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//===--- VoucherShims.h - Shims for OS vouchers --------------------*- C++ -*-//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// Shims for interfacing with OS voucher calls.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#ifndef SWIFT_CONCURRENCY_VOUCHERSHIMS_H
18+
#define SWIFT_CONCURRENCY_VOUCHERSHIMS_H
19+
20+
#include "Config.h"
21+
22+
// swift-corelibs-libdispatch has os/voucher_private.h but it doesn't work for
23+
// us yet, so only look for it on Apple platforms.
24+
#if __APPLE__ && __has_include(<os/voucher_private.h>)
25+
#define SWIFT_HAS_VOUCHER_HEADER 1
26+
#include <os/voucher_private.h>
27+
#endif
28+
29+
// A "dead" voucher pointer, indicating that a voucher has been removed from
30+
// a Job, distinct from a NULL voucher that could just mean no voucher was
31+
// present. This allows us to catch problems like adopting a voucher from the
32+
// same Job twice without restoring it.
33+
#define SWIFT_DEAD_VOUCHER ((voucher_t)-1)
34+
35+
// The OS has voucher support if it has the header or if it has ObjC interop.
36+
#if SWIFT_HAS_VOUCHER_HEADER || SWIFT_OBJC_INTEROP
37+
#define SWIFT_HAS_VOUCHERS 1
38+
#endif
39+
40+
#if SWIFT_HAS_VOUCHERS
41+
42+
// If the header isn't available, declare the necessary calls here.
43+
#if !SWIFT_HAS_VOUCHER_HEADER
44+
45+
#include <os/object.h>
46+
47+
#pragma clang diagnostic push
48+
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
49+
OS_OBJECT_DECL_CLASS(voucher);
50+
#pragma clang diagnostic pop
51+
52+
extern "C" voucher_t _Nullable voucher_copy(void);
53+
54+
// Consumes argument, returns retained value.
55+
extern "C" voucher_t _Nullable voucher_adopt(voucher_t _Nullable voucher);
56+
57+
static inline bool voucher_needs_adopt(voucher_t _Nullable voucher) {
58+
return true;
59+
}
60+
61+
#endif // __has_include(<os/voucher_private.h>)
62+
63+
static inline void swift_voucher_release(voucher_t _Nullable voucher) {
64+
// This NULL check isn't necessary, but NULL vouchers will be common, so
65+
// optimize for that.
66+
if (!voucher)
67+
return;
68+
if (voucher == SWIFT_DEAD_VOUCHER)
69+
return;
70+
os_release(voucher);
71+
}
72+
73+
#else // __APPLE__
74+
75+
// Declare some do-nothing stubs for OSes without voucher support.
76+
typedef void *voucher_t;
77+
static inline voucher_t _Nullable voucher_copy(void) { return nullptr; }
78+
static inline voucher_t _Nullable voucher_adopt(voucher_t _Nullable voucher) {
79+
return nullptr;
80+
}
81+
static inline bool voucher_needs_adopt(voucher_t _Nullable voucher) {
82+
return true;
83+
}
84+
static inline void swift_voucher_release(voucher_t _Nullable voucher) {}
85+
#endif // __APPLE__
86+
87+
#endif

stdlib/public/Concurrency/Actor.cpp

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "swift/ABI/Actor.h"
3737
#include "llvm/ADT/PointerIntPair.h"
3838
#include "TaskPrivate.h"
39+
#include "VoucherSupport.h"
3940
#include <dispatch/dispatch.h>
4041

4142
#if defined(__APPLE__)
@@ -123,6 +124,8 @@ class ExecutorTrackingInfo {
123124
/// unless the passed-in executor is generic.
124125
bool AllowsSwitching = true;
125126

127+
VoucherManager voucherManager;
128+
126129
/// The tracking info that was active when this one was entered.
127130
ExecutorTrackingInfo *SavedInfo;
128131

@@ -141,23 +144,9 @@ class ExecutorTrackingInfo {
141144
ActiveInfoInThread.set(this);
142145
}
143146

144-
/// Initialize a tracking state on the current thread if there
145-
/// isn't one already, or else update the current tracking state.
146-
///
147-
/// Returns a pointer to the active tracking info. If this is the
148-
/// same as the object on which this was called, leave() must be
149-
/// called before the object goes out of scope.
150-
ExecutorTrackingInfo *enterOrUpdate(ExecutorRef currentExecutor) {
151-
if (auto activeInfo = ActiveInfoInThread.get()) {
152-
activeInfo->ActiveExecutor = currentExecutor;
153-
return activeInfo;
154-
}
147+
void swapToJob(Job *job) { voucherManager.swapToJob(job); }
155148

156-
ActiveExecutor = currentExecutor;
157-
SavedInfo = nullptr;
158-
ActiveInfoInThread.set(this);
159-
return this;
160-
}
149+
void restoreVoucher(AsyncTask *task) { voucherManager.restoreVoucher(task); }
161150

162151
ExecutorRef getActiveExecutor() const {
163152
return ActiveExecutor;
@@ -183,6 +172,7 @@ class ExecutorTrackingInfo {
183172
}
184173

185174
void leave() {
175+
voucherManager.leave();
186176
ActiveInfoInThread.set(SavedInfo);
187177
}
188178
};
@@ -233,7 +223,9 @@ void swift::runJobInEstablishedExecutorContext(Job *job) {
233223
// Clear the active task.
234224
ActiveTask::set(nullptr);
235225
} else {
236-
// There's no extra bookkeeping to do for simple jobs.
226+
// There's no extra bookkeeping to do for simple jobs besides swapping in
227+
// the voucher.
228+
ExecutorTrackingInfo::current()->swapToJob(job);
237229
job->runSimpleInFullyEstablishedContext();
238230
}
239231

@@ -244,6 +236,14 @@ void swift::runJobInEstablishedExecutorContext(Job *job) {
244236
_swift_tsan_release(job);
245237
}
246238

239+
void swift::adoptTaskVoucher(AsyncTask *task) {
240+
ExecutorTrackingInfo::current()->swapToJob(task);
241+
}
242+
243+
void swift::restoreTaskVoucher(AsyncTask *task) {
244+
ExecutorTrackingInfo::current()->restoreVoucher(task);
245+
}
246+
247247
SWIFT_CC(swift)
248248
AsyncTask *swift::swift_task_getCurrent() {
249249
return ActiveTask::get();
@@ -661,6 +661,12 @@ class DefaultActorImpl : public HeapObject {
661661

662662
friend class ProcessInlineJob;
663663
union {
664+
// When the ProcessInlineJob storage is initialized, its metadata pointer
665+
// will point to Job's metadata. When it isn't, the metadata pointer is
666+
// NULL. Use HeapObject to initialize the metadata pointer to NULL and allow
667+
// it to be checked without fully initializing the ProcessInlineJob.
668+
HeapObject JobStorageHeapObject{nullptr};
669+
664670
ProcessInlineJob JobStorage;
665671
};
666672

@@ -669,6 +675,7 @@ class DefaultActorImpl : public HeapObject {
669675
/// Properly construct an actor, except for the heap header.
670676
void initialize() {
671677
new (&CurrentState) std::atomic<State>(State{JobRef(), Flags()});
678+
JobStorageHeapObject.metadata = nullptr;
672679
}
673680

674681
/// Properly destruct an actor, except for the heap header.
@@ -790,6 +797,8 @@ void DefaultActorImpl::deallocate() {
790797
}
791798

792799
void DefaultActorImpl::deallocateUnconditional() {
800+
if (JobStorageHeapObject.metadata != nullptr)
801+
JobStorage.~ProcessInlineJob();
793802
auto metadata = cast<ClassMetadata>(this->metadata);
794803
swift_deallocObject(this, metadata->getInstanceSize(),
795804
metadata->getInstanceAlignMask());
@@ -826,6 +835,8 @@ void DefaultActorImpl::scheduleNonOverrideProcessJob(JobPriority priority,
826835
if (hasActiveInlineJob) {
827836
job = new ProcessOutOfLineJob(this, priority);
828837
} else {
838+
if (JobStorageHeapObject.metadata != nullptr)
839+
JobStorage.~ProcessInlineJob();
829840
job = new (&JobStorage) ProcessInlineJob(priority);
830841
}
831842
swift_task_enqueueGlobal(job);
@@ -1437,6 +1448,7 @@ static void swift_job_runImpl(Job *job, ExecutorRef executor) {
14371448

14381449
trackingInfo.enterAndShadow(executor);
14391450

1451+
SWIFT_TASK_DEBUG_LOG("%s(%p)", __func__, job);
14401452
runJobInEstablishedExecutorContext(job);
14411453

14421454
trackingInfo.leave();

stdlib/public/Concurrency/Task.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -596,15 +596,18 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
596596
// Initialize the task so that resuming it will run the given
597597
// function on the initial context.
598598
AsyncTask *task = nullptr;
599+
bool captureCurrentVoucher = taskCreateFlags.copyTaskLocals() || jobFlags.task_isChildTask();
599600
if (asyncLet) {
600601
// Initialize the refcount bits to "immortal", so that
601602
// ARC operations don't have any effect on the task.
602603
task = new(allocation) AsyncTask(&taskHeapMetadata,
603604
InlineRefCounts::Immortal, jobFlags,
604-
function, initialContext);
605+
function, initialContext,
606+
captureCurrentVoucher);
605607
} else {
606608
task = new(allocation) AsyncTask(&taskHeapMetadata, jobFlags,
607-
function, initialContext);
609+
function, initialContext,
610+
captureCurrentVoucher);
608611
}
609612

610613
// Initialize the child fragment if applicable.

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ void _swift_task_dealloc_specific(AsyncTask *task, void *ptr);
5050
/// related to the active task.
5151
void runJobInEstablishedExecutorContext(Job *job);
5252

53+
/// Adopt the voucher stored in `task`. This removes the voucher from the task
54+
/// and adopts it on the current thread.
55+
void adoptTaskVoucher(AsyncTask *task);
56+
57+
/// Restore the voucher for `task`. This un-adopts the current thread's voucher
58+
/// and stores it back into the task again.
59+
void restoreTaskVoucher(AsyncTask *task);
60+
5361
/// Initialize the async let storage for the given async-let child task.
5462
void asyncLet_addImpl(AsyncTask *task, AsyncLet *asyncLet,
5563
bool didAllocateInParentTask);
@@ -322,11 +330,13 @@ inline bool AsyncTask::isCancelled() const {
322330
}
323331

324332
inline void AsyncTask::flagAsRunning() {
333+
SWIFT_TASK_DEBUG_LOG("%p->flagAsRunning()", this);
325334
auto oldStatus = _private().Status.load(std::memory_order_relaxed);
326335
while (true) {
327336
assert(!oldStatus.isRunning());
328337
if (oldStatus.isLocked()) {
329338
flagAsRunning_slow();
339+
adoptTaskVoucher(this);
330340
swift_task_enterThreadLocalContext(
331341
(char *)&_private().ExclusivityAccessSet[0]);
332342
return;
@@ -341,6 +351,7 @@ inline void AsyncTask::flagAsRunning() {
341351
if (_private().Status.compare_exchange_weak(oldStatus, newStatus,
342352
std::memory_order_relaxed,
343353
std::memory_order_relaxed)) {
354+
adoptTaskVoucher(this);
344355
swift_task_enterThreadLocalContext(
345356
(char *)&_private().ExclusivityAccessSet[0]);
346357
return;
@@ -349,13 +360,15 @@ inline void AsyncTask::flagAsRunning() {
349360
}
350361

351362
inline void AsyncTask::flagAsSuspended() {
363+
SWIFT_TASK_DEBUG_LOG("%p->flagAsSuspended()", this);
352364
auto oldStatus = _private().Status.load(std::memory_order_relaxed);
353365
while (true) {
354366
assert(oldStatus.isRunning());
355367
if (oldStatus.isLocked()) {
356368
flagAsSuspended_slow();
357369
swift_task_exitThreadLocalContext(
358370
(char *)&_private().ExclusivityAccessSet[0]);
371+
restoreTaskVoucher(this);
359372
return;
360373
}
361374

@@ -370,6 +383,7 @@ inline void AsyncTask::flagAsSuspended() {
370383
std::memory_order_relaxed)) {
371384
swift_task_exitThreadLocalContext(
372385
(char *)&_private().ExclusivityAccessSet[0]);
386+
restoreTaskVoucher(this);
373387
return;
374388
}
375389
}

0 commit comments

Comments
 (0)