Skip to content

Commit 72d15f6

Browse files
committed
Priority escalation support when actors run into contention
Radar-Id: rdar://problem/86100521
1 parent 6a9f55f commit 72d15f6

File tree

1 file changed

+114
-21
lines changed

1 file changed

+114
-21
lines changed

stdlib/public/Concurrency/Actor.cpp

Lines changed: 114 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -639,10 +639,13 @@ class alignas(sizeof(void *) * 2) ActiveActorStatus {
639639

640640
// Bit 3
641641
DistributedRemote = 0x8,
642+
// Bit 4
643+
isPriorityEscalated = 0x10,
642644

643645
// Bits 8 - 15. We only need 8 bits of the whole size_t to represent Job
644646
// Priority
645647
PriorityMask = 0xFF00,
648+
PriorityAndOverrideMask = PriorityMask | isPriorityEscalated,
646649
PriorityShift = 0x8,
647650
};
648651

@@ -758,11 +761,37 @@ class alignas(sizeof(void *) * 2) ActiveActorStatus {
758761
JobPriority getMaxPriority() const {
759762
return (JobPriority) ((Flags & PriorityMask) >> PriorityShift);
760763
}
761-
ActiveActorStatus withMaxPriority(JobPriority priority) const {
764+
ActiveActorStatus withNewPriority(JobPriority priority) const {
765+
uint32_t flags = Flags & ~PriorityAndOverrideMask;
766+
flags |= (uint32_t(priority) << PriorityShift);
762767
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
763-
return ActiveActorStatus((Flags & ~PriorityMask) | (uint32_t(priority) << PriorityShift), DrainLock, FirstJob);
768+
return ActiveActorStatus(flags, DrainLock, FirstJob);
764769
#else
765-
return ActiveActorStatus((Flags & ~PriorityMask) | (uint32_t(priority) << PriorityShift), FirstJob);
770+
return ActiveActorStatus(flags, FirstJob);
771+
#endif
772+
}
773+
ActiveActorStatus resetPriority() const {
774+
return withNewPriority(JobPriority::Unspecified);
775+
}
776+
777+
bool isMaxPriorityEscalated() const { return Flags & isPriorityEscalated; }
778+
ActiveActorStatus withEscalatedPriority(JobPriority priority) const {
779+
JobPriority currentPriority = JobPriority((Flags & PriorityMask) >> PriorityShift);
780+
assert(priority > currentPriority);
781+
782+
uint32_t flags = (Flags & ~PriorityMask) | (uint32_t(priority) << PriorityShift);
783+
flags |= isPriorityEscalated;
784+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
785+
return ActiveActorStatus(flags, DrainLock, FirstJob);
786+
#else
787+
return ActiveActorStatus(flags, FirstJob);
788+
#endif
789+
}
790+
ActiveActorStatus withoutEscalatedPriority() const {
791+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
792+
return ActiveActorStatus(Flags & ~isPriorityEscalated, DrainLock, FirstJob);
793+
#else
794+
return ActiveActorStatus(Flags & ~isPriorityEscalated, FirstJob);
766795
#endif
767796
}
768797

@@ -780,13 +809,20 @@ class alignas(sizeof(void *) * 2) ActiveActorStatus {
780809
uint32_t getOpaqueFlags() const {
781810
return Flags;
782811
}
812+
783813
uint32_t currentDrainer() const {
784814
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
785815
return dispatch_lock_owner(DrainLock);
786816
#else
787817
return 0;
788818
#endif
789819
}
820+
821+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
822+
static size_t drainLockOffset() {
823+
return offsetof(ActiveActorStatus, DrainLock);
824+
}
825+
#endif
790826
};
791827

792828
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION && SWIFT_POINTER_IS_4_BYTES
@@ -917,6 +953,10 @@ class DefaultActorImpl : public HeapObject {
917953
}
918954

919955
private:
956+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
957+
dispatch_lock_t *drainLockAddr();
958+
#endif
959+
920960
void deallocateUnconditional();
921961

922962
/// Schedule an inline processing job. This can generally only be
@@ -1085,6 +1125,13 @@ void DefaultActorImpl::deallocate() {
10851125
deallocateUnconditional();
10861126
}
10871127

1128+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1129+
dispatch_lock_t *DefaultActorImpl::drainLockAddr() {
1130+
ActiveActorStatus *actorStatus = (ActiveActorStatus *) &this->StatusStorage;
1131+
return (dispatch_lock_t *) (((char *) actorStatus) + ActiveActorStatus::drainLockOffset());
1132+
}
1133+
#endif
1134+
10881135
void DefaultActorImpl::deallocateUnconditional() {
10891136
concurrency::trace::actor_deallocate(this);
10901137

@@ -1113,23 +1160,48 @@ void DefaultActorImpl::scheduleActorProcessJob(JobPriority priority, bool useInl
11131160

11141161

11151162
bool DefaultActorImpl::tryLock(bool asDrainer) {
1116-
SWIFT_TASK_DEBUG_LOG("Attempting to jump onto %p, as drainer = %d", this, asDrainer);
1163+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1164+
SWIFT_TASK_DEBUG_LOG("Thread %#x attempting to jump onto %p, as drainer = %d", dispatch_lock_value_for_self(), this, asDrainer);
1165+
dispatch_thread_override_info_s threadOverrideInfo;
1166+
threadOverrideInfo = swift_dispatch_thread_get_current_override_qos_floor();
1167+
qos_class_t overrideFloor = threadOverrideInfo.override_qos_floor;
1168+
1169+
retry:;
1170+
#else
1171+
SWIFT_TASK_DEBUG_LOG("Thread attempting to jump onto %p, as drainer = %d", this, asDrainer);
1172+
#endif
11171173

11181174
auto oldState = _status().load(std::memory_order_relaxed);
11191175
while (true) {
11201176

1121-
while (true) {
11221177
if (asDrainer) {
11231178
// TODO (rokhinip): Once we have OOL process job support, this assert can
11241179
// potentially fail due to a race with an actor stealer that might have
11251180
// won the race and started running the actor
11261181
assert(oldState.isScheduled());
1182+
1183+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1184+
// We only want to self override a thread if we are taking the actor lock
1185+
// as a drainer because there might have been higher priority work
1186+
// enqueued that might have escalated the max priority of the actor to be
1187+
// higher than the original thread request.
1188+
qos_class_t maxActorPriority = (qos_class_t) oldState.getMaxPriority();
1189+
1190+
if (threadOverrideInfo.can_override && (maxActorPriority > overrideFloor)) {
1191+
SWIFT_TASK_DEBUG_LOG("[Override] Self-override thread with oq_floor %#x to match max actor %p's priority %#x", overrideFloor, this, maxActorPriority);
1192+
1193+
(void) swift_dispatch_thread_override_self(maxActorPriority);
1194+
overrideFloor = maxActorPriority;
1195+
goto retry;
1196+
}
1197+
#endif
11271198
} else {
11281199
// We're trying to take the lock in an uncontended manner
11291200
if (oldState.isRunning() || oldState.isScheduled()) {
11301201
SWIFT_TASK_DEBUG_LOG("Failed to jump to %p in fast path", this);
11311202
return false;
11321203
}
1204+
assert(oldState.getMaxPriority() == JobPriority::Unspecified);
11331205
}
11341206

11351207
auto newState = oldState.withRunning();
@@ -1162,11 +1234,10 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
11621234
// Someone gave up the actor lock after we failed fast path.
11631235
// Schedule the actor
11641236
newState = newState.withScheduled();
1165-
newState = newState.withMaxPriority(priority);
1166-
1237+
newState = newState.withNewPriority(priority);
11671238
} else {
11681239
if (priority > oldState.getMaxPriority()) {
1169-
newState = newState.withMaxPriority(priority);
1240+
newState = newState.withEscalatedPriority(priority);
11701241
}
11711242
}
11721243

@@ -1181,14 +1252,24 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
11811252
return scheduleActorProcessJob(newState.getMaxPriority(), true);
11821253
}
11831254

1255+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
11841256
if (oldState.getMaxPriority() != newState.getMaxPriority()) {
11851257
if (newState.isRunning()) {
1186-
// TODO (rokhinip): Override the thread running the actor
1187-
return;
1258+
// Actor is running on a thread, escalate the thread running it
1259+
SWIFT_TASK_DEBUG_LOG("[Override] Escalating actor %p which is running on %#x to %#x priority", this, newState.currentDrainer(), priority);
1260+
dispatch_lock_t *lockAddr = this->drainLockAddr();
1261+
swift_dispatch_lock_override_start_with_debounce(lockAddr, newState.currentDrainer(),
1262+
(qos_class_t) priority);
11881263
} else {
1189-
// TODO (rokhinip): Schedule the stealer
1264+
// TODO (rokhinip): Actor is scheduled - we need to schedule a
1265+
// stealer at the higher priority
1266+
//
1267+
// TODO (rokhinip): Add a signpost to flag that this is a potential
1268+
// priority inversion
1269+
SWIFT_TASK_DEBUG_LOG("[Override] Escalating actor %p which is enqueued", this);
11901270
}
11911271
}
1272+
#endif
11921273
return;
11931274
}
11941275
}
@@ -1212,30 +1293,38 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
12121293
_swift_tsan_release(this);
12131294
while (true) {
12141295
assert(oldState.isAnyRunning());
1215-
// TODO (rokhinip): Further assert that the current thread is the one
1216-
// running the actor
1296+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1297+
assert(dispatch_lock_is_locked_by_self(*(this->drainLockAddr())));
1298+
#endif
12171299

12181300
if (oldState.isZombie_ReadyForDeallocation()) {
1219-
// TODO (rokhinip): This is where we need to reset any override the thread
1220-
// might have as a result of this actor
1301+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1302+
// Reset any override on this thread as a result of this thread running
1303+
// the actor
1304+
if (oldState.isMaxPriorityEscalated()) {
1305+
swift_dispatch_lock_override_end((qos_class_t)oldState.getMaxPriority());
1306+
}
1307+
#endif
12211308
deallocateUnconditional();
12221309
SWIFT_TASK_DEBUG_LOG("Unlock-ing actor %p succeeded with full deallocation", this);
12231310
return true;
12241311
}
12251312

12261313
auto newState = oldState;
12271314
if (oldState.getFirstJob() != NULL) {
1228-
// There is work left to do
1315+
// There is work left to do, don't unlock the actor
12291316
if (!forceUnlock) {
12301317
SWIFT_TASK_DEBUG_LOG("Unlock-ing actor %p failed", this);
12311318
return false;
12321319
}
1233-
1320+
// We need to schedule the actor - remove any escalation bits since we'll
1321+
// schedule the actor at the max priority currently on it
12341322
newState = newState.withScheduled();
1323+
newState = newState.withoutEscalatedPriority();
12351324
} else {
12361325
// There is no work left to do - actor goes idle
12371326
newState = newState.withIdle();
1238-
newState = newState.withMaxPriority(JobPriority::Unspecified);
1327+
newState = newState.resetPriority();
12391328
}
12401329

12411330
if (_status().compare_exchange_weak(oldState, newState,
@@ -1252,9 +1341,13 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
12521341
SWIFT_TASK_DEBUG_LOG("Actor %p is idle now", this);
12531342
}
12541343

1255-
// TODO (rokhinip): Reset any overrides the thread might have had as a
1256-
// result of the actor
1257-
1344+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1345+
// Reset any override on this thread as a result of this thread running
1346+
// the actor. Only do this after we have reenqueued the actor
1347+
if (oldState.isMaxPriorityEscalated()) {
1348+
swift_dispatch_lock_override_end((qos_class_t) oldState.getMaxPriority());
1349+
}
1350+
#endif
12581351
return true;
12591352
}
12601353
}

0 commit comments

Comments
 (0)