Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-14841] Concurrency: Resuming a stored continuation from an actor does not work #57188

Closed
mickeyl opened this issue Jun 28, 2021 · 7 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior.

Comments

@mickeyl
Copy link

mickeyl commented Jun 28, 2021

Previous ID SR-14841
Radar rdar://problem/79926026
Original Reporter @mickeyl
Type Bug
Status Closed
Resolution Done
Environment
  • Xcode 13 beta 4, iOS 15 simulator on macOS 11.4 on Mac6,1 (2013).

  • Xcode 13 beta 4, iOS 15 simulator on macOS 12b4 on M1-Mac (2020).

  • Xcode 13 beta 4, macOS/Catalyst on macOS 12b4 on M1-Mac (2020).

Additional Detail from JIRA
Votes 0
Component/s
Labels Bug
Assignee None
Priority Medium

md5: 42907e573028b4f88cb3e358fca70d55

relates to:

  • SR-14875 Resuming continuations from actor contexts can hang

Issue Description:

Please consider the following example program which seems to expose an incompatibility when storing a continuation somewhere and resuming it later from within an actor.

  • Changing the `actor` to a `class` makes the program work.
import Foundation
typealias Continuation = CheckedContinuation<String, Error>

public enum StreamError: Error {
    case invalidEncoding
}

public class StreamCommand {
    let continuation: Continuation

    init(continuation: Continuation) {
        self.continuation = continuation
    }

    func resumeContinuation() {
        let response = "fooBar"
        print("<triggering continuation resume> \(self.continuation)")
        self.continuation.resume(returning: response)
        print("</triggering continuation resume>")
    }
}

public actor StreamCommandQueue: NSObject {
    var activeCommand: StreamCommand?

    func send(string: String, timeout: TimeInterval) async throws -> String {
        print("awaiting...")
        let response: String = try await withCheckedThrowingContinuation { continuation in
            print("continuation: \(continuation)")
            self.activeCommand = StreamCommand(continuation: continuation)
            self.outputActiveCommand()
        }
        print("came back after awaiting")
        return response
    }

    func outputActiveCommand() {
        async {
            self.activeCommand?.resumeContinuation()
        }
    }

    func inputActiveCommand() { }
}

func doIt() {
    async {
        let streamQueue = StreamCommandQueue()
        do {
            let identification = try await streamQueue.send(string: "ATI\r", timeout: 1)
            print("identification: \(identification)")
        } catch {
            print("can't get identification: \(error)")
        }
    }
}
@typesanitizer
Copy link

@swift-ci create

@DougGregor
Copy link
Member

This is working for me with the main and 5.5 branches. However, I note that this is missing the code to run doIt() anywhere. If you only had:

doIt()

then the program will likely exit before the async task completes. Put a silly

Thread.sleep(forTimeInterval: 1)

after it and things should be fine. Or, better yet, use `@main` with an async main function

@mickeyl
Copy link
Author

mickeyl commented Jul 10, 2021

@DougGregor Thanks for taking a look at it.

Obviously the example file was not self-contained, but a stripped down part of a larger program, my bad.

It's still not working for me with the latest 5.5 snapshot. Please use the example project @ https://github.com/mickeyl/swift-bugs and run it in iOS 15 simulator.

With the `StreamCommandQueue being an `actor`, the output is:

awaiting... continuation: CheckedContinuation<String, Error>(canary: Swift.CheckedContinuationCanary)
<triggering continuation resume> CheckedContinuation<String, Error>(canary: Swift.CheckedContinuationCanary)
</triggering continuation resume>

The continuation hangs.

With the `StreamCommandQueue` being a `class`, the output is:

awaiting... continuation: CheckedContinuation<String, Error>(canary: Swift.CheckedContinuationCanary) 
<triggering continuation resume> CheckedContinuation<String, Error>(canary: Swift.CheckedContinuationCanary)
</triggering continuation resume>
came back after awaiting

The continuation continues (sic!).

@mickeyl
Copy link
Author

mickeyl commented Jul 15, 2021

No changes in Xcode 12b3.

@mickeyl
Copy link
Author

mickeyl commented Jul 27, 2021

No changes in Xcode 12b4. It would be nice if this issue would either be confirmed or If I'm "holding it wrong", please tell me how actors and continuations are supposed to be used.

@typesanitizer
Copy link

Hi, this is confirmed in Xcode 13 beta 4 release notes (https://developer.apple.com/documentation/xcode-release-notes/xcode-13-beta-release-notes) under Known Issues

Resuming a stored continuation in a Task.init(priority:operation:) context (previously async(priority:operation:)). (SR-14802, SR-14841, SR-14875) 

with a potential workaround of using a detached task instead of Task.init/async { }.

@mickeyl
Copy link
Author

mickeyl commented Oct 13, 2021

This seems to work now with Xcode13b5

Swift version 5.5 (swiftlang-1300.0.29.102 clang-1300.0.28.1)

on macOS 12 beta.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior.
Projects
None yet
Development

No branches or pull requests

3 participants