@@ -284,15 +284,24 @@ class TaskGroupImpl: public TaskGroupTaskStatusRecord {
284
284
};
285
285
286
286
private:
287
- #if !SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY
287
+ #if SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY || SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
288
+ // Synchronization is simple here. In a single threaded mode, all swift tasks
289
+ // run on a single thread so no coordination is needed. In a task-to-thread
290
+ // model, only the parent task which created the task group can
291
+ //
292
+ // (a) add child tasks to a group
293
+ // (b) run the child tasks
294
+ //
295
+ // So we shouldn't need to worry about coordinating between child tasks and
296
+ // parents in a task group
297
+ void lock () const {}
298
+ void unlock () const {}
299
+ #else
288
300
// TODO: move to lockless via the status atomic (make readyQueue an mpsc_queue_t<ReadyQueueItem>)
289
301
mutable std::mutex mutex_;
290
302
291
303
void lock () const { mutex_.lock (); }
292
304
void unlock () const { mutex_.unlock (); }
293
- #else
294
- void lock () const {}
295
- void unlock () const {}
296
305
#endif
297
306
298
307
// / Used for queue management, counting number of waiting and ready tasks
@@ -437,6 +446,11 @@ class TaskGroupImpl: public TaskGroupTaskStatusRecord {
437
446
// / or a `PollStatus::MustWait` result if there are tasks in flight
438
447
// / and the waitingTask eventually be woken up by a completion.
439
448
PollResult poll (AsyncTask *waitingTask);
449
+
450
+ private:
451
+ // Enqueue the completed task onto ready queue if there are no waiting tasks
452
+ // yet
453
+ void enqueueCompletedTask (AsyncTask *completedTask, bool hadErrorResult);
440
454
};
441
455
442
456
} // end anonymous namespace
@@ -569,6 +583,22 @@ static void fillGroupNextResult(TaskFutureWaitAsyncContext *context,
569
583
}
570
584
}
571
585
586
+ // TaskGroup is locked upon entry and exit
587
+ void TaskGroupImpl::enqueueCompletedTask (AsyncTask *completedTask, bool hadErrorResult) {
588
+ // Retain the task while it is in the queue;
589
+ // it must remain alive until the task group is alive.
590
+ swift_retain (completedTask);
591
+
592
+ auto readyItem = ReadyQueueItem::get (
593
+ hadErrorResult ? ReadyStatus::Error : ReadyStatus::Success,
594
+ completedTask
595
+ );
596
+
597
+ assert (completedTask == readyItem.getTask ());
598
+ assert (readyItem.getTask ()->isFuture ());
599
+ readyQueue.enqueue (readyItem);
600
+ }
601
+
572
602
void TaskGroupImpl::offer (AsyncTask *completedTask, AsyncContext *context) {
573
603
assert (completedTask);
574
604
assert (completedTask->isFuture ());
@@ -610,52 +640,58 @@ void TaskGroupImpl::offer(AsyncTask *completedTask, AsyncContext *context) {
610
640
if (waitQueue.compare_exchange_strong (
611
641
waitingTask, nullptr ,
612
642
/* success*/ std::memory_order_release,
613
- /* failure*/ std::memory_order_acquire) &&
614
- statusCompletePendingReadyWaiting (assumed)) {
615
- // Run the task.
616
- auto result = PollResult::get (completedTask, hadErrorResult);
643
+ /* failure*/ std::memory_order_acquire)) {
644
+ #if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
645
+ // We have completed a child task in a task group task and we know
646
+ // there is a waiting task who will reevaluate TaskGroupImpl::poll once
647
+ // we return, by virtue of being in the task-to-thread model.
648
+ // We want poll() to then satisfy the condition of having readyTasks()
649
+ // that it can dequeue from the readyQueue so we need to enqueue our
650
+ // completion.
651
+
652
+ // TODO (rokhinip): There's probably a more efficient way to deal with
653
+ // this since the child task can directly offer the result to the
654
+ // parent who will run next but that requires a fair bit of plumbing
655
+ enqueueCompletedTask (completedTask, hadErrorResult);
656
+ unlock (); // TODO: remove fragment lock, and use status for synchronization
657
+ return ;
658
+ #else /* SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL */
659
+ if (statusCompletePendingReadyWaiting (assumed)) {
660
+ // Run the task.
661
+ auto result = PollResult::get (completedTask, hadErrorResult);
617
662
618
- unlock (); // TODO: remove fragment lock, and use status for synchronization
619
-
620
- auto waitingContext =
621
- static_cast <TaskFutureWaitAsyncContext *>(
622
- waitingTask->ResumeContext );
663
+ unlock (); // TODO: remove fragment lock, and use status for synchronization
623
664
624
- fillGroupNextResult (waitingContext, result);
625
- detachChild (result.retainedTask );
665
+ auto waitingContext =
666
+ static_cast <TaskFutureWaitAsyncContext *>(
667
+ waitingTask->ResumeContext );
626
668
627
- _swift_tsan_acquire (static_cast <Job *>(waitingTask));
669
+ fillGroupNextResult (waitingContext, result);
670
+ detachChild (result.retainedTask );
628
671
629
- // TODO: allow the caller to suggest an executor
630
- waitingTask->flagAsAndEnqueueOnExecutor (ExecutorRef::generic ());
631
- return ;
632
- } // else, try again
672
+ _swift_tsan_acquire (static_cast <Job *>(waitingTask));
673
+ // TODO: allow the caller to suggest an executor
674
+ waitingTask->flagAsAndEnqueueOnExecutor (ExecutorRef::generic ());
675
+ return ;
676
+ } // else, try again
677
+ #endif
678
+ }
633
679
}
634
-
635
680
llvm_unreachable (" should have enqueued and returned." );
681
+ } else {
682
+ // ==== b) enqueue completion ------------------------------------------------
683
+ //
684
+ // else, no-one was waiting (yet), so we have to instead enqueue to the message
685
+ // queue when a task polls during next() it will notice that we have a value
686
+ // ready for it, and will process it immediately without suspending.
687
+ assert (!waitQueue.load (std::memory_order_relaxed));
688
+
689
+ SWIFT_TASK_DEBUG_LOG (" group has no waiting tasks, RETAIN and store ready task = %p" ,
690
+ completedTask);
691
+ enqueueCompletedTask (completedTask, hadErrorResult);
692
+ unlock (); // TODO: remove fragment lock, and use status for synchronization
636
693
}
637
694
638
- // ==== b) enqueue completion ------------------------------------------------
639
- //
640
- // else, no-one was waiting (yet), so we have to instead enqueue to the message
641
- // queue when a task polls during next() it will notice that we have a value
642
- // ready for it, and will process it immediately without suspending.
643
- assert (!waitQueue.load (std::memory_order_relaxed));
644
- SWIFT_TASK_DEBUG_LOG (" group has no waiting tasks, RETAIN and store ready task = %p" ,
645
- completedTask);
646
- // Retain the task while it is in the queue;
647
- // it must remain alive until the task group is alive.
648
- swift_retain (completedTask);
649
-
650
- auto readyItem = ReadyQueueItem::get (
651
- hadErrorResult ? ReadyStatus::Error : ReadyStatus::Success,
652
- completedTask
653
- );
654
-
655
- assert (completedTask == readyItem.getTask ());
656
- assert (readyItem.getTask ()->isFuture ());
657
- readyQueue.enqueue (readyItem);
658
- unlock (); // TODO: remove fragment lock, and use status for synchronization
659
695
return ;
660
696
}
661
697
@@ -694,7 +730,7 @@ static void swift_taskGroup_wait_next_throwingImpl(
694
730
OpaqueValue *resultPointer, SWIFT_ASYNC_CONTEXT AsyncContext *callerContext,
695
731
TaskGroup *_group,
696
732
ThrowingTaskFutureWaitContinuationFunction *resumeFunction,
697
- AsyncContext *rawContext) {
733
+ AsyncContext *rawContext) SWIFT_OPTNONE {
698
734
auto waitingTask = swift_task_getCurrent ();
699
735
waitingTask->ResumeTask = task_group_wait_resume_adapter;
700
736
waitingTask->ResumeContext = rawContext;
@@ -719,9 +755,9 @@ static void swift_taskGroup_wait_next_throwingImpl(
719
755
#ifdef __ARM_ARCH_7K__
720
756
return workaround_function_swift_taskGroup_wait_next_throwingImpl (
721
757
resultPointer, callerContext, _group, resumeFunction, rawContext);
722
- #else
758
+ #else /* __ARM_ARCH_7K__ */
723
759
return ;
724
- #endif
760
+ #endif /* __ARM_ARCH_7K__ */
725
761
726
762
case PollStatus::Empty:
727
763
case PollStatus::Error:
@@ -739,16 +775,25 @@ static void swift_taskGroup_wait_next_throwingImpl(
739
775
}
740
776
}
741
777
742
- PollResult TaskGroupImpl::poll (AsyncTask *waitingTask) {
778
+ PollResult TaskGroupImpl::poll (AsyncTask *waitingTask) SWIFT_OPTNONE {
743
779
lock (); // TODO: remove group lock, and use status for synchronization
744
780
SWIFT_TASK_DEBUG_LOG (" poll group = %p" , this );
745
- auto assumed = statusMarkWaitingAssumeAcquire ();
746
781
747
782
PollResult result;
748
783
result.storage = nullptr ;
749
784
result.successType = nullptr ;
750
785
result.retainedTask = nullptr ;
751
786
787
+ // Have we suspended the task?
788
+ bool hasSuspended = false ;
789
+ bool haveRunOneChildTaskInline = false ;
790
+
791
+ reevaluate_if_taskgroup_has_results:;
792
+ auto assumed = statusMarkWaitingAssumeAcquire ();
793
+ if (haveRunOneChildTaskInline) {
794
+ assert (assumed.readyTasks ());
795
+ }
796
+
752
797
// ==== 1) bail out early if no tasks are pending ----------------------------
753
798
if (assumed.isEmpty ()) {
754
799
SWIFT_TASK_DEBUG_LOG (" poll group = %p, group is empty, no pending tasks" , this );
@@ -762,9 +807,6 @@ PollResult TaskGroupImpl::poll(AsyncTask *waitingTask) {
762
807
return result;
763
808
}
764
809
765
- // Have we suspended the task?
766
- bool hasSuspended = false ;
767
-
768
810
auto waitHead = waitQueue.load (std::memory_order_acquire);
769
811
770
812
// ==== 2) Ready task was polled, return with it immediately -----------------
@@ -779,17 +821,17 @@ PollResult TaskGroupImpl::poll(AsyncTask *waitingTask) {
779
821
/* success*/ std::memory_order_relaxed,
780
822
/* failure*/ std::memory_order_acquire)) {
781
823
782
- // Success! We are allowed to poll.
783
- ReadyQueueItem item;
784
- bool taskDequeued = readyQueue.dequeue (item);
785
- assert (taskDequeued); (void ) taskDequeued;
786
-
787
824
// We're going back to running the task, so if we suspended before,
788
825
// we need to flag it as running again.
789
826
if (hasSuspended) {
790
827
waitingTask->flagAsRunning ();
791
828
}
792
829
830
+ // Success! We are allowed to poll.
831
+ ReadyQueueItem item;
832
+ bool taskDequeued = readyQueue.dequeue (item);
833
+ assert (taskDequeued); (void ) taskDequeued;
834
+
793
835
assert (item.getTask ()->isFuture ());
794
836
auto futureFragment = item.getTask ()->futureFragment ();
795
837
@@ -845,6 +887,28 @@ PollResult TaskGroupImpl::poll(AsyncTask *waitingTask) {
845
887
/* success*/ std::memory_order_release,
846
888
/* failure*/ std::memory_order_acquire)) {
847
889
unlock (); // TODO: remove fragment lock, and use status for synchronization
890
+ #if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
891
+ // The logic here is paired with the logic in TaskGroupImpl::offer. Once
892
+ // we run the
893
+ auto oldTask = _swift_task_clearCurrent ();
894
+ assert (oldTask == waitingTask);
895
+
896
+ auto childTask = getTaskRecord ()->getFirstChild ();
897
+ assert (childTask != NULL );
898
+
899
+ SWIFT_TASK_DEBUG_LOG (" [RunInline] Switching away from running %p to now running %p" , oldTask, childTask);
900
+ // Run the new task on the same thread now - this should run the new task to
901
+ // completion. All swift tasks in task-to-thread model run on generic
902
+ // executor
903
+ swift_job_run (childTask, ExecutorRef::generic ());
904
+ haveRunOneChildTaskInline = true ;
905
+
906
+ SWIFT_TASK_DEBUG_LOG (" [RunInline] Switching back from running %p to now running %p" , childTask, oldTask);
907
+ // We are back to being the parent task and now that we've run the child
908
+ // task, we should reevaluate parent task
909
+ _swift_task_setCurrent (oldTask);
910
+ goto reevaluate_if_taskgroup_has_results;
911
+ #endif
848
912
// no ready tasks, so we must wait.
849
913
result.status = PollStatus::MustWait;
850
914
_swift_task_clearCurrent ();
0 commit comments