I have a bit of code here that worked on macOS deployments, but failed on iOS. I tested on both Xcode 13.4 and Xcode 14, with iOS 15.5 and 16.0 (admittedly, not comprehensive with all combinations, but thus far it's all consistent).
The code is:
...
private func startDelayedCheck(iteration: Int = 0) {
if let delayCheckInterval = delayCheckInterval {
Task { [weak self, isFatal, file, line, function, context] in // works if I set any value for `priority`, like `(priority: .low)`
let delay = UInt64(delayCheckInterval * 1_000_000_000)
try await Task.sleep(nanoseconds: delay)
if let self = self {
let error = SafeContinuationError.alreadyRun(file: file, line: line, function: function, context: context)
let message = "WARNING: Continuation completed \(delayCheckInterval * TimeInterval(iteration + 1)) seconds ago and hasn't been released from memory!: \(error)"
print(message)
NotificationCenter.default.post(name: Statics.potentialMemoryLeak, object: self)
if isFatal.contains(.onPostRunDelayCheck) {
fatalError(message)
}
self.startDelayedCheck(iteration: iteration + 1)
}
}
}
}
...
What happens is the try await Task.sleep(nanoseconds: delay)
line never resumes from sleep until the object is released. This is called from the following unit test code
func testInvokeThenMemoryLeak() async throws {
let notificationExpectation = expectation(forNotification: SaferContinuation.potentialMemoryLeak, object: nil)
let printed = expectation(description: "wait for print statement")
var listen = NotificationCenter.default
.publisher(for: SaferContinuation.potentialMemoryLeak)
.receive(on: DispatchQueue.global())
.sink {
print("got potential leak: \($0.object)")
}
let _: Void = try await withCheckedThrowingContinuation { continuation in
var safer: SaferContinuation? = SaferContinuation(continuation, delayCheckInterval: 0.25)
DispatchQueue.global().asyncAfter(deadline: .now() + 0.25) { [safer] in
safer?.resume(with: .success(Void()))
}
DispatchQueue.global().asyncAfter(deadline: .now() + 0.75) { [safer] in
print(safer)
printed.fulfill()
}
safer = nil
}
wait(for: [notificationExpectation, printed], timeout: 3.0)
listen.cancel()
}
and the behavior is consistent, ultimately matching the duration of the timeout in wait(for: [notificationExpectation, printed], timeout: 3.0)
. I've tested with a timeout of up to 30 seconds.
Like I said, it seems to work as expected (roughly match the sleep duration) when using a macOS target. It also works when I set any priority value for the enclosing Task
around the sleep call.
This is code from my project located here if more context is needed: GitHub - mredig/SaferContinuation
Is this the right place to report Swift bugs? Or should I be using Apple Feedback Assistant or GitHub issues?