@@ -198,7 +198,7 @@ public void WriteResponseHeaders(int streamId, int statusCode, Http2HeadersFrame
198198 }
199199 }
200200
201- public ValueTask < FlushResult > WriteResponseTrailers ( int streamId , HttpResponseTrailers headers )
201+ public ValueTask < FlushResult > WriteResponseTrailersAsync ( int streamId , HttpResponseTrailers headers )
202202 {
203203 lock ( _writeLock )
204204 {
@@ -256,6 +256,9 @@ private void FinishWritingHeaders(int streamId, int payloadLength, bool done)
256256
257257 public ValueTask < FlushResult > WriteDataAsync ( int streamId , StreamOutputFlowControl flowControl , in ReadOnlySequence < byte > data , bool endStream , bool firstWrite , bool forceFlush )
258258 {
259+ // Logic in this method is replicated in WriteDataAndTrailersAsync.
260+ // Changes here may need to be mirrored in WriteDataAndTrailersAsync.
261+
259262 // The Length property of a ReadOnlySequence can be expensive, so we cache the value.
260263 var dataLength = data . Length ;
261264
@@ -286,6 +289,43 @@ public ValueTask<FlushResult> WriteDataAsync(int streamId, StreamOutputFlowContr
286289 }
287290 }
288291
292+ public ValueTask < FlushResult > WriteDataAndTrailersAsync ( int streamId , StreamOutputFlowControl flowControl , in ReadOnlySequence < byte > data , bool firstWrite , HttpResponseTrailers headers )
293+ {
294+ // This method combines WriteDataAsync and WriteResponseTrailers.
295+ // Changes here may need to be mirrored in WriteDataAsync.
296+
297+ // The Length property of a ReadOnlySequence can be expensive, so we cache the value.
298+ var dataLength = data . Length ;
299+
300+ lock ( _writeLock )
301+ {
302+ if ( _completed || flowControl . IsAborted )
303+ {
304+ return default ;
305+ }
306+
307+ // Zero-length data frames are allowed to be sent immediately even if there is no space available in the flow control window.
308+ // https://httpwg.org/specs/rfc7540.html#rfc.section.6.9.1
309+ if ( dataLength != 0 && dataLength > flowControl . Available )
310+ {
311+ return WriteDataAndTrailersAsyncCore ( this , streamId , flowControl , data , dataLength , firstWrite , headers ) ;
312+ }
313+
314+ // This cast is safe since if dataLength would overflow an int, it's guaranteed to be greater than the available flow control window.
315+ flowControl . Advance ( ( int ) dataLength ) ;
316+ WriteDataUnsynchronized ( streamId , data , dataLength , endStream : false ) ;
317+
318+ return WriteResponseTrailersAsync ( streamId , headers ) ;
319+ }
320+
321+ static async ValueTask < FlushResult > WriteDataAndTrailersAsyncCore ( Http2FrameWriter writer , int streamId , StreamOutputFlowControl flowControl , ReadOnlySequence < byte > data , long dataLength , bool firstWrite , HttpResponseTrailers headers )
322+ {
323+ await writer . WriteDataAsync ( streamId , flowControl , data , dataLength , endStream : false , firstWrite ) ;
324+
325+ return await writer . WriteResponseTrailersAsync ( streamId , headers ) ;
326+ }
327+ }
328+
289329 /* Padding is not implemented
290330 +---------------+
291331 |Pad Length? (8)|
0 commit comments