-
Notifications
You must be signed in to change notification settings - Fork 123
Always access Task’s connection and cancelled property through a lock #385
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
//===----------------------------------------------------------------------===// | ||
|
||
@testable import AsyncHTTPClient | ||
import Logging | ||
import NIO | ||
import NIOConcurrencyHelpers | ||
import NIOHTTP1 | ||
|
@@ -323,21 +324,25 @@ class HTTPClientInternalTests: XCTestCase { | |
// of 4 bytes. This will guarantee that if we see first byte of the message, other | ||
// bytes a ready to be read as well. This will allow us to test if subsequent reads | ||
// are waiting for backpressure promise. | ||
func testUploadStreamingBackpressure() throws { | ||
func testDownloadStreamingBackpressure() throws { | ||
class BackpressureTestDelegate: HTTPClientResponseDelegate { | ||
typealias Response = Void | ||
|
||
var _reads = 0 | ||
let lock: Lock | ||
let backpressurePromise: EventLoopPromise<Void> | ||
let channel: Channel | ||
|
||
let optionsApplied: EventLoopPromise<Void> | ||
let messageReceived: EventLoopPromise<Void> | ||
let backpressureFuture: EventLoopFuture<Void> | ||
let firstBodyPartReceived: EventLoopPromise<Void> | ||
|
||
init(eventLoop: EventLoop) { | ||
init(channel: Channel, writeBodyPromise: EventLoopPromise<Void>, writeEndFuture: EventLoopFuture<Void>) { | ||
self.lock = Lock() | ||
self.backpressurePromise = eventLoop.makePromise() | ||
self.optionsApplied = eventLoop.makePromise() | ||
self.messageReceived = eventLoop.makePromise() | ||
|
||
self.channel = channel | ||
self.optionsApplied = writeBodyPromise | ||
self.backpressureFuture = writeEndFuture | ||
self.firstBodyPartReceived = channel.eventLoop.makePromise() | ||
} | ||
|
||
var reads: Int { | ||
|
@@ -348,8 +353,8 @@ class HTTPClientInternalTests: XCTestCase { | |
|
||
func didReceiveHead(task: HTTPClient.Task<Void>, _ head: HTTPResponseHead) -> EventLoopFuture<Void> { | ||
// This is to force NIO to send only 1 byte at a time. | ||
let future = task.connection!.channel.setOption(ChannelOptions.maxMessagesPerRead, value: 1).flatMap { | ||
task.connection!.channel.setOption(ChannelOptions.recvAllocator, value: FixedSizeRecvByteBufferAllocator(capacity: 1)) | ||
let future = self.channel.setOption(ChannelOptions.maxMessagesPerRead, value: 1).flatMap { | ||
self.channel.setOption(ChannelOptions.recvAllocator, value: FixedSizeRecvByteBufferAllocator(capacity: 1)) | ||
} | ||
future.cascade(to: self.optionsApplied) | ||
return future | ||
|
@@ -361,8 +366,8 @@ class HTTPClientInternalTests: XCTestCase { | |
self._reads += 1 | ||
} | ||
// We need to notify the test when first byte of the message is arrived. | ||
self.messageReceived.succeed(()) | ||
return self.backpressurePromise.futureResult | ||
self.firstBodyPartReceived.succeed(()) | ||
return self.self.backpressureFuture | ||
} | ||
|
||
func didFinishRequest(task: HTTPClient.Task<Response>) throws {} | ||
|
@@ -403,37 +408,68 @@ class HTTPClientInternalTests: XCTestCase { | |
|
||
// cannot test with NIOTS as `maxMessagesPerRead` is not supported | ||
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) | ||
let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) | ||
let delegate = BackpressureTestDelegate(eventLoop: httpClient.eventLoopGroup.next()) | ||
let eventLoop = eventLoopGroup.next() | ||
let writeBodyPromise = eventLoop.makePromise(of: Void.self) | ||
let writeEndPromise = eventLoop.makePromise(of: Void.self) | ||
let httpBin = HTTPBin { _ in | ||
WriteAfterFutureSucceedsHandler( | ||
bodyFuture: delegate.optionsApplied.futureResult, | ||
endFuture: delegate.backpressurePromise.futureResult | ||
bodyFuture: writeBodyPromise.futureResult, | ||
endFuture: writeEndPromise.futureResult | ||
) | ||
} | ||
|
||
defer { | ||
XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) | ||
XCTAssertNoThrow(try httpBin.shutdown()) | ||
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) | ||
} | ||
|
||
let request = try Request(url: "http://localhost:\(httpBin.port)/custom") | ||
|
||
let requestFuture = httpClient.execute(request: request, delegate: delegate).futureResult | ||
let logger = Logger(label: "test-connection") | ||
|
||
let clientFactory = HTTPConnectionPool.ConnectionFactory( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why has this dropped down to be so low-level? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would really like to not expose the connection on the task in the future. In order to have control over the channel, we need to create it ourselves here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is only one other test remaining that accesses the
We should be able to remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, can we encapsulate this new code in a function that clearly explains what it does? The code here is very aware of implementation details of AHC, which suggests the test as a whole needs rewriting, but in the near term I'd be ok with just hiding the implementation details in a function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is still open. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bump. 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, ignored it for now, since I think we should be able to just remove this test, once other PRs have landed. The functionality will be tested by unit tests. |
||
key: .init(request), | ||
tlsConfiguration: nil, | ||
clientConfiguration: .init(), | ||
sslContextCache: .init() | ||
) | ||
var maybeChannel: Channel? | ||
XCTAssertNoThrow(maybeChannel = try clientFactory.makeHTTP1Channel( | ||
connectionID: 1, | ||
deadline: .now() + .seconds(10), | ||
eventLoop: eventLoopGroup.next(), | ||
logger: logger | ||
).wait()) | ||
|
||
guard let channel = maybeChannel else { return XCTFail("Expected to have a channel at this point") } | ||
|
||
let delegate = BackpressureTestDelegate( | ||
channel: channel, | ||
writeBodyPromise: writeBodyPromise, | ||
writeEndFuture: writeEndPromise.futureResult | ||
) | ||
let task = HTTPClient.Task<BackpressureTestDelegate.Response>(eventLoop: eventLoop, logger: logger) | ||
|
||
let taskHandler = TaskHandler(task: task, | ||
kind: request.kind, | ||
delegate: delegate, | ||
redirectHandler: nil, | ||
ignoreUncleanSSLShutdown: true, | ||
logger: logger) | ||
|
||
XCTAssertNoThrow(try channel.pipeline.addHandler(taskHandler).wait()) | ||
XCTAssertNoThrow(try channel.writeAndFlush(request).wait()) | ||
|
||
// We need to wait for channel options that limit NIO to sending only one byte at a time. | ||
try delegate.optionsApplied.futureResult.wait() | ||
XCTAssertNoThrow(try delegate.optionsApplied.futureResult.wait()) | ||
|
||
// Send 4 bytes, but only one should be received until the backpressure promise is succeeded. | ||
|
||
// Now we wait until message is delivered to client channel pipeline | ||
try delegate.messageReceived.futureResult.wait() | ||
XCTAssertNoThrow(try delegate.firstBodyPartReceived.futureResult.wait()) | ||
XCTAssertEqual(delegate.reads, 1) | ||
|
||
// Succeed the backpressure promise. | ||
delegate.backpressurePromise.succeed(()) | ||
try requestFuture.wait() | ||
writeEndPromise.succeed(()) | ||
XCTAssertNoThrow(try task.futureResult.wait()) | ||
|
||
// At this point all other bytes should be delivered. | ||
XCTAssertEqual(delegate.reads, 4) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this is only used in tests: can we mark it clearly for that purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I wasn't clear: I meant changing the name to
var _testOnly_connection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used on L715, maybe make it private and add an internal
_testOnly_connection
per Cory's suggestion?