Skip to content

Implement IDisposable on VirtualizationInstance to prevent zombie processes#104

Draft
miniksa wants to merge 2 commits intomicrosoft:mainfrom
miniksa:user/miniksa/idisposable
Draft

Implement IDisposable on VirtualizationInstance to prevent zombie processes#104
miniksa wants to merge 2 commits intomicrosoft:mainfrom
miniksa:user/miniksa/idisposable

Conversation

@miniksa
Copy link
Member

@miniksa miniksa commented Mar 7, 2026

VirtualizationInstance holds native ProjFS handles (PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT) and GCHandles that must be released when the instance is no longer needed. Previously, StopVirtualizing() had to be called explicitly — if the process crashed or exited without calling it, ProjFS kept the virtualization root alive, creating zombie processes.

Changes:

  • IVirtualizationInstance now extends IDisposable
  • VirtualizationInstance implements full dispose pattern:
    • Dispose() calls PrjStopVirtualizing, frees GCHandles, frees Marshal.StringToHGlobalUni notification strings
    • Finalizer ~VirtualizationInstance() as safety net
    • StopVirtualizing() delegates to Dispose(true) for compat
    • Thread-safe: _disposed flag prevents double-free
  • Fixed memory leak: _notificationRootStrings (allocated via Marshal.StringToHGlobalUni) and _notificationMappingsHandle were never freed in StopVirtualizing

Consumers can now use 'using' or 'using var' for automatic cleanup:
using var instance = new VirtualizationInstance(...);

Version bumped to 2.1.0 (breaking: IVirtualizationInstance now requires Dispose)

@miniksa miniksa force-pushed the user/miniksa/idisposable branch 5 times, most recently from 0d194bf to fa02ce0 Compare March 7, 2026 05:19
…cesses

VirtualizationInstance holds native ProjFS handles (PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT)
and GCHandles that must be released when the instance is no longer needed. Previously,
StopVirtualizing() had to be called explicitly — if the process crashed or exited without
calling it, ProjFS kept the virtualization root alive, creating zombie processes.

Changes:
- IVirtualizationInstance now extends IDisposable
- VirtualizationInstance implements full dispose pattern:
  - Dispose() calls PrjStopVirtualizing, frees GCHandles, frees
    Marshal.StringToHGlobalUni notification strings
  - Finalizer ~VirtualizationInstance() as safety net
  - StopVirtualizing() calls Dispose() for backward compat (Stream.Close pattern)
  - Thread-safe: _disposed flag prevents double-free
  - All public methods throw ObjectDisposedException after disposal
- Fixed memory leak: _notificationRootStrings and _notificationMappingsHandle
  were never freed in StopVirtualizing
- Enabled .NET analyzers (CA1001/CA2213 would have caught this)
- Added 8 unit tests for disposal mechanics (all pass without ProjFS feature)

Version bumped to 2.1.0 (breaking: IVirtualizationInstance now requires Dispose)
@miniksa miniksa force-pushed the user/miniksa/idisposable branch from fa02ce0 to 0db5837 Compare March 7, 2026 05:22
…lizing

The constructor previously used PrjGetOnDiskFileState to check if a directory
was already a ProjFS root and skipped MarkDirectoryAsVirtualizationRoot when
fileState != 0. This caused StartVirtualizing to fail with 0x80071126
(ERROR_FILE_SYSTEM_VIRTUALIZATION_PROVIDER_UNKNOWN) because the ProjFS driver
requires the mark call to register/refresh its in-memory state for the
directory, even if the directory already has ProjFS associations.

Fix: always call MarkDirectoryAsVirtualizationRoot with a fresh GUID.
Treat ReparsePointEncountered (0x8007112B) as success — this just means
the driver already knows about this directory, which is fine.
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.

2 participants