-
Notifications
You must be signed in to change notification settings - Fork 547
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
Add a mode to the receive buffer to handle external buffers #4758
base: main
Are you sure you want to change the base?
Changes from 16 commits
73d23ab
4a1a71d
f373abf
eb6f865
4357a6f
70ada48
ca02b31
3f568a8
077ea51
0f1dd2e
39bdea9
674b550
2640ab4
58c518f
50f4284
288ad7a
180d008
2a88f7d
c3f292b
f2dd1f0
45d32c0
d70b754
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 |
---|---|---|
|
@@ -658,6 +658,15 @@ | |
goto Error; | ||
} | ||
|
||
if (!!(Flags & QUIC_STREAM_OPEN_FLAG_EXTERNAL_BUFFERS) && | ||
Connection->Settings.StreamMultiReceiveEnabled) { | ||
// | ||
// External buffers are not supported with multi-receive. | ||
// | ||
Status = QUIC_STATUS_INVALID_PARAMETER; | ||
goto Error; | ||
} | ||
|
||
Status = QuicStreamInitialize(Connection, FALSE, Flags, (QUIC_STREAM**)NewStream); | ||
if (QUIC_FAILED(Status)) { | ||
goto Error; | ||
|
@@ -1351,6 +1360,148 @@ | |
"[ api] Exit"); | ||
} | ||
|
||
_IRQL_requires_max_(DISPATCH_LEVEL) | ||
QUIC_STATUS | ||
QUIC_API | ||
MsQuicStreamProvideReceiveBuffers( | ||
_In_ _Pre_defensive_ HQUIC Handle, | ||
_In_ uint32_t BufferCount, | ||
_In_reads_(BufferCount) const QUIC_BUFFER* Buffers | ||
) | ||
{ | ||
QUIC_STATUS Status; | ||
QUIC_OPERATION* Oper; | ||
CXPLAT_LIST_ENTRY ChunkList; | ||
CxPlatListInitializeHead(&ChunkList); | ||
|
||
QuicTraceEvent( | ||
ApiEnter, | ||
"[ api] Enter %u (%p).", | ||
QUIC_TRACE_API_STREAM_PROVIDE_RECEIVE_BUFFERS, | ||
Handle); | ||
|
||
if (!IS_STREAM_HANDLE(Handle) || Buffers == NULL || BufferCount == 0) { | ||
Status = QUIC_STATUS_INVALID_PARAMETER; | ||
goto Error; | ||
} | ||
|
||
for (uint32_t i = 0; i < BufferCount; ++i) { | ||
if (Buffers[i].Length == 0) { | ||
Status = QUIC_STATUS_INVALID_PARAMETER; | ||
goto Error; | ||
} | ||
} | ||
|
||
#pragma prefast(suppress: __WARNING_25024, "Pointer cast already validated.") | ||
QUIC_STREAM* Stream = (QUIC_STREAM*)Handle; | ||
|
||
CXPLAT_TEL_ASSERT(!Stream->Flags.HandleClosed); | ||
CXPLAT_TEL_ASSERT(!Stream->Flags.Freed); | ||
|
||
QUIC_CONNECTION* Connection = Stream->Connection; | ||
QUIC_CONN_VERIFY(Connection, !Connection->State.Freed); | ||
|
||
// | ||
// Execute this API call inline if called on the worker thread. | ||
// | ||
BOOLEAN IsWorkerThread = Connection->WorkerThreadID == CxPlatCurThreadID(); | ||
BOOLEAN IsAlreadyInline = Connection->State.InlineApiExecution; | ||
|
||
if (!Stream->Flags.UseExternalRecvBuffers) { | ||
if (Stream->Flags.PeerStreamStartEventActive) { | ||
CXPLAT_DBG_ASSERT(IsWorkerThread); | ||
// | ||
// We are inline from the callback indicating a peer opened a stream. | ||
// No data was received yet so we can setup external buffers. | ||
// | ||
Connection->State.InlineApiExecution = TRUE; | ||
QuicStreamSwitchToExternalBuffers(Stream); | ||
Connection->State.InlineApiExecution = IsAlreadyInline; | ||
} else { | ||
// | ||
// External buffers can't be provided after the stream has been | ||
// started using internal buffers. | ||
// | ||
Status = QUIC_STATUS_INVALID_STATE; | ||
goto Error; | ||
} | ||
} | ||
|
||
// | ||
// Allocate a chunk for each buffer, linking them together. | ||
// The allocation is done here to make the worker thread task failure free. | ||
// | ||
for (uint32_t i = 0; i < BufferCount; ++i) { | ||
QUIC_RECV_CHUNK* Chunk = CXPLAT_ALLOC_NONPAGED(sizeof(QUIC_RECV_CHUNK), QUIC_POOL_RECVBUF); | ||
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. Should use a pool allocator (to be stored in the worker, like the others) since this is (a) fixed size and (b) on the datapath. 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 means each chunk will need a flag to track whether they are allocated from the pool or not then. The size of the chunk seems to be rather optimized (AllocLength restricted to 31bits to avoid a few extra bytes), is it ok to simply add a "flag" byte? Which makes me realize I need to check the size of the app provided buffers won't override that bit. |
||
if (Chunk == NULL) { | ||
QuicTraceEvent( | ||
AllocFailure, | ||
"Allocation of '%s' failed. (%llu bytes)", | ||
"provided_chunk", | ||
0); | ||
Status = QUIC_STATUS_OUT_OF_MEMORY; | ||
goto Error; | ||
} | ||
QuicRecvChunkInitialize(Chunk, Buffers[i].Length, Buffers[i].Buffer); | ||
CxPlatListInsertTail(&ChunkList, &Chunk->Link); | ||
} | ||
|
||
if (IsWorkerThread) { | ||
// | ||
// Execute this API call inline if called on the worker thread. | ||
// | ||
Connection->State.InlineApiExecution = TRUE; | ||
Status = QuicStreamProvideRecvBuffers(Stream, &ChunkList); | ||
Connection->State.InlineApiExecution = IsAlreadyInline; | ||
} else { | ||
// | ||
// Queue the operation to insert the chunks in the recv buffer, without waiting for the result. | ||
// | ||
Oper = QuicOperationAlloc(Connection->Worker, QUIC_OPER_TYPE_API_CALL); | ||
if (Oper == NULL) { | ||
Status = QUIC_STATUS_OUT_OF_MEMORY; | ||
QuicTraceEvent( | ||
AllocFailure, | ||
"Allocation of '%s' failed. (%llu bytes)", | ||
"STRM_PROVIDE_RECV_BUFFERS, operation", | ||
0); | ||
goto Error; | ||
} | ||
Oper->API_CALL.Context->Type = QUIC_API_TYPE_STRM_PROVIDE_RECV_BUFFERS; | ||
Oper->API_CALL.Context->STRM_PROVIDE_RECV_BUFFERS.Stream = Stream; | ||
CxPlatListInitializeHead(&Oper->API_CALL.Context->STRM_PROVIDE_RECV_BUFFERS.Chunks); | ||
CxPlatListMoveItems(&ChunkList, &Oper->API_CALL.Context->STRM_PROVIDE_RECV_BUFFERS.Chunks); | ||
|
||
// | ||
// Async stream operations need to hold a ref on the stream so that the | ||
// stream isn't freed before the operation can be processed. The ref is | ||
// released after the operation is processed. | ||
// | ||
QuicStreamAddRef(Stream, QUIC_STREAM_REF_OPERATION); | ||
|
||
// | ||
// Queue the operation but don't wait for the completion. | ||
// | ||
QuicConnQueueOper(Connection, Oper); | ||
} | ||
|
||
Status = QUIC_STATUS_SUCCESS; | ||
|
||
Error: | ||
// Cleanup allocated chunks if the operation failed. | ||
guhetier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
while (!CxPlatListIsEmpty(&ChunkList)) { | ||
QUIC_RECV_CHUNK* Chunk = CXPLAT_CONTAINING_RECORD(CxPlatListRemoveHead(&ChunkList), QUIC_RECV_CHUNK, Link); | ||
CXPLAT_FREE(Chunk, QUIC_POOL_RECVBUF); | ||
guhetier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
QuicTraceEvent( | ||
ApiExitStatus, | ||
"[ api] Exit %u", | ||
Status); | ||
|
||
return Status; | ||
} | ||
|
||
_IRQL_requires_max_(PASSIVE_LEVEL) | ||
QUIC_STATUS | ||
QUIC_API | ||
|
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.
What if they want to use multi-receive for streams that don't use external buffers? I think we can't block that.
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.
You would allow an "app buffer" stream when multi-receive is enabled?
The problem is that this stream would not support the multi-receive behavior (at least for now, we could make it happen), which could be confusing for the app.
And if app takes dependency on the fact that "app buffer" stream ignore multi-receive, then we can't add support without an API break or new flags.