@@ -280,6 +280,12 @@ class alignas(2 * sizeof(void*)) ActiveTaskStatus {
280280 // / actor. This bit is cleared when a starts running on a thread, suspends
281281 // / or is completed.
282282 IsEnqueued = 0x1000 ,
283+
284+ #ifndef NDEBUG
285+ // / Task has been completed. This is purely used to enable an assertion
286+ // / that the task is completed when we destroy it.
287+ IsComplete = 0x2000 ,
288+ #endif
283289 };
284290
285291#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION && SWIFT_POINTER_IS_4_BYTES
@@ -393,6 +399,20 @@ class alignas(2 * sizeof(void*)) ActiveTaskStatus {
393399#endif
394400 }
395401
402+ #ifndef NDEBUG
403+ bool isComplete () const {
404+ return Flags & IsComplete;
405+ }
406+
407+ ActiveTaskStatus withComplete () const {
408+ #if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
409+ return ActiveTaskStatus (Record, Flags | IsComplete, ExecutionLock);
410+ #else
411+ return ActiveTaskStatus (Record, Flags | IsComplete);
412+ #endif
413+ }
414+ #endif
415+
396416 // / Is there a lock on the linked list of status records?
397417 bool isStatusRecordLocked () const { return Flags & IsStatusRecordLocked; }
398418 ActiveTaskStatus withLockingRecord (TaskStatusRecord *lockRecord) const {
@@ -564,6 +584,9 @@ struct AsyncTask::PrivateStorage {
564584 auto newStatus = oldStatus.withRunning (false );
565585 newStatus = newStatus.withoutStoredPriorityEscalation ();
566586 newStatus = newStatus.withoutEnqueued ();
587+ #ifndef NDEBUG
588+ newStatus = newStatus.withComplete ();
589+ #endif
567590
568591 // This can fail since the task can still get concurrently cancelled or
569592 // escalated.
@@ -650,6 +673,7 @@ retry:;
650673 while (true ) {
651674 // We can get here from being suspended or being enqueued
652675 assert (!oldStatus.isRunning ());
676+ assert (!oldStatus.isComplete ());
653677
654678#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
655679 // Task's priority is greater than the thread's - do a self escalation
0 commit comments