Skip to content

Land AHC HTTPClient conformance#123

Merged
fabianfett merged 15 commits intomainfrom
ff-add-ahc-conformance
Mar 6, 2026
Merged

Land AHC HTTPClient conformance#123
fabianfett merged 15 commits intomainfrom
ff-add-ahc-conformance

Conversation

@fabianfett
Copy link
Member

Move the AHC HTTPClient conformance into this repo, so that we can use AHC to implement the new default HTTPClient.

@xbhatnag xbhatnag self-requested a review March 3, 2026 17:55
@xbhatnag
Copy link
Collaborator

xbhatnag commented Mar 3, 2026

This means that AHC conformance will now be a blocking presubmit check, which may require more tests to be disabled entirely until we figure out how to disable on a per-client basis. Are we OK with this?

edit: With #124 , this is no longer an issue

Package.swift Outdated
swiftSettings: extraSettings
),
.target(
name: "AHCConformance",
Copy link
Member

Choose a reason for hiding this comment

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

NIT: Can we name this target AsyncHTTPClientConformance

Comment on lines +72 to +82
var localArray = RigidArray<UInt8>(capacity: 0)
swap(&localArray, &self.rigidArray)
unsafe localArray.span.withUnsafeBufferPointer { bufferPtr in
self.byteBuffer.reserveCapacity(bufferPtr.count)
unsafe self.byteBuffer.withUnsafeMutableWritableBytes { byteBufferPtr in
unsafe byteBufferPtr.copyBytes(from: bufferPtr)
}
self.byteBuffer.moveWriterIndex(forwardBy: bufferPtr.count)
}

swap(&localArray, &self.rigidArray)
Copy link
Member

Choose a reason for hiding this comment

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

Can we just do this by using https://github.com/apple/swift-nio/blob/1e51266e86d27cd9bdfa567dc7794b10bd5adabe/Sources/NIOCore/ByteBuffer-aux.swift#L572

Suggested change
var localArray = RigidArray<UInt8>(capacity: 0)
swap(&localArray, &self.rigidArray)
unsafe localArray.span.withUnsafeBufferPointer { bufferPtr in
self.byteBuffer.reserveCapacity(bufferPtr.count)
unsafe self.byteBuffer.withUnsafeMutableWritableBytes { byteBufferPtr in
unsafe byteBufferPtr.copyBytes(from: bufferPtr)
}
self.byteBuffer.moveWriterIndex(forwardBy: bufferPtr.count)
}
swap(&localArray, &self.rigidArray)
unsafe self.byteBuffer.write(self.rigidArray.span.rawSpan)

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 still need the localArray swap. But the rest can be replaced.


public consuming func consumeAndConclude<Return, Failure>(
body:
nonisolated(nonsending) (consuming sending ResponseBodyReader) async throws(Failure) ->
Copy link
Member

Choose a reason for hiding this comment

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

This should be implicit due to our feature being turned on

Suggested change
nonisolated(nonsending) (consuming sending ResponseBodyReader) async throws(Failure) ->
(consuming sending ResponseBodyReader) async throws(Failure) ->

}

public mutating func write<Result, Failure>(
_ body: nonisolated(nonsending) (inout OutputSpan<UInt8>) async throws(Failure) -> Result
Copy link
Member

Choose a reason for hiding this comment

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

This should be implicit due to our feature being turned on

Suggested change
_ body: nonisolated(nonsending) (inout OutputSpan<UInt8>) async throws(Failure) -> Result
_ body: (inout OutputSpan<UInt8>) async throws(Failure) -> Result


public mutating func read<Return, Failure>(
maximumCount: Int?,
body: nonisolated(nonsending) (consuming Span<UInt8>) async throws(Failure) -> Return
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
body: nonisolated(nonsending) (consuming Span<UInt8>) async throws(Failure) -> Return
body: (consuming Span<UInt8>) async throws(Failure) -> Return

maximumCount: Int?,
body: nonisolated(nonsending) (consuming Span<UInt8>) async throws(Failure) -> Return
) async throws(AsyncStreaming.EitherError<ReadFailure, Failure>) -> Return where Failure: Error {

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change

Comment on lines +141 to +147
let capcity = maximumCount != nil ? min(maximumCount!, buffer.readableBytes) : buffer.readableBytes
array.reserveCapacity(capcity)
unsafe buffer.withUnsafeReadableBytes { rawBufferPtr in
let usbptr = unsafe rawBufferPtr.assumingMemoryBound(to: UInt8.self)
unsafe array.append(copying: usbptr[0..<capcity])
}
return try await body(array.span)
Copy link
Member

Choose a reason for hiding this comment

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

This is dropping bytes if maximumCount is smaller than the buffer.readableBytes right?

Copy link
Member Author

Choose a reason for hiding this comment

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

great catch!

@fabianfett fabianfett added the 🆕 semver/minor Adds new public API. label Mar 4, 2026
FranzBusch pushed a commit that referenced this pull request Mar 6, 2026
### Motivation

PR #123 was hanging on presubmit for AHC conformance tests on Linux.

This was root-caused to an issue with `testCancelPreHeaders` and
`testCancelPreBody`.

Here is the sequence of events:
1. `testCancelPreBody` cancels the task making the request to
`/stall_body`.
2. The task cancellation causes AHC to close the connection to the
server.
3. The server then throws an error from the indefinite `try await
Task.sleep(..)` due to client disconnection.
4. This error propagates up from the server handler into
`NIOHTTPServer`.
5. Under Linux, the server is unable to handle this correctly and
subsequent tests end up failing.

**Note**: This issue does not happen when the tests run under Mac + AHC.
Further investigation into possible server-side issues is warranted
here. Why does it only occur for Linux and not Mac?

### Modifications

Instead of letting the error propagate up into the server, the handler
swallows any errors from the `/stall` and `/stall_body` endpoints.

### Result

The AHC conformance tests in #123 now pass under Linux.

### Test Plan

N/A. This fixes a broken test.
@fabianfett fabianfett merged commit 95e0b42 into main Mar 6, 2026
34 of 41 checks passed
@fabianfett fabianfett deleted the ff-add-ahc-conformance branch March 6, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🆕 semver/minor Adds new public API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants