Skip to content

Commit 09f3c1b

Browse files
authored
Receive buffer simplication [4/4] (#5079)
* Simplify the buffer-reallocation code * Simplify pre-allocated chunk memory management
1 parent cfc5b5e commit 09f3c1b

File tree

4 files changed

+162
-173
lines changed

4 files changed

+162
-173
lines changed

src/core/recv_buffer.c

Lines changed: 79 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -167,27 +167,27 @@ QuicRecvChunkInitialize(
167167
_Inout_ QUIC_RECV_CHUNK* Chunk,
168168
_In_ uint32_t AllocLength,
169169
_Inout_updates_(AllocLength) uint8_t* Buffer,
170-
_In_ BOOLEAN AppOwnedBuffer
170+
_In_ BOOLEAN AllocatedFromPool
171171
)
172172
{
173173
Chunk->AllocLength = AllocLength;
174174
Chunk->Buffer = Buffer;
175175
Chunk->ExternalReference = FALSE;
176-
Chunk->AppOwnedBuffer = AppOwnedBuffer;
176+
Chunk->AllocatedFromPool = AllocatedFromPool;
177177
}
178178

179179
_IRQL_requires_max_(DISPATCH_LEVEL)
180180
void
181181
QuicRecvChunkFree(
182-
_In_ QUIC_RECV_BUFFER* RecvBuffer,
183182
_In_ QUIC_RECV_CHUNK* Chunk
184183
)
185184
{
186-
if (Chunk == RecvBuffer->PreallocatedChunk) {
187-
return;
188-
}
189-
190-
if (Chunk->AppOwnedBuffer) {
185+
//
186+
// The data buffer of the chunk is allocated in the same allocation
187+
// as the chunk itself if and only if it is owned by the receive buffer:
188+
// freeing the chunk will free the data buffer as needed.
189+
//
190+
if (Chunk->AllocatedFromPool) {
191191
CxPlatPoolFree(Chunk);
192192
} else {
193193
CXPLAT_FREE(Chunk, QUIC_POOL_RECVBUF);
@@ -273,6 +273,7 @@ QuicRecvBufferInitialize(
273273
{
274274
CXPLAT_DBG_ASSERT(AllocBufferLength != 0 || RecvMode == QUIC_RECV_BUF_MODE_APP_OWNED);
275275
CXPLAT_DBG_ASSERT(VirtualBufferLength != 0 || RecvMode == QUIC_RECV_BUF_MODE_APP_OWNED);
276+
CXPLAT_DBG_ASSERT(PreallocatedChunk == NULL || RecvMode != QUIC_RECV_BUF_MODE_APP_OWNED);
276277
CXPLAT_DBG_ASSERT((AllocBufferLength & (AllocBufferLength - 1)) == 0); // Power of 2
277278
CXPLAT_DBG_ASSERT((VirtualBufferLength & (VirtualBufferLength - 1)) == 0); // Power of 2
278279
CXPLAT_DBG_ASSERT(AllocBufferLength <= VirtualBufferLength);
@@ -282,7 +283,6 @@ QuicRecvBufferInitialize(
282283
RecvBuffer->ReadPendingLength = 0;
283284
RecvBuffer->ReadLength = 0;
284285
RecvBuffer->RecvMode = RecvMode;
285-
RecvBuffer->PreallocatedChunk = PreallocatedChunk;
286286
RecvBuffer->RetiredChunk = NULL;
287287
QuicRangeInitialize(QUIC_MAX_RANGE_ALLOC_SIZE, &RecvBuffer->WrittenRanges);
288288
CxPlatListInitializeHead(&RecvBuffer->Chunks);
@@ -330,11 +330,11 @@ QuicRecvBufferUninitialize(
330330
CxPlatListRemoveHead(&RecvBuffer->Chunks),
331331
QUIC_RECV_CHUNK,
332332
Link);
333-
QuicRecvChunkFree(RecvBuffer, Chunk);
333+
QuicRecvChunkFree(Chunk);
334334
}
335335

336336
if (RecvBuffer->RetiredChunk != NULL) {
337-
QuicRecvChunkFree(RecvBuffer, RecvBuffer->RetiredChunk);
337+
QuicRecvChunkFree(RecvBuffer->RetiredChunk);
338338
}
339339
}
340340

@@ -454,11 +454,9 @@ QuicRecvBufferResize(
454454
TargetBufferLength != 0 &&
455455
(TargetBufferLength & (TargetBufferLength - 1)) == 0); // Power of 2
456456
CXPLAT_DBG_ASSERT(!CxPlatListIsEmpty(&RecvBuffer->Chunks)); // Should always have at least one chunk
457+
457458
QUIC_RECV_CHUNK* LastChunk =
458-
CXPLAT_CONTAINING_RECORD(
459-
RecvBuffer->Chunks.Blink,
460-
QUIC_RECV_CHUNK,
461-
Link);
459+
CXPLAT_CONTAINING_RECORD(RecvBuffer->Chunks.Blink, QUIC_RECV_CHUNK, Link);
462460
CXPLAT_DBG_ASSERT(TargetBufferLength > LastChunk->AllocLength); // Should only be called when buffer needs to grow
463461
BOOLEAN LastChunkIsFirst = LastChunk->Link.Blink == &RecvBuffer->Chunks;
464462

@@ -476,95 +474,69 @@ QuicRecvBufferResize(
476474
QuicRecvChunkInitialize(NewChunk, TargetBufferLength, (uint8_t*)(NewChunk + 1), FALSE);
477475
CxPlatListInsertTail(&RecvBuffer->Chunks, &NewChunk->Link);
478476

479-
if (!LastChunk->ExternalReference) {
477+
if (RecvBuffer->RecvMode == QUIC_RECV_BUF_MODE_MULTIPLE && LastChunk->ExternalReference) {
480478
//
481-
// If the last chunk isn't externally referenced, then we can just
482-
// replace it with the new chunk.
479+
// In Multiple mode, if the last chunk is referenced, simply add the new chunk to the list.
480+
// The last chunk is still used for reads and writes but drains reduce its capacity until it
481+
// can be freed.
483482
//
484-
if (LastChunkIsFirst) {
485-
//
486-
// If it's the first chunk, then the data may not start from the
487-
// beginning.
488-
//
489-
uint32_t Span = QuicRecvBufferGetSpan(RecvBuffer);
490-
if (Span < LastChunk->AllocLength) {
491-
Span = LastChunk->AllocLength;
492-
}
493-
uint32_t LengthTillWrap = LastChunk->AllocLength - RecvBuffer->ReadStart;
494-
if (Span <= LengthTillWrap) {
495-
CxPlatCopyMemory(
496-
NewChunk->Buffer,
497-
LastChunk->Buffer + RecvBuffer->ReadStart,
498-
Span);
499-
} else {
500-
CxPlatCopyMemory(
501-
NewChunk->Buffer,
502-
LastChunk->Buffer + RecvBuffer->ReadStart,
503-
LengthTillWrap);
504-
CxPlatCopyMemory(
505-
NewChunk->Buffer + LengthTillWrap,
506-
LastChunk->Buffer,
507-
Span - LengthTillWrap);
508-
}
509-
RecvBuffer->ReadStart = 0;
510-
CXPLAT_DBG_ASSERT(NewChunk->AllocLength == TargetBufferLength);
511-
RecvBuffer->Capacity = NewChunk->AllocLength;
483+
return TRUE;
484+
}
485+
486+
//
487+
// In Single and Circular modes, or in Multiple mode when the last chunk is not referenced,
488+
// replace the last chunk is with the new one:
489+
// - copy the data to the new chunk
490+
// - remove the last chunk from the list
491+
//
512492

493+
if (LastChunkIsFirst) {
494+
uint32_t WrittenSpan =
495+
CXPLAT_MIN(LastChunk->AllocLength, QuicRecvBufferGetSpan(RecvBuffer));
496+
uint32_t LengthBeforeWrap = LastChunk->AllocLength - RecvBuffer->ReadStart;
497+
if (WrittenSpan <= LengthBeforeWrap) {
498+
CxPlatCopyMemory(
499+
NewChunk->Buffer,
500+
LastChunk->Buffer + RecvBuffer->ReadStart,
501+
WrittenSpan);
513502
} else {
514-
//
515-
// If it's not the first chunk, then it always starts from the
516-
// beginning of the buffer.
517-
//
518503
CxPlatCopyMemory(
519504
NewChunk->Buffer,
505+
LastChunk->Buffer + RecvBuffer->ReadStart,
506+
LengthBeforeWrap);
507+
CxPlatCopyMemory(
508+
NewChunk->Buffer + LengthBeforeWrap,
520509
LastChunk->Buffer,
521-
LastChunk->AllocLength);
510+
WrittenSpan - LengthBeforeWrap);
522511
}
523-
524-
CxPlatListEntryRemove(&LastChunk->Link);
525-
QuicRecvChunkFree(RecvBuffer, LastChunk);
526-
527-
return TRUE;
512+
RecvBuffer->ReadStart = 0;
513+
RecvBuffer->Capacity = NewChunk->AllocLength;
514+
} else {
515+
//
516+
// If it isn't the first chunk, it always starts from the beginning of the buffer.
517+
//
518+
CxPlatCopyMemory(NewChunk->Buffer, LastChunk->Buffer, LastChunk->AllocLength);
528519
}
529520

530521
//
531-
// If the chunk is already referenced, and if we're in multiple receive
532-
// mode, we can just add the new chunk to the end of the list.
522+
// The chunk data has been copied, remove the chunk from the list.
533523
//
534-
if (RecvBuffer->RecvMode == QUIC_RECV_BUF_MODE_MULTIPLE) {
535-
return TRUE;
536-
}
524+
CxPlatListEntryRemove(&LastChunk->Link);
537525

538-
//
539-
// Otherwise, we need to copy the data from the existing chunk
540-
// into the new chunk, and retire the existing chunk until we can free it.
541-
//
526+
if (LastChunk->ExternalReference) {
527+
//
528+
// The chunk is referenced, so we need to retire it until we can free it.
529+
// (only one read can be pending at a time, so there is no retired chunk)
530+
//
531+
CXPLAT_DBG_ASSERT(
532+
RecvBuffer->RecvMode == QUIC_RECV_BUF_MODE_SINGLE ||
533+
RecvBuffer->RecvMode == QUIC_RECV_BUF_MODE_CIRCULAR);
534+
CXPLAT_DBG_ASSERT(RecvBuffer->RetiredChunk == NULL);
542535

543-
//
544-
// If it's the first chunk, then it may not start from the beginning.
545-
//
546-
uint32_t Span = QuicRecvBufferGetSpan(RecvBuffer);
547-
uint32_t LengthTillWrap = LastChunk->AllocLength - RecvBuffer->ReadStart;
548-
if (Span <= LengthTillWrap) {
549-
CxPlatCopyMemory(
550-
NewChunk->Buffer,
551-
LastChunk->Buffer + RecvBuffer->ReadStart,
552-
Span);
536+
RecvBuffer->RetiredChunk = LastChunk;
553537
} else {
554-
CxPlatCopyMemory(
555-
NewChunk->Buffer,
556-
LastChunk->Buffer + RecvBuffer->ReadStart,
557-
LengthTillWrap);
558-
CxPlatCopyMemory(
559-
NewChunk->Buffer + LengthTillWrap,
560-
LastChunk->Buffer,
561-
Span - LengthTillWrap);
538+
QuicRecvChunkFree(LastChunk);
562539
}
563-
RecvBuffer->ReadStart = 0;
564-
RecvBuffer->Capacity = NewChunk->AllocLength;
565-
CxPlatListEntryRemove(&LastChunk->Link);
566-
CXPLAT_DBG_ASSERT(RecvBuffer->RetiredChunk == NULL);
567-
RecvBuffer->RetiredChunk = LastChunk;
568540

569541
return TRUE;
570542
}
@@ -575,55 +547,17 @@ QuicRecvBufferGetTotalAllocLength(
575547
_In_ QUIC_RECV_BUFFER* RecvBuffer
576548
)
577549
{
578-
if (RecvBuffer->RecvMode == QUIC_RECV_BUF_MODE_SINGLE ||
579-
RecvBuffer->RecvMode == QUIC_RECV_BUF_MODE_CIRCULAR) {
580-
//
581-
// In single and circular mode, the last chunk is the only chunk being
582-
// written to at any given time, and therefore the only chunk we care
583-
// about in terms of total allocation space.
584-
//
585-
QUIC_RECV_CHUNK* Chunk =
586-
CXPLAT_CONTAINING_RECORD(
587-
RecvBuffer->Chunks.Blink,
588-
QUIC_RECV_CHUNK,
589-
Link);
590-
return Chunk->AllocLength;
591-
}
592-
593-
//
594-
// For multiple mode and app-owned mode, several chunks may be used at any
595-
// point in time, so we need to consider the space allocated for all of them.
596-
// Additionally, the first one is special because it may be already partially
597-
// drained, making it only partially usable.
598-
//
599-
QUIC_RECV_CHUNK* Chunk =
600-
CXPLAT_CONTAINING_RECORD(
601-
RecvBuffer->Chunks.Flink,
602-
QUIC_RECV_CHUNK,
603-
Link);
604-
if (RecvBuffer->RecvMode == QUIC_RECV_BUF_MODE_MULTIPLE &&
605-
Chunk->Link.Flink == &RecvBuffer->Chunks) {
606-
//
607-
// In Multiple mode, only one chunk means we don't have an artificial
608-
// "end", and are using the full allocated length of the buffer in a
609-
// circular fashion.
610-
//
611-
return Chunk->AllocLength;
612-
}
550+
CXPLAT_DBG_ASSERT(!CxPlatListIsEmpty(&RecvBuffer->Chunks));
613551

614552
//
615-
// Otherwise, it is possible part of the first chunk has already been
616-
// drained, so we don't use the allocated length, but the Capacity instead
617-
// when calculating total available space.
553+
// The first chunk might have a reduced capacity (if more chunks are present and it is being
554+
// consumed). Other chunks are always allocated at their full alloc size.
618555
//
619556
uint32_t AllocLength = RecvBuffer->Capacity;
620-
while (Chunk->Link.Flink != &RecvBuffer->Chunks) {
621-
Chunk =
622-
CXPLAT_CONTAINING_RECORD(
623-
Chunk->Link.Flink,
624-
QUIC_RECV_CHUNK,
625-
Link);
626-
CXPLAT_DBG_ASSERT((uint64_t)AllocLength + (uint64_t)Chunk->AllocLength < UINT32_MAX);
557+
for (CXPLAT_LIST_ENTRY* Link = RecvBuffer->Chunks.Flink->Flink; // Skip the first chunk
558+
Link != &RecvBuffer->Chunks;
559+
Link = Link->Flink) {
560+
QUIC_RECV_CHUNK* Chunk = CXPLAT_CONTAINING_RECORD(Link, QUIC_RECV_CHUNK, Link);
627561
AllocLength += Chunk->AllocLength;
628562
}
629563
return AllocLength;
@@ -702,7 +636,7 @@ QuicRecvBufferWrite(
702636
//
703637
// Check if the write buffer has already been completely written before.
704638
//
705-
uint64_t AbsoluteLength = WriteOffset + WriteLength;
639+
const uint64_t AbsoluteLength = WriteOffset + WriteLength;
706640
if (AbsoluteLength <= RecvBuffer->BaseOffset) {
707641
*WriteLimit = 0;
708642
return QUIC_STATUS_SUCCESS;
@@ -744,15 +678,13 @@ QuicRecvBufferWrite(
744678
uint32_t AllocLength = QuicRecvBufferGetTotalAllocLength(RecvBuffer);
745679
if (AbsoluteLength > RecvBuffer->BaseOffset + AllocLength) {
746680
//
747-
// If we don't currently have enough room then we will want to resize
748-
// the last chunk to be big enough to hold everything. We do this by
749-
// repeatedly doubling its size until it is large enough.
681+
// There isn't enough space to write the data.
682+
// Add a new chunk (or replace the existing one), doubling the size of the largest chunk
683+
// until there is enough space for the write.
750684
//
751-
uint32_t NewBufferLength =
752-
CXPLAT_CONTAINING_RECORD(
753-
RecvBuffer->Chunks.Blink,
754-
QUIC_RECV_CHUNK,
755-
Link)->AllocLength << 1;
685+
QUIC_RECV_CHUNK* LastChunk =
686+
CXPLAT_CONTAINING_RECORD(RecvBuffer->Chunks.Blink, QUIC_RECV_CHUNK, Link);
687+
uint32_t NewBufferLength = LastChunk->AllocLength << 1;
756688
while (AbsoluteLength > RecvBuffer->BaseOffset + NewBufferLength) {
757689
NewBufferLength <<= 1;
758690
}
@@ -979,7 +911,7 @@ QuicRecvBufferDrainFullChunks(
979911
ChunkIt = ChunkIt->Flink;
980912

981913
CxPlatListEntryRemove(&Chunk->Link);
982-
QuicRecvChunkFree(RecvBuffer, Chunk);
914+
QuicRecvChunkFree(Chunk);
983915
}
984916

985917
RecvBuffer->Capacity = NewFirstChunk != NULL ? NewFirstChunk->AllocLength : 0;
@@ -1025,10 +957,10 @@ QuicRecvBufferDrainFirstChunk(
1025957
// In Single mode, the readable data must always start at the front of the buffer,
1026958
// move all written data if needed.
1027959
//
960+
uint32_t WrittenSpan =
961+
CXPLAT_MIN(FirstChunk->AllocLength, QuicRecvBufferGetSpan(RecvBuffer));
1028962
CxPlatMoveMemory(
1029-
FirstChunk->Buffer,
1030-
FirstChunk->Buffer + RecvBuffer->ReadStart,
1031-
FirstChunk->AllocLength - RecvBuffer->ReadStart); // TODO - Might be able to copy less than the full alloc length
963+
FirstChunk->Buffer, FirstChunk->Buffer + RecvBuffer->ReadStart, WrittenSpan);
1032964
RecvBuffer->ReadStart = 0;
1033965
}
1034966
}
@@ -1071,7 +1003,7 @@ QuicRecvBufferDrain(
10711003
RecvBuffer->RecvMode == QUIC_RECV_BUF_MODE_SINGLE ||
10721004
RecvBuffer->RecvMode == QUIC_RECV_BUF_MODE_CIRCULAR);
10731005

1074-
QuicRecvChunkFree(RecvBuffer, RecvBuffer->RetiredChunk);
1006+
QuicRecvChunkFree(RecvBuffer->RetiredChunk);
10751007
RecvBuffer->RetiredChunk = NULL;
10761008
}
10771009

src/core/recv_buffer.h

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ typedef struct QUIC_RECV_CHUNK {
2424
CXPLAT_LIST_ENTRY Link; // Link in the list of chunks.
2525
uint32_t AllocLength; // Allocation size of Buffer
2626
uint8_t ExternalReference : 1; // Indicates the buffer is being used externally.
27-
uint8_t AppOwnedBuffer : 1; // Indicates the buffer is managed by the app.
27+
uint8_t AllocatedFromPool : 1; // Indicates the buffer is was allocated from a pool.
2828
uint8_t* Buffer; // Pointer to the buffer itself. Doesn't need to be freed independently:
2929
// - for internally allocated buffers, points in the same allocation.
30-
// - for exteral buffers, the buffer isn't owned
30+
// - for app-owned buffers, the buffer isn't owned
3131
} QUIC_RECV_CHUNK;
3232

3333
_IRQL_requires_max_(DISPATCH_LEVEL)
@@ -36,7 +36,7 @@ QuicRecvChunkInitialize(
3636
_Inout_ QUIC_RECV_CHUNK* Chunk,
3737
_In_ uint32_t AllocLength,
3838
_Inout_updates_(AllocLength) uint8_t* Buffer,
39-
_In_ BOOLEAN AppOwnedBuffer
39+
_In_ BOOLEAN BufferAllocatedFromPool
4040
);
4141

4242
typedef struct QUIC_RECV_BUFFER {
@@ -51,11 +51,6 @@ typedef struct QUIC_RECV_BUFFER {
5151
//
5252
QUIC_RECV_CHUNK* RetiredChunk;
5353

54-
//
55-
// Optional, preallocated initial chunk.
56-
//
57-
QUIC_RECV_CHUNK* PreallocatedChunk;
58-
5954
//
6055
// Ranges of stream offsets that have been written to the buffer,
6156
// starting from 0 (not only what is currently in the buffer).
@@ -110,7 +105,8 @@ typedef struct QUIC_RECV_BUFFER {
110105
//
111106
// Initialize a QUIC_RECV_BUFFER.
112107
// Can only fail if PreallocatedChunk == NULL && RecvMode != QUIC_RECV_BUF_MODE_APP_OWNED.
113-
// PreallocatedChunk is owned by the caller and must be freed afte the buffer is uninitialized.
108+
// PreallocatedChunk ownership is given to the receive buffer.
109+
// PreallocatedChunk must be null if RecvMode == QUIC_RECV_BUF_MODE_APP_OWNED.
114110
//
115111
_IRQL_requires_max_(DISPATCH_LEVEL)
116112
QUIC_STATUS

0 commit comments

Comments
 (0)