Repository navigation
ManifestWriter.WriteManifest unexpectedly disposes the preserved manifest input stream #14881
Description
Summary
ManifestWriter.WriteManifest unexpectedly disposes the manifest's preserved input stream after writing.
This prevents callers from resetting, reading, retrying with, or otherwise reusing the stream. Writing the same manifest twice is one observable consequence, but the underlying regression is the change in stream ownership.
Regression
This behavior was introduced in d61038949d120d889624056a1be29292fd42c529 as part of #9983:
public static void WriteFile(string path, Stream stream)
{
- StreamReader reader = new StreamReader(stream);
+ using StreamReader reader = new StreamReader(stream);
WriteFile(path, reader.ReadToEnd());
}StreamReader(Stream) defaults to leaveOpen: false. Disposing the reader therefore also disposes the supplied stream.
The affected call path is:
ManifestWriter.WriteManifest
→ Util.WriteTempFile(manifest.InputStream)
→ Util.WriteFile(path, manifest.InputStream)
→ StreamReader.Dispose()
→ manifest.InputStream.Dispose()
Expected behavior
Writing a manifest should not dispose its preserved input stream. ManifestWriter did not create that stream, and ownership is not transferred to Util.WriteFile.
After writing, callers should still be able to reset and read the stream:
Stream preservedInput = manifest.InputStream;
ManifestWriter.WriteManifest(manifest, firstOutputPath);
Assert.True(preservedInput.CanRead);
preservedInput.Position = 0;
using var reader = new StreamReader(
preservedInput,
Encoding.UTF8,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true);
Assert.NotEmpty(reader.ReadToEnd());Resetting the stream and writing another output should also remain possible:
preservedInput.Position = 0;
ManifestWriter.WriteManifest(manifest, secondOutputPath);Actual behavior
After the first call to ManifestWriter.WriteManifest:
preservedInput.CanRead == falseAttempting to read or reuse it fails. A subsequent manifest write produces:
System.ArgumentException: Stream was not readable.
at System.IO.StreamReader..ctor(Stream stream)
at Microsoft.Build.Tasks.Deployment.ManifestUtilities.Util.WriteFile(...)
at Microsoft.Build.Tasks.Deployment.ManifestUtilities.Util.WriteTempFile(...)
at Microsoft.Build.Tasks.Deployment.ManifestUtilities.ManifestWriter.WriteManifest(...)
Affected versions
The regression is present in Microsoft.Build.Tasks.Core 17.11.x. The same reset-and-reuse scenario succeeds with 17.10.46.
The implementation on the current main branch still constructs the reader without leaveOpen: true.
Proposed fix
Leave the caller-owned stream open when disposing the temporary reader:
using var reader = new StreamReader(
stream,
Encoding.UTF8,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true);This is consistent with Util.WriteLogFile, which already preserves its supplied stream using leaveOpen: true.
Suggested tests
Add coverage that verifies:
ManifestWriter.WriteManifestleaves the preserved input stream readable;- the stream can be reset and read after writing;
- the same manifest can be written again after resetting its input stream.
Activity
[-]Preserve manifest input streams when writing temporary files[/-][+]ManifestWriter.WriteManifest unexpectedly disposes the preserved manifest input stream[/+]Preserve manifest input streams when writing manifests (#14883)