@@ -1311,7 +1311,9 @@ static AsyncTask *swift_continuation_initImpl(ContinuationAsyncContext *context,
1311
1311
? ContinuationStatus::Awaited
1312
1312
: ContinuationStatus::Pending,
1313
1313
std::memory_order_relaxed);
1314
-
1314
+ #if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
1315
+ context->Cond = nullptr ;
1316
+ #endif
1315
1317
AsyncTask *task;
1316
1318
1317
1319
// A preawait immediately suspends the task.
@@ -1351,8 +1353,7 @@ static void swift_continuation_awaitImpl(ContinuationAsyncContext *context) {
1351
1353
" awaiting a corrupt or already-awaited continuation" );
1352
1354
1353
1355
// If the status is already Resumed, we can resume immediately.
1354
- // Comparing against Pending may be very slightly more compact.
1355
- if (oldStatus != ContinuationStatus::Pending) {
1356
+ if (oldStatus == ContinuationStatus::Resumed) {
1356
1357
if (context->isExecutorSwitchForced ())
1357
1358
return swift_task_switch (context, context->ResumeParent ,
1358
1359
context->ResumeToExecutor );
@@ -1364,28 +1365,72 @@ static void swift_continuation_awaitImpl(ContinuationAsyncContext *context) {
1364
1365
auto task = swift_task_getCurrent ();
1365
1366
#endif
1366
1367
1368
+ #if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
1369
+ // In the task to thread model, we do not suspend the task that is waiting on
1370
+ // the continuation resumption. Instead we simply block the thread on a
1371
+ // condition variable keep the task alive on the thread.
1372
+ //
1373
+ // This condition variable can be allocated on the stack of the blocking
1374
+ // thread - with the address of it published to the resuming thread via the
1375
+ // context.
1376
+ ConditionVariable Cond;
1377
+
1378
+ context->Cond = &Cond;
1379
+ #else /* SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL */
1367
1380
// Flag the task as suspended.
1368
1381
task->flagAsSuspended ();
1382
+ #endif /* SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL */
1369
1383
1370
- // Try to transition to Awaited.
1384
+ #if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
1385
+ // If the cmpxchg is successful, the store release also publishes the write to
1386
+ // the Cond in the ContinuationAsyncContext to any concurrent accessing
1387
+ // thread.
1388
+ //
1389
+ // If it failed, then someone concurrently resumed the continuation in which
1390
+ // case, we don't care about publishing the Cond in the
1391
+ // ContinuationAsyncContext anyway.
1392
+ #endif
1393
+ // Try to transition to Awaited
1371
1394
bool success =
1372
1395
sync.compare_exchange_strong (oldStatus, ContinuationStatus::Awaited,
1373
1396
/* success*/ std::memory_order_release,
1374
1397
/* failure*/ std::memory_order_acquire);
1375
1398
1376
- // If that succeeded, we have nothing to do.
1377
1399
if (success) {
1400
+ #if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
1401
+ // This lock really protects nothing but we need to hold it
1402
+ // while calling the condition wait
1403
+ Cond.lock ();
1404
+
1405
+ // Condition variables can have spurious wakeups so we need to check this in
1406
+ // a do-while loop.
1407
+ do {
1408
+ Cond.wait ();
1409
+ oldStatus = sync.load (std::memory_order_relaxed);
1410
+ } while (oldStatus != ContinuationStatus::Resumed);
1411
+
1412
+ Cond.unlock ();
1413
+ #else
1414
+ // If that succeeded, we have nothing to do since we've successfully
1415
+ // suspended the task
1378
1416
_swift_task_clearCurrent ();
1379
1417
return ;
1418
+ #endif /* SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL */
1380
1419
}
1381
1420
1382
1421
// If it failed, it should be because someone concurrently resumed
1383
1422
// (note that the compare-exchange above is strong).
1384
1423
assert (oldStatus == ContinuationStatus::Resumed &&
1385
1424
" continuation was concurrently corrupted or awaited" );
1386
1425
1426
+ #if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
1427
+ // Since the condition variable is stack allocated, we don't need to do
1428
+ // anything here to clean up
1429
+ #else
1387
1430
// Restore the running state of the task and resume it.
1388
1431
task->flagAsRunning ();
1432
+ #endif /* SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL */
1433
+
1389
1434
if (context->isExecutorSwitchForced ())
1390
1435
return swift_task_switch (context, context->ResumeParent ,
1391
1436
context->ResumeToExecutor );
@@ -1397,6 +1442,7 @@ static void resumeTaskAfterContinuation(AsyncTask *task,
1397
1442
continuationChecking::willResume (context);
1398
1443
1399
1444
auto &sync = context->AwaitSynchronization ;
1445
+
1400
1446
auto status = sync.load (std::memory_order_acquire);
1401
1447
assert (status != ContinuationStatus::Resumed &&
1402
1448
" continuation was already resumed" );
@@ -1405,27 +1451,41 @@ static void resumeTaskAfterContinuation(AsyncTask *task,
1405
1451
// restarting.
1406
1452
_swift_tsan_release (static_cast <Job *>(task));
1407
1453
1408
- // The status should be either Pending or Awaited. If it's Awaited,
1409
- // which is probably the most likely option, then we should immediately
1410
- // enqueue; we don't need to update the state because there shouldn't
1411
- // be a racing attempt to resume the continuation. If it's Pending,
1412
- // we need to set it to Resumed; if that fails (with a strong cmpxchg),
1413
- // it should be because the original thread concurrently set it to
1414
- // Awaited, and so we need to enqueue.
1454
+ // The status should be either Pending or Awaited.
1455
+ //
1456
+ // Case 1: Status is Pending
1457
+ // No one has awaited us, we just need to set it to Resumed; if that fails
1458
+ // (with a strong cmpxchg), it should be because the original thread
1459
+ // concurrently set it to Awaited, in which case, we fall into Case 2.
1460
+ //
1461
+ // Case 2: Status is Awaited
1462
+ // This is probably the more frequently hit case.
1463
+ // In task-to-thread model, we update status to be Resumed and signal the
1464
+ // waiting thread. In regular model, we immediately enqueue the task and can
1465
+ // skip updates to the continuation state since there shouldn't be a racing
1466
+ // attempt to resume the continuation.
1415
1467
if (status == ContinuationStatus::Pending &&
1416
1468
sync.compare_exchange_strong (status, ContinuationStatus::Resumed,
1417
1469
/* success*/ std::memory_order_release,
1418
- /* failure*/ std::memory_order_relaxed )) {
1470
+ /* failure*/ std::memory_order_acquire )) {
1419
1471
return ;
1420
1472
}
1421
1473
assert (status == ContinuationStatus::Awaited &&
1422
1474
" detected concurrent attempt to resume continuation" );
1475
+ #if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
1476
+ // If we see status == ContinuationStatus::Awaited, then we should also be
1477
+ // seeing a pointer to the cond var since we're doing a load acquire on sync
1478
+ // which pairs with the store release in swift_continuation_awaitImpl
1479
+ assert (context->Cond != nullptr );
1423
1480
1481
+ sync.store (ContinuationStatus::Resumed, std::memory_order_relaxed);
1482
+ context->Cond ->signal ();
1483
+ #else
1424
1484
// TODO: maybe in some mode we should set the status to Resumed here
1425
1485
// to make a stronger best-effort attempt to catch racing attempts to
1426
1486
// resume the continuation?
1427
-
1428
1487
task->flagAsAndEnqueueOnExecutor (context->ResumeToExecutor );
1488
+ #endif /* SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL */
1429
1489
}
1430
1490
1431
1491
SWIFT_CC (swift)
0 commit comments