Skip to content

Commit 2442598

Browse files
Call didSendRequestPart after the write has hit the socket (#566)
### Motivation Today `didSendRequestPart` is called after a request body part has been passed to the executor. However, this does not mean that the write hit the socket. Users may depend on this behavior to implement back-pressure. For this reason, we should only call this `didSendRequestPart` once the write was successful. ### Modification Pass a promise to the actual channel write and only call the delegate once that promise succeeds. ### Result The delegate method `didSendRequestPart` is only called after the write was successful. Fixes #565. Co-authored-by: Fabian Fett <[email protected]>
1 parent a586fba commit 2442598

11 files changed

+327
-165
lines changed

Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ final class Transaction: @unchecked Sendable {
6363

6464
switch writeAction {
6565
case .writeAndWait(let executor), .writeAndContinue(let executor):
66-
executor.writeRequestBodyPart(.byteBuffer(byteBuffer), request: self)
66+
executor.writeRequestBodyPart(.byteBuffer(byteBuffer), request: self, promise: nil)
6767

6868
case .fail:
6969
// an error/cancellation has happened. we don't need to continue here
@@ -105,14 +105,14 @@ final class Transaction: @unchecked Sendable {
105105
switch self.state.writeNextRequestPart() {
106106
case .writeAndContinue(let executor):
107107
self.stateLock.unlock()
108-
executor.writeRequestBodyPart(.byteBuffer(part), request: self)
108+
executor.writeRequestBodyPart(.byteBuffer(part), request: self, promise: nil)
109109

110110
case .writeAndWait(let executor):
111111
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
112112
self.state.waitForRequestBodyDemand(continuation: continuation)
113113
self.stateLock.unlock()
114114

115-
executor.writeRequestBodyPart(.byteBuffer(part), request: self)
115+
executor.writeRequestBodyPart(.byteBuffer(part), request: self, promise: nil)
116116
}
117117

118118
case .fail:
@@ -132,7 +132,7 @@ final class Transaction: @unchecked Sendable {
132132
break
133133

134134
case .forwardStreamFinished(let executor, let succeedContinuation):
135-
executor.finishRequestBodyStream(self)
135+
executor.finishRequestBodyStream(self, promise: nil)
136136
succeedContinuation?.resume(returning: nil)
137137
}
138138
return

Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1ClientChannelHandler.swift

+44-25
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,11 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler {
185185
case .sendRequestHead(let head, startBody: let startBody):
186186
self.sendRequestHead(head, startBody: startBody, context: context)
187187

188-
case .sendBodyPart(let part):
189-
context.writeAndFlush(self.wrapOutboundOut(.body(part)), promise: nil)
188+
case .sendBodyPart(let part, let writePromise):
189+
context.writeAndFlush(self.wrapOutboundOut(.body(part)), promise: writePromise)
190190

191-
case .sendRequestEnd:
192-
context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
191+
case .sendRequestEnd(let writePromise):
192+
context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: writePromise)
193193

194194
if let timeoutAction = self.idleReadTimeoutStateMachine?.requestEndSent() {
195195
self.runTimeoutAction(timeoutAction, context: context)
@@ -260,34 +260,51 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler {
260260
switch finalAction {
261261
case .close:
262262
context.close(promise: nil)
263-
case .sendRequestEnd:
264-
context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
263+
oldRequest.succeedRequest(buffer)
264+
case .sendRequestEnd(let writePromise):
265+
let writePromise = writePromise ?? context.eventLoop.makePromise(of: Void.self)
266+
// We need to defer succeeding the old request to avoid ordering issues
267+
writePromise.futureResult.whenComplete { result in
268+
switch result {
269+
case .success:
270+
oldRequest.succeedRequest(buffer)
271+
case .failure(let error):
272+
oldRequest.fail(error)
273+
}
274+
}
275+
276+
context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: writePromise)
265277
case .informConnectionIsIdle:
266278
self.connection.taskCompleted()
267-
case .none:
268-
break
279+
oldRequest.succeedRequest(buffer)
269280
}
270281

271-
oldRequest.succeedRequest(buffer)
272-
273282
case .failRequest(let error, let finalAction):
274283
// see comment in the `succeedRequest` case.
275284
let oldRequest = self.request!
276285
self.request = nil
277286
self.runTimeoutAction(.clearIdleReadTimeoutTimer, context: context)
278287

279288
switch finalAction {
280-
case .close:
289+
case .close(let writePromise):
281290
context.close(promise: nil)
282-
case .sendRequestEnd:
283-
context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
291+
writePromise?.fail(error)
292+
oldRequest.fail(error)
293+
284294
case .informConnectionIsIdle:
285295
self.connection.taskCompleted()
296+
oldRequest.fail(error)
297+
298+
case .failWritePromise(let writePromise):
299+
writePromise?.fail(error)
300+
oldRequest.fail(error)
301+
286302
case .none:
287-
break
303+
oldRequest.fail(error)
288304
}
289305

290-
oldRequest.fail(error)
306+
case .failSendBodyPart(let error, let writePromise), .failSendStreamFinished(let error, let writePromise):
307+
writePromise?.fail(error)
291308
}
292309
}
293310

@@ -355,27 +372,29 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler {
355372

356373
// MARK: Private HTTPRequestExecutor
357374

358-
private func writeRequestBodyPart0(_ data: IOData, request: HTTPExecutableRequest) {
375+
private func writeRequestBodyPart0(_ data: IOData, request: HTTPExecutableRequest, promise: EventLoopPromise<Void>?) {
359376
guard self.request === request, let context = self.channelContext else {
360377
// Because the HTTPExecutableRequest may run in a different thread to our eventLoop,
361378
// calls from the HTTPExecutableRequest to our ChannelHandler may arrive here after
362379
// the request has been popped by the state machine or the ChannelHandler has been
363380
// removed from the Channel pipeline. This is a normal threading issue, noone has
364381
// screwed up.
382+
promise?.fail(HTTPClientError.requestStreamCancelled)
365383
return
366384
}
367385

368-
let action = self.state.requestStreamPartReceived(data)
386+
let action = self.state.requestStreamPartReceived(data, promise: promise)
369387
self.run(action, context: context)
370388
}
371389

372-
private func finishRequestBodyStream0(_ request: HTTPExecutableRequest) {
390+
private func finishRequestBodyStream0(_ request: HTTPExecutableRequest, promise: EventLoopPromise<Void>?) {
373391
guard self.request === request, let context = self.channelContext else {
374392
// See code comment in `writeRequestBodyPart0`
393+
promise?.fail(HTTPClientError.requestStreamCancelled)
375394
return
376395
}
377396

378-
let action = self.state.requestStreamFinished()
397+
let action = self.state.requestStreamFinished(promise: promise)
379398
self.run(action, context: context)
380399
}
381400

@@ -405,22 +424,22 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler {
405424
}
406425

407426
extension HTTP1ClientChannelHandler: HTTPRequestExecutor {
408-
func writeRequestBodyPart(_ data: IOData, request: HTTPExecutableRequest) {
427+
func writeRequestBodyPart(_ data: IOData, request: HTTPExecutableRequest, promise: EventLoopPromise<Void>?) {
409428
if self.eventLoop.inEventLoop {
410-
self.writeRequestBodyPart0(data, request: request)
429+
self.writeRequestBodyPart0(data, request: request, promise: promise)
411430
} else {
412431
self.eventLoop.execute {
413-
self.writeRequestBodyPart0(data, request: request)
432+
self.writeRequestBodyPart0(data, request: request, promise: promise)
414433
}
415434
}
416435
}
417436

418-
func finishRequestBodyStream(_ request: HTTPExecutableRequest) {
437+
func finishRequestBodyStream(_ request: HTTPExecutableRequest, promise: EventLoopPromise<Void>?) {
419438
if self.eventLoop.inEventLoop {
420-
self.finishRequestBodyStream0(request)
439+
self.finishRequestBodyStream0(request, promise: promise)
421440
} else {
422441
self.eventLoop.execute {
423-
self.finishRequestBodyStream0(request)
442+
self.finishRequestBodyStream0(request, promise: promise)
424443
}
425444
}
426445
}

Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1ConnectionStateMachine.swift

+44-19
Original file line numberDiff line numberDiff line change
@@ -28,30 +28,46 @@ struct HTTP1ConnectionStateMachine {
2828

2929
enum Action {
3030
/// A action to execute, when we consider a request "done".
31-
enum FinalStreamAction {
31+
enum FinalSuccessfulStreamAction {
3232
/// Close the connection
3333
case close
3434
/// If the server has replied, with a status of 200...300 before all data was sent, a request is considered succeeded,
3535
/// as soon as we wrote the request end onto the wire.
36-
case sendRequestEnd
36+
///
37+
/// The promise is an optional write promise.
38+
case sendRequestEnd(EventLoopPromise<Void>?)
3739
/// Inform an observer that the connection has become idle
3840
case informConnectionIsIdle
41+
}
42+
43+
/// A action to execute, when we consider a request "done".
44+
enum FinalFailedStreamAction {
45+
/// Close the connection
46+
///
47+
/// The promise is an optional write promise.
48+
case close(EventLoopPromise<Void>?)
49+
/// Inform an observer that the connection has become idle
50+
case informConnectionIsIdle
51+
/// Fail the write promise
52+
case failWritePromise(EventLoopPromise<Void>?)
3953
/// Do nothing.
4054
case none
4155
}
4256

4357
case sendRequestHead(HTTPRequestHead, startBody: Bool)
44-
case sendBodyPart(IOData)
45-
case sendRequestEnd
58+
case sendBodyPart(IOData, EventLoopPromise<Void>?)
59+
case sendRequestEnd(EventLoopPromise<Void>?)
60+
case failSendBodyPart(Error, EventLoopPromise<Void>?)
61+
case failSendStreamFinished(Error, EventLoopPromise<Void>?)
4662

4763
case pauseRequestBodyStream
4864
case resumeRequestBodyStream
4965

5066
case forwardResponseHead(HTTPResponseHead, pauseRequestBodyStream: Bool)
5167
case forwardResponseBodyParts(CircularBuffer<ByteBuffer>)
5268

53-
case failRequest(Error, FinalStreamAction)
54-
case succeedRequest(FinalStreamAction, CircularBuffer<ByteBuffer>)
69+
case failRequest(Error, FinalFailedStreamAction)
70+
case succeedRequest(FinalSuccessfulStreamAction, CircularBuffer<ByteBuffer>)
5571

5672
case read
5773
case close
@@ -189,25 +205,25 @@ struct HTTP1ConnectionStateMachine {
189205
}
190206
}
191207

192-
mutating func requestStreamPartReceived(_ part: IOData) -> Action {
208+
mutating func requestStreamPartReceived(_ part: IOData, promise: EventLoopPromise<Void>?) -> Action {
193209
guard case .inRequest(var requestStateMachine, let close) = self.state else {
194210
preconditionFailure("Invalid state: \(self.state)")
195211
}
196212

197213
return self.avoidingStateMachineCoW { state -> Action in
198-
let action = requestStateMachine.requestStreamPartReceived(part)
214+
let action = requestStateMachine.requestStreamPartReceived(part, promise: promise)
199215
state = .inRequest(requestStateMachine, close: close)
200216
return state.modify(with: action)
201217
}
202218
}
203219

204-
mutating func requestStreamFinished() -> Action {
220+
mutating func requestStreamFinished(promise: EventLoopPromise<Void>?) -> Action {
205221
guard case .inRequest(var requestStateMachine, let close) = self.state else {
206222
preconditionFailure("Invalid state: \(self.state)")
207223
}
208224

209225
return self.avoidingStateMachineCoW { state -> Action in
210-
let action = requestStateMachine.requestStreamFinished()
226+
let action = requestStateMachine.requestStreamFinished(promise: promise)
211227
state = .inRequest(requestStateMachine, close: close)
212228
return state.modify(with: action)
213229
}
@@ -377,10 +393,10 @@ extension HTTP1ConnectionStateMachine.State {
377393
return .pauseRequestBodyStream
378394
case .resumeRequestBodyStream:
379395
return .resumeRequestBodyStream
380-
case .sendBodyPart(let part):
381-
return .sendBodyPart(part)
382-
case .sendRequestEnd:
383-
return .sendRequestEnd
396+
case .sendBodyPart(let part, let writePromise):
397+
return .sendBodyPart(part, writePromise)
398+
case .sendRequestEnd(let writePromise):
399+
return .sendRequestEnd(writePromise)
384400
case .forwardResponseHead(let head, let pauseRequestBodyStream):
385401
return .forwardResponseHead(head, pauseRequestBodyStream: pauseRequestBodyStream)
386402
case .forwardResponseBodyParts(let parts):
@@ -390,13 +406,13 @@ extension HTTP1ConnectionStateMachine.State {
390406
preconditionFailure("Invalid state: \(self)")
391407
}
392408

393-
let newFinalAction: HTTP1ConnectionStateMachine.Action.FinalStreamAction
409+
let newFinalAction: HTTP1ConnectionStateMachine.Action.FinalSuccessfulStreamAction
394410
switch finalAction {
395411
case .close:
396412
self = .closing
397413
newFinalAction = .close
398-
case .sendRequestEnd:
399-
newFinalAction = .sendRequestEnd
414+
case .sendRequestEnd(let writePromise):
415+
newFinalAction = .sendRequestEnd(writePromise)
400416
case .none:
401417
self = .idle
402418
newFinalAction = close ? .close : .informConnectionIsIdle
@@ -410,9 +426,12 @@ extension HTTP1ConnectionStateMachine.State {
410426
case .idle:
411427
preconditionFailure("How can we fail a task, if we are idle")
412428
case .inRequest(_, close: let close):
413-
if close || finalAction == .close {
429+
if case .close(let promise) = finalAction {
430+
self = .closing
431+
return .failRequest(error, .close(promise))
432+
} else if close {
414433
self = .closing
415-
return .failRequest(error, .close)
434+
return .failRequest(error, .close(nil))
416435
} else {
417436
self = .idle
418437
return .failRequest(error, .informConnectionIsIdle)
@@ -433,6 +452,12 @@ extension HTTP1ConnectionStateMachine.State {
433452

434453
case .wait:
435454
return .wait
455+
456+
case .failSendBodyPart(let error, let writePromise):
457+
return .failSendBodyPart(error, writePromise)
458+
459+
case .failSendStreamFinished(let error, let writePromise):
460+
return .failSendStreamFinished(error, writePromise)
436461
}
437462
}
438463
}

0 commit comments

Comments
 (0)