@@ -973,7 +973,6 @@ class DefaultActorImplHeader : public HeapObject {
973
973
class DefaultActorImplFooter {
974
974
protected:
975
975
#if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
976
- using SimpleQueue = swift::SimpleQueue<Job *, JobQueueTraits>;
977
976
using PriorityQueue = swift::PriorityQueue<Job *, JobQueueTraits>;
978
977
979
978
// When enqueued, jobs are atomically added to a linked list with the head
@@ -1078,9 +1077,13 @@ class DefaultActorImpl
1078
1077
// / new priority
1079
1078
void enqueueStealer (Job *job, JobPriority priority);
1080
1079
1081
- // / Dequeues one job from `prioritizedJobs `.
1080
+ // / Dequeues one job from `prioritisedJobs `.
1082
1081
// / The calling thread must be holding the actor lock while calling this
1083
1082
Job *drainOne ();
1083
+
1084
+ // / Atomically claims incoming jobs from ActiveActorStatus, and calls `handleUnprioritizedJobs()`.
1085
+ // / Called with actor lock held on current thread.
1086
+ void processIncomingQueue ();
1084
1087
#endif
1085
1088
1086
1089
// / Check if the actor is actually a distributed *remote* actor.
@@ -1117,14 +1120,10 @@ class DefaultActorImpl
1117
1120
// / when actor gets a priority override and we schedule a stealer.
1118
1121
void scheduleActorProcessJob (JobPriority priority);
1119
1122
1120
- // / Atomically takes a list of jobs from ActiveActorStatus, reversing them in
1121
- // / the process. Returns jobs of mixed priorities in FIFO order.
1122
- SimpleQueue collectJobs ();
1123
-
1124
- // / Check for new jobs in the incoming queue and move them to the
1125
- // / processing queue.
1123
+ // / Processes claimed incoming jobs into `prioritizedJobs`.
1124
+ // / Incoming jobs are of mixed priorities and in LIFO order.
1126
1125
// / Called with actor lock held on current thread.
1127
- void processJobs ( );
1126
+ void handleUnprioritizedJobs (Job *head );
1128
1127
#endif /* !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS */
1129
1128
1130
1129
void deallocateUnconditional ();
@@ -1370,13 +1369,12 @@ void DefaultActorImpl::enqueueStealer(Job *job, JobPriority priority) {
1370
1369
1371
1370
}
1372
1371
1373
- DefaultActorImpl::SimpleQueue DefaultActorImpl::collectJobs () {
1372
+ void DefaultActorImpl::processIncomingQueue () {
1374
1373
// Pairs with the store release in DefaultActorImpl::enqueue
1375
1374
bool distributedActorIsRemote = swift_distributed_actor_is_remote (this );
1376
1375
auto oldState = _status ().load (SWIFT_MEMORY_ORDER_CONSUME);
1377
1376
_swift_tsan_consume (this );
1378
1377
1379
- SimpleQueue result;
1380
1378
// We must ensure that any jobs not seen by the collectJobs() don't have any
1381
1379
// dangling references to the jobs that have been collected. For that we must
1382
1380
// atomically set head pointer to NULL. If it fails because more jobs have
@@ -1385,7 +1383,7 @@ DefaultActorImpl::SimpleQueue DefaultActorImpl::collectJobs() {
1385
1383
// If there aren't any new jobs in the incoming queue, we can return
1386
1384
// immediately without updating the status.
1387
1385
if (!oldState.getFirstUnprioritisedJob ()) {
1388
- return result ;
1386
+ return ;
1389
1387
}
1390
1388
assert (oldState.isAnyRunning ());
1391
1389
@@ -1403,28 +1401,26 @@ DefaultActorImpl::SimpleQueue DefaultActorImpl::collectJobs() {
1403
1401
}
1404
1402
}
1405
1403
1406
- // Collect jobs, reversing them in the process
1407
- auto job = oldState.getFirstUnprioritisedJob ();
1408
- while (job) {
1409
- auto next = getNextJob (job);
1410
- result.prepend (job);
1411
- job = next;
1412
- }
1413
-
1414
- return result;
1404
+ handleUnprioritizedJobs (oldState.getFirstUnprioritisedJob ());
1415
1405
}
1416
1406
1417
1407
// Called with actor lock held on current thread
1418
- void DefaultActorImpl::processJobs () {
1419
- SimpleQueue jobs = collectJobs ();
1420
- prioritizedJobs.enqueueContentsOf (jobs.head );
1408
+ void DefaultActorImpl::handleUnprioritizedJobs (Job *head) {
1409
+ // Reverse jobs from LIFO to FIFO order
1410
+ Job *reversed = nullptr ;
1411
+ while (head) {
1412
+ auto next = getNextJob (head);
1413
+ setNextJob (head, reversed);
1414
+ reversed = head;
1415
+ head = next;
1416
+ }
1417
+ prioritizedJobs.enqueueContentsOf (reversed);
1421
1418
}
1422
1419
1423
1420
// Called with actor lock held on current thread
1424
1421
Job *DefaultActorImpl::drainOne () {
1425
1422
SWIFT_TASK_DEBUG_LOG (" Draining one job from default actor %p" , this );
1426
1423
1427
- processJobs ();
1428
1424
traceJobQueue (this , prioritizedJobs.peek ());
1429
1425
auto firstJob = prioritizedJobs.dequeue ();
1430
1426
if (!firstJob) {
@@ -1488,39 +1484,40 @@ static void defaultActorDrain(DefaultActorImpl *actor) {
1488
1484
TaskExecutorRef::undefined ());
1489
1485
1490
1486
while (true ) {
1491
- if (shouldYieldThread ()) {
1492
- currentActor->unlock (true );
1493
- break ;
1494
- }
1495
-
1496
1487
Job *job = currentActor->drainOne ();
1497
1488
if (job == NULL ) {
1498
1489
// No work left to do, try unlocking the actor. This may fail if there is
1499
1490
// work concurrently enqueued in which case, we'd try again in the loop
1500
- if (!currentActor->unlock (false )) {
1501
- continue ;
1491
+ if (currentActor->unlock (false )) {
1492
+ break ;
1493
+ }
1494
+ } else {
1495
+ if (AsyncTask *task = dyn_cast<AsyncTask>(job)) {
1496
+ auto taskExecutor = task->getPreferredTaskExecutor ();
1497
+ trackingInfo.setTaskExecutor (taskExecutor);
1502
1498
}
1503
- break ;
1504
- }
1505
1499
1506
- if (AsyncTask *task = dyn_cast<AsyncTask>(job)) {
1507
- auto taskExecutor = task->getPreferredTaskExecutor ();
1508
- trackingInfo.setTaskExecutor (taskExecutor);
1500
+ // This thread is now going to follow the task on this actor. It may hop off
1501
+ // the actor
1502
+ runJobInEstablishedExecutorContext (job);
1503
+
1504
+ // We could have come back from the job on a generic executor and not as
1505
+ // part of a default actor. If so, there is no more work left for us to do
1506
+ // here.
1507
+ auto currentExecutor = trackingInfo.getActiveExecutor ();
1508
+ if (!currentExecutor.isDefaultActor ()) {
1509
+ currentActor = nullptr ;
1510
+ break ;
1511
+ }
1512
+ currentActor = asImpl (currentExecutor.getDefaultActor ());
1509
1513
}
1510
1514
1511
- // This thread is now going to follow the task on this actor. It may hop off
1512
- // the actor
1513
- runJobInEstablishedExecutorContext (job);
1514
-
1515
- // We could have come back from the job on a generic executor and not as
1516
- // part of a default actor. If so, there is no more work left for us to do
1517
- // here.
1518
- auto currentExecutor = trackingInfo.getActiveExecutor ();
1519
- if (!currentExecutor.isDefaultActor ()) {
1520
- currentActor = nullptr ;
1515
+ if (shouldYieldThread ()) {
1516
+ currentActor->unlock (true );
1521
1517
break ;
1522
1518
}
1523
- currentActor = asImpl (currentExecutor.getDefaultActor ());
1519
+
1520
+ currentActor->processIncomingQueue ();
1524
1521
}
1525
1522
1526
1523
// Leave the tracking info.
@@ -1666,6 +1663,17 @@ retry:;
1666
1663
auto newState = oldState.withRunning ();
1667
1664
newState = newState.withoutEscalatedPriority ();
1668
1665
1666
+ // Claim incoming jobs when obtaining lock as a drainer, to save one
1667
+ // round of atomic load and compare-exchange.
1668
+ // This is not useful when obtaining lock for assuming thread during actor
1669
+ // switching, because arbitrary use code can run between locking and
1670
+ // draining the next job. So we still need to call processIncomingQueue() to
1671
+ // check for higher priority jobs that could have been scheduled in the
1672
+ // meantime. And processing is more efficient when done in larger batches.
1673
+ if (asDrainer) {
1674
+ newState = newState.withFirstUnprioritisedJob (nullptr );
1675
+ }
1676
+
1669
1677
// This needs an acquire since we are taking a lock
1670
1678
if (_status ().compare_exchange_weak (oldState, newState,
1671
1679
std::memory_order_acquire,
@@ -1675,6 +1683,9 @@ retry:;
1675
1683
assert (prioritizedJobs.empty ());
1676
1684
}
1677
1685
traceActorStateTransition (this , oldState, newState, distributedActorIsRemote);
1686
+ if (asDrainer) {
1687
+ handleUnprioritizedJobs (oldState.getFirstUnprioritisedJob ());
1688
+ }
1678
1689
return true ;
1679
1690
}
1680
1691
}
0 commit comments