Implement IDisposable on VirtualizationInstance to prevent zombie processes#104
Draft
miniksa wants to merge 2 commits intomicrosoft:mainfrom
Draft
Implement IDisposable on VirtualizationInstance to prevent zombie processes#104miniksa wants to merge 2 commits intomicrosoft:mainfrom
miniksa wants to merge 2 commits intomicrosoft:mainfrom
Conversation
0d194bf to
fa02ce0
Compare
…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)
fa02ce0 to
0db5837
Compare
…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.
cgallred
approved these changes
Mar 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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)