Skip to content

Commit fbfbd41

Browse files
committed
Priority escalation support when actors run into contention
Radar-Id: rdar://problem/86100521
1 parent d9d5bbd commit fbfbd41

File tree

1 file changed

+116
-21
lines changed

1 file changed

+116
-21
lines changed

stdlib/public/Concurrency/Actor.cpp

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

618618
// Bit 3
619619
DistributedRemote = 0x8,
620+
// Bit 4
621+
isPriorityEscalated = 0x10,
620622

621623
// Bits 8 - 15. We only need 8 bits of the whole size_t to represent Job
622624
// Priority
623625
PriorityMask = 0xFF00,
626+
PriorityAndOverrideMask = PriorityMask | isPriorityEscalated,
624627
PriorityShift = 0x8,
625628
};
626629

@@ -725,11 +728,37 @@ class alignas(sizeof(void *) * 2) ActiveActorStatus {
725728
JobPriority getMaxPriority() const {
726729
return (JobPriority) ((Flags & PriorityMask) >> PriorityShift);
727730
}
728-
ActiveActorStatus withMaxPriority(JobPriority priority) const {
731+
ActiveActorStatus withNewPriority(JobPriority priority) const {
732+
uint32_t flags = Flags & ~PriorityAndOverrideMask;
733+
flags |= (uint32_t(priority) << PriorityShift);
729734
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
730-
return ActiveActorStatus((Flags & ~PriorityMask) | (uint32_t(priority) << PriorityShift), DrainLock, FirstJob);
735+
return ActiveActorStatus(flags, DrainLock, FirstJob);
731736
#else
732-
return ActiveActorStatus((Flags & ~PriorityMask) | (uint32_t(priority) << PriorityShift), FirstJob);
737+
return ActiveActorStatus(flags, FirstJob);
738+
#endif
739+
}
740+
ActiveActorStatus resetPriority() const {
741+
return withNewPriority(JobPriority::Unspecified);
742+
}
743+
744+
bool isMaxPriorityEscalated() const { return Flags & isPriorityEscalated; }
745+
ActiveActorStatus withEscalatedPriority(JobPriority priority) const {
746+
JobPriority currentPriority = JobPriority((Flags & PriorityMask) >> PriorityShift);
747+
assert(priority > currentPriority);
748+
749+
uint32_t flags = (Flags & ~PriorityMask) | (uint32_t(priority) << PriorityShift);
750+
flags |= isPriorityEscalated;
751+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
752+
return ActiveActorStatus(flags, DrainLock, FirstJob);
753+
#else
754+
return ActiveActorStatus(flags, FirstJob);
755+
#endif
756+
}
757+
ActiveActorStatus withoutEscalatedPriority() const {
758+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
759+
return ActiveActorStatus(Flags & ~isPriorityEscalated, DrainLock, FirstJob);
760+
#else
761+
return ActiveActorStatus(Flags & ~isPriorityEscalated, FirstJob);
733762
#endif
734763
}
735764

@@ -747,13 +776,20 @@ class alignas(sizeof(void *) * 2) ActiveActorStatus {
747776
uint32_t getOpaqueFlags() const {
748777
return Flags;
749778
}
779+
750780
uint32_t currentDrainer() const {
751781
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
752782
return dispatch_lock_owner(DrainLock);
753783
#else
754784
return 0;
755785
#endif
756786
}
787+
788+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
789+
static size_t drainLockOffset() {
790+
return offsetof(ActiveActorStatus, DrainLock);
791+
}
792+
#endif
757793
};
758794

759795
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION && SWIFT_POINTER_IS_4_BYTES
@@ -865,6 +901,10 @@ class DefaultActorImpl : public HeapObject {
865901
bool isDistributedRemote();
866902

867903
private:
904+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
905+
dispatch_lock_t *drainLockAddr();
906+
#endif
907+
868908
void deallocateUnconditional();
869909

870910
/// Schedule an inline processing job. This can generally only be
@@ -1031,6 +1071,13 @@ void DefaultActorImpl::deallocate() {
10311071
deallocateUnconditional();
10321072
}
10331073

1074+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1075+
dispatch_lock_t *DefaultActorImpl::drainLockAddr() {
1076+
ActiveActorStatus *actorStatus = (ActiveActorStatus *) &this->CurrentState;
1077+
return (dispatch_lock_t *) (((char *) actorStatus) + ActiveActorStatus::drainLockOffset());
1078+
}
1079+
#endif
1080+
10341081
void DefaultActorImpl::deallocateUnconditional() {
10351082
concurrency::trace::actor_deallocate(this);
10361083

@@ -1059,21 +1106,48 @@ void DefaultActorImpl::scheduleActorProcessJob(JobPriority priority, bool useInl
10591106

10601107

10611108
bool DefaultActorImpl::tryLock(bool asDrainer) {
1062-
SWIFT_TASK_DEBUG_LOG("Attempting to jump onto %p, as drainer = %d", this, asDrainer);
1063-
auto oldState = CurrentState.load(std::memory_order_relaxed);
1109+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1110+
SWIFT_TASK_DEBUG_LOG("Thread %#x attempting to jump onto %p, as drainer = %d", dispatch_lock_value_for_self(), this, asDrainer);
1111+
dispatch_thread_override_info_s threadOverrideInfo;
1112+
threadOverrideInfo = swift_dispatch_thread_get_current_override_qos_floor();
1113+
qos_class_t overrideFloor = threadOverrideInfo.override_qos_floor;
1114+
1115+
retry:;
1116+
#else
1117+
SWIFT_TASK_DEBUG_LOG("Thread attempting to jump onto %p, as drainer = %d", this, asDrainer);
1118+
#endif
10641119

1120+
auto oldState = CurrentState.load(std::memory_order_relaxed);
10651121
while (true) {
1122+
10661123
if (asDrainer) {
10671124
// TODO (rokhinip): Once we have OOL process job support, this assert can
10681125
// potentially fail due to a race with an actor stealer that might have
10691126
// won and started running the task
10701127
assert(oldState.isScheduled());
1128+
1129+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1130+
// We only want to self override a thread if we are taking the actor lock
1131+
// as a drainer because there might have been higher priority work
1132+
// enqueued that might have escalated the max priority of the actor to be
1133+
// higher than the original thread request.
1134+
qos_class_t maxActorPriority = (qos_class_t) oldState.getMaxPriority();
1135+
1136+
if (threadOverrideInfo.can_override && (maxActorPriority > overrideFloor)) {
1137+
SWIFT_TASK_DEBUG_LOG("[Override] Self-override thread with oq_floor %#x to match max actor %p's priority %#x", overrideFloor, this, maxActorPriority);
1138+
1139+
(void) swift_dispatch_thread_override_self(maxActorPriority);
1140+
overrideFloor = maxActorPriority;
1141+
goto retry;
1142+
}
1143+
#endif
10711144
} else {
10721145
// We're trying to take the lock in an uncontended manner
10731146
if (oldState.isRunning() || oldState.isScheduled()) {
10741147
SWIFT_TASK_DEBUG_LOG("Failed to jump to %p in fast path", this);
10751148
return false;
10761149
}
1150+
assert(oldState.getMaxPriority() == JobPriority::Unspecified);
10771151
}
10781152

10791153
auto newState = oldState.withRunning();
@@ -1106,11 +1180,10 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
11061180
// Someone gave up the actor lock after we failed fast path.
11071181
// Schedule the actor
11081182
newState = newState.withScheduled();
1109-
newState = newState.withMaxPriority(priority);
1110-
1183+
newState = newState.withNewPriority(priority);
11111184
} else {
11121185
if (priority > oldState.getMaxPriority()) {
1113-
newState = newState.withMaxPriority(priority);
1186+
newState = newState.withEscalatedPriority(priority);
11141187
}
11151188
}
11161189

@@ -1124,14 +1197,24 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
11241197
return scheduleActorProcessJob(newState.getMaxPriority(), true);
11251198
}
11261199

1200+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
11271201
if (oldState.getMaxPriority() != newState.getMaxPriority()) {
11281202
if (newState.isRunning()) {
1129-
// TODO (rokhinip): Override the thread running the actor
1130-
return;
1203+
// Actor is running on a thread, escalate the thread running it
1204+
SWIFT_TASK_DEBUG_LOG("[Override] Escalating actor %p which is running on %#x to %#x priority", this, newState.currentDrainer(), priority);
1205+
dispatch_lock_t *lockAddr = this->drainLockAddr();
1206+
swift_dispatch_lock_override_start_with_debounce(lockAddr, newState.currentDrainer(),
1207+
(qos_class_t) priority);
11311208
} else {
1132-
// TODO (rokhinip): Schedule the stealer
1209+
// TODO (rokhinip): Actor is scheduled - we need to schedule a
1210+
// stealer at the higher priority
1211+
//
1212+
// TODO (rokhinip): Add a signpost to flag that this is a potential
1213+
// priority inversion
1214+
SWIFT_TASK_DEBUG_LOG("[Override] Escalating actor %p which is enqueued", this);
11331215
}
11341216
}
1217+
#endif
11351218
return;
11361219
}
11371220
}
@@ -1153,30 +1236,38 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
11531236
_swift_tsan_release(this);
11541237
while (true) {
11551238
assert(oldState.isAnyRunning());
1156-
// TODO (rokhinip): Further assert that the current thread is the one
1157-
// running the actor
1239+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1240+
assert(dispatch_lock_is_locked_by_self(*(this->drainLockAddr())));
1241+
#endif
11581242

11591243
if (oldState.isZombie_ReadyForDeallocation()) {
1160-
// TODO (rokhinip): This is where we need to reset any override the thread
1161-
// might have as a result of this actor
1244+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1245+
// Reset any override on this thread as a result of this thread running
1246+
// the actor
1247+
if (oldState.isMaxPriorityEscalated()) {
1248+
swift_dispatch_lock_override_end((qos_class_t)oldState.getMaxPriority());
1249+
}
1250+
#endif
11621251
deallocateUnconditional();
11631252
SWIFT_TASK_DEBUG_LOG("Unlock-ing actor %p succeeded with full deallocation", this);
11641253
return true;
11651254
}
11661255

11671256
auto newState = oldState;
11681257
if (oldState.getFirstJob() != NULL) {
1169-
// There is work left to do
1258+
// There is work left to do, don't unlock the actor
11701259
if (!forceUnlock) {
11711260
SWIFT_TASK_DEBUG_LOG("Unlock-ing actor %p failed", this);
11721261
return false;
11731262
}
1174-
1263+
// We need to schedule the actor - remove any escalation bits since we'll
1264+
// schedule the actor at the max priority currently on it
11751265
newState = newState.withScheduled();
1266+
newState = newState.withoutEscalatedPriority();
11761267
} else {
11771268
// There is no work left to do - actor goes idle
11781269
newState = newState.withIdle();
1179-
newState = newState.withMaxPriority(JobPriority::Unspecified);
1270+
newState = newState.resetPriority();
11801271
}
11811272

11821273
if (CurrentState.compare_exchange_weak(oldState, newState,
@@ -1191,9 +1282,13 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
11911282
SWIFT_TASK_DEBUG_LOG("Actor %p is idle now", this);
11921283
}
11931284

1194-
// TODO (rokhinip): Reset any overrides the thread might have had as a
1195-
// result of the actor
1196-
1285+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1286+
// Reset any override on this thread as a result of this thread running
1287+
// the actor. Only do this after we have reenqueued the actor
1288+
if (oldState.isMaxPriorityEscalated()) {
1289+
swift_dispatch_lock_override_end((qos_class_t) oldState.getMaxPriority());
1290+
}
1291+
#endif
11971292
return true;
11981293
}
11991294
}

0 commit comments

Comments
 (0)