-
Notifications
You must be signed in to change notification settings - Fork 125
HTTPClientResponseDelegate is not notified of some errors (connection related?). #242
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
Comments
I do share your expectation and I agree the delegate should get all errors. This is quite a serious bug so this needs fixing before 1.2.0. Thank you so much for putting together the test for this! |
I think the error is not in the client, but in the delegate, it should be: func didReceiveError(task: HTTPClient.Task<Response>, _ error: Error) {
self.error = error
} and not: func didReceiveError(task: HTTPClient.Task<Response>, _ error: Error) -> EventLoopFuture<Void> {
} Delegate is method is not called because its not overriding it, it seems having default implementation here can hide errors... |
@weissi do you think we should drop default implementations in 2.0.0? |
Oops. You're absolutely right, I messed that test up completely. Apologies! I've managed to reproduce the same thing with a corrected delegate. I think this is much closer to the actual failure mode we see in our real application. What we see in our application is a connectTimeout error when talking to S3. This happens rarely, maybe every 10k requests or so. It isn't unusual behaviour for S3, we've see the same thing on a range of clients/languages and normally you just retry the request and it succeeds. So what I've done is written a stupid HTTP server in C with some sleeps that are designed to provoke the connectTimeout error in NIO. With the correct didReceiveError function signature you can see the first few requests return readTimeout (you'd expect those errors given the sleep in the server). You can then see a bunch of requests where the futureResult error is connectTimeout but the delegate did not see any error. error:HTTPClientError.readTimeout vs delegate:Optional(HTTPClientError.readTimeout) Revised test code:
Server code:
|
Hmm, I think Swift should get its act together and offer a |
Ah, that does indeed look like bug, I think I even can see there reason for it. |
Good catch, @kuwerty, thank you! |
That's awesome, thanks for the speedy response! |
Cool, this server can be super straightforwardly implemented in NIO too if that helps for a test case. Happy to write the server code |
@weissi are you looking for a test case that provokes the connectTimeout error specifically? I took a stab at that in NIO, using HTTPBin, but I just couldn't get it to result in the same error. If you have a good idea on how to do that then go ahead ;-) The PR from @artemredkin forces a connection refused by shutting down HTTPBin early, it looks like the same class of error with the same behaviour in the delegate. |
@kuwerty I don't think import NIO
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
try! group.syncShutdownGracefully()
}
final class DelayReadByFourSecondsHandler: ChannelDuplexHandler {
typealias InboundIn = Channel
typealias OutboundIn = Never
private var numberOfReadsSeen = 0
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
print("server accepted connection from \(self.unwrapInboundIn(data).remoteAddress!)")
context.fireChannelRead(data)
}
func read(context: ChannelHandlerContext) {
defer {
self.numberOfReadsSeen += 1
}
if self.numberOfReadsSeen == 0 {
context.read() // First read goes through straight away
} else {
// All subsequent reads need to wait 8s (4 + 4).
context.eventLoop.scheduleTask(in: .seconds(8)) {
context.read()
}
}
}
}
final class SendBackCannedResponseHandler: ChannelInboundHandler {
typealias InboundIn = ByteBuffer
typealias OutboundOut = ByteBuffer
func channelActive(context: ChannelHandlerContext) {
// Four seconds after we accepted a connection, send back some bytes.
context.eventLoop.scheduleTask(in: .seconds(4)) {
let buffer = context.channel.allocator.buffer(string: "HTTP/1.0 200 OK\nContent-Length:5\n\nHello")
context.writeAndFlush(self.wrapOutboundOut(buffer)).whenComplete { _ in
context.channel.close(promise: nil)
}
}
}
}
let server = try ServerBootstrap(group: group)
.serverChannelOption(ChannelOptions.backlog, value: 4) // Backlog 4
.serverChannelOption(ChannelOptions.maxMessagesPerRead, value: 1) // Accept only 1 connection at a time.
.serverChannelInitializer { channel in
// In the server channel (where we accept connections), we want to delay the accepting.
channel.pipeline.addHandler(DelayReadByFourSecondsHandler())
}
.childChannelInitializer { channel in
// In the client channel (the actual TCP connection), we want to send back a canned response.
channel.pipeline.addHandler(SendBackCannedResponseHandler())
}
.bind(to: SocketAddress(ipAddress: "0.0.0.0", port: 8080))
.wait()
print("Server up and running at \(server.localAddress!)")
try server.closeFuture.wait() // wait for the server to close (never). into NIO. Could you see if that reproduces your problem in the same way? |
Tested against commit 8add6b8 on Ubuntu 16.04 with Swift version 5.2.4 (swift-5.2.4-RELEASE)
I've been debugging an issue where an HTTPClientResponseDelegate does not get notified of some errors via the didReceiveError(task:...) method.
My expectation was that HTTPClientResponseDelegate would see any error throughout the entire lifecycle of the request, not just some subset of them.
I've pasted a test case below that demonstrates the issue with remoteConnectionClosed errors but I've seen it with connectTimeout too.
I can appreciate that the error was returned in Task.futureResult but it took quite a bit of effort to work that out (no matter how obvious it is in retrospect). However if the current behaviour is intended then I would still push that the documentation for didReceiveError is updated to emphasize it only sees some subset of errors and not "any network-related error" as it states now.
Thank you!
The text was updated successfully, but these errors were encountered: