@@ -399,15 +399,28 @@ class DefaultActorImpl : public HeapObject {
399399
400400 // / There is currently a thread processing the actor at the stored
401401 // / max priority.
402- Running
402+ Running,
403+
404+ // / The actor is a zombie that's been fully released but is still
405+ // / running. We delay deallocation until its running thread gives
406+ // / it up, which fortunately doesn't touch anything in the
407+ // / actor except for the DefaultActorImpl.
408+ // /
409+ // / To coordinate between the releasing thread and the running
410+ // / thread, we have a two-stage "latch". This is because the
411+ // / releasing thread does not atomically decide to not deallocate.
412+ // / Fortunately almost all of the overhead here is only incurred
413+ // / when we actually do end up in the zombie state.
414+ Zombie_Latching,
415+ Zombie_ReadyForDeallocation
403416 };
404417
405418 struct Flags : public FlagSet <size_t > {
406419 enum : size_t {
407420 Status_offset = 0 ,
408- Status_width = 2 ,
421+ Status_width = 3 ,
409422
410- HasActiveInlineJob = 2 ,
423+ HasActiveInlineJob = 3 ,
411424
412425 MaxPriority = 8 ,
413426 MaxPriority_width = JobFlags::Priority_width,
@@ -420,6 +433,19 @@ class DefaultActorImpl : public HeapObject {
420433 FLAGSET_DEFINE_FIELD_ACCESSORS (Status_offset, Status_width, Status,
421434 getStatus, setStatus)
422435
436+ bool isAnyRunningStatus () const {
437+ auto status = getStatus ();
438+ return status == Status::Running ||
439+ status == Status::Zombie_Latching ||
440+ status == Status::Zombie_ReadyForDeallocation;
441+ }
442+
443+ bool isAnyZombieStatus () const {
444+ auto status = getStatus ();
445+ return status == Status::Zombie_Latching ||
446+ status == Status::Zombie_ReadyForDeallocation;
447+ }
448+
423449 // / Is there currently an active processing job allocated inline
424450 // / in the actor?
425451 FLAGSET_DEFINE_FLAG_ACCESSORS (HasActiveInlineJob,
@@ -459,10 +485,12 @@ class DefaultActorImpl : public HeapObject {
459485 }
460486
461487 // / Properly destruct an actor, except for the heap header.
462- void destroy () {
463- assert (CurrentState.load (std::memory_order_relaxed).Flags .getStatus ()
464- == Status::Idle && " actor not idle during destruction?" );
465- }
488+ void destroy ();
489+
490+ // / Properly respond to the last release of a default actor. Note
491+ // / that the actor will have been completely torn down by the time
492+ // / we reach this point.
493+ void deallocate ();
466494
467495 // / Add a job to this actor.
468496 void enqueue (Job *job);
@@ -477,6 +505,8 @@ class DefaultActorImpl : public HeapObject {
477505 Job *claimNextJobOrGiveUp (bool actorIsOwned, RunningJobInfo runner);
478506
479507private:
508+ void deallocateUnconditional ();
509+
480510 // / Schedule an inline processing job. This can generally only be
481511 // / done if we know nobody else is trying to do it at the same time,
482512 // / e.g. if this thread just sucessfully transitioned the actor from
@@ -531,6 +561,53 @@ static DefaultActor *asAbstract(DefaultActorImpl *actor) {
531561/* ********************** DEFAULT ACTOR IMPLEMENTATION ************************/
532562/* ****************************************************************************/
533563
564+ void DefaultActorImpl::destroy () {
565+ auto oldState = CurrentState.load (std::memory_order_relaxed);
566+ while (true ) {
567+ assert (!oldState.FirstJob && " actor has queued jobs at destruction" );
568+ if (oldState.Flags .getStatus () == Status::Idle) return ;
569+
570+ assert (oldState.Flags .getStatus () == Status::Running &&
571+ " actor scheduled but not running at destruction" );
572+
573+ // If the actor is currently running, set it to zombie status
574+ // so that we know to deallocate it when it's given up.
575+ auto newState = oldState;
576+ newState.Flags .setStatus (Status::Zombie_Latching);
577+ if (CurrentState.compare_exchange_weak (oldState, newState,
578+ std::memory_order_relaxed,
579+ std::memory_order_relaxed))
580+ return ;
581+ }
582+ }
583+
584+ void DefaultActorImpl::deallocate () {
585+ // If we're in a zombie state waiting to latch, put the actor in the
586+ // ready-for-deallocation state, but don't actually deallocate yet.
587+ // Note that we should never see the actor already in the
588+ // ready-for-deallocation state; giving up the actor while in the
589+ // latching state will always put it in Idle state.
590+ auto oldState = CurrentState.load (std::memory_order_relaxed);
591+ while (oldState.Flags .getStatus () == Status::Zombie_Latching) {
592+ auto newState = oldState;
593+ newState.Flags .setStatus (Status::Zombie_ReadyForDeallocation);
594+ if (CurrentState.compare_exchange_weak (oldState, newState,
595+ std::memory_order_relaxed,
596+ std::memory_order_relaxed))
597+ return ;
598+ }
599+
600+ assert (oldState.Flags .getStatus () == Status::Idle);
601+
602+ deallocateUnconditional ();
603+ }
604+
605+ void DefaultActorImpl::deallocateUnconditional () {
606+ auto metadata = cast<ClassMetadata>(this ->metadata );
607+ swift_deallocObject (this , metadata->getInstanceSize (),
608+ metadata->getInstanceAlignMask ());
609+ }
610+
534611// / Given that a job is enqueued normally on a default actor, get/set
535612// / the next job in the actor's queue.
536613// /
@@ -888,17 +965,30 @@ void DefaultActorImpl::giveUpThread(RunningJobInfo runner) {
888965 fprintf (stderr, " [%p] giving up thread for actor %p\n " , pthread_self (), this );
889966#endif
890967 auto oldState = CurrentState.load (std::memory_order_acquire);
891- assert (oldState.Flags .getStatus () == Status::Running );
968+ assert (oldState.Flags .isAnyRunningStatus () );
892969
893970 ProcessOverrideJob *overridesToWake = nullptr ;
894971 auto firstNewJob = preprocessQueue (oldState.FirstJob , JobRef (), nullptr ,
895972 overridesToWake);
896973
897974 _swift_tsan_release (this );
898975 while (true ) {
976+ // In Zombie_ReadyForDeallocation state, nothing else should
977+ // be touching the atomic, and there's no point updating it.
978+ if (oldState.Flags .getStatus () == Status::Zombie_ReadyForDeallocation) {
979+ wakeOverrides (overridesToWake, oldState.Flags .getMaxPriority ());
980+ deallocateUnconditional ();
981+ return ;
982+ }
983+
984+ // In Zombie_Latching state, we should try to update to Idle;
985+ // if we beat the releasing thread, it'll deallocate.
986+ // In Running state, we may need to schedule a processing job.
987+
899988 State newState = oldState;
900989 newState.FirstJob = JobRef::getPreprocessed (firstNewJob);
901990 if (firstNewJob) {
991+ assert (oldState.Flags .getStatus () == Status::Running);
902992 newState.Flags .setStatus (Status::Scheduled);
903993 } else {
904994 newState.Flags .setStatus (Status::Idle);
@@ -946,7 +1036,7 @@ void DefaultActorImpl::giveUpThread(RunningJobInfo runner) {
9461036 // The priority of the remaining work.
9471037 auto newPriority = newState.Flags.getMaxPriority();
9481038
949- // Wake any overrides .
1039+ // Process the override commands we found .
9501040 wakeOverrides (overridesToWake, newPriority);
9511041
9521042 // This is the actor's owning job; per the ownership rules (see
@@ -983,13 +1073,16 @@ Job *DefaultActorImpl::claimNextJobOrGiveUp(bool actorIsOwned,
9831073
9841074 // The status had better be Running unless we're trying to acquire
9851075 // our first job.
986- assert (oldState.Flags .getStatus () == Status::Running ||
987- !actorIsOwned);
1076+ assert (oldState.Flags .isAnyRunningStatus () || !actorIsOwned);
9881077
9891078 // If we don't yet own the actor, we need to try to claim the actor
9901079 // first; we cannot safely access the queue memory yet because other
9911080 // threads may concurrently be trying to do this.
9921081 if (!actorIsOwned) {
1082+ // We really shouldn't ever be in a state where we're trying to take
1083+ // over a non-running actor if the actor is in a zombie state.
1084+ assert (!oldState.Flags .isAnyZombieStatus ());
1085+
9931086 while (true ) {
9941087 // A helper function when the only change we need to try is to
9951088 // update for an inline runner.
@@ -1061,7 +1154,7 @@ Job *DefaultActorImpl::claimNextJobOrGiveUp(bool actorIsOwned,
10611154 }
10621155 }
10631156
1064- assert (oldState.Flags .getStatus () == Status::Running );
1157+ assert (oldState.Flags .isAnyRunningStatus () );
10651158
10661159 // We should have taken care of the inline-job bookkeeping now.
10671160 assert (!oldState.Flags .hasActiveInlineJob () || !runner.wasInlineJob ());
@@ -1075,6 +1168,14 @@ Job *DefaultActorImpl::claimNextJobOrGiveUp(bool actorIsOwned,
10751168 Optional<JobPriority> remainingJobPriority;
10761169 _swift_tsan_release (this );
10771170 while (true ) {
1171+ // In Zombie_ReadyForDeallocation state, nothing else should
1172+ // be touching the atomic, and there's no point updating it.
1173+ if (oldState.Flags .getStatus () == Status::Zombie_ReadyForDeallocation) {
1174+ wakeOverrides (overridesToWake, oldState.Flags .getMaxPriority ());
1175+ deallocateUnconditional ();
1176+ return nullptr ;
1177+ }
1178+
10781179 State newState = oldState;
10791180
10801181 // If the priority we're currently running with is adqeuate for
@@ -1300,6 +1401,8 @@ void DefaultActorImpl::enqueue(Job *job) {
13001401 OverrideJobCache overrideJob;
13011402
13021403 while (true ) {
1404+ assert (!oldState.Flags .isAnyZombieStatus () &&
1405+ " enqueuing work on a zombie actor" );
13031406 auto newState = oldState;
13041407
13051408 // Put the job at the front of the job list (which will get
@@ -1387,6 +1490,9 @@ bool DefaultActorImpl::tryAssumeThread(RunningJobInfo runner) {
13871490 }
13881491 }
13891492
1493+ assert (!oldState.Flags .isAnyZombieStatus () &&
1494+ " trying to assume a zombie actor" );
1495+
13901496 return false ;
13911497}
13921498
@@ -1402,6 +1508,34 @@ void swift::swift_defaultActor_enqueue(Job *job, DefaultActor *_actor) {
14021508 asImpl (_actor)->enqueue (job);
14031509}
14041510
1511+ void swift::swift_defaultActor_deallocate (DefaultActor *_actor) {
1512+ asImpl (_actor)->deallocate ();
1513+ }
1514+
1515+ static bool isDefaultActorClass (const ClassMetadata *metadata) {
1516+ assert (metadata->isTypeMetadata ());
1517+ while (true ) {
1518+ // Trust the class descriptor if it says it's a default actor.
1519+ if (metadata->getDescription ()->isDefaultActor ())
1520+ return true ;
1521+
1522+ // Go to the superclass.
1523+ metadata = metadata->Superclass ;
1524+
1525+ // If we run out of Swift classes, it's not a default actor.
1526+ if (!metadata || !metadata->isTypeMetadata ()) return false ;
1527+ }
1528+ }
1529+
1530+ void swift::swift_defaultActor_deallocateResilient (HeapObject *actor) {
1531+ auto metadata = cast<ClassMetadata>(actor->metadata );
1532+ if (isDefaultActorClass (metadata))
1533+ return swift_defaultActor_deallocate (static_cast <DefaultActor*>(actor));
1534+
1535+ swift_deallocObject (actor, metadata->getInstanceSize (),
1536+ metadata->getInstanceAlignMask ());
1537+ }
1538+
14051539// / FIXME: only exists for the quick-and-dirty MainActor implementation.
14061540namespace swift {
14071541 Metadata* MainActorMetadata = nullptr ;
0 commit comments