Skip to content

Fix 2GB image/snapshot save limits; add disk throughput chart - #116

Merged
coldhighsun merged 4 commits into
mainfrom
large-disk-persistence-fixes
Jul 28, 2026
Merged

Fix 2GB image/snapshot save limits; add disk throughput chart#116
coldhighsun merged 4 commits into
mainfrom
large-disk-persistence-fixes

Conversation

@coldhighsun

Copy link
Copy Markdown
Owner

Summary

  • Fix .mdr disk image save/load failing with IOException: Stream was too long once a disk's content approaches ~2GB, by streaming the node region instead of buffering it whole, and switching encrypted saves to chunked AES-256-GCM (new image format version 4; version 3 still readable).
  • Apply the same fix to SnapshotStore's individual content-addressed file blobs (write/read now stream via FileContent, encrypted blobs use the same shared chunked-AES-GCM helper, extracted into ChunkedGcm). Also fixes an int truncation of file size in the snapshot writer.
  • Add per-disk read/write throughput tracking (ThroughputTracker) with a SpeedHistoryChart popup on the disk card and tray tooltip.
  • Address 4 findings from /code-review on the above: restored a dropped corrupted-blob-length check, fixed a tray high-usage-warning state bug, made image version dispatch explicit instead of implicit, and made the speed-history popup lookup robust instead of relying on WPF logical-tree parentage.

Test plan

  • dotnet build — solution builds with 0 warnings/errors introduced
  • dotnet test tests/ManagedDrive.Tests — 236/236 pass, including new chunked round-trip, legacy-format-compat, and corrupted-blob regression tests
  • Manual: mount an encrypted disk, write a large file, save/unmount/remount with password, verify content
  • Manual: verify the speed-history popup opens/stays open correctly on hover

DiskImageSerializer.Save buffered the entire node region into a
MemoryStream before compressing/encrypting it, which throws
"Stream was too long" once a disk's actual content approaches the
~2GB int32 length limit -- including on the final save during
unmount/app-exit/OS-shutdown, silently losing unsaved changes.

Unencrypted saves/loads now stream directly to/from the file instead
of buffering. Encrypted saves/loads switch to chunked AES-256-GCM
(new image format version 4) so no single AES-GCM call or buffer
needs to hold the whole node region; version 3's whole-blob encrypted
format is still read for backward compatibility.
Individual snapshot file blobs had the same class of bug fixed for
DiskImageSerializer: EnsureBlobWritten/ReadBlob materialized the full
compressed/encrypted payload as a single buffer, and WriteNode
truncated file size to int before that -- both breaking for single
files approaching/exceeding ~2GB.

Extracted the chunked AES-256-GCM stream pair used by
DiskImageSerializer into a shared ChunkedGcm helper and reused it here
so encrypted blobs are written/read chunk-by-chunk. Unencrypted blobs
now stream straight through FileContent/GZipStream instead of via
MemoryStream. Old whole-blob encrypted blobs are still read via a
legacy branch for backward compatibility.
Tracks cumulative bytes read/written per mounted disk (MemoryFileSystem,
RamDisk), derives an instantaneous rate via ThroughputTracker, and
surfaces it on the tray icon tooltip and a new SpeedHistoryChart control
on the disk card.
- SnapshotStore.ReadBlob: restore the corrupted-length check that was
  dropped when the blob read path was rewritten to stream via
  FillFromStream; FillFromStream now returns the actual byte count
  read so callers can detect truncated/corrupt sources.
- DiskImageSerializer.LoadCurrent: dispatch encrypted-region loading on
  an explicit version switch (3/4) instead of a boolean version == 3
  check, so an unexpected version value fails loudly instead of
  silently falling into the chunked path.
- TrayIconController.SetHighUsageWarningActive: record the active
  state before bailing out on missing tray icons, so a call that
  arrives before icon generation finishes doesn't get silently
  dropped and desync future calls from the real state.
- MainWindow: resolve the speed-history popup via an explicit
  Tag binding instead of FrameworkElement.Parent, which is not a
  reliable way to reach a Popup from its hosted content.
@coldhighsun
coldhighsun merged commit c71a4e0 into main Jul 28, 2026
4 checks passed
@coldhighsun
coldhighsun deleted the large-disk-persistence-fixes branch July 28, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant