@@ -912,10 +912,9 @@ class DefaultActorImpl : public HeapObject {
912
912
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
913
913
dispatch_lock_t *drainLockAddr ();
914
914
#endif
915
- // / Schedule a processing job. This can generally only be
916
- // / done if we know nobody else is trying to do it at the same time,
917
- // / e.g. if this thread just successfully transitioned the actor from
918
- // / Idle to Scheduled.
915
+ // / Schedule a processing job.
916
+ // / It can be done when actor transitions from Idle to Scheduled or
917
+ // / when actor gets a priority override and we schedule a stealer.
919
918
void scheduleActorProcessJob (JobPriority priority);
920
919
#endif /* !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS */
921
920
@@ -1153,12 +1152,14 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
1153
1152
swift_dispatch_lock_override_start_with_debounce (lockAddr, newState.currentDrainer (),
1154
1153
(qos_class_t ) priority);
1155
1154
} else {
1156
- // TODO (rokhinip): Actor is scheduled - we need to schedule a
1157
- // stealer at the higher priority
1158
- //
1159
- // TODO (rokhinip): Add a signpost to flag that this is a potential
1160
- // priority inversion
1161
- SWIFT_TASK_DEBUG_LOG (" [Override] Escalating actor %p which is enqueued" , this );
1155
+ // We are scheduling a stealer for an actor due to priority override.
1156
+ // This extra processing job has a reference on the actor. See
1157
+ // ownership rule (2).
1158
+ SWIFT_TASK_DEBUG_LOG (
1159
+ " [Override] Scheduling a stealer for actor %p at %#x priority" ,
1160
+ this , newState.getMaxPriority ());
1161
+ swift_retain (this );
1162
+ scheduleActorProcessJob (newState.getMaxPriority ());
1162
1163
}
1163
1164
}
1164
1165
#endif
@@ -1251,8 +1252,13 @@ static void defaultActorDrain(DefaultActorImpl *actor) {
1251
1252
DefaultActorImpl *currentActor = actor;
1252
1253
1253
1254
bool actorLockAcquired = actor->tryLock (true );
1254
- // We always must succeed in taking the actor lock that we are draining
1255
- // because we don't have to compete with OOL jobs. See ownership rule (3)
1255
+ #if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1256
+ if (!actorLockAcquired) {
1257
+ // tryLock may fail when we compete with other stealers for the actor.
1258
+ goto done;
1259
+ }
1260
+ #endif
1261
+
1256
1262
(void )actorLockAcquired;
1257
1263
assert (actorLockAcquired);
1258
1264
@@ -1295,6 +1301,9 @@ static void defaultActorDrain(DefaultActorImpl *actor) {
1295
1301
// Leave the tracking info.
1296
1302
trackingInfo.leave ();
1297
1303
1304
+ #if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1305
+ done:
1306
+ #endif
1298
1307
// Balances with the retain taken in ProcessOutOfLineJob::process
1299
1308
swift_release (actor);
1300
1309
}
@@ -1370,7 +1379,7 @@ bool DefaultActorImpl::tryLock(bool asDrainer) {
1370
1379
dispatch_thread_override_info_s threadOverrideInfo;
1371
1380
threadOverrideInfo = swift_dispatch_thread_get_current_override_qos_floor ();
1372
1381
qos_class_t overrideFloor = threadOverrideInfo.override_qos_floor ;
1373
-
1382
+ bool receivedOverride = false ;
1374
1383
retry:;
1375
1384
#else
1376
1385
SWIFT_TASK_DEBUG_LOG (" Thread attempting to jump onto %p, as drainer = %d" , this , asDrainer);
@@ -1380,9 +1389,24 @@ retry:;
1380
1389
while (true ) {
1381
1390
1382
1391
if (asDrainer) {
1383
- // TODO (rokhinip): Once we have multiple OOL process job support, this
1384
- // assert can potentially fail due to a race with an actor stealer that
1385
- // might have won the race and started running the actor
1392
+ #if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1393
+ if (!oldState.isScheduled ()) {
1394
+ // Some other actor stealer won the race and started running the actor
1395
+ // and potentially be done with it if state is observed as idle here.
1396
+
1397
+ // This extra processing jobs releases its reference. See ownership rule
1398
+ // (4).
1399
+ swift_release (this );
1400
+
1401
+ if (receivedOverride) {
1402
+ // Reset any override as a result of contending for the actor lock.
1403
+ swift_dispatch_lock_override_end (overrideFloor);
1404
+ }
1405
+ return false ;
1406
+ }
1407
+ #endif
1408
+
1409
+ // We are still in the race with other stealers to take over the actor.
1386
1410
assert (oldState.isScheduled ());
1387
1411
1388
1412
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
@@ -1397,6 +1421,7 @@ retry:;
1397
1421
1398
1422
(void ) swift_dispatch_thread_override_self (maxActorPriority);
1399
1423
overrideFloor = maxActorPriority;
1424
+ receivedOverride = true ;
1400
1425
goto retry;
1401
1426
}
1402
1427
#endif /* SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION */
@@ -1471,10 +1496,23 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
1471
1496
}
1472
1497
// We need to schedule the actor - remove any escalation bits since we'll
1473
1498
// schedule the actor at the max priority currently on it
1499
+
1500
+ // N decreases by 1 as this processing job is going away; but R is
1501
+ // still 1. We schedule a new processing job to maintain N >= R.
1502
+
1503
+ // It is possible that there are stealers scheduled for the actor already;
1504
+ // but, we still schedule one anyway. This is because it is possible that
1505
+ // those stealers got scheduled when we were running the actor and gone
1506
+ // away. (See tryLock function.)
1474
1507
newState = newState.withScheduled ();
1475
1508
newState = newState.withoutEscalatedPriority ();
1476
1509
} else {
1477
1510
// There is no work left to do - actor goes idle
1511
+
1512
+ // R becomes 0 and N descreases by 1.
1513
+ // But, we may still have stealers scheduled so N could be > 0. This is
1514
+ // fine since N >= R. Every such stealer, once scheduled, will observe
1515
+ // actor as idle, will release its ref and return. (See tryLock function.)
1478
1516
newState = newState.withIdle ();
1479
1517
newState = newState.resetPriority ();
1480
1518
}
0 commit comments