SWIFT TASK CONTINUATION MISUSE - leaked its continuation!

SWIFT TASK CONTINUATION MISUSE: saveAndClose() leaked its continuation!

Inside for loop, after executing few items, at one of the item, in save and close function, it is blocked, and says above error, and not moving to next item, so not able to return result.

What could be wrong here, and is there any way to optimize any of these snippets?

private func processTags(reqItems: [FTShelfItemProtocol], selectedTags: [String]) async throws -> FTShelfTagsResult { let items: [FTDocumentItemProtocol] = reqItems.filter({ ($0.URL.downloadStatus() == .downloaded) }).compactMap({ $0 as? FTDocumentItemProtocol })

     var totalTagItems: [FTShelfTagsItem] = [FTShelfTagsItem]()

     for case let item in items where item.documentUUID != nil {
         guard let docUUID = item.documentUUID else { continue }//, item.URL.downloadStatus() == .downloaded else { continue }
         let destinationURL = FTDocumentCache.shared.cachedLocation(for: docUUID)
         print(destinationURL.path)
         // move to post processing phace
         do {
             let document = await FTNoteshelfDocument(fileURL: destinationURL)
             let isOpen = try await document.openDocument(purpose: FTDocumentOpenPurpose.read)
             if isOpen {
                 let tags = await document.documentTags()
                 let considerForResult = selectedTags.allSatisfy(tags.contains(_:))
                 if considerForResult && !tags.isEmpty {
                     var tagsBook = FTShelfTagsItem(shelfItem: item, type: .book)
                     tagsBook.tags = tags
                     totalTagItems.append(tagsBook)
                 }
             }

             let tagsPage = await document.fetchSearchTagsPages(shelfItem: item, selectedTags: selectedTags)
             totalTagItems.append(contentsOf: tagsPage)
             _ = await document.saveAndClose()
         } catch {
             cacheLog(.error, error, destinationURL.lastPathComponent)
         }
     }
     cacheLog(.success, totalTagItems.count)
     let result = FTShelfTagsResult(tagsItems: totalTagItems)
     return result
 }


func saveAndClose() async -> Bool {
    return await withCheckedContinuation({ continuation in
        self.saveAndCloseWithCompletionHandler { isSuccess in
            continuation.resume(returning: isSuccess)
        }
    })
}

Consider this tiny test program:

import Foundation

func sillyDelay() async {
    await withCheckedContinuation { (continuation: CheckedContinuation<(), Never>) in
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            continuation.resume()
        }
    }
}

func main() async {
    print("will delay")
    await sillyDelay()
    print("did delay")
}

await main()

When you run it, it prints:

will delay
did delay

with a one second delay between then. If you then comment out the line container the resume() call and run it again, you get this:

will delay
SWIFT TASK CONTINUATION MISUSE: sillyDelay() leaked its continuation!
2023-06-22 09:46:28.258075+0100 xxst[33855:1768419] SWIFT TASK CONTINUATION MISUSE: sillyDelay() leaked its continuation!

The checked continuation API has detecting that the continuation was deallocated without anyone calling resume() on it. That’s bad because the calling task is now stuck forever: It’s waiting for the continuation to be resumed but the continuation is gone.

I’m not exactly sure how that’s happening in your code — the snippets you posted didn’t include the code for your saveAndCloseWithCompletionHandler(…) method — but the cause of the error is the same. Your code has released the last reference to continuation which has caused it to be deallocated which is causing this failure.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

func saveAndCloseWithCompletionHandler(_ onCompletion :((Bool) -> Void)?)
{
    self.prepareForClosing();
    self.saveDocument { (saveSuccess) in
        if(saveSuccess) {
            self.closeDocument(completionHandler: { (_) in
                onCompletion?(saveSuccess);
            });
        }
        else {
            onCompletion?(saveSuccess);
        }
    }
}


func prepareForClosing() {
    #if !NS2_SIRI_APP
    //save local meta data cache
    self.localCacheWrapper?.saveMetadataCache();
    self.deleteUnusedFileItems();
    #endif
}


func saveDocument(completionHandler : ((Bool) -> Void)?)
{
    if(self.openPurpose == .read) {
        completionHandler?(true);
        return;
    }
    if(self.hasAnyUnsavedChanges) {
        (self.delegate as? FTNoteshelfDocumentDelegate)?.documentWillStartSaving(self);
    }
    #if !NS2_SIRI_APP
    self.recognitionCache?.saveRecognitionInfoToDisk(forcibly: true);
    if(self.hasAnyUnsavedChanges) {
        if let cache = self.recognitionCache, let cachePlist = cache.recognitionCachePlist() {
            let mutableDict = NSMutableDictionary.init(dictionary: cachePlist.contentDictionary);
            self.recognitionInfoPlist()?.updateContent(mutableDict);
        }
        if let cache = self.recognitionCache, let cachePlist = cache.visionRecognitionCachePlist() {
            let mutableDict = NSMutableDictionary.init(dictionary: cachePlist.contentDictionary);
            self.visionRecognitionInfoPlist()?.updateContent(mutableDict);
        }

        //This was added in version 6.2, when we removed the bounding rect from the Segment level storage.
        updateDocumentVersionToLatest()
    }
    #endif

    super.save { (success) in
        if(success) {
            self.previousFileModeificationDate = self.fileModificationDate;
            let pages = self.pages();
            for eachPage in pages {
                eachPage.isDirty = false;
            }
        }
        completionHandler?(success);
    }
}


func closeDocument(completionHandler: ((Bool) -> Void)?)
{
    #if !NS2_SIRI_APP
    self.recognitionCache?.saveRecognitionInfoToDisk(forcibly: true)
    #endif
    super.close { (success) in
        self.removeObservers();
        completionHandler?(success);
    }
}

// in above function, super is --> UIDocument

SWIFT TASK CONTINUATION MISUSE - leaked its continuation!
 
 
Q