dotnet/wpf · critical · InvalidOperationException

SR.PackageControllerStreamCorruption

Error message

SR.PackageControllerStreamCorruption

What it means

PackageController.SaveCommit throws InvalidOperationException with SR.PackageControllerStreamCorruption when the source and destination streams are the same object (detected via equal hash codes), which would corrupt the package, and also when the copy failed (source and destination lengths differ). It is a safety check protecting package integrity during commit.

Solutions

  1. Ensure the save destination is a different stream than the source; open a new FileStream/StreamProxy for the target.
  2. Check that the destination stream supports writing and was fully written; compare lengths before commit.
  3. If a copier failed, fix the underlying IO error (permissions, disk space, stream capabilities) and retry the save.
  4. Dispose/reopen the document cleanly instead of committing from a partially copied package.

Example fix

// before
StreamProxy dest = new StreamProxy(sourceStream); // same stream as source
packageController.SaveCommit(doc);
// after
var dest = new StreamProxy(new FileStream(targetPath, FileMode.Create, FileAccess.Write));
packageController.SaveCommit(doc);
Defensive patterns

Strategy: validation

Validate before calling

bool safeToCommit = !ReferenceEquals(doc.Source, doc.Destination) && doc.Source.Length == doc.Destination.Length;

Try / catch

try { packageController.SaveCommit(doc); }
catch (InvalidOperationException) { /* reopen document; do not commit partial copy */ }

Prevention

When it happens

Trigger: Saving a document where doc.Destination is a StreamProxy wrapping the same underlying stream as doc.Source; or where no registered copier succeeded so Destination.Length != Source.Length at commit time.

Common situations: Programming mistakes where save target and source reference the same stream; stream types unsupported by any copier causing a truncated/incomplete copy; an earlier IO error leaving the destination partially written.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/23752204ba669a9a. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationUI/MS/Internal/Documents/Application/PackageController.cs:123

    bool IDocumentController.SaveCommit(Document document)
    {
        PackageDocument doc = (PackageDocument)document;

        if (doc.Destination == null)
        {
            return false;
        }

        if (doc.Package.IsDirty || !doc.IsDestinationIdenticalToSource)
        {
            StreamProxy source = doc.Source as StreamProxy;
            StreamProxy destination = doc.Destination as StreamProxy;

            // this will catch the case where our source stream is our destination
            // stream, which would cause corruption of the package
            if (source.GetHashCode() == destination.GetHashCode())
            {
                throw new InvalidOperationException(
                    SR.PackageControllerStreamCorruption);
            }

            // this will catch the case where no one was able to copy the stream
            // thus the lengths will not match
            if (doc.Source.Length != doc.Destination.Length)
            {
                throw new InvalidOperationException(
                    SR.PackageControllerStreamCorruption);
            }

            // Flush the package to ensure that the relationship parts are
            // written out before changes are merged
            doc.Package.Flush();

            doc.Package.MergeChanges(doc.Destination);
            Trace.SafeWrite(Trace.File, "Destination Merged.");
        }

View on GitHub (pinned to 81131a70a4)