Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 46 additions & 26 deletions src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,36 @@ public class StreamReader : TextReader
// We don't guarantee thread safety on StreamReader, but we should at
// least prevent users from trying to read anything while an Async
// read from the same thread is in progress.
private Task _asyncReadTask = Task.CompletedTask;
private bool _asyncIOInProgress;

private void CheckAsyncTaskInProgress()
{
// We are not locking the access to _asyncReadTask because this is not meant to guarantee thread safety.
// We are not locking this access because this is not meant to guarantee thread safety.
// We are simply trying to deter calling any Read APIs while an async Read from the same thread is in progress.
if (!_asyncReadTask.IsCompleted)
if (_asyncIOInProgress)
Comment thread
jakobbotsch marked this conversation as resolved.
Outdated
{
ThrowAsyncIOInProgress();
}
}

private ThrowOnReadsScope GuardAgainstReads()
{
return new ThrowOnReadsScope(this);
}

private readonly struct ThrowOnReadsScope : IDisposable
{
private readonly StreamReader _reader;

public ThrowOnReadsScope(StreamReader reader)
{
_reader = reader;
_reader._asyncIOInProgress = true;
}

public void Dispose() => _reader._asyncIOInProgress = false;
}

[DoesNotReturn]
private static void ThrowAsyncIOInProgress() =>
throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress);
Expand Down Expand Up @@ -898,14 +916,13 @@ private int ReadBuffer(Span<char> userBuffer, out bool readToUserBuffer)
ThrowIfDisposed();
CheckAsyncTaskInProgress();

Task<string?> task = ReadLineAsyncInternal(cancellationToken);
_asyncReadTask = task;

return new ValueTask<string?>(task);
return new ValueTask<string?>(ReadLineAsyncInternal(cancellationToken));
}

private async Task<string?> ReadLineAsyncInternal(CancellationToken cancellationToken)
{
using ThrowOnReadsScope _ = GuardAgainstReads();

if (_charPos == _charLen && (await ReadBufferAsync(cancellationToken).ConfigureAwait(false)) == 0)
{
Comment thread
jakobbotsch marked this conversation as resolved.
return null;
Expand Down Expand Up @@ -1026,14 +1043,13 @@ public override Task<string> ReadToEndAsync(CancellationToken cancellationToken)
ThrowIfDisposed();
CheckAsyncTaskInProgress();

Task<string> task = ReadToEndAsyncInternal(cancellationToken);
_asyncReadTask = task;

return task;
return ReadToEndAsyncInternal(cancellationToken);
}

private async Task<string> ReadToEndAsyncInternal(CancellationToken cancellationToken)
{
using ThrowOnReadsScope _ = GuardAgainstReads();

// Call ReadBuffer, then pull data out of charBuffer.
StringBuilder sb = new StringBuilder(_charLen - _charPos);
do
Expand Down Expand Up @@ -1070,10 +1086,7 @@ public override Task<int> ReadAsync(char[] buffer, int index, int count)
ThrowIfDisposed();
CheckAsyncTaskInProgress();

Task<int> task = ReadAsyncInternal(new Memory<char>(buffer, index, count), CancellationToken.None).AsTask();
_asyncReadTask = task;

return task;
return ReadAsyncInternalWithGuard(new Memory<char>(buffer, index, count), CancellationToken.None);
}

public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default)
Expand All @@ -1095,6 +1108,12 @@ public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken
return ReadAsyncInternal(buffer, cancellationToken);
}

private async Task<int> ReadAsyncInternalWithGuard(Memory<char> buffer, CancellationToken cancellationToken)
{
using ThrowOnReadsScope _ = GuardAgainstReads();
return await ReadAsyncInternal(buffer, cancellationToken).ConfigureAwait(false);
}
Comment thread
jakobbotsch marked this conversation as resolved.
Outdated

internal override async ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken)
{
if (_charPos == _charLen && (await ReadBufferAsync(cancellationToken).ConfigureAwait(false)) == 0)
Expand Down Expand Up @@ -1281,10 +1300,13 @@ public override Task<int> ReadBlockAsync(char[] buffer, int index, int count)
ThrowIfDisposed();
CheckAsyncTaskInProgress();

Task<int> task = base.ReadBlockAsync(buffer, index, count);
_asyncReadTask = task;
return ReadBlockAsyncWithGuard(buffer, index, count);
}

return task;
private async Task<int> ReadBlockAsyncWithGuard(char[] buffer, int index, int count)
{
using ThrowOnReadsScope _ = GuardAgainstReads();
return await base.ReadBlockAsync(buffer, index, count).ConfigureAwait(false);
}

public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default)
Expand All @@ -1304,15 +1326,13 @@ public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationT
return ValueTask.FromCanceled<int>(cancellationToken);
}

ValueTask<int> vt = ReadBlockAsyncInternal(buffer, cancellationToken);
if (vt.IsCompletedSuccessfully)
{
return vt;
}
return ReadBlockAsyncInternalWithGuard(buffer, cancellationToken);
}

Task<int> t = vt.AsTask();
_asyncReadTask = t;
return new ValueTask<int>(t);
private async ValueTask<int> ReadBlockAsyncInternalWithGuard(Memory<char> buffer, CancellationToken token)
{
using ThrowOnReadsScope _ = GuardAgainstReads();
return await ReadBlockAsyncInternal(buffer, token).ConfigureAwait(false);
}

private async ValueTask<int> ReadBufferAsync(CancellationToken cancellationToken)
Expand Down
Loading