Description
Previous ID | SR-14875 |
Radar | rdar://problem/80238311 |
Original Reporter | @josephlord |
Type | Bug |
Status | Resolved |
Resolution | Duplicate |
Attachment: Download
Environment
Xcode 13 beta 2. Snapshot builds 5.5 toolchain 2021-07-03, 2021-07-01, 2021-06-29, M1 Mac mini, Monterey beta2, Xcode beta 3, Monterey beta 3
Additional Detail from JIRA
Votes | 1 |
Component/s | |
Labels | Bug |
Assignee | None |
Priority | Medium |
md5: 0f377d3ebb1ee24957d4228c49aeb885
duplicates:
- SR-14802 Concurrency: Unstructured Task Triggering Continuation Can Hang
relates to:
- SR-14841 Concurrency: Resuming a stored continuation from an actor does not work
Issue Description:
Note also separate hang report here: SR-14802
I've been playing with and testing creating my own AsyncSequences. The first implementation below works well, the second is highly unreliable. The only difference is that the first one returns the continuation from the actor which then gets resumed and the second one it is resumed from the actor context.
I'm not aware of anything in the specification about not firing continuations from actor context. If it really shouldn't be done there should be compile time errors if there is an attempt to resume a continuation from an actor context.
@available(macOS 12.0, iOS 15.0, *)
public struct AsyncTimerActorSequence : AsyncSequence {
public typealias AsyncIterator = Iterator
public typealias Element = Void
let interval: TimeInterval
public init(interval: TimeInterval) {
self.interval = interval
}
public func makeAsyncIterator() -> Iterator {
Iterator(interval: interval)
}
public class Iterator : AsyncIteratorProtocol {
private actor InnerActor {
private var continuations: Deque<CheckedContinuation<(), Never>> = []
fileprivate func getContinuation() -> CheckedContinuation<(), Never>? {
return continuations.popFirst()
}
fileprivate func addContinuation(_ continuation: CheckedContinuation<(), Never>) {
continuations.append(continuation)
}
}
private let safeContinuations = InnerActor()
private var timer: Timer?
fileprivate init(interval: TimeInterval) {
let safeConts = safeContinuations
let t = Timer(fire: .now, interval: interval, repeats: true) { _ in
Task {
let continuation = await safeConts.getContinuation()
continuation?.resume()
}
}
self.timer = t
RunLoop.main.add(t, forMode: .default)
}
public func next() async throws -> ()? {
await withCheckedContinuation { continuation in
Task {
await safeContinuations.addContinuation(continuation)
}
}
return ()
}
deinit {
timer?.invalidate()
}
}
}
@available(macOS 12.0, iOS 15.0, *)
public struct AsyncTimerActorSequenceUnreliable : AsyncSequence {
public typealias AsyncIterator = Iterator
public typealias Element = Void
let interval: TimeInterval
public init(interval: TimeInterval) {
self.interval = interval
}
public func makeAsyncIterator() -> Iterator {
Iterator(interval: interval)
}
public class Iterator : AsyncIteratorProtocol {
private actor InnerActor {
private var continuations: Deque<CheckedContinuation<(), Never>> = []
fileprivate func fireContinuation() {
continuations.popFirst()?.resume()
}
fileprivate func addContinuation(_ continuation: CheckedContinuation<(), Never>) {
continuations.append(continuation)
}
}
private let safeContinuations = InnerActor()
private var timer: Timer?
fileprivate init(interval: TimeInterval) {
let safeConts = safeContinuations
let t = Timer(fire: .now, interval: interval, repeats: true) { _ in
Task {
await safeConts.fireContinuation()
}
}
self.timer = t
RunLoop.main.add(t, forMode: .default)
}
public func next() async throws -> ()? {
await withCheckedContinuation { continuation in
Task {
await safeContinuations.addContinuation(continuation)
}
}
return ()
}
deinit {
timer?.invalidate()
}
}
}
To reproduce open the package in Xcode and run the AsyncTimerSequenceUnreliableActorTests repeatedly (100 times). There should be some errors. Whereas the similar AsyncTimerSequenceActorTests should be the same tests but on the working version of the sequence.