Skip to content

Commit d9d5bbd

Browse files
committed
Track the identity of the task draining the actor in the
ActiveActorStatus Radar-Id: rdar://problem/86100521
1 parent d63bf06 commit d9d5bbd

File tree

1 file changed

+121
-5
lines changed

1 file changed

+121
-5
lines changed

stdlib/public/Concurrency/Actor.cpp

Lines changed: 121 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,64 @@ class JobRef {
528528
}
529529
};
530530

531-
/// This is designed to fit into two words, which can generally be
532-
/// done lock-free on all our supported platforms.
531+
/// Similar to the ActiveTaskStatus, this denotes the ActiveActorState for
532+
/// tracking the atomic state of the actor
533+
///
534+
/// The runtime needs to track the following state about the actor within the
535+
/// same atomic:
536+
///
537+
/// * The current status of the actor - scheduled, running, idle, etc
538+
/// * The current maximum priority of the jobs enqueued in the actor
539+
/// * The identity of the thread currently holding the actor lock
540+
/// * Pointer to list of jobs enqueued in actor
541+
///
542+
/// It is important for all of this information to be in the same atomic so that
543+
/// when the actor's state changes, the information is visible to all threads that
544+
/// may be modifying the actor, allowing the algorithm to eventually converge.
545+
///
546+
/// In order to provide priority escalation support with actors, deeper integration is
547+
/// required with the OS in order to have the intended side effects. On Darwin, Swift
548+
/// Concurrency Tasks runs on dispatch's queues. As such, we need to use an
549+
/// encoding of thread identity vended by libdispatch called dispatch_lock_t,
550+
/// and a futex-style dispatch API in order to escalate the priority of a
551+
/// thread. Henceforth, the dispatch_lock_t tracked in the ActiveActorStatus
552+
/// will be called the DrainLock.
553+
///
554+
/// When a thread starts running on an actor, it's identity is recorded in the
555+
/// ActiveActorStatus. This way, if a higher priority job is enqueued behind the
556+
/// thread executing the actor, we can escalate the thread holding the actor
557+
/// lock, thereby resolving the priority inversion. When a thread hops off of
558+
/// the actor, any priority boosts it may have gotten as a result of contention
559+
/// on the actor, is removed as well.
560+
///
561+
/// In order to keep the entire ActiveActorStatus size to 2 words, the thread
562+
/// identity is only tracked on platforms which can support 128 bit atomic
563+
/// operations. The ActiveActorStatus's layout has thus been changed to have the
564+
/// following layout depending on the system configuration supported:
565+
///
566+
/// 32 bit systems with SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION=1
567+
///
568+
/// Flags Drain Lock Unused JobRef
569+
/// |----------------------|----------------------|----------------------|-------------------|
570+
/// 32 bits 32 bits 32 bits 32 bits
571+
///
572+
/// 64 bit systems with SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION=1
573+
///
574+
/// Flags Drain Lock JobRef
575+
/// |----------------------|-------------------|----------------------|
576+
/// 32 bits 32 bits 64 bits
577+
///
578+
/// 32 bit systems with SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION=0
579+
///
580+
/// Flags JobRef
581+
/// |----------------------|----------------------|
582+
/// 32 bits 32 bits
583+
//
584+
/// 64 bit systems with SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION=0
585+
///
586+
/// Flags Unused JobRef
587+
/// |----------------------|----------------------|---------------------|
588+
/// 32 bits 32 bits 64 bits
533589
class alignas(sizeof(void *) * 2) ActiveActorStatus {
534590
enum : uint32_t {
535591
// Bits 0-2: Actor state
@@ -567,11 +623,29 @@ class alignas(sizeof(void *) * 2) ActiveActorStatus {
567623
PriorityMask = 0xFF00,
568624
PriorityShift = 0x8,
569625
};
626+
627+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION && SWIFT_POINTER_IS_4_BYTES
628+
uint32_t Flags;
629+
dispatch_lock_t DrainLock;
630+
uint32_t Unused;
631+
#elif SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION && SWIFT_POINTER_IS_8_BYTES
570632
uint32_t Flags;
633+
dispatch_lock_t DrainLock;
634+
#elif !SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION && SWIFT_POINTER_IS_4_BYTES
635+
uint32_t Flags;
636+
#else /* !SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION && SWIFT_POINTER_IS_8_BYTES */
637+
uint32_t Flags;
638+
uint32_t Unused;
639+
#endif
571640
JobRef FirstJob;
572641

642+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
643+
ActiveActorStatus(uint32_t flags, dispatch_lock_t drainLockValue, JobRef job)
644+
: Flags(flags), DrainLock(drainLockValue), FirstJob(job) {}
645+
#else
573646
ActiveActorStatus(uint32_t flags, JobRef job)
574647
: Flags(flags), FirstJob(job) {}
648+
#endif
575649

576650
public:
577651
#ifdef __GLIBCXX__
@@ -582,11 +656,15 @@ class alignas(sizeof(void *) * 2) ActiveActorStatus {
582656
#endif
583657

584658
constexpr ActiveActorStatus()
585-
: Flags(), FirstJob(JobRef()) {}
659+
: Flags(), DrainLock(DLOCK_OWNER_NULL), FirstJob(JobRef()) {}
586660

587661
bool isDistributedRemote() const { return Flags & DistributedRemote; }
588662
ActiveActorStatus withDistributedRemote() const {
663+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
664+
return ActiveActorStatus(Flags | DistributedRemote, DrainLock, FirstJob);
665+
#else
589666
return ActiveActorStatus(Flags | DistributedRemote, FirstJob);
667+
#endif
590668
}
591669

592670
bool isIdle() const {
@@ -597,7 +675,11 @@ class alignas(sizeof(void *) * 2) ActiveActorStatus {
597675
return isIdle;
598676
}
599677
ActiveActorStatus withIdle() const {
678+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
679+
return ActiveActorStatus((Flags & ~ActorStateMask) | Idle, DLOCK_OWNER_NULL, FirstJob);
680+
#else
600681
return ActiveActorStatus((Flags & ~ActorStateMask) | Idle, FirstJob);
682+
#endif
601683
}
602684

603685
bool isAnyRunning() const {
@@ -609,44 +691,78 @@ class alignas(sizeof(void *) * 2) ActiveActorStatus {
609691
}
610692
ActiveActorStatus withRunning() const {
611693
uint32_t flags = (Flags & ~ActorStateMask) | Running;
694+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
695+
return ActiveActorStatus(flags, dispatch_lock_value_for_self(), FirstJob);
696+
#else
612697
return ActiveActorStatus(flags, FirstJob);
698+
#endif
613699
}
614700

615701
bool isScheduled() const {
616702
return (Flags & ActorStateMask) == Scheduled;
617703
}
704+
618705
ActiveActorStatus withScheduled() const {
706+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
707+
return ActiveActorStatus((Flags & ~ActorStateMask) | Scheduled, DLOCK_OWNER_NULL, FirstJob);
708+
#else
619709
return ActiveActorStatus((Flags & ~ActorStateMask) | Scheduled, FirstJob);
710+
#endif
620711
}
621712

622713
bool isZombie_ReadyForDeallocation() const {
623714
return (Flags & ActorStateMask) == Zombie_ReadyForDeallocation;
624715
}
625716
ActiveActorStatus withZombie_ReadyForDeallocation() const {
717+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
718+
assert(dispatch_lock_owner(DrainLock) != DLOCK_OWNER_NULL);
719+
return ActiveActorStatus((Flags & ~ActorStateMask) | Zombie_ReadyForDeallocation, DrainLock, FirstJob);
720+
#else
626721
return ActiveActorStatus((Flags & ~ActorStateMask) | Zombie_ReadyForDeallocation, FirstJob);
722+
#endif
627723
}
628724

629725
JobPriority getMaxPriority() const {
630726
return (JobPriority) ((Flags & PriorityMask) >> PriorityShift);
631727
}
632728
ActiveActorStatus withMaxPriority(JobPriority priority) const {
633-
return ActiveActorStatus((Flags & ~PriorityMask) | (uint32_t(priority) << PriorityShift) , FirstJob);
729+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
730+
return ActiveActorStatus((Flags & ~PriorityMask) | (uint32_t(priority) << PriorityShift), DrainLock, FirstJob);
731+
#else
732+
return ActiveActorStatus((Flags & ~PriorityMask) | (uint32_t(priority) << PriorityShift), FirstJob);
733+
#endif
634734
}
635735

636736
JobRef getFirstJob() const {
637737
return FirstJob;
638738
}
639739
ActiveActorStatus withFirstJob(JobRef firstJob) const {
740+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
741+
return ActiveActorStatus(Flags, DrainLock, firstJob);
742+
#else
640743
return ActiveActorStatus(Flags, firstJob);
744+
#endif
641745
}
642746

643747
uint32_t getOpaqueFlags() const {
644748
return Flags;
645749
}
750+
uint32_t currentDrainer() const {
751+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
752+
return dispatch_lock_owner(DrainLock);
753+
#else
754+
return 0;
755+
#endif
756+
}
646757
};
647758

759+
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION && SWIFT_POINTER_IS_4_BYTES
760+
static_assert(sizeof(ActiveActorStatus) == 4 * sizeof(uintptr_t),
761+
"ActiveActorStatus is 4 words large");
762+
#else
648763
static_assert(sizeof(ActiveActorStatus) == 2 * sizeof(uintptr_t),
649-
"ActiveTaskStatus is 2 words large");
764+
"ActiveActorStatus is 2 words large");
765+
#endif
650766

651767
/// The default actor implementation.
652768
///

0 commit comments

Comments
 (0)