Skip to content
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

Improve set_position to reuse buffer. #54991

Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,94 @@ public async Task ShouldNotFlushUnderlyingStreamIfReadOnly(bool underlyingCanSee
Assert.Equal(0, wrapper.TimesCalled(nameof(wrapper.FlushAsync)));
}

[Theory]
[MemberData(nameof(SetPosMethods))]
public void SetPositionInsideBufferRange_Read_WillNotReadUnderlyingStreamAgain(int sharedBufSize, Action<Stream, long> setPos)
{
var trackingStream = new CallTrackingStream(new MemoryStream());
var bufferedStream = new BufferedStream(trackingStream, sharedBufSize);
bufferedStream.Write(Enumerable.Range(0, sharedBufSize * 2).Select(i => (byte)i).ToArray(), 0, sharedBufSize * 2);
setPos(bufferedStream, 0);

var readBuf = new byte[sharedBufSize - 1];

// First half part verification
byte[] expectedReadBuf = Enumerable.Range(0, sharedBufSize - 1).Select(i => (byte)i).ToArray();

// Call Read() to fill shared read buffer
int readBytes = bufferedStream.Read(readBuf, 0, readBuf.Length);
Assert.Equal(readBuf.Length, readBytes);
Assert.Equal(sharedBufSize - 1, bufferedStream.Position);
Assert.Equal(expectedReadBuf, readBuf);
Assert.Equal(1, trackingStream.TimesCalled(nameof(trackingStream.Read)));

// Set position inside range of shared read buffer
for (int pos = 0; pos < sharedBufSize - 1; pos++)
{
setPos(bufferedStream, pos);

readBytes = bufferedStream.Read(readBuf, pos, readBuf.Length - pos);
Assert.Equal(readBuf.Length - pos, readBytes);
Assert.Equal(sharedBufSize - 1, bufferedStream.Position);
Assert.Equal(expectedReadBuf, readBuf);
// Should not trigger underlying stream's Read()
Assert.Equal(1, trackingStream.TimesCalled(nameof(trackingStream.Read)));
}

Assert.Equal(sharedBufSize - 1, bufferedStream.ReadByte());
Assert.Equal(sharedBufSize, bufferedStream.Position);
// Should not trigger underlying stream's Read()
Assert.Equal(1, trackingStream.TimesCalled(nameof(trackingStream.Read)));

// Second half part verification
expectedReadBuf = Enumerable.Range(sharedBufSize, sharedBufSize - 1).Select(i => (byte)i).ToArray();
// Call Read() to fill shared read buffer
readBytes = bufferedStream.Read(readBuf, 0, readBuf.Length);
Assert.Equal(readBuf.Length, readBytes);
Assert.Equal(sharedBufSize * 2 - 1, bufferedStream.Position);
Assert.Equal(expectedReadBuf, readBuf);
Assert.Equal(2, trackingStream.TimesCalled(nameof(trackingStream.Read)));

// Set position inside range of shared read buffer
for (int pos = 0; pos < sharedBufSize - 1; pos++)
{
setPos(bufferedStream, sharedBufSize + pos);

readBytes = bufferedStream.Read(readBuf, pos, readBuf.Length - pos);
Assert.Equal(readBuf.Length - pos, readBytes);
Assert.Equal(sharedBufSize * 2 - 1, bufferedStream.Position);
Assert.Equal(expectedReadBuf, readBuf);
// Should not trigger underlying stream's Read()
Assert.Equal(2, trackingStream.TimesCalled(nameof(trackingStream.Read)));
}

Assert.Equal(sharedBufSize * 2 - 1, bufferedStream.ReadByte());
Assert.Equal(sharedBufSize * 2, bufferedStream.Position);
// Should not trigger underlying stream's Read()
Assert.Equal(2, trackingStream.TimesCalled(nameof(trackingStream.Read)));
}

public static IEnumerable<object[]> SetPosMethods
{
get
{
var setByPosition = (Action<Stream, long>)((stream, pos) => stream.Position = pos);
var seekFromBegin = (Action<Stream, long>)((stream, pos) => stream.Seek(pos, SeekOrigin.Begin));
var seekFromCurrent = (Action<Stream, long>)((stream, pos) => stream.Seek(pos - stream.Position, SeekOrigin.Current));
var seekFromEnd = (Action<Stream, long>)((stream, pos) => stream.Seek(pos - stream.Length, SeekOrigin.End));

yield return new object[] { 3, setByPosition };
yield return new object[] { 3, seekFromBegin };
yield return new object[] { 3, seekFromCurrent };
yield return new object[] { 3, seekFromEnd };

yield return new object[] { 10, setByPosition };
yield return new object[] { 10, seekFromBegin };
yield return new object[] { 10, seekFromCurrent };
yield return new object[] { 10, seekFromEnd };
}
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
public async Task ConcurrentOperationsAreSerialized()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,15 +205,7 @@ public override long Position
if (value < 0)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);

EnsureNotClosed();
EnsureCanSeek();

if (_writePos > 0)
FlushWrite();

_readPos = 0;
_readLen = 0;
_stream!.Seek(value, SeekOrigin.Begin);
Seek(value, SeekOrigin.Begin);
}
}

Expand Down