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

Introduce a new nonblocking run() method on Application #2938

Closed
wants to merge 4 commits into from

Conversation

Joannis
Copy link
Member

@Joannis Joannis commented Jan 19, 2023

Fixes #2937

⚠️ This is considered a breaking change

Be careful releasing this.

  • Adds a new asynchronous implementation for run()
  • Marks the old, blocking implementation, as not available in async contexts

@Joannis Joannis requested review from 0xTim and gwynne January 19, 2023 10:16
/// If your application is started without arguments, the default argument is used.
///
/// Under normal circumstances, `run()` starts the webserver, then wait for the web server to (manually) shut down before returning.
public func run() async throws {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has been my repeated experience that this must be marked @MainActor in order to work reliably.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that so? I've not needed that, shall I change it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing in the server world should require @MainActor. Maybe @gwynne hit some Vapor thread-safety issues that were accidentally worked around by @MainActor?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought so already @weissi

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@weissi Yeah, that was my assumption, but I lacked the necessary tooling (and spare moments) at the time to dig into it further. It's definitely not a real solution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gwynne when you have a repro, try swift build -c release --sanitize=thread and then run that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@weissi Will do, but it occurred to me a few minutes ago that what probably happened is I ended up attempting to perform the stop from the same event loop on which the future would complete (before bridging back over to Concurrency), which was already invalid before async came along — after all, the old version is a .wait(), it's a guaranteed deadlock!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gwynne is this PR a go then?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's still nothing in place that ensures that, essentially, awaiting the onStop promise happens on the same thread that called start() - I specifically mean "thread" here, not "workqueue worker", which for concurrency at the moment will be a concurrent DispatchQueue and therefore might be any old OS-level thread. As best I can work out, the actual issue is that something in the lifecycle (during boot, or command running, server setup and teardown, whatever) makes an unspoken and probably completely accidental assumption of always being on the main thread/queue, so that awaiting the stop promise can effectively make it complete too soon, or never. Again, needs more investigation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gwynne I don't see how that potential scenario would differ from the existing implementation - which uses .wait(). There's no asynchronous/offloaded work happening inside app.start() that would cause the console command (defaults to serve) to delay creating a server socket.

While I'd concur if it's sub-optimal to - for example - create a TCP server socket descriptor in a non-blocking fashion, that would be out of scope for this PR and a breaking change. So what do you suggest we do?

@Joannis Joannis requested a review from gwynne February 15, 2023 15:52
@Joannis Joannis self-assigned this Feb 22, 2023
@Joannis Joannis linked an issue Apr 4, 2023 that may be closed by this pull request
@Joannis Joannis added the semver-minor Contains new API label Apr 4, 2023
@0xTim
Copy link
Member

0xTim commented Dec 6, 2023

Superseded by #3114

@0xTim 0xTim closed this Dec 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver-minor Contains new API
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Async functions in Xcode 14.3 are bugged Application.run() should be noasync
4 participants