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

Add support for asynchronous body stream writing #2939

Merged
merged 13 commits into from Mar 9, 2023

Conversation

Joannis
Copy link
Member

@Joannis Joannis commented Jan 19, 2023

  • Fixes Crash during upload  #2930 - a crash when users try to write a body from within a task towards the ELF APIs.
  • Introduces a new API for writing chunked HTTP response bodies
  • Adds a helper that automatically managed failing and closing streams

… crashes when users try to write a body from within a task towards the ELF APIs
@Joannis Joannis requested review from 0xTim and gwynne January 19, 2023 12:49
@Joannis Joannis linked an issue Jan 19, 2023 that may be closed by this pull request
… crashes when users try to write a body from within a task towards the ELF APIs
… into feature/jo-async-body-stream

# Conflicts:
#	Tests/VaporTests/PipelineTests.swift
@Joannis
Copy link
Member Author

Joannis commented Feb 21, 2023

@gwynne what's your verdict on the crash inside Request.Body's AsyncSequence implementation? Does that block this PR or not?

@gwynne
Copy link
Member

gwynne commented Feb 21, 2023

@Joannis It is definitely a blocker. It's obviously a race condition, and not an easy one to hit.

@gwynne
Copy link
Member

gwynne commented Feb 21, 2023

Have you been able to reproduce the failure with Thread Sanitizer enabled at all?

@Joannis
Copy link
Member Author

Joannis commented Feb 21, 2023

Even though I didn't touch the crashing code?

@Joannis
Copy link
Member Author

Joannis commented Feb 21, 2023

I'll check with TSAN later.

@gwynne
Copy link
Member

gwynne commented Feb 21, 2023

Odds are very good you either 1) revealed an existing race condition we hadn't happened to trip over yet, or 2) introduced one in the new code that nonetheless happens to manifest deeper in the guts of things (a fairly strong probability pretty much by definition, given it's a precondition that's failing).

@gwynne
Copy link
Member

gwynne commented Feb 21, 2023

(Or, even more painfully, both...)

@tib
Copy link

tib commented Feb 22, 2023

I suppose, it's an already existing issue, just had some trouble when using the Request.Body as an AsyncSequence. I'm very much looking forward for this PR & fix. Thank you. 🙏

@Joannis
Copy link
Member Author

Joannis commented Feb 22, 2023

Found it, it's really stupid and really just a bug in the test

@Joannis Joannis self-assigned this Feb 22, 2023
Copy link
Member

@0xTim 0xTim left a comment

Choose a reason for hiding this comment

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

Thanks for this! This looks in a good shape. There are a couple of left over TODOs we need to resolve and all new public APIs will need DocC comments then we're good to merge

@@ -51,3 +51,14 @@ extension BodyStreamWriter {
return promise.futureResult
}
}

public protocol AsyncBodyStreamWriter {
Copy link
Member

Choose a reason for hiding this comment

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

Can you add DocC comments to these as they're public? Thanks!

Sources/Vapor/HTTP/Server/HTTPServerResponseEncoder.swift Outdated Show resolved Hide resolved
Comment on lines 172 to 193
public init(asyncStream: @escaping @Sendable (AsyncBodyStreamWriter) async throws -> (), count: Int, byteBufferAllocator: ByteBufferAllocator = ByteBufferAllocator()) {
self.byteBufferAllocator = byteBufferAllocator
self.storage = .asyncStream(.init(count: count, callback: asyncStream))
}

public init(asyncStream: @escaping @Sendable (AsyncBodyStreamWriter) async throws -> (), byteBufferAllocator: ByteBufferAllocator = ByteBufferAllocator()) {
self.init(asyncStream: asyncStream, count: -1, byteBufferAllocator: byteBufferAllocator)
}

public init(managedAsyncStream: @escaping @Sendable (AsyncBodyStreamWriter) async throws -> (), count: Int, byteBufferAllocator: ByteBufferAllocator = ByteBufferAllocator()) {
self.byteBufferAllocator = byteBufferAllocator
self.storage = .asyncStream(.init(count: count) { writer in
do {
try await managedAsyncStream(writer)
try await writer.write(.end)
} catch {
try await writer.write(.error(error))
}
})
}

public init(managedAsyncStream: @escaping @Sendable (AsyncBodyStreamWriter) async throws -> (), byteBufferAllocator: ByteBufferAllocator = ByteBufferAllocator()) {
Copy link
Member

Choose a reason for hiding this comment

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

New public APIs will need DocC comments

@@ -159,6 +169,31 @@ extension Response {
self.init(stream: stream, count: -1, byteBufferAllocator: byteBufferAllocator)
}

public init(asyncStream: @escaping @Sendable (AsyncBodyStreamWriter) async throws -> (), count: Int, byteBufferAllocator: ByteBufferAllocator = ByteBufferAllocator()) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm interested to see how Sendable works on 5.5 - but we're ready to bump to 5.6 if needed

Copy link
Member Author

Choose a reason for hiding this comment

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

Sendable does work in 5.5.

@@ -56,6 +57,73 @@ final class PipelineTests: XCTestCase {
try XCTAssertEqual(channel.readOutbound(as: ByteBuffer.self)?.string, "0\r\n\r\n")
try XCTAssertNil(channel.readOutbound(as: ByteBuffer.self)?.string)
}

#if compiler(>=5.7) && canImport(_Concurrency)
Copy link
Member

Choose a reason for hiding this comment

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

We should test on 5.6 as well

@Joannis Joannis requested review from 0xTim March 7, 2023 14:46
@0xTim 0xTim added the semver-minor Contains new API label Mar 7, 2023
Copy link
Member

@0xTim 0xTim left a comment

Choose a reason for hiding this comment

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

Thanks! Looks good to go, just a 5.6 compiler issue to fix

@Joannis Joannis merged commit 07b8b5d into main Mar 9, 2023
@Joannis Joannis deleted the feature/jo-async-body-stream branch March 9, 2023 07:37
@VaporBot
Copy link
Contributor

VaporBot commented Mar 9, 2023

These changes are now available in 4.73.0

gwynne added a commit that referenced this pull request Mar 9, 2023
gwynne added a commit that referenced this pull request Mar 9, 2023
gwynne added a commit that referenced this pull request Mar 9, 2023
Revert "Add support for asynchronous body stream writing (#2939)"

This reverts commit 07b8b5d.
@gwynne gwynne restored the feature/jo-async-body-stream branch March 9, 2023 10:10
@makaGeorge
Copy link

Hi all, I was looking forward to this feature... Any news on if/when it will be merged into main? Thanks!

@tib
Copy link

tib commented Apr 12, 2023

Same here. I'd love to get some updates about this feature. Thank you. 🙏

@gwynne
Copy link
Member

gwynne commented Apr 12, 2023

@Joannis, if you want to open a fresh PR from the branch (and update it to reflect 5.6 now being the minimum Swift version - don't need all the conditionals anymore!), we can re-review and make sure there aren't any outstanding concerns remaining.

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.

Crash during upload Response.Body(stream:) is not async/await ready
6 participants