@@ -399,15 +399,28 @@ class DefaultActorImpl : public HeapObject {
399
399
400
400
// / There is currently a thread processing the actor at the stored
401
401
// / 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
403
416
};
404
417
405
418
struct Flags : public FlagSet <size_t > {
406
419
enum : size_t {
407
420
Status_offset = 0 ,
408
- Status_width = 2 ,
421
+ Status_width = 3 ,
409
422
410
- HasActiveInlineJob = 2 ,
423
+ HasActiveInlineJob = 3 ,
411
424
412
425
MaxPriority = 8 ,
413
426
MaxPriority_width = JobFlags::Priority_width,
@@ -420,6 +433,19 @@ class DefaultActorImpl : public HeapObject {
420
433
FLAGSET_DEFINE_FIELD_ACCESSORS (Status_offset, Status_width, Status,
421
434
getStatus, setStatus)
422
435
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
+
423
449
// / Is there currently an active processing job allocated inline
424
450
// / in the actor?
425
451
FLAGSET_DEFINE_FLAG_ACCESSORS (HasActiveInlineJob,
@@ -459,10 +485,12 @@ class DefaultActorImpl : public HeapObject {
459
485
}
460
486
461
487
// / 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 ();
466
494
467
495
// / Add a job to this actor.
468
496
void enqueue (Job *job);
@@ -477,6 +505,8 @@ class DefaultActorImpl : public HeapObject {
477
505
Job *claimNextJobOrGiveUp (bool actorIsOwned, RunningJobInfo runner);
478
506
479
507
private:
508
+ void deallocateUnconditional ();
509
+
480
510
// / Schedule an inline processing job. This can generally only be
481
511
// / done if we know nobody else is trying to do it at the same time,
482
512
// / e.g. if this thread just sucessfully transitioned the actor from
@@ -531,6 +561,53 @@ static DefaultActor *asAbstract(DefaultActorImpl *actor) {
531
561
/* ********************** DEFAULT ACTOR IMPLEMENTATION ************************/
532
562
/* ****************************************************************************/
533
563
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
+
534
611
// / Given that a job is enqueued normally on a default actor, get/set
535
612
// / the next job in the actor's queue.
536
613
// /
@@ -888,17 +965,30 @@ void DefaultActorImpl::giveUpThread(RunningJobInfo runner) {
888
965
fprintf (stderr, " [%p] giving up thread for actor %p\n " , pthread_self (), this );
889
966
#endif
890
967
auto oldState = CurrentState.load (std::memory_order_acquire);
891
- assert (oldState.Flags .getStatus () == Status::Running );
968
+ assert (oldState.Flags .isAnyRunningStatus () );
892
969
893
970
ProcessOverrideJob *overridesToWake = nullptr ;
894
971
auto firstNewJob = preprocessQueue (oldState.FirstJob , JobRef (), nullptr ,
895
972
overridesToWake);
896
973
897
974
_swift_tsan_release (this );
898
975
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
+
899
988
State newState = oldState;
900
989
newState.FirstJob = JobRef::getPreprocessed (firstNewJob);
901
990
if (firstNewJob) {
991
+ assert (oldState.Flags .getStatus () == Status::Running);
902
992
newState.Flags .setStatus (Status::Scheduled);
903
993
} else {
904
994
newState.Flags .setStatus (Status::Idle);
@@ -946,7 +1036,7 @@ void DefaultActorImpl::giveUpThread(RunningJobInfo runner) {
946
1036
// The priority of the remaining work.
947
1037
auto newPriority = newState.Flags.getMaxPriority();
948
1038
949
- // Wake any overrides .
1039
+ // Process the override commands we found .
950
1040
wakeOverrides (overridesToWake, newPriority);
951
1041
952
1042
// This is the actor's owning job; per the ownership rules (see
@@ -983,13 +1073,16 @@ Job *DefaultActorImpl::claimNextJobOrGiveUp(bool actorIsOwned,
983
1073
984
1074
// The status had better be Running unless we're trying to acquire
985
1075
// our first job.
986
- assert (oldState.Flags .getStatus () == Status::Running ||
987
- !actorIsOwned);
1076
+ assert (oldState.Flags .isAnyRunningStatus () || !actorIsOwned);
988
1077
989
1078
// If we don't yet own the actor, we need to try to claim the actor
990
1079
// first; we cannot safely access the queue memory yet because other
991
1080
// threads may concurrently be trying to do this.
992
1081
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
+
993
1086
while (true ) {
994
1087
// A helper function when the only change we need to try is to
995
1088
// update for an inline runner.
@@ -1061,7 +1154,7 @@ Job *DefaultActorImpl::claimNextJobOrGiveUp(bool actorIsOwned,
1061
1154
}
1062
1155
}
1063
1156
1064
- assert (oldState.Flags .getStatus () == Status::Running );
1157
+ assert (oldState.Flags .isAnyRunningStatus () );
1065
1158
1066
1159
// We should have taken care of the inline-job bookkeeping now.
1067
1160
assert (!oldState.Flags .hasActiveInlineJob () || !runner.wasInlineJob ());
@@ -1075,6 +1168,14 @@ Job *DefaultActorImpl::claimNextJobOrGiveUp(bool actorIsOwned,
1075
1168
Optional<JobPriority> remainingJobPriority;
1076
1169
_swift_tsan_release (this );
1077
1170
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
+
1078
1179
State newState = oldState;
1079
1180
1080
1181
// If the priority we're currently running with is adqeuate for
@@ -1300,6 +1401,8 @@ void DefaultActorImpl::enqueue(Job *job) {
1300
1401
OverrideJobCache overrideJob;
1301
1402
1302
1403
while (true ) {
1404
+ assert (!oldState.Flags .isAnyZombieStatus () &&
1405
+ " enqueuing work on a zombie actor" );
1303
1406
auto newState = oldState;
1304
1407
1305
1408
// Put the job at the front of the job list (which will get
@@ -1387,6 +1490,9 @@ bool DefaultActorImpl::tryAssumeThread(RunningJobInfo runner) {
1387
1490
}
1388
1491
}
1389
1492
1493
+ assert (!oldState.Flags .isAnyZombieStatus () &&
1494
+ " trying to assume a zombie actor" );
1495
+
1390
1496
return false ;
1391
1497
}
1392
1498
@@ -1402,6 +1508,34 @@ void swift::swift_defaultActor_enqueue(Job *job, DefaultActor *_actor) {
1402
1508
asImpl (_actor)->enqueue (job);
1403
1509
}
1404
1510
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
+
1405
1539
// / FIXME: only exists for the quick-and-dirty MainActor implementation.
1406
1540
namespace swift {
1407
1541
Metadata* MainActorMetadata = nullptr ;
0 commit comments