Skip to content

Commit 06ecf9b

Browse files
authored
Merge branch 'main' into static-table-find
2 parents ab178c7 + a646c53 commit 06ecf9b

3 files changed

Lines changed: 101 additions & 36 deletions

File tree

Sources/HTTP3/HTTP3StreamStateMachine.swift

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -370,14 +370,14 @@ package struct HTTP3StreamStateMachine: ~Copyable {
370370
}
371371

372372
enum WriteAction {
373-
/// Bytes are ready to be written out.
374-
case writeBytes(ByteBuffer)
373+
/// The frame's bytes were appended to the provided buffer.
374+
case wroteBytes
375375
/// We need this header to be encoder.
376376
case encodeHeaders([HTTPField])
377377
}
378378

379-
/// Write a frame out.
380-
mutating func write(frame: HTTP3Frame) -> WriteAction {
379+
/// Write a frame out by appending its encoded bytes to `buffer`.
380+
mutating func write(frame: HTTP3Frame, into buffer: inout ByteBuffer) -> WriteAction {
381381
switch consume self.state {
382382
case .idle(let idleState):
383383
let maybePartial = MaybePartialFrame(frame)
@@ -394,24 +394,24 @@ package struct HTTP3StreamStateMachine: ~Copyable {
394394
// So it won't get this far.
395395
fatalError("Tried to write a push promise, which is not supported")
396396
case .partial(let partial):
397-
var buffer = ByteBuffer()
398397
buffer.writeHTTP3PartialFrame(partial, preferHuffmanEncoding: idleState.preferHuffmanEncoding)
399398
self = .init(state: .idle(idleState))
400-
return .writeBytes(buffer)
399+
return .wroteBytes
401400
}
402401
case .waitingForEncode:
403402
fatalError("Cannot call write whilst waiting for a QPACK encode result")
404403
}
405404
}
406405

407406
enum HeaderEncodeResultAction {
408-
/// Bytes are ready to be written out.
409-
case writeBytes(ByteBuffer)
407+
/// The header's bytes were appended to the provided buffer.
408+
case wroteBytes
410409
}
411410

412411
mutating func gotHeaderEncodeResult(
413412
_ result: HTTP3PartialFrame.Headers,
414-
from: [HTTPField]
413+
from: [HTTPField],
414+
into buffer: inout ByteBuffer
415415
) -> HeaderEncodeResultAction {
416416
switch consume self.state {
417417
case .idle:
@@ -420,13 +420,12 @@ package struct HTTP3StreamStateMachine: ~Copyable {
420420
guard from == waitingState.fields else {
421421
fatalError("Unexpected encode result")
422422
}
423-
var buffer = ByteBuffer()
424423
buffer.writeHTTP3PartialFrame(
425424
.headers(result),
426425
preferHuffmanEncoding: waitingState.preferHuffmanEncoding
427426
)
428427
self = .init(state: .idle(.init(preferHuffmanEncoding: waitingState.preferHuffmanEncoding)))
429-
return .writeBytes(buffer)
428+
return .wroteBytes
430429
}
431430
}
432431
}
@@ -466,8 +465,8 @@ package struct HTTP3StreamStateMachine: ~Copyable {
466465
}
467466

468467
package enum WriteFrameAction {
469-
/// You should write out the following bytes to the wire.
470-
case returnBytes(ByteBuffer)
468+
/// The frame's bytes were appended to the buffer you provided.
469+
case wroteBytes
471470
/// You should encode the given headers and call back with the result.
472471
case encodeHeaders([HTTPField])
473472
/// The frame can't be written, because doing so would be a stream error.
@@ -480,8 +479,8 @@ package struct HTTP3StreamStateMachine: ~Copyable {
480479
case previousError
481480
}
482481

483-
/// Write out a frame.
484-
package mutating func writeFrame(frame: HTTP3Frame) -> WriteFrameAction {
482+
/// Write out a frame by appending its encoded bytes to `buffer`.
483+
package mutating func writeFrame(frame: HTTP3Frame, into buffer: inout ByteBuffer) -> WriteFrameAction {
485484
switch self.state {
486485
case .idle(var idleState):
487486
guard idleState.readState.checkCanWrite() else {
@@ -491,11 +490,11 @@ package struct HTTP3StreamStateMachine: ~Copyable {
491490
let validationResult = idleState.validator.processOutboundFrame(frame)
492491
switch validationResult {
493492
case .forwardFrame(let validatedFrame):
494-
let writeAction = idleState.writeState.write(frame: validatedFrame)
493+
let writeAction = idleState.writeState.write(frame: validatedFrame, into: &buffer)
495494
switch writeAction {
496-
case .writeBytes(let bytes):
495+
case .wroteBytes:
497496
self = .init(state: .idle(idleState))
498-
return .returnBytes(bytes)
497+
return .wroteBytes
499498
case .encodeHeaders(let fields):
500499
self = .init(state: .idle(idleState))
501500
return .encodeHeaders(fields)
@@ -520,8 +519,8 @@ package struct HTTP3StreamStateMachine: ~Copyable {
520519
}
521520

522521
package enum HeaderEncodeResultAction {
523-
/// You should write out the following bytes to the wire.
524-
case returnBytes(ByteBuffer)
522+
/// The header's bytes were appended to the buffer you provided.
523+
case wroteBytes
525524
/// This header can't be encoded because the stream is already in an error state.
526525
case previousError(HTTP3Error)
527526
/// You should fail the current write because the stream is already closed
@@ -530,15 +529,16 @@ package struct HTTP3StreamStateMachine: ~Copyable {
530529

531530
package mutating func gotHeaderEncodeResult(
532531
_ result: HTTP3PartialFrame.Headers,
533-
from: [HTTPField]
532+
from: [HTTPField],
533+
into buffer: inout ByteBuffer
534534
) -> HeaderEncodeResultAction {
535535
switch self.state {
536536
case .idle(var idleState):
537-
let writeAction = idleState.writeState.gotHeaderEncodeResult(result, from: from)
537+
let writeAction = idleState.writeState.gotHeaderEncodeResult(result, from: from, into: &buffer)
538538
self = .init(state: .idle(idleState))
539539
switch writeAction {
540-
case .writeBytes(let bytes):
541-
return .returnBytes(bytes)
540+
case .wroteBytes:
541+
return .wroteBytes
542542
}
543543
case .finished:
544544
// We shouldn't get a header decode result on a finished stream.

Sources/NIOHTTP3/HTTP3StreamHandler.swift

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ package final class HTTP3StreamHandler: ChannelDuplexHandler {
4545
/// The channel context. This handler can only be in one channel at a time.
4646
private var context: ChannelHandlerContext?
4747

48+
/// Bytes for frames that have been written but not yet flushed.
49+
private var pendingBytes: ByteBuffer?
50+
51+
/// The promise which will be fulfilled when `pendingBytes` has been written.
52+
private var pendingPromise: EventLoopPromise<Void>?
53+
4854
/// The state machine which handles processing incoming bytes into frames, including validating them and decoding QPACK.
4955
private var stateMachine: HTTP3StreamStateMachine
5056

@@ -79,6 +85,11 @@ package final class HTTP3StreamHandler: ChannelDuplexHandler {
7985

8086
package func channelInactive(context: ChannelHandlerContext) {
8187
self.logger.trace("HTTP3StreamHandler.channelInactive")
88+
89+
// Don't leak the pending promise.
90+
self.pendingBytes = nil
91+
self.pendingPromise.take()?.fail(ChannelError.ioOnClosedChannel)
92+
8293
// We want to flush out anything that's buffered which can be flushed.
8394
// There's unlikely to be anything...only if we got a channelInactive between a read and a readComplete.
8495
// We need to buffer any such actions into an array and save it for after we close the state machine
@@ -134,6 +145,10 @@ package final class HTTP3StreamHandler: ChannelDuplexHandler {
134145
}
135146

136147
package func handlerRemoved(context: ChannelHandlerContext) {
148+
// Don't leak the pending promise.
149+
self.pendingBytes = nil
150+
self.pendingPromise.take()?.fail(ChannelError.ioOnClosedChannel)
151+
137152
// Cleanup reference to avoid leaks.
138153
self.context = nil
139154
}
@@ -187,7 +202,13 @@ package final class HTTP3StreamHandler: ChannelDuplexHandler {
187202
package func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
188203
let frame = self.unwrapOutboundIn(data)
189204
self.logger.trace("HTTP3StreamHandler.write", metadata: [LoggingKeys.h3FrameType: "\(frame.type)"])
190-
let action = self.stateMachine.writeFrame(frame: frame)
205+
206+
if self.pendingBytes == nil {
207+
self.pendingBytes = context.channel.allocator.buffer(capacity: 256)
208+
}
209+
210+
let action = self.stateMachine.writeFrame(frame: frame, into: &self.pendingBytes!)
211+
191212
switch action {
192213
case .previousError:
193214
// Just drop the byte
@@ -200,8 +221,8 @@ package final class HTTP3StreamHandler: ChannelDuplexHandler {
200221
location: .here()
201222
)
202223
)
203-
case .returnBytes(let bytes):
204-
context.write(wrapOutboundOut(bytes), promise: promise)
224+
case .wroteBytes:
225+
self.pendingPromise.setOrCascade(to: promise)
205226
case .wouldBeStreamError(let error):
206227
context.fireErrorCaught(error)
207228
promise?.fail(error)
@@ -213,7 +234,8 @@ package final class HTTP3StreamHandler: ChannelDuplexHandler {
213234
promise?.fail(error)
214235
case .encodeHeaders(let fields):
215236
let encoded = self.qpackEncoder(fields, self.streamID)
216-
let action = self.stateMachine.gotHeaderEncodeResult(encoded, from: fields)
237+
let action = self.stateMachine.gotHeaderEncodeResult(encoded, from: fields, into: &self.pendingBytes!)
238+
217239
switch action {
218240
case .previousError(let previousError):
219241
promise?.fail(
@@ -225,14 +247,41 @@ package final class HTTP3StreamHandler: ChannelDuplexHandler {
225247
location: .here()
226248
)
227249
)
228-
case .returnBytes(let bytes):
229-
context.write(wrapOutboundOut(bytes), promise: promise)
250+
case .wroteBytes:
251+
self.pendingPromise.setOrCascade(to: promise)
230252
case .alreadyClosed:
231253
promise?.fail(ChannelError.ioOnClosedChannel)
232254
}
233255
}
234256
}
235257

258+
package func flush(context: ChannelHandlerContext) {
259+
self.emitPendingBytes(context: context)
260+
context.flush()
261+
}
262+
263+
package func close(
264+
context: ChannelHandlerContext,
265+
mode: CloseMode,
266+
promise: EventLoopPromise<Void>?
267+
) {
268+
switch mode {
269+
case .output, .all:
270+
self.emitPendingBytes(context: context)
271+
case .input:
272+
()
273+
}
274+
context.close(mode: mode, promise: promise)
275+
}
276+
277+
/// Write any pending bytes.
278+
private func emitPendingBytes(context: ChannelHandlerContext) {
279+
if let bytes = self.pendingBytes.take() {
280+
let promise = self.pendingPromise.take()
281+
context.write(HTTP3StreamHandler.wrapOutboundOut(bytes), promise: promise)
282+
}
283+
}
284+
236285
package func errorCaught(context: ChannelHandlerContext, error: any Error) {
237286
switch error {
238287
case let error as QUICStreamResetError:

Tests/HTTP3Tests/HTTP3StreamStateMachineTests.swift

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,21 @@ extension HTTP3StreamStateMachine.WriteFrameAction {
836836
}
837837

838838
extension HTTP3StreamStateMachine {
839+
/// Test convenience: write a frame using a throwaway buffer, for assertions that don't inspect bytes.
840+
fileprivate mutating func writeFrame(frame: HTTP3Frame) -> WriteFrameAction {
841+
var buffer = ByteBuffer()
842+
return self.writeFrame(frame: frame, into: &buffer)
843+
}
844+
845+
/// Test convenience: deliver an encode result using a throwaway buffer.
846+
fileprivate mutating func gotHeaderEncodeResult(
847+
_ result: HTTP3PartialFrame.Headers,
848+
from: [HTTPField]
849+
) -> HeaderEncodeResultAction {
850+
var buffer = ByteBuffer()
851+
return self.gotHeaderEncodeResult(result, from: from, into: &buffer)
852+
}
853+
839854
fileprivate enum ResolvedAction {
840855
case returnBytes(ByteBuffer)
841856
case wouldBeStreamError(HTTP3Error)
@@ -846,12 +861,13 @@ extension HTTP3StreamStateMachine {
846861
/// Do a write, and do the qpack too, and return just one action.
847862
fileprivate mutating func writeFrameAndQPACK(frame: HTTP3Frame) -> ResolvedAction? {
848863
let encoder = StaticQPACKEncoder()
849-
let action = self.writeFrame(frame: frame)
864+
var buffer = ByteBuffer()
865+
let action = self.writeFrame(frame: frame, into: &buffer)
850866
switch action {
851867
case .previousError:
852868
return nil
853-
case .returnBytes(let bytes):
854-
return .returnBytes(bytes)
869+
case .wroteBytes:
870+
return .returnBytes(buffer)
855871
case .wouldBeStreamError(let error):
856872
return .wouldBeStreamError(error)
857873
case .wouldBeConnectionError(let error):
@@ -860,10 +876,10 @@ extension HTTP3StreamStateMachine {
860876
return .alreadyClosed
861877
case .encodeHeaders(let fields):
862878
let qpackResult = encoder.encode(headers: fields)
863-
let action2 = self.gotHeaderEncodeResult(.init(fieldSection: qpackResult), from: fields)
879+
let action2 = self.gotHeaderEncodeResult(.init(fieldSection: qpackResult), from: fields, into: &buffer)
864880
switch action2 {
865-
case .returnBytes(let bytes):
866-
return .returnBytes(bytes)
881+
case .wroteBytes:
882+
return .returnBytes(buffer)
867883
case .previousError:
868884
return nil
869885
case .alreadyClosed:

0 commit comments

Comments
 (0)