|
| 1 | +# .NET 10 NativeAOT Migration — Status & Remaining Work |
| 2 | + |
| 3 | +**Branch:** `tyrielv/net10-tfm-retarget` |
| 4 | +**PR:** [#1947](https://github.com/microsoft/VFSForGit/pull/1947) |
| 5 | +**Base:** `upstream/master` (includes merged Phases 1–2) |
| 6 | + |
| 7 | +## Migration Phases Overview |
| 8 | + |
| 9 | +| Phase | Description | Status | |
| 10 | +|-------|-------------|--------| |
| 11 | +| 1 | Remove dead/deprecated code | ✅ Merged (PRs #1937, #1946, #1941, #1939, #1938) | |
| 12 | +| 2 | System.Text.Json migration | ✅ Merged (PR above) | |
| 13 | +| 3 | TFM retarget + API migrations | ✅ In this PR | |
| 14 | +| 4 | NativeAOT self-contained deployment | ✅ In this PR | |
| 15 | +| 5 | Cleanup & optimization | 🔲 Future PR | |
| 16 | + |
| 17 | +## What This PR Contains |
| 18 | + |
| 19 | +### Commit 1: TFM Retarget + API Migrations + CI |
| 20 | +- Retargeted all 16 managed csproj files from `net471` to `net10.0-windows10.0.17763.0` |
| 21 | +- Updated `global.json`, `Directory.Build.props`, `Version.props`, `Directory.Packages.props` |
| 22 | +- Fixed API migrations: `ProcessHelper` (Environment.ProcessPath), `OrgInfoApiClient` (WebUtility.UrlEncode), `WindowsPlatform` (NamedPipeServerStreamAcl.Create), `HttpRequestor` (removed UseDefaultCredentials), `WindowsFileSystem` (DirectoryInfo/FileSecurity APIs) |
| 23 | +- Updated CI `build.yaml`, scripts (`Build.bat`, `RunUnitTests.bat`, `RunFunctionalTests.bat`, `CreateBuildArtifacts.bat`, `RunFunctionalTests-Dev.ps1`) |
| 24 | +- Fixed obsolete API warnings (Uri.EscapeUriString, X509Certificate2 ctor) |
| 25 | +- 803 unit tests passing |
| 26 | + |
| 27 | +### Commit 2: NativeAOT Self-Contained Deployment |
| 28 | +- Added `SelfContained=true`, `PublishAot=true`, `OptimizationPreference=Speed` to Directory.Build.props |
| 29 | +- Opted out test projects and GVFS.MSBuild from AOT |
| 30 | +- Rewrote `Build.bat`: 3-step approach (dotnet restore → VS MSBuild for C++ → dotnet publish for managed) |
| 31 | +- Updated all script paths for `publish\` subdirectory |
| 32 | +- Created `GVFSJsonContext.cs` — source-generated JSON serializer context (25+ types) |
| 33 | +- Updated `GVFSJsonOptions.cs` with TypeInfoResolverChain (source-gen + reflection fallback) |
| 34 | +- Cleaned installer payload: removed stray `.runtimeconfig.json`, orphaned PDBs |
| 35 | +- AOT binaries: GVFS.exe ~20MB, GVFS.Mount.exe ~20MB, GVFS.Service.exe ~6.7MB |
| 36 | + |
| 37 | +### Commit 3: CI Diagnostic Logging + Timeout Handling |
| 38 | +- Added `[CI-DEBUG]` logging to clone/mount/gvfs process invocations |
| 39 | +- Added 5-minute timeout per gvfs process call |
| 40 | +- Added `--workers=1` for sequential CI test execution |
| 41 | +- Added `timeout-minutes: 60` to functional-tests.yaml |
| 42 | + |
| 43 | +### Commit 4: Null Guard for ProjFS triggeringProcessImageFileName |
| 44 | +- Fixed `ArgumentNullException` crash in mount process callbacks |
| 45 | +- Root cause: ProjFS managed API v2.1.0 returns null for kernel-level operations |
| 46 | +- Fix: null-coalesce to `string.Empty` in `FileSystemCallbacks` |
| 47 | + |
| 48 | +### Commit 5: CountingStream for Truncated Object Detection |
| 49 | +- Fixed `TruncatedObjectRedownloaded` test failure (4/4 in CI) |
| 50 | +- Root cause: .NET 10 `DeflateStream` silently returns partial data on truncated zlib |
| 51 | +- Fix: `CountingStream` wrapper verifies bytes read matches header-declared size |
| 52 | + |
| 53 | +## Current CI Results (as of last push) |
| 54 | + |
| 55 | +| Slice | Status | Notes | |
| 56 | +|-------|--------|-------| |
| 57 | +| 0 | ✅ 4/4 | | |
| 58 | +| 1 | ✅ 4/4 | Fixed by CountingStream | |
| 59 | +| 2 | ⚠️ 2/4 | Intermittent: `gvfs prefetch --hydrate` AV crash (0xC0000005) | |
| 60 | +| 3 | ⚠️ 1/4 | Intermittent: same prefetch AV crash | |
| 61 | +| 4 | ✅ 4/4 | | |
| 62 | +| 5 | ✅ 4/4 | | |
| 63 | +| 6 | ✅ 4/4 | | |
| 64 | +| 7 | ❌ 0/4 | `ChangeTimestampAndDiff`: pathspec not found — see below | |
| 65 | +| 8 | ⚠️ 3/4 | 1 intermittent failure | |
| 66 | +| 9 | ❌ 0/4 | All flavors hang/timeout — see below | |
| 67 | + |
| 68 | +**Overall: 7 of 10 slices fully passing, up from 0 at start of CI debugging.** |
| 69 | + |
| 70 | +## Remaining Failures to Investigate |
| 71 | + |
| 72 | +### 1. `ChangeTimestampAndDiff` (Slice 7, 4/4 fail) |
| 73 | + |
| 74 | +**Error:** `git checkout GVFS\GVFS.Common\GVFSContext.cs` fails with "pathspec did not match any file(s) known to git" |
| 75 | + |
| 76 | +**Analysis:** |
| 77 | +- The file path is hardcoded in `GitCommandsTests.EditFilePath` (line 23) |
| 78 | +- The file DOES exist in the test repo (`ForTests`) — confirmed by manual clone |
| 79 | +- `ValidateGitCommand` runs git in both the GVFS enlistment and a control repo |
| 80 | +- The error comes from the GVFS enlistment side, suggesting a projection issue |
| 81 | +- Could be related to the mount process or index projection failing for this specific file |
| 82 | +- **Not yet locally reproduced** — local test framework was setting up 20 parallel fixtures due to wrong NUnit filter. Use `--test=` (not `--where=`) to run individual tests |
| 83 | + |
| 84 | +### 2. Slice 9 Hang (4/4 timeout) |
| 85 | + |
| 86 | +**Analysis:** |
| 87 | +- All 4 flavors time out at 60 minutes |
| 88 | +- Need to identify which test(s) fall into slice 9 — run test discovery with `--slice=9,10` |
| 89 | +- Could be the same mount crash or a deadlock in a specific test |
| 90 | + |
| 91 | +### 3. Prefetch --hydrate AV (Slices 2/3, intermittent) |
| 92 | + |
| 93 | +**Error:** `gvfs prefetch` exits with -1073741819 (0xC0000005 ACCESS_VIOLATION) |
| 94 | + |
| 95 | +**Analysis:** |
| 96 | +- Happens during `PrefetchWithStats` → multithreaded pipeline (diff → blobFinder → downloader → packIndexer → fileHydrator) |
| 97 | +- `HydrateFilesStage` calls `NativeFileReader.TryReadFirstByteOfFile` via P/Invoke (CreateFile/ReadFile) |
| 98 | +- Intermittent (2/4 and 3/4) — likely a race condition or timing-dependent AOT issue |
| 99 | +- P/Invoke declarations use `DllImport` — could benefit from `LibraryImport` for AOT |
| 100 | +- The `SafeFileHandle` marshaling under AOT may have edge cases |
| 101 | + |
| 102 | +## .NET 10 Behavioral Changes Discovered |
| 103 | + |
| 104 | +### DeflateStream Truncation Behavior |
| 105 | +- **Old (.NET 4.7.1):** Throws `InvalidDataException` on truncated zlib data |
| 106 | +- **New (.NET 10):** Silently returns partial data, no exception |
| 107 | +- **Impact:** Any code relying on DeflateStream exceptions for corruption detection needs explicit length checks |
| 108 | +- **Fix applied:** `CountingStream` in `GitRepo.GetLooseBlobStateAtPath` |
| 109 | + |
| 110 | +### ProjFS Managed API Null Behavior |
| 111 | +- **Old (C++/CLI):** `TriggeringProcessImageFileName` returned `String.Empty` for NULL native pointers |
| 112 | +- **New (P/Invoke v2.1.0):** Returns `null` from `Marshal.PtrToStringUni(IntPtr.Zero)` |
| 113 | +- **Impact:** Any consumer using the value as a dictionary key or in null-intolerant APIs will crash |
| 114 | +- **Fix applied:** Null-coalesce in `FileSystemCallbacks` |
| 115 | +- **Upstream fix needed:** `Microsoft.Windows.ProjFS` package should match old behavior |
| 116 | + |
| 117 | +## Deferred Items (Phase 5 / Future PRs) |
| 118 | + |
| 119 | +- ProjFS driver installation removal (branch `tyrielv/remove-projfs-install` created but not implemented) |
| 120 | +- `upgrade.ring` config removal |
| 121 | +- Full `WinHttpHandler` replacement (see miniksa's approach) |
| 122 | +- `info.bat` update for ProjFS paths |
| 123 | +- Remove CI debug logging (`[CI-DEBUG]` lines) after tests stabilize |
| 124 | +- Re-enable parallel workers (remove `--workers=1`) after fixing remaining issues |
| 125 | +- File ProjFS upstream issue for null `TriggeringProcessImageFileName` |
| 126 | + |
| 127 | +## Build & Test Commands |
| 128 | + |
| 129 | +```powershell |
| 130 | +# Build (from src\) |
| 131 | +scripts\Build.bat Debug |
| 132 | +
|
| 133 | +# Unit tests |
| 134 | +..\out\GVFS.UnitTests\bin\Debug\net10.0-windows10.0.17763.0\win-x64\publish\GVFS.UnitTests.exe |
| 135 | +
|
| 136 | +# Functional tests (dev mode, no admin) |
| 137 | +powershell -File scripts\RunFunctionalTests-Dev.ps1 Debug '--test=<fully.qualified.test.name>' |
| 138 | +
|
| 139 | +# Install local build |
| 140 | +$v = & { $n = [DateTime]::Now; "0.2.$($n.ToString('yy'))$($n.DayOfYear.ToString('D3')).$([int]($n.TimeOfDay.TotalSeconds / 2))" } |
| 141 | +scripts\Build.bat Debug $v |
| 142 | +# Then run installer from out\GVFS.Installers\bin\Debug\win-x64\ |
| 143 | +``` |
| 144 | + |
| 145 | +## CI NuGet Feed |
| 146 | + |
| 147 | +CI uses `https://pkgs.dev.azure.com/gvfs/ci/_packaging/Dependencies/nuget/v3/index.json` as the sole NuGet source. To pull through new packages: |
| 148 | +1. Clear ALL local caches: `dotnet nuget locals all --clear` |
| 149 | +2. Run `dotnet restore` — packages automatically pull through from nuget.org |
| 150 | +3. Never push packages manually to the feed |
| 151 | + |
| 152 | +SDK version is pinned in `global.json` to `10.0.202` with `rollForward: disable`. |
0 commit comments