-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
StreamReader.Peek() on a DeflateStream returns -1 early #68983
Comments
Tagging subscribers to this area: @dotnet/area-system-io Issue DetailsDescriptionI have code that has a DeflateStream and uses a StreamReader on it - this code works fine in .NET Framework, .NET Core 3.1, and .NET 5 - but it breaks in .NET 6. When trying to Peek() on the stream, internally StreamReader reads a block out that ends up being smaller than it requested, I'm pretty sure this is due to this breaking change. After that happens, it sets a flag called "_isBlocked" and starts returning -1 from Peek() which is not expected. This seems like a bug in StreamReader? Reproduction StepsConstruct a StreamReader over a DeflateStream Expected behaviorPeek() returns -1 only at the end of the stream, as it did before Actual behaviorPeek() returns -1 early in the middle of the file. Regression?Yes, regressed in .NET 6 Known WorkaroundsCan use StreamReader.EndOfStream instead of looking for -1 from Peek(), but this was still a difficult to hunt down bug on upgrading the .NET version of this library. ConfigurationNo response Other informationNo response
|
Can you please share a small, isolated repro? |
Sounds like this could be #61325 (comment). |
This issue has been marked |
This comes from a large project, I will see if I can extract a minimal repro - it may take me a couple days. |
@stephentoub, I have made a really small repro. Take this code: using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Runtime.InteropServices;
Console.WriteLine($"Running in {RuntimeInformation.FrameworkDescription}");
var localPath = Path.GetTempFileName();
new WebClient().DownloadFile(@"https://github.com/dotnet/runtime/archive/refs/tags/v6.0.5.zip", localPath);
var numChars = 0;
using var archive = new ZipArchive(File.OpenRead(localPath), ZipArchiveMode.Read);
foreach (var entry in archive.Entries)
{
using var sr = new StreamReader(entry.Open());
while (sr.Peek() != -1)
{
char next = (char)sr.Read();
numChars++;
}
}
Console.WriteLine($"Read {numChars} characters in {archive.Entries.Count} zip entries"); When run on .NET 5 vs. 6, I get different numbers of characters read because Peek() returns -1 "early" sometimes in .NET 6, probably due to the breaking change I mentioned in the original post in this issue. I get this output:
vs.
|
Here is a self-contained repro of this behavior: using System;
using System.IO;
using var stream = new AStream();
using var reader = new StreamReader(stream);
// prints "42" once
while (reader.Peek() != -1)
{
Console.WriteLine(reader.Read());
}
class AStream : Stream
{
public override int Read(byte[] buffer, int offset, int count)
{
buffer[offset] = 42;
return 1;
}
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => throw new NotImplementedException();
public override long Length => throw new NotImplementedException();
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public override void Flush() => throw new NotImplementedException();
public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
public override void SetLength(long value) => throw new NotImplementedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
} Note that the documentation of |
Thanks for the repros. I'll take a look... |
StreamReader has a private _isBlocked field that it's using to track whether the last read on the underlying Stream returned fewer bytes than requested: runtime/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs Lines 58 to 64 in 336a5d2
It then uses this field in various ReadXx methods to determine whether to try to read more data from the underlying Stream or to just give back to the caller just the non-zero amount it gathered thus far. I think that's a misguided design but at least defensible. However, it's then also using that _isBlocked in Peek():
which while potentially "by design" is to me a flawed design. I think the fix here at a minimum is to change the semantics of Peek to stop looking at _isBlocked at all. |
This becomes even worse with Http streams as using System;
using System.IO;
using System.Net.Http;
namespace ConsoleApp
{
class Program
{
public static void Main(string[] args)
{
var client = new HttpClient();
var stream = client.GetStreamAsync("https://en.wikipedia.org/wiki/Software_bug").Result;
var reader = new StreamReader(stream);
var r = 0;
var i = -1;
while (r >= 0)
{
i++;
var b = reader.Peek();
r = reader.Read();
if (r != b)
{
Console.WriteLine($"difference {i,6}: {r} {b}");
}
}
Console.WriteLine("done");
}
}
} Differences are happening at different places in the stream |
Replaced Peek() function because of issue that occurs only in .net6.0 dotnet/runtime#68983 when downloading large object (e.g. 8000+ child objects)
Replaced Peek() function because of issue that occurs only in .net6.0 dotnet/runtime#68983 when downloading large object (e.g. 8000+ child objects) Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
Description
I have code that has a DeflateStream and uses a StreamReader on it - this code works fine in .NET Framework, .NET Core 3.1, and .NET 5 - but it breaks in .NET 6.
When trying to Peek() on the stream, internally StreamReader reads a block out that ends up being smaller than it requested, I'm pretty sure this is due to this breaking change. After that happens, it sets a flag called "_isBlocked" and starts returning -1 from Peek() which is not expected.
This seems like a bug in StreamReader?
Reproduction Steps
Construct a StreamReader over a DeflateStream
Read for a while, peeking as you go
Expected behavior
Peek() returns -1 only at the end of the stream, as it did before
Actual behavior
Peek() returns -1 early in the middle of the file.
Regression?
Yes, regressed in .NET 6
Known Workarounds
Can use StreamReader.EndOfStream instead of looking for -1 from Peek(), but this was still a difficult to hunt down bug on upgrading the .NET version of this library.
Configuration
No response
Other information
No response
The text was updated successfully, but these errors were encountered: