Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 04b86e3

Browse files
authored
Fix skipping last segment logic in ROS.TryGet (#28266)
1 parent cad1465 commit 04b86e3

File tree

2 files changed

+129
-121
lines changed

2 files changed

+129
-121
lines changed

src/System.Memory/src/System/Buffers/ReadOnlySequence_helpers.cs

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,18 @@ internal bool TryGetBuffer(in SequencePosition start, in SequencePosition end, o
4141
Debug.Assert(startObject is ReadOnlySequenceSegment<T>);
4242
ReadOnlySequenceSegment<T> startSegment = Unsafe.As<ReadOnlySequenceSegment<T>>(startObject);
4343

44+
data = startSegment.Memory;
45+
4446
if (startSegment != endObject)
4547
{
46-
next = GetBufferCrossSegment(startIndex, endObject, ref startSegment, ref length);
47-
}
48+
ReadOnlySequenceSegment<T> nextSegment = startSegment.Next;
4849

49-
data = startSegment.Memory;
50+
if (nextSegment == null && startSegment != endObject)
51+
ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
52+
53+
next = new SequencePosition(nextSegment, 0);
54+
length = data.Length - startIndex;
55+
}
5056
}
5157
else if (type == SequenceType.Array)
5258
{
@@ -135,47 +141,6 @@ internal ReadOnlyMemory<T> GetFirstBuffer(in SequencePosition start, in Sequence
135141
return memory.Slice(startIndex, length);
136142
}
137143

138-
private static SequencePosition GetBufferCrossSegment(int startIndex, object endObject, ref ReadOnlySequenceSegment<T> startSegment, ref int length)
139-
{
140-
Debug.Assert(startSegment != null);
141-
142-
ReadOnlySequenceSegment<T> nextSegment = startSegment.Next;
143-
int currentLength = startSegment.Memory.Length - startIndex;
144-
145-
while (currentLength == 0 && nextSegment != endObject && nextSegment != null)
146-
{
147-
// startSegment is at the end of a segment; move it to start of the next.
148-
// However, skip any empty segments, else the caller will immedately run out of data and call back
149-
startSegment = nextSegment;
150-
nextSegment = nextSegment.Next;
151-
currentLength = startSegment.Memory.Length;
152-
}
153-
154-
length = currentLength;
155-
while (nextSegment != null && nextSegment.Memory.Length == 0)
156-
{
157-
// nextSegment is an empty segment, skip until one is found with data.
158-
if (nextSegment == endObject)
159-
{
160-
// Reached the end and no segment with data found,
161-
// set nextSegment to null as if there were no following segments.
162-
endObject = nextSegment = null;
163-
break;
164-
}
165-
166-
nextSegment = nextSegment.Next;
167-
}
168-
169-
if (nextSegment != null)
170-
{
171-
return new SequencePosition(nextSegment, 0);
172-
}
173-
else if (endObject != null)
174-
ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
175-
176-
return default;
177-
}
178-
179144
[MethodImpl(MethodImplOptions.AggressiveInlining)]
180145
internal SequencePosition Seek(in SequencePosition start, in SequencePosition end, int count) => Seek(start, end, (long)count);
181146

src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceTests.Common.cs

Lines changed: 120 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -163,153 +163,196 @@ public void SeekSkipsEmptySegments()
163163
}
164164

165165
[Fact]
166-
public void SeekEmptySkipDoesNotCrossPastEnd()
166+
public void TryGetReturnsEmptySegments()
167167
{
168-
var bufferSegment1 = new BufferSegment<byte>(new byte[100]);
168+
var bufferSegment1 = new BufferSegment<byte>(new byte[0]);
169169
BufferSegment<byte> bufferSegment2 = bufferSegment1.Append(new byte[0]);
170170
BufferSegment<byte> bufferSegment3 = bufferSegment2.Append(new byte[0]);
171+
BufferSegment<byte> bufferSegment4 = bufferSegment3.Append(new byte[0]);
172+
173+
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment3, 0);
174+
175+
var start = buffer.Start;
176+
Assert.True(buffer.TryGet(ref start, out var memory));
177+
Assert.Equal(0, memory.Length);
178+
Assert.True(buffer.TryGet(ref start, out memory));
179+
Assert.Equal(0, memory.Length);
180+
Assert.True(buffer.TryGet(ref start, out memory));
181+
Assert.Equal(0, memory.Length);
182+
Assert.False(buffer.TryGet(ref start, out memory));
183+
}
184+
185+
[Fact]
186+
public void TryGetStopsAtEnd()
187+
{
188+
var bufferSegment1 = new BufferSegment<byte>(new byte[100]);
189+
BufferSegment<byte> bufferSegment2 = bufferSegment1.Append(new byte[100]);
190+
BufferSegment<byte> bufferSegment3 = bufferSegment2.Append(new byte[100]);
171191
BufferSegment<byte> bufferSegment4 = bufferSegment3.Append(new byte[100]);
192+
BufferSegment<byte> bufferSegment5 = bufferSegment4.Append(new byte[100]);
193+
194+
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment3, 100);
195+
196+
var start = buffer.Start;
197+
Assert.True(buffer.TryGet(ref start, out var memory));
198+
Assert.Equal(100, memory.Length);
199+
Assert.True(buffer.TryGet(ref start, out memory));
200+
Assert.Equal(100, memory.Length);
201+
Assert.True(buffer.TryGet(ref start, out memory));
202+
Assert.Equal(100, memory.Length);
203+
Assert.False(buffer.TryGet(ref start, out memory));
204+
}
172205

173-
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment2, 0);
206+
[Fact]
207+
public void TryGetStopsAtEndWhenEndIsLastByteOfFull()
208+
{
209+
var bufferSegment1 = new BufferSegment<byte>(new byte[100]);
210+
BufferSegment<byte> bufferSegment2 = bufferSegment1.Append(new byte[100]);
174211

175-
SequencePosition c1 = buffer.GetPosition(100);
212+
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment1, 100);
176213

177-
Assert.Equal(0, c1.GetInteger());
178-
Assert.Equal(bufferSegment2, c1.GetObject());
214+
var start = buffer.Start;
215+
Assert.True(buffer.TryGet(ref start, out var memory));
216+
Assert.Equal(100, memory.Length);
217+
Assert.False(buffer.TryGet(ref start, out memory));
218+
}
179219

180-
c1 = buffer.GetPosition(100, buffer.Start);
220+
[Fact]
221+
public void TryGetStopsAtEndWhenEndIsFirstByteOfFull()
222+
{
223+
var bufferSegment1 = new BufferSegment<byte>(new byte[100]);
224+
BufferSegment<byte> bufferSegment2 = bufferSegment1.Append(new byte[100]);
181225

182-
Assert.Equal(0, c1.GetInteger());
183-
Assert.Equal(bufferSegment2, c1.GetObject());
226+
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment2, 0);
184227

185-
// Go out of bounds for segment
186-
Assert.Throws<ArgumentOutOfRangeException>(() => c1 = buffer.GetPosition(150, buffer.Start));
187-
Assert.Throws<ArgumentOutOfRangeException>(() => c1 = buffer.GetPosition(250, buffer.Start));
228+
var start = buffer.Start;
229+
Assert.True(buffer.TryGet(ref start, out var memory));
230+
Assert.Equal(100, memory.Length);
231+
Assert.True(buffer.TryGet(ref start, out memory));
232+
Assert.Equal(0, memory.Length);
233+
Assert.False(buffer.TryGet(ref start, out memory));
188234
}
189235

190236
[Fact]
191-
public void SeekEmptySkipDoesNotCrossPastEndWithExtraChainedBlocks()
237+
public void TryGetStopsAtEndWhenEndIsFirstByteOfEmpty()
192238
{
193239
var bufferSegment1 = new BufferSegment<byte>(new byte[100]);
194240
BufferSegment<byte> bufferSegment2 = bufferSegment1.Append(new byte[0]);
195-
BufferSegment<byte> bufferSegment3 = bufferSegment2.Append(new byte[0]);
196-
BufferSegment<byte> bufferSegment4 = bufferSegment3.Append(new byte[100]);
197-
BufferSegment<byte> bufferSegment5 = bufferSegment4.Append(new byte[0]);
198-
BufferSegment<byte> bufferSegment6 = bufferSegment5.Append(new byte[100]);
199241

200242
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment2, 0);
201243

202-
SequencePosition c1 = buffer.GetPosition(100);
244+
var start = buffer.Start;
245+
Assert.True(buffer.TryGet(ref start, out var memory));
246+
Assert.Equal(100, memory.Length);
247+
Assert.True(buffer.TryGet(ref start, out memory));
248+
Assert.Equal(0, memory.Length);
249+
Assert.False(buffer.TryGet(ref start, out memory));
250+
}
203251

204-
Assert.Equal(0, c1.GetInteger());
205-
Assert.Equal(bufferSegment2, c1.GetObject());
252+
[Fact]
253+
public void EnumerableStopsAtEndWhenEndIsLastByteOfFull()
254+
{
255+
var bufferSegment1 = new BufferSegment<byte>(new byte[100]);
256+
BufferSegment<byte> bufferSegment2 = bufferSegment1.Append(new byte[100]);
206257

207-
c1 = buffer.GetPosition(100, buffer.Start);
258+
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment1, 100);
208259

209-
Assert.Equal(0, c1.GetInteger());
210-
Assert.Equal(bufferSegment2, c1.GetObject());
260+
List<int> sizes = new List<int>();
261+
foreach (var memory in buffer)
262+
{
263+
sizes.Add(memory.Length);
264+
}
211265

212-
// Go out of bounds for segment
213-
Assert.Throws<ArgumentOutOfRangeException>(() => c1 = buffer.GetPosition(150, buffer.Start));
214-
Assert.Throws<ArgumentOutOfRangeException>(() => c1 = buffer.GetPosition(250, buffer.Start));
266+
Assert.Equal(1, sizes.Count);
267+
Assert.Equal(new [] { 100 }, sizes);
215268
}
216269

217270
[Fact]
218-
public void TryGetSkipsEmptyForNextUsingGetPositionWithOffset()
271+
public void EnumerableStopsAtEndWhenEndIsFirstByteOfFull()
219272
{
220273
var bufferSegment1 = new BufferSegment<byte>(new byte[100]);
221-
BufferSegment<byte> bufferSegment2 = bufferSegment1.Append(new byte[0]);
222-
BufferSegment<byte> bufferSegment3 = bufferSegment2.Append(new byte[0]);
223-
BufferSegment<byte> bufferSegment4 = bufferSegment3.Append(new byte[100]);
224-
225-
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment4, 100);
226-
SequencePosition c1 = buffer.GetPosition(0);
227-
Assert.Equal(0, c1.GetInteger());
228-
Assert.Equal(bufferSegment1, c1.GetObject());
229-
230-
ReadOnlyMemory<byte> data;
231-
Assert.True(buffer.TryGet(ref c1, out data, advance: true));
232-
233-
Assert.Equal(0, c1.GetInteger());
234-
Assert.Equal(bufferSegment4, c1.GetObject());
274+
BufferSegment<byte> bufferSegment2 = bufferSegment1.Append(new byte[100]);
235275

236-
Assert.True(buffer.TryGet(ref c1, out data, advance: true));
276+
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment2, 0);
237277

238-
Assert.Equal(0, c1.GetInteger());
239-
Assert.Equal(null, c1.GetObject());
278+
List<int> sizes = new List<int>();
279+
foreach (var memory in buffer)
280+
{
281+
sizes.Add(memory.Length);
282+
}
240283

241-
Assert.False(buffer.TryGet(ref c1, out data, advance: true));
284+
Assert.Equal(2, sizes.Count);
285+
Assert.Equal(new [] { 100, 0 }, sizes);
242286
}
243287

244288
[Fact]
245-
public void TryGetSkipsEmptyForNextUsingGetPositionWithOffsetAndOrigin()
289+
public void EnumerableStopsAtEndWhenEndIsFirstByteOfEmpty()
246290
{
247291
var bufferSegment1 = new BufferSegment<byte>(new byte[100]);
248292
BufferSegment<byte> bufferSegment2 = bufferSegment1.Append(new byte[0]);
249-
BufferSegment<byte> bufferSegment3 = bufferSegment2.Append(new byte[0]);
250-
BufferSegment<byte> bufferSegment4 = bufferSegment3.Append(new byte[100]);
251-
252-
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment4, 100);
253-
SequencePosition c1 = buffer.GetPosition(0, buffer.Start);
254-
Assert.Equal(0, c1.GetInteger());
255-
Assert.Equal(bufferSegment1, c1.GetObject());
256-
257-
ReadOnlyMemory<byte> data;
258-
Assert.True(buffer.TryGet(ref c1, out data, advance: true));
259-
260-
Assert.Equal(0, c1.GetInteger());
261-
Assert.Equal(bufferSegment4, c1.GetObject());
262293

263-
Assert.True(buffer.TryGet(ref c1, out data, advance: true));
294+
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment2, 0);
264295

265-
Assert.Equal(0, c1.GetInteger());
266-
Assert.Equal(null, c1.GetObject());
296+
List<int> sizes = new List<int>();
297+
foreach (var memory in buffer)
298+
{
299+
sizes.Add(memory.Length);
300+
}
267301

268-
Assert.False(buffer.TryGet(ref c1, out data, advance: true));
302+
Assert.Equal(2, sizes.Count);
303+
Assert.Equal(new [] { 100, 0 }, sizes);
269304
}
270305

271306
[Fact]
272-
public void TryGetSkipDoesNotCrossPastEndUsingGetPositionWithOffset()
307+
public void SeekEmptySkipDoesNotCrossPastEnd()
273308
{
274309
var bufferSegment1 = new BufferSegment<byte>(new byte[100]);
275310
BufferSegment<byte> bufferSegment2 = bufferSegment1.Append(new byte[0]);
276311
BufferSegment<byte> bufferSegment3 = bufferSegment2.Append(new byte[0]);
277312
BufferSegment<byte> bufferSegment4 = bufferSegment3.Append(new byte[100]);
278313

279314
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment2, 0);
280-
SequencePosition c1 = buffer.GetPosition(0);
315+
316+
SequencePosition c1 = buffer.GetPosition(100);
317+
281318
Assert.Equal(0, c1.GetInteger());
282-
Assert.Equal(bufferSegment1, c1.GetObject());
319+
Assert.Equal(bufferSegment2, c1.GetObject());
283320

284-
ReadOnlyMemory<byte> data;
285-
Assert.True(buffer.TryGet(ref c1, out data, advance: true));
321+
c1 = buffer.GetPosition(100, buffer.Start);
286322

287323
Assert.Equal(0, c1.GetInteger());
288-
Assert.Equal(null, c1.GetObject());
324+
Assert.Equal(bufferSegment2, c1.GetObject());
289325

290-
Assert.False(buffer.TryGet(ref c1, out data, advance: true));
326+
// Go out of bounds for segment
327+
Assert.Throws<ArgumentOutOfRangeException>(() => c1 = buffer.GetPosition(150, buffer.Start));
328+
Assert.Throws<ArgumentOutOfRangeException>(() => c1 = buffer.GetPosition(250, buffer.Start));
291329
}
292330

293331
[Fact]
294-
public void TryGetSkipDoesNotCrossPastEndUsingGetPositionWithOffsetAndOrigin()
332+
public void SeekEmptySkipDoesNotCrossPastEndWithExtraChainedBlocks()
295333
{
296334
var bufferSegment1 = new BufferSegment<byte>(new byte[100]);
297335
BufferSegment<byte> bufferSegment2 = bufferSegment1.Append(new byte[0]);
298336
BufferSegment<byte> bufferSegment3 = bufferSegment2.Append(new byte[0]);
299337
BufferSegment<byte> bufferSegment4 = bufferSegment3.Append(new byte[100]);
338+
BufferSegment<byte> bufferSegment5 = bufferSegment4.Append(new byte[0]);
339+
BufferSegment<byte> bufferSegment6 = bufferSegment5.Append(new byte[100]);
300340

301341
var buffer = new ReadOnlySequence<byte>(bufferSegment1, 0, bufferSegment2, 0);
302-
SequencePosition c1 = buffer.GetPosition(0, buffer.Start);
342+
343+
SequencePosition c1 = buffer.GetPosition(100);
344+
303345
Assert.Equal(0, c1.GetInteger());
304-
Assert.Equal(bufferSegment1, c1.GetObject());
346+
Assert.Equal(bufferSegment2, c1.GetObject());
305347

306-
ReadOnlyMemory<byte> data;
307-
Assert.True(buffer.TryGet(ref c1, out data, advance: true));
348+
c1 = buffer.GetPosition(100, buffer.Start);
308349

309350
Assert.Equal(0, c1.GetInteger());
310-
Assert.Equal(null, c1.GetObject());
351+
Assert.Equal(bufferSegment2, c1.GetObject());
311352

312-
Assert.False(buffer.TryGet(ref c1, out data, advance: true));
353+
// Go out of bounds for segment
354+
Assert.Throws<ArgumentOutOfRangeException>(() => c1 = buffer.GetPosition(150, buffer.Start));
355+
Assert.Throws<ArgumentOutOfRangeException>(() => c1 = buffer.GetPosition(250, buffer.Start));
313356
}
314357

315358
[Fact]

0 commit comments

Comments
 (0)