Skip to content

Commit a5b2b7e

Browse files
Sync shared code from runtime (#60782)
Co-authored-by: MihaZupan <MihaZupan@users.noreply.github.com>
1 parent c992587 commit a5b2b7e

File tree

3 files changed

+72
-28
lines changed

3 files changed

+72
-28
lines changed

Diff for: src/Shared/runtime/Http2/Hpack/DynamicTable.cs

+37-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
5+
46
namespace System.Net.Http.HPack
57
{
68
internal sealed class DynamicTable
@@ -14,7 +16,7 @@ internal sealed class DynamicTable
1416

1517
public DynamicTable(int maxSize)
1618
{
17-
_buffer = new HeaderField[maxSize / HeaderField.RfcOverhead];
19+
_buffer = [];
1820
_maxSize = maxSize;
1921
}
2022

@@ -67,18 +69,17 @@ public void Insert(int? staticTableIndex, ReadOnlySpan<byte> name, ReadOnlySpan<
6769
return;
6870
}
6971

70-
var entry = new HeaderField(staticTableIndex, name, value);
71-
_buffer[_insertIndex] = entry;
72-
_insertIndex = (_insertIndex + 1) % _buffer.Length;
73-
_size += entry.Length;
74-
_count++;
75-
}
76-
77-
public void Resize(int maxSize)
78-
{
79-
if (maxSize > _maxSize)
72+
// Ensure that we have at least one slot available.
73+
if (_count == _buffer.Length)
8074
{
81-
var newBuffer = new HeaderField[maxSize / HeaderField.RfcOverhead];
75+
int maxCapacity = _maxSize / HeaderField.RfcOverhead;
76+
Debug.Assert(_count + 1 <= maxCapacity);
77+
78+
// Double the size of the current buffer, starting with at least 16 entries.
79+
int newBufferSize = Math.Min(Math.Max(16, _buffer.Length * 2), maxCapacity);
80+
Debug.Assert(newBufferSize > _count);
81+
82+
var newBuffer = new HeaderField[newBufferSize];
8283

8384
int headCount = Math.Min(_buffer.Length - _removeIndex, _count);
8485
int tailCount = _count - headCount;
@@ -89,11 +90,27 @@ public void Resize(int maxSize)
8990
_buffer = newBuffer;
9091
_removeIndex = 0;
9192
_insertIndex = _count;
92-
_maxSize = maxSize;
9393
}
94-
else
94+
95+
var entry = new HeaderField(staticTableIndex, name, value);
96+
_buffer[_insertIndex] = entry;
97+
98+
if (++_insertIndex == _buffer.Length)
99+
{
100+
_insertIndex = 0;
101+
}
102+
103+
_size += entry.Length;
104+
_count++;
105+
}
106+
107+
public void UpdateMaxSize(int maxSize)
108+
{
109+
int previousMax = _maxSize;
110+
_maxSize = maxSize;
111+
112+
if (maxSize < previousMax)
95113
{
96-
_maxSize = maxSize;
97114
EnsureAvailable(0);
98115
}
99116
}
@@ -107,7 +124,11 @@ private void EnsureAvailable(int available)
107124
field = default;
108125

109126
_count--;
110-
_removeIndex = (_removeIndex + 1) % _buffer.Length;
127+
128+
if (++_removeIndex == _buffer.Length)
129+
{
130+
_removeIndex = 0;
131+
}
111132
}
112133
}
113134
}

Diff for: src/Shared/runtime/Http2/Hpack/HPackDecoder.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,13 @@ private enum State : byte
2727
DynamicTableSizeUpdate
2828
}
2929

30+
// https://datatracker.ietf.org/doc/html/rfc9113#name-defined-settings
31+
// Initial value for SETTINGS_HEADER_TABLE_SIZE is 4,096 octets.
3032
public const int DefaultHeaderTableSize = 4096;
31-
public const int DefaultStringOctetsSize = 4096;
33+
34+
// This is the initial size. Buffers will be dynamically resized as needed.
35+
public const int DefaultStringOctetsSize = 32;
36+
3237
public const int DefaultMaxHeadersLength = 64 * 1024;
3338

3439
// http://httpwg.org/specs/rfc7541.html#rfc.section.6.1
@@ -670,7 +675,7 @@ private void SetDynamicHeaderTableSize(int size)
670675
throw new HPackDecodingException(SR.Format(SR.net_http_hpack_large_table_size_update, size, _maxDynamicTableSize));
671676
}
672677

673-
_dynamicTable.Resize(size);
678+
_dynamicTable.UpdateMaxSize(size);
674679
}
675680
}
676681
}

Diff for: src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs

+28-10
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,33 @@ public void DynamicTable_InsertEntryLargerThanRemainingSpace_NoOp()
9494
Assert.Equal(0, dynamicTable.Size);
9595
}
9696

97+
public static IEnumerable<object[]> DynamicTable_WrapsRingBuffer_Success_MemberData()
98+
{
99+
foreach (int maxSize in new[] { 100, 256, 1000 })
100+
{
101+
int maxCount = maxSize / (2 * "header-0".Length + HeaderField.RfcOverhead);
102+
103+
for (int i = 0; i < maxCount; i++)
104+
{
105+
yield return new object[] { maxSize, i };
106+
}
107+
}
108+
}
109+
97110
[Theory]
98-
[InlineData(0)]
99-
[InlineData(1)]
100-
[InlineData(2)]
101-
[InlineData(3)]
102-
public void DynamicTable_WrapsRingBuffer_Success(int targetInsertIndex)
111+
[MemberData(nameof(DynamicTable_WrapsRingBuffer_Success_MemberData))]
112+
public void DynamicTable_WrapsRingBuffer_Success(int maxSize, int targetInsertIndex)
103113
{
114+
DynamicTable table = new DynamicTable(maxSize);
115+
116+
// The table grows its internal buffer dynamically.
117+
// Fill it with enough entries to force it to grow to max size.
118+
for (int i = 0; i < table.MaxSize / HeaderField.RfcOverhead; i++)
119+
{
120+
table.Insert("a"u8, "b"u8);
121+
}
122+
104123
FieldInfo insertIndexField = typeof(DynamicTable).GetField("_insertIndex", BindingFlags.NonPublic | BindingFlags.Instance);
105-
DynamicTable table = new DynamicTable(maxSize: 256);
106124
Stack<byte[]> insertedHeaders = new Stack<byte[]>();
107125

108126
// Insert into dynamic table until its insert index into its ring buffer loops back to 0.
@@ -167,7 +185,7 @@ public void DynamicTable_Resize_Success(int initialMaxSize, int finalMaxSize, in
167185
headers.Add(dynamicTable[i]);
168186
}
169187

170-
dynamicTable.Resize(finalMaxSize);
188+
dynamicTable.UpdateMaxSize(finalMaxSize);
171189

172190
int expectedCount = Math.Min(finalMaxSize / 64, headers.Count);
173191
Assert.Equal(expectedCount, dynamicTable.Count);
@@ -188,7 +206,7 @@ public void DynamicTable_ResizingEvictsOldestEntries()
188206

189207
VerifyTableEntries(dynamicTable, _header2, _header1);
190208

191-
dynamicTable.Resize(_header2.Length);
209+
dynamicTable.UpdateMaxSize(_header2.Length);
192210

193211
VerifyTableEntries(dynamicTable, _header2);
194212
}
@@ -200,7 +218,7 @@ public void DynamicTable_ResizingToZeroEvictsAllEntries()
200218
dynamicTable.Insert(_header1.Name, _header1.Value);
201219
dynamicTable.Insert(_header2.Name, _header2.Value);
202220

203-
dynamicTable.Resize(0);
221+
dynamicTable.UpdateMaxSize(0);
204222

205223
Assert.Equal(0, dynamicTable.Count);
206224
Assert.Equal(0, dynamicTable.Size);
@@ -219,7 +237,7 @@ public void DynamicTable_CanBeResizedToLargerMaxSize()
219237
Assert.Equal(0, dynamicTable.Count);
220238
Assert.Equal(0, dynamicTable.Size);
221239

222-
dynamicTable.Resize(dynamicTable.MaxSize + _header2.Length);
240+
dynamicTable.UpdateMaxSize(dynamicTable.MaxSize + _header2.Length);
223241
dynamicTable.Insert(_header2.Name, _header2.Value);
224242

225243
VerifyTableEntries(dynamicTable, _header2);

0 commit comments

Comments
 (0)