Potential Task.sleep never finishing bug

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?

I don't mean to be rude at all, but I want to make sure this issue is seen. I believe it to be an issue that is with the compiler itself or somewhere in the toolchain.

Would there be a better place to have reported this behavior? I'd be happy to move/repost elsewhere.

/bump

The right place for reporting bugs is GitHub - apple/swift: The Swift Programming Language

You mention iOS, but is it perhaps only on the simulator and device is ok?

1 Like

Thank you! I'll be sure to create an issue there, then.

I'll have to test it!