Skip to content

ShadowCopyAnalyzerPathResolver: Use cache to amortize cost of AV scans - #84765

Open
RikkiGibson wants to merge 19 commits into
dotnet:mainfrom
RikkiGibson:shadow-hard-link
Open

ShadowCopyAnalyzerPathResolver: Use cache to amortize cost of AV scans#84765
RikkiGibson wants to merge 19 commits into
dotnet:mainfrom
RikkiGibson:shadow-hard-link

Conversation

@RikkiGibson

@RikkiGibson RikkiGibson commented Aug 5, 2026

Copy link
Copy Markdown
Member

Closes #13863. Specifically motivated by a recent comment on that issue. The original suggestion was likely around reducing file copies, which seems to be fairly negligible on modern nvme drives, even under decent load. The real overhead these days is in antivirus scanning.

I have some empirical evidence showing that this change improves Windows Defender behavior. It seems to indicate that Defender can reuse a scan result for different paths when they are hard links to each other. I performed the following steps, with and without my change:

# Build + warmup cache if applicable
dotnet test -f net10.0 -l html .\src\LanguageServer\Microsoft.CodeAnalysis.LanguageServer.UnitTests\ --filter "FileBasedProgramsWorkspaceTests.TestFileBasedProgram_Simple"

# Then, in another shell:
New-MpPerformanceRecording -RecordTo .\Defender-scans.etl

dotnet test --no-build -f net10.0 -l html .\src\LanguageServer\Microsoft.CodeAnalysis.LanguageServer.UnitTests\ --filter "FileBasedProgramsWorkspaceTests.TestFileBasedProgram_Simple"

# Stop recording and get report:
Get-MpPerformanceReport -Path .\Defender-scans.etl -TopScans 100
Results without cache
> Get-MpPerformanceReport -Path .\Defender-scans-slow.etl -TopScans 100


TopScans
========

ScanType       Duration Reason  SkipReason  Comments Process                   Path
--------       -------- ------  ----------  -------- -------                   ----
RealTimeScan 341.6908ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.CSharp.dll
RealTimeScan 326.1057ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.CSharp.dll
RealTimeScan 311.9628ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Compiler.Test.Resources.dll
RealTimeScan 299.4210ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Compiler.Test.Resources.dll
RealTimeScan 199.6804ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Features.dll
RealTimeScan 199.6754ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Features.dll
RealTimeScan 187.4760ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.VisualBasic.dll
RealTimeScan 184.2940ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.VisualBasic.dll
RealTimeScan 183.8822ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.dll
RealTimeScan 180.3950ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.dll
RealTimeScan 162.0960ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.CSharp.Workspaces.dll
RealTimeScan 156.7831ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Razor.Compiler.dll
RealTimeScan 156.6597ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.CSharp.Workspaces.dll
RealTimeScan 151.5103ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.VisualBasic.Features.dll
RealTimeScan 149.0838ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.VisualBasic.Features.dll
RealTimeScan 146.1410ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Razor.Compiler.dll
RealTimeScan 145.2542ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Workspaces.dll
RealTimeScan 141.1708ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Workspaces.dll
RealTimeScan 136.2515ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Features.Test.Utilities.dll
RealTimeScan 134.9704ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Features.Test.Utilities.dll
RealTimeScan 126.5283ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.CSharp.Test.Utilities.dll
RealTimeScan 126.4684ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.CSharp.Features.dll
RealTimeScan 124.5245ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.VisualBasic.Test.Utilities.dll
RealTimeScan 122.8526ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.CSharp.Features.dll
RealTimeScan 120.8586ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.VisualBasic.Test.Utilities.dll
RealTimeScan 120.6527ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Test.Utilities.dll
RealTimeScan 119.4244ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Test.Utilities.dll
RealTimeScan 118.9202ms OnClose Not skipped        0 pwsh.exe (8340)           C:\Users\rikki\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
RealTimeScan 118.5137ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.CSharp.Test.Utilities.dll
RealTimeScan 115.0020ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll
RealTimeScan 113.2194ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll
RealTimeScan 104.3690ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Workspaces.MSBuild.dll
RealTimeScan 104.3516ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Workspaces.MSBuild.dll
RealTimeScan 100.7956ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Remote.Razor.dll
RealTimeScan  99.3412ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Remote.Workspaces.dll
RealTimeScan  98.9927ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Remote.Razor.dll
RealTimeScan  98.2320ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Remote.Workspaces.dll
RealTimeScan  96.3098ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Remote.ServiceHub.dll
RealTimeScan  95.0506ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Remote.ServiceHub.dll
RealTimeScan  93.7257ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Razor.Workspaces.dll
RealTimeScan  91.1035ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Workspaces.Test.Utilities.dll
RealTimeScan  91.0142ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Razor.Workspaces.dll
RealTimeScan  90.5466ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Scripting.dll
RealTimeScan  88.6807ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Scripting.dll
RealTimeScan  86.6855ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Workspaces.Test.Utilities.dll
RealTimeScan  79.0504ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.ExternalAccess.Razor.Features.dll
RealTimeScan  78.1111ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.ExternalAccess.Razor.Features.dll
RealTimeScan  69.4697ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Workspaces.MSBuild.Contracts.dll
RealTimeScan  68.6038ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Workspaces.MSBuild.Contracts.dll
RealTimeScan  60.3399ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Features.ExternalAccess.dll
RealTimeScan  60.0636ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Features.ExternalAccess.dll
RealTimeScan  34.2282ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.ExternalAccess.Extensions.dll
RealTimeScan  33.0914ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.ExternalAccess.Extensions.dll
RealTimeScan  23.4217ms OnClose Not skipped        0                           C:\Users\rikki\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
RealTimeScan  22.5607ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.ExternalAccess.CompilerDeveloperSDK.dll
RealTimeScan  22.4211ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler.dll
RealTimeScan  22.3301ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler.dll
RealTimeScan  21.8157ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.ExternalAccess.CompilerDeveloperSDK.dll
RealTimeScan  21.3210ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.ExternalAccess.TestDiscovery.dll
RealTimeScan  21.1939ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.dll
RealTimeScan  20.4163ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.ExternalAccess.Copilot.dll
RealTimeScan  20.1724ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.ExternalAccess.TestDiscovery.dll
RealTimeScan  20.1713ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.ExternalAccess.AspNetCore.dll
RealTimeScan  19.6275ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.ExternalAccess.AspNetCore.dll
RealTimeScan  19.6053ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.ExternalAccess.Xaml.dll
RealTimeScan  19.4564ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.ExternalAccess.Copilot.dll
RealTimeScan  19.3704ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.ExternalAccess.Xaml.dll
RealTimeScan  19.3606ms OnClose Not skipped        0 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.dll
RealTimeScan  15.9144ms OnClose Not skipped        0 dotnet.exe (12780)        C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-857eb4265da8de54a3132fb011b2ab30470a72f6a9af04eed29e96fa29934473\obj\34e604c5-e908-4a89-9d57-bad03ce01136.tmp
RealTimeScan  15.4589ms OnClose Not skipped        0 dotnet.exe (12780)        C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-857eb4265da8de54a3132fb011b2ab30470a72f6a9af04eed29e96fa29934473\obj\a76c9ca7-acbe-44f5-8e8d-42be92e82e3f.tmp
RealTimeScan  15.1186ms OnClose Not skipped        0 dotnet.exe (29128)        C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-9ad3aba6ef22cebdc86d6e001b14f68ae4fe81b2b8e0c7c3f8d2776da3f72ce0\obj\42e29499-2742-4f43-aa62-31f5479e97bd.tmp
RealTimeScan  14.9803ms OnClose Not skipped        0 dotnet.exe (29128)        C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-9ad3aba6ef22cebdc86d6e001b14f68ae4fe81b2b8e0c7c3f8d2776da3f72ce0\obj\a78bd921-b406-47c2-bfff-033514298151.tmp
RealTimeScan   8.8895ms OnClose Not skipped        0 dotnet.exe (12780)        C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-857eb4265da8de54a3132fb011b2ab30470a72f6a9af04eed29e96fa29934473\obj\578ae633-2f4a-4331-a3f0-ad72a8d3ca90.tmp
RealTimeScan   8.7980ms OnOpen  Not skipped        0 dotnet.exe (36940)        C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-9ad3aba6ef22cebdc86d6e001b14f68ae4fe81b2b8e0c7c3f8d2776da3f72ce0\obj\debug\SomeFile.cs.assets.cache
RealTimeScan   8.7552ms OnClose Not skipped        0 dotnet.exe (29128)        C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-9ad3aba6ef22cebdc86d6e001b14f68ae4fe81b2b8e0c7c3f8d2776da3f72ce0\obj\2cfd1c08-cba7-4f31-90bd-ea557da9f94b.tmp
RealTimeScan   8.3297ms OnOpen  Not skipped        0 dotnet.exe (6000)         C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-857eb4265da8de54a3132fb011b2ab30470a72f6a9af04eed29e96fa29934473\obj\debug\SomeFile.cs.assets.cache
RealTimeScan   7.7003ms OnOpen  Not skipped        0 dotnet.exe (36940)        C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-9ad3aba6ef22cebdc86d6e001b14f68ae4fe81b2b8e0c7c3f8d2776da3f72ce0\obj\debug\SomeFile.cs.GeneratedMSBuildEditorConfig.editorconfig
RealTimeScan   7.4217ms OnOpen  Not skipped        0 dotnet.exe (6000)         C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-857eb4265da8de54a3132fb011b2ab30470a72f6a9af04eed29e96fa29934473\obj\debug\SomeFile.cs.GeneratedMSBuildEditorConfig.editorconfig
RealTimeScan   7.1121ms OnClose Not skipped        0 AggregatorHost.exe (8664) C:\ProgramData\Microsoft\Diagnosis\AggregatorStorage\MissionControlAggregator$
RealTimeScan   6.3875ms OnOpen  Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\582da4a5e0d74953bb03fd5dcc59a59f\1\Microsoft.CodeAnalysis.NetAnalyzers.dll
RealTimeScan   6.3379ms OnOpen  Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0c25922461f74d7d8b99bb292ad36252\1\Microsoft.CodeAnalysis.NetAnalyzers.dll
RealTimeScan   6.1400ms OnOpen  Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\5784ed4828934119a998200b3fa06894\1\Microsoft.CodeAnalysis.NetAnalyzers.dll
RealTimeScan   2.9244ms OnClose Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Analyzer.Testing.dll
RealTimeScan   2.7140ms OnClose Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.Analyzer.Testing.dll
RealTimeScan   2.5923ms OnOpen  Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\582da4a5e0d74953bb03fd5dcc59a59f\2\Microsoft.Interop.SourceGeneration.dll
RealTimeScan   2.5443ms OnOpen  Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0c25922461f74d7d8b99bb292ad36252\2\Microsoft.Interop.SourceGeneration.dll
RealTimeScan   2.4211ms OnOpen  Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0c25922461f74d7d8b99bb292ad36252\2\Microsoft.Interop.ComInterfaceGenerator.dll
RealTimeScan   2.4152ms OnOpen  Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\582da4a5e0d74953bb03fd5dcc59a59f\1\Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll
RealTimeScan   2.4041ms OnClose Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\582da4a5e0d74953bb03fd5dcc59a59f\3\ILLink.RoslynAnalyzer.dll
RealTimeScan   2.3654ms OnClose Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.dll
RealTimeScan   2.3633ms OnClose Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0c25922461f74d7d8b99bb292ad36252\3\ILLink.RoslynAnalyzer.dll
RealTimeScan   2.3577ms OnOpen  Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\582da4a5e0d74953bb03fd5dcc59a59f\2\Microsoft.Interop.ComInterfaceGenerator.dll
RealTimeScan   2.3456ms OnClose Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.Elfie.dll
RealTimeScan   2.3372ms OnOpen  Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\5784ed4828934119a998200b3fa06894\2\Microsoft.Interop.SourceGeneration.dll
RealTimeScan   2.3273ms OnClose Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.dll
RealTimeScan   2.3223ms OnClose Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\582da4a5e0d74953bb03fd5dcc59a59f\3\ILLink.CodeFixProvider.dll
RealTimeScan   2.3191ms OnOpen  Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0c25922461f74d7d8b99bb292ad36252\1\Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll
RealTimeScan   2.3186ms OnClose Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\0bede650c5054c229c347ef022106d68\1\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.dll
RealTimeScan   2.3091ms OnClose Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\58f180a1879a402aaf30c937daad59ad\1\Microsoft.CodeAnalysis.CodeRefactoring.Testing.dll
RealTimeScan   2.3009ms OnClose Not skipped        1 testhost.exe (16396)      C:\Users\rikki\AppData\Local\Temp\Roslyn\AnalyzerAssemblyLoader\5784ed4828934119a998200b3fa06894\3\ILLink.RoslynAnalyzer.dll
Results with cache
> Get-MpPerformanceReport -Path .\Defender-scans.etl -TopScans 100


TopScans
========

ScanType       Duration Reason  SkipReason   Comments Process              Path
--------       -------- ------  ----------   -------- -------              ----
RealTimeScan 124.2581ms OnClose Not skipped         0 pwsh.exe (8340)      C:\Users\rikki\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
RealTimeScan  15.8427ms OnClose Not skipped         0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\418da487-d399-426f-9962-00a289acb2a4.tmp
RealTimeScan  15.6377ms OnClose Not skipped         0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\ac1bbec3-a460-4447-a86e-e942a7d53e62.tmp
RealTimeScan  15.1609ms OnClose Not skipped         0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\3cb28a18-2826-4666-9b50-f2bcf3dde4fb.tmp
RealTimeScan  15.0634ms OnClose Not skipped         0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\689931e4-164d-45c0-9493-480a2f21747e.tmp
RealTimeScan   9.6320ms OnOpen  Not skipped         0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\debug\SomeFile.cs.assets.cache
RealTimeScan   8.8562ms OnClose Not skipped         0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\a7a0c860-5a7f-474c-ad7d-daee1319c56a.tmp
RealTimeScan   8.7092ms OnClose Not skipped         0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\862bd291-8c5f-4781-9bf6-01cfcc36621c.tmp
RealTimeScan   8.2313ms OnOpen  Not skipped         0 dotnet.exe (39628)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\debug\SomeFile.cs.assets.cache
RealTimeScan   7.7906ms OnClose Not skipped         0 pwsh.exe (39824)     C:\Users\rikki\AppData\Local\Microsoft\PowerShell\StartupProfileData-Interactive
RealTimeScan   7.3420ms OnOpen  Not skipped         0 dotnet.exe (39628)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\debug\SomeFile.cs.GeneratedMSBuildEditorConfig.editorconfig
RealTimeScan   7.2929ms OnOpen  Not skipped         0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\debug\SomeFile.cs.GeneratedMSBuildEditorConfig.editorconfig
RealTimeScan   1.1572ms OnClose Not skipped         0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\3ecdafb2-386f-4556-9959-80d0fb74eea6.tmp
RealTimeScan   1.0476ms OnClose Not skipped         0 testhost.exe (23808) C:\Users\rikki\AppData\Local\Temp\RoslynTests\6c3ea52b-5cdd-4d99-a794-f984d156041d\global.json
RealTimeScan   1.0471ms OnClose Not skipped         0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\debug\byafqumf.fw0~
RealTimeScan   1.0354ms OnClose Not skipped         0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\e54b8059-49dc-4f05-8b07-03660f39a60d.tmp
RealTimeScan   1.0308ms OnClose Not skipped         0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\debug\vffj5gyt.4k3~
RealTimeScan   1.0219ms OnClose Not skipped         0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\cb52b806-8f17-480c-a64b-34d9ce798f85.tmp
RealTimeScan   1.0196ms OnClose Not skipped         0 testhost.exe (23808) C:\Users\rikki\AppData\Local\Temp\RoslynTests\3a66cef1-2746-4610-896d-2b2df3fcdc36\global.json
RealTimeScan   0.9145ms OnClose Not skipped         0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\debug\SomeFile.cs.AssemblyInfo.cs
RealTimeScan   0.9120ms OnClose Not skipped         0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\debug\SomeFile.cs.AssemblyInfo.cs
RealTimeScan   0.8988ms OnClose Not skipped         0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\debug\wcneld2i.unw~
RealTimeScan   0.8575ms OnClose Not skipped         0 testhost.exe (23808) C:\Users\rikki\AppData\Local\Temp\RoslynTests\3a66cef1-2746-4610-896d-2b2df3fcdc36\SomeFile.cs
RealTimeScan   0.8247ms OnClose Not skipped         0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\debug\rgmlby1b.cuw~
RealTimeScan   0.8138ms OnClose Not skipped         0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\debug\clh2twxl.be5~
RealTimeScan   0.7985ms OnClose Not skipped         0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\2a98523b-fbfe-4a3b-bd97-9843ad82c7d1.tmp
RealTimeScan   0.7427ms OnClose Not skipped         0 testhost.exe (23808) C:\Users\rikki\AppData\Local\Temp\RoslynTests\6c3ea52b-5cdd-4d99-a794-f984d156041d\SomeFile.cs
RealTimeScan   0.6288ms OnClose Not skipped         0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\debug\dhsgjbva.xns~
RealTimeScan   0.3025ms OnOpen  Optimization        0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.TargetFrameworkInference.targets
RealTimeScan   0.2252ms OnOpen  Optimization        0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.GenerateSupportedRuntime.targets
RealTimeScan   0.2052ms OnOpen  Optimization        0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.sdk.maui\10.0.0\WorkloadManifest.targets
RealTimeScan   0.2035ms OnOpen  Optimization        0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Microsoft.Managed.After.targets
RealTimeScan   0.1861ms OnOpen  Optimization        0 svchost.exe (4352)   C:\Windows\Prefetch\WPR.EXE-5ED1C0A2.pf
RealTimeScan   0.1662ms OnClose Optimization        0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\debug\SomeFile.cs.GeneratedMSBuildEditorConfig.editorconfig
RealTimeScan   0.1659ms AMSI    Not skipped         0                      MemScanVfz-AMSI-0B076E75-9CDC-EF8F-5FE8-7FA34B9C2DCC
RealTimeScan   0.1655ms OnOpen  Optimization        0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.DefaultOutputPaths.targets
RealTimeScan   0.1598ms OnOpen  Optimization        0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.ImportWorkloads.targets
RealTimeScan   0.1582ms OnOpen  Optimization        0 dotnet.exe (39628)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.DefaultOutputPaths.targets
RealTimeScan   0.1580ms OnOpen  Optimization        0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.DefaultOutputPaths.targets
RealTimeScan   0.1545ms OnOpen  Optimization        0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.sdk.android\36.1.2\WorkloadManifest.targets
RealTimeScan   0.1527ms OnOpen  Optimization        0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Containers\build\Microsoft.NET.Build.Containers.targets
RealTimeScan   0.1511ms OnOpen  Optimization        0 dotnet.exe (29724)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.ImportWorkloads.targets
RealTimeScan   0.1510ms OnClose Optimization        0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\debug\SomeFile.cs.GeneratedMSBuildEditorConfig.editorconfig
RealTimeScan   0.1505ms OnOpen  Optimization        0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.sdk.ios\26.0.11017\WorkloadManifest.targets
RealTimeScan   0.1500ms OnOpen  Optimization        0 dotnet.exe (39628)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.sdk.android\36.1.2\WorkloadManifest.targets
RealTimeScan   0.1494ms OnOpen  Optimization        0 dotnet.exe (39628)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.ImportPublishProfile.targets
RealTimeScan   0.1478ms OnClose Optimization        0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\debug\SomeFile.cs.assets.cache
RealTimeScan   0.1476ms OnOpen  Optimization        0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.DefaultOutputPaths.targets
RealTimeScan   0.1469ms OnOpen  Optimization        0 dotnet.exe (39628)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Containers\build\Microsoft.NET.Build.Containers.targets
RealTimeScan   0.1459ms OnOpen  Optimization        0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.PackProjectTool.targets
RealTimeScan   0.1447ms OnOpen  Optimization        0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\NuGet.targets
RealTimeScan   0.1441ms OnOpen  Optimization        0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.targets
RealTimeScan   0.1438ms OnOpen  Optimization        0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.PackProjectTool.targets
RealTimeScan   0.1432ms OnOpen  Optimization        0 dotnet.exe (29724)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Microsoft.Managed.After.targets
RealTimeScan   0.1427ms OnClose Optimization        0 svchost.exe (4352)   C:\Windows\Prefetch\WPR.EXE-5ED1C0A2.pf
RealTimeScan   0.1413ms OnOpen  Optimization        0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.DefaultOutputPaths.targets
RealTimeScan   0.1396ms OnOpen  Optimization        0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.DefaultOutputPaths.targets
RealTimeScan   0.1395ms OnOpen  Optimization        0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.targets
RealTimeScan   0.1391ms OnOpen  Optimization        0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.sdk.macos\26.0.11017\WorkloadManifest.targets
RealTimeScan   0.1388ms OnOpen  Optimization        0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.sdk.android\36.1.2\WorkloadManifest.targets
RealTimeScan   0.1379ms OnOpen  Optimization        0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.targets
RealTimeScan   0.1379ms OnClose Optimization        0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-35a02546daebf07e3b2901e17e5ee26dd4e3ffbb87758559852559d12d06e1ff\obj\debug\SomeFile.cs.GeneratedMSBuildEditorConfig.editorconfig
RealTimeScan   0.1374ms OnOpen  Optimization        0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\NuGet.targets
RealTimeScan   0.1363ms OnOpen  Optimization        0 dotnet.exe (29724)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.sdk.android\36.1.2\WorkloadManifest.targets
RealTimeScan   0.1359ms OnOpen  Optimization        0 dotnet.exe (29724)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.targets
RealTimeScan   0.1336ms OnOpen  Optimization        0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.SourceLink.AzureRepos.Git\build\Microsoft.SourceLink.AzureRepos.Git.targets
RealTimeScan   0.1336ms OnOpen  Optimization        0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.Analyzers.targets
RealTimeScan   0.1336ms OnOpen  Optimization        0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\NuGet.targets
RealTimeScan   0.1336ms OnOpen  Optimization        0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.DefaultAssemblyInfo.targets
RealTimeScan   0.1331ms OnOpen  Optimization        0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\NuGet.targets
RealTimeScan   0.1326ms OnOpen  Optimization        0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.sdk.android\36.1.2\WorkloadManifest.targets
RealTimeScan   0.1323ms OnOpen  Optimization        0 dotnet.exe (39628)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.Analyzers.targets
RealTimeScan   0.1322ms OnOpen  Optimization        0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.sdk.android\36.1.2\WorkloadManifest.targets
RealTimeScan   0.1321ms OnOpen  Optimization        0 dotnet.exe (29724)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.sdk.maccatalyst\26.0.11017\WorkloadManifest.targets
RealTimeScan   0.1318ms OnOpen  Optimization        0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.targets
RealTimeScan   0.1303ms OnOpen  Optimization        0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.workload.emscripten.net9\10.0.110\WorkloadManifest.targets
RealTimeScan   0.1291ms OnClose Optimization        0 dotnet.exe (39628)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\debug\SomeFile.cs.assets.cache
RealTimeScan   0.1286ms OnOpen  Optimization        0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Containers\build\Microsoft.NET.Build.Containers.targets
RealTimeScan   0.1284ms OnOpen  Optimization        0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.sdk.android\36.1.2\WorkloadManifest.targets
RealTimeScan   0.1270ms OnOpen  Optimization        0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.PackTool.targets
RealTimeScan   0.1267ms AMSI    Not skipped         0                      MemScanVfz-AMSI-E131C060-1346-0F11-FC76-F83D6443B160
RealTimeScan   0.1267ms OnOpen  Optimization        0 dotnet.exe (39628)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\NuGet.targets
RealTimeScan   0.1255ms OnOpen  Optimization        0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Current\Microsoft.Common.targets\ImportAfter\Microsoft.NET.Build.Extensions.targets
RealTimeScan   0.1234ms OnOpen  Optimization        0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.workload.mono.toolchain.current\10.0.110\WorkloadManifest.targets
RealTimeScan   0.1233ms OnOpen  Optimization        0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.SourceLink.targets
RealTimeScan   0.1230ms OnOpen  Optimization        0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\NuGet.targets
RealTimeScan   0.1220ms OnOpen  Optimization        0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.ApiCompat.targets
RealTimeScan   0.1218ms OnOpen  Optimization        0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.Common.targets
RealTimeScan   0.1197ms OnOpen  Optimization        0 dotnet.exe (29724)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\NuGet.targets
RealTimeScan   0.1183ms OnOpen  Optimization        0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.targets
RealTimeScan   0.1180ms OnOpen  Optimization        0 dotnet.exe (39452)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.ImportWorkloads.targets
RealTimeScan   0.1177ms OnOpen  Optimization        0 dotnet.exe (39628)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.targets
RealTimeScan   0.1172ms OnOpen  Optimization        0 dotnet.exe (39588)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.NuGetOfflineCache.targets
RealTimeScan   0.1172ms OnClose Optimization        0 dotnet.exe (39628)   C:\Users\rikki\AppData\Local\Temp\dotnet\runfile\SomeFile-a8efcfc9bdb6f89753ca95f68d0b80f9bada05edefaa37997675274da8d22c3f\obj\debug\SomeFile.cs.GeneratedMSBuildEditorConfig.editorconfig
RealTimeScan   0.1171ms OnOpen  Optimization        0 dotnet.exe (39628)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Current\Microsoft.Common.targets\ImportAfter\Microsoft.NET.Build.Extensions.targets
RealTimeScan   0.1170ms OnOpen  Optimization        0 dotnet.exe (29724)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Current\Microsoft.Common.targets\ImportAfter\Microsoft.TestPlatform.ImportAfter.targets
RealTimeScan   0.1128ms OnOpen  Optimization        0 dotnet.exe (38008)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.ImportPublishProfile.targets
RealTimeScan   0.1127ms OnOpen  Optimization        0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Roslyn\Microsoft.Managed.Core.targets
RealTimeScan   0.1125ms OnOpen  Optimization        0 dotnet.exe (25432)   C:\Users\rikki\AppData\Local\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.EolTargetFrameworks.targets
RealTimeScan   0.1120ms OnOpen  Optimization        0 dotnet.exe (39476)   C:\Users\rikki\AppData\Local\dotnet\sdk-manifests\10.0.100\microsoft.net.workload.mono.toolchain.current\10.0.110\WorkloadManifest.targets

Note that these particular FBA tests are just ones I've noticed being slow in the past, due to having a big defender penalty when loading solution-level analyzers. I think this is true for the LS tests, as well as startup of the real LS, in addition to analyzer loading from user projects. See also #82447 which was a separate strategy for addressing that in isolation.

With this change we could actually get rid of the AsParallel() added by #82447. We now go faster than that with or without the parallelism.

I also added AssemblyFileOperationBenchmarks which shows the following results on my machine:

Scenario Mean
Copy 5.174 ms
Hard link 0.761 ms
Copy + ALC load 284.577 ms
Hard link + ALC load 1.805 ms

I still want to manually test putting temp directory on a Dev Drive where hard links don't work (IIRC). This change was implemented with the expectation that temp may be on a drive which doesn't support hard links, and everything needs to still work even if every attempt to hard-link fails. (Possibly allowing substituting a TryCreateHardLink function for testing, and trying an impl where that always fails, would be good.)

Microsoft Reviewers: Open in CodeFlow

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Comment thread src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs Outdated
Comment thread src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR changes the analyzer shadow-copy mechanism to optionally hard-link shadow-copied analyzer assemblies through a shared cache directory (primarily for Windows), aiming to reduce repeated overhead (e.g., AV rescans) across runs. It also introduces new file utility helpers to create hard links and to query hard-link counts for cache pruning.

Changes:

  • Add FileUtilities.TryCreateHardLink and FileUtilities.CountHardLinks (Win32 P/Invokes) to support hard-linking and pruning logic.
  • Extend ShadowCopyAnalyzerPathResolver with a CacheDirectory, hard-link-from-cache / hard-link-to-cache behavior, and a best-effort cache pruning step during cleanup.
  • Add hashing helpers (HashToHex, GetCacheKey) to derive stable cache filenames.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Compilers/Core/Portable/FileSystem/FileUtilities.cs Adds Win32 hard-link creation + hard-link-count querying helpers used by the shadow-copy cache.
src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs Adds cache directory support, cache pruning, and hard-linking behavior when shadow-copying analyzer assemblies.
Suppressed comments (3)

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:333

  • copyFile takes originalPath as a parameter, but this call uses the outer originalFilePath instead. They are currently the same, but this makes the local function easier to misuse/refactor incorrectly later and is inconsistent with the parameter naming.
                if (File.Exists(originalPath))
                {
                    linkFromCacheOrFallbackToCopy(originalFilePath, shadowCopyPath);
                    ClearReadOnlyFlagOnFile(new FileInfo(shadowCopyPath));

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:349

  • GetCacheKey calls AssemblyUtilities.ReadMvid, which throws for non-assemblies. Existing unit tests for this resolver shadow-copy plain text files (e.g. writing "test" to analyzer.dll), so this change will start throwing BadImageFormatException instead of copying. Also, even if this resolver is ever used on non-Windows, the cache key computation adds extra I/O despite always falling back to File.Copy. Consider: (1) short-circuiting the cache logic entirely when not on Windows, and (2) treating failures to compute a cache key as a signal to fall back to a normal copy to preserve prior behavior.
            void linkFromCacheOrFallbackToCopy(string originalPath, string shadowCopyPath)
            {
                var cachePath = Path.Combine(CacheDirectory, GetCacheKey(originalPath));
                if (File.Exists(cachePath))
                {
                    // File is already present in cache. First try to hard-link from cache to the shadow copy path. Failing that just copy from the original path.
                    if (!PlatformInformation.IsWindows || !haveMatchingMvidAndSize(originalPath, cachePath) || !FileUtilities.TryCreateHardLink(cachePath, shadowCopyPath))
                        File.Copy(originalPath, shadowCopyPath);
                }

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:219

  • Directory.EnumerateFiles(CacheDirectory) will throw when the cache directory hasn't been created yet. This gets swallowed by the outer catch, but it means we rely on exceptions for the common case (no cache) and silently skip any pruning work. Add an explicit Directory.Exists check inside the lockTaken block before enumerating.
                        // Permit up to 200 unlinked files (not hard-linked to a specific shadow loader directory).
                        // Delete the oldest files which exceed this limit.
                        const int maxUnlinkedCount = 200;
                        var filesToEvict = Directory.EnumerateFiles(CacheDirectory)
                            .Where(file =>

Comment on lines +131 to +132
if (subDirectory == CacheDirectory)
continue;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This specific comparison was deleted, unsure what the other ones were, we'll see if it brings it back up or if we can spot it. I don't think I introduced any new use of == to compare paths.

Comment on lines +463 to +466
public static bool TryCreateHardLink(string path, string pathToTarget)
{
return CreateHardLink(pathToTarget, path, IntPtr.Zero);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was intentional to use same parameter order and names as https://learn.microsoft.com/en-us/dotnet/api/system.io.file.createhardlink?view=net-11.0.

@RikkiGibson
RikkiGibson marked this pull request as ready for review August 5, 2026 04:18
@RikkiGibson
RikkiGibson requested a review from a team as a code owner August 5, 2026 04:18
Copilot AI review requested due to automatic review settings August 5, 2026 04:18
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:132

  • Skipping the cache directory via subDirectory == CacheDirectory is brittle on Windows (path casing/normalization differences can make this false even for the same directory). If this fails, the cleanup loop may delete the cache directory, negating the optimization. Use the existing path comparer (e.g., AnalyzerAssemblyLoader.OriginalPathComparer) or compare just the directory name with an ignore-case comparison.
                    if (subDirectory == CacheDirectory)
                        continue;

src/Compilers/Core/Portable/FileSystem/FileUtilities.cs:466

  • TryCreateHardLink's implementation inverts the parameter semantics of File.CreateHardLink(path, pathToTarget) (where path is the new link location and pathToTarget is the existing file). With the current CreateHardLink(pathToTarget, path, ...) call, any future caller using it like the BCL API will create the link in the wrong place. Consider aligning this helper with the BCL semantics and updating callers accordingly (and keeping the XML doc link accurate).
        public static bool TryCreateHardLink(string path, string pathToTarget)
        {
            return CreateHardLink(pathToTarget, path, IntPtr.Zero);

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:230

  • pruneCacheIfNeeded enumerates CacheDirectory unconditionally and relies on the outer catch to swallow DirectoryNotFoundException when the cache directory hasn't been created. This will commonly incur avoidable exceptions. Also, this is compiler product code in src/Compilers/—prefer avoiding the LINQ pipeline here to reduce allocations and make cost clearer.
                        // Permit up to 200 unlinked files (not hard-linked to a specific shadow loader directory).
                        // Delete the oldest files which exceed this limit.
                        const int maxUnlinkedCount = 200;
                        var filesToEvict = Directory.EnumerateFiles(CacheDirectory)
                            .Where(file =>
                            {
                                Debug.Assert(PlatformInformation.IsWindows);
                                return FileUtilities.CountHardLinks(file) == 1;
                            })
                            .OrderByDescending(File.GetLastWriteTimeUtc)
                            .Skip(maxUnlinkedCount);

                        foreach (var file in filesToEvict)
                        {
                            File.Delete(file);
                        }

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:349

  • The cache key computation (GetCacheKey) reads the assembly MVID even on non-Windows platforms (where the cache is never populated/pruned). This adds extra I/O/metadata reads on Linux/macOS for every shadow-copied analyzer without any benefit. Consider an early !PlatformInformation.IsWindows fast-path that just copies the file.
            void linkFromCacheOrFallbackToCopy(string originalPath, string shadowCopyPath)
            {
                var cachePath = Path.Combine(CacheDirectory, GetCacheKey(originalPath));
                if (File.Exists(cachePath))
                {

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:341

  • This PR introduces new behaviors (cache population, hard-linking vs copying, cache pruning) but existing unit tests for ShadowCopyAnalyzerPathResolver only validate basic copy/grouping behavior. Adding regression tests would help ensure: (1) the non-Windows fast-path still just copies, (2) hard-link failures reliably fall back to copying, and (3) cache cleanup does not delete active/in-use cached files.
            // Optimization for antivirus scanning on Windows:
            // - Shadow copied files are hard-linked to/from a cache directory if possible.
            // - We continue to use per-session 'ShadowDirectory' for ease of implementing correct loading semantics and cleanup.
            // - Hard linking a file from the cache instead of copying it is empirically observed to reduce time spent running AV scans when loading assemblies.
            void linkFromCacheOrFallbackToCopy(string originalPath, string shadowCopyPath)

Comment thread src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider starting a validation VS insertion for this change.

// - Hard linking a file from the cache instead of copying it is empirically observed to reduce time spent running AV scans when loading assemblies.
void linkFromCacheOrFallbackToCopy(string originalPath, string shadowCopyPath)
{
var cachePath = Path.Combine(CacheDirectory, GetCacheKey(originalPath));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like on non-Windows, we don't even need to compute this and call File.Exists for it.


return hashToHex(hash);

// See also 'PrivateImplementationDetails.HashToHex'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could extract this into some shared utility?

{
// Key format: (original filename) + (file path hash) + (mvid)
var hexHash = HashToHex(originalPath);
var mvid = AssemblyUtilities.ReadMvid(originalPath);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this might throw for corrupted files where we previously didn't throw (and it also seems some CI tests caught this).


foreach (var subDirectory in subDirectories)
{
if (subDirectory == CacheDirectory)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we just place the cache directory elsewhere? So that also older roslyn versions wouldn't delete it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should, it's just painful to have multiple roslyn versions potentially operating on the same directories here. Can/should we introduce the Roslyn version itself as a path component somewhere to avoid that?

To solve for now I added a second parameter. This affects an OmniSharp EA but I was advised that making changes to that is fine, O# can react when/if it updates its Roslyn version.

{
// File is already present in cache. First try to hard-link from cache to the shadow copy path. Failing that just copy from the original path.
if (!PlatformInformation.IsWindows || !haveMatchingMvidAndSize(originalPath, cachePath) || !FileUtilities.TryCreateHardLink(cachePath, shadowCopyPath))
File.Copy(originalPath, shadowCopyPath);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we fall back to copying should we delete the cache entry to avoid needing to check the mvid etc every time?

Comment on lines +197 to +198
if (!PlatformInformation.IsWindows)
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you checking for Windows here? This code should run equally well on Windows or Linux. The decision point is usually whether to run this at all there.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think there was a need/benefit to use the cache on Linux. We could strictly make the code work on Linux also, but, it would require stubbing out more native methods. I guess it is the case that we usually avoid running this code on Linux by simply not using this path resolver by default there.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There also isn't a benefit to using shadow copy at all on Linux. That's why this type isn't used there. Basically this entire type is a windows specific artifact, nowhere else do we do sub-feature checknig for windows.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am leaning toward resolving by checking PlatformInformation.IsWindows in constructor, and throwing if that is not met. Then Debug.Assert()ing if we need to in order to make the platform analyzer happy.

Because if we want all of this code to "work" on linux, it's signing up for additional work to make things like cache pruning work sensibly on linux, e.g. by writing the native call to count the links.

Comment thread src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs Outdated
Copilot AI review requested due to automatic review settings August 5, 2026 22:04
@RikkiGibson
RikkiGibson requested a review from a team as a code owner August 5, 2026 22:04
…zerPathResolver.cs

Co-authored-by: Jan Jones <jan.jones.cz@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/Compilers/Core/Portable/FileSystem/FileUtilities.cs:463

  • TryCreateHardLink’s XML doc points to System.IO.File.CreateHardLink, but this helper’s parameter order/semantics are the inverse (the current implementation treats the first parameter as the existing file and the second as the new link path). This mismatch makes it easy for future call sites to accidentally reverse the arguments.
        /// <seealso href="https://learn.microsoft.com/en-us/dotnet/api/system.io.file.createhardlink?view=net-11.0" />
#if NET
        [SupportedOSPlatform("windows")]
#endif
        public static bool TryCreateHardLink(string path, string pathToTarget)

src/Features/ExternalAccess/OmniSharp/Analyzers/OmnisharpAnalyzerLoaderFactory.cs:15

  • This change removes the optional baseDirectory parameter, but InternalAPI.Unshipped.txt still lists CreateShadowCopyAnalyzerAssemblyLoader(string? baseDirectory = null). Restoring the original signature here avoids an internal-API mismatch (and preserves flexibility for callers) while still allowing a cache directory to be introduced.
    public static IAnalyzerAssemblyLoader CreateShadowCopyAnalyzerAssemblyLoader()
    {
        var baseDirectory = Path.Combine(Path.GetTempPath(), "CodeAnalysis", "OmnisharpAnalyzerShadowCopies");
        var cacheDirectory = Path.Combine(Path.GetTempPath(), "CodeAnalysis", "OmnisharpAnalyzerShadowCopies-cache");
        return AnalyzerAssemblyLoader.CreateNonLockingLoader(baseDirectory, cacheDirectory);

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:224

  • pruneCacheIfNeeded enumerates CacheDirectory without checking whether it exists. On first run (or when caching was never used) this can throw DirectoryNotFoundException, which gets swallowed but still incurs repeated first-chance exceptions and prevents pruning from running.
                        // Permit up to 200 unlinked files (not hard-linked to a specific shadow loader directory).
                        // Delete the oldest files which exceed this limit.
                        const int maxUnlinkedCount = 200;
                        var filesToEvict = Directory.EnumerateFiles(CacheDirectory)
                            .Where(static file =>

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:337

  • Inside copyFile, the call to linkFromCacheOrFallbackToCopy uses the outer originalFilePath instead of the originalPath parameter. This currently works because the caller passes the same value, but it makes the local function easy to misuse if it’s refactored or reused later.
                if (File.Exists(originalPath))
                {
                    linkFromCacheOrFallbackToCopy(originalFilePath, shadowCopyPath);
                    ClearReadOnlyFlagOnFile(new FileInfo(shadowCopyPath));

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:346

  • This PR introduces new caching + hard-linking behavior in the shadow-copy path, but the existing ShadowCopyAnalyzerPathResolverTests were only updated for the new constructor signature. It would be good to add focused tests that (on Windows) validate: (1) cache population creates an additional hard link, (2) a subsequent resolve uses a hard link instead of a copy when possible, and (3) behavior still succeeds when hard-link creation fails (e.g., different volume / unsupported FS).
            // Optimization for antivirus scanning on Windows:
            // - Shadow copied files are hard-linked to/from a cache directory if possible.
            // - We continue to use per-session 'ShadowDirectory' for ease of implementing correct loading semantics and cleanup.
            // - Hard linking a file from the cache instead of copying it is empirically observed to reduce time spent running AV scans when loading assemblies.
            void linkFromCacheOrFallbackToCopy(string originalPath, string shadowCopyPath)

Copilot AI review requested due to automatic review settings August 5, 2026 22:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (6)

src/Compilers/Core/Portable/FileSystem/FileUtilities.cs:466

  • TryCreateHardLink's signature and <seealso> point at File.CreateHardLink(path, pathToTarget), but the implementation calls the Win32 API with the arguments reversed (CreateHardLink(pathToTarget, path, ...)). With the new caching call sites added, this mismatch is likely to cause accidental misuse (especially with named arguments) and makes it unclear which order callers should use. Consider aligning the implementation to the documented .NET order, and updating the new call sites accordingly (or, alternatively, update the XML/docs/parameter names to reflect the actual order).
        /// <summary>Create a hard link to a file.</summary>
        /// <seealso href="https://learn.microsoft.com/en-us/dotnet/api/system.io.file.createhardlink?view=net-11.0" />
#if NET
        [SupportedOSPlatform("windows")]
#endif
        public static bool TryCreateHardLink(string path, string pathToTarget)
        {
            return CreateHardLink(pathToTarget, path, IntPtr.Zero);

src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.cs:409

  • The CreateNonLockingLoader XML doc comment documents windowsShadowPath, but not the newly added windowsCachePath parameter. Adding a <param> entry here will help callers understand the cache requirements (same volume, best-effort optimization).
        /// <summary>
        /// Return an <see cref="IAnalyzerAssemblyLoader"/> which does not lock assemblies on disk that is
        /// most appropriate for the current platform.
        /// </summary>
        /// <param name="windowsShadowPath">A shadow copy path will be created on Windows and this value 
        /// will be the base directory where shadow copy assemblies are stored. </param>
        internal static IAnalyzerAssemblyLoaderInternal CreateNonLockingLoader(
            string windowsShadowPath,
            string windowsCachePath,
            ImmutableArray<IAnalyzerPathResolver> pathResolvers = default,

src/Features/ExternalAccess/OmniSharp/Analyzers/OmnisharpAnalyzerLoaderFactory.cs:16

  • CreateShadowCopyAnalyzerAssemblyLoader removed the optional baseDirectory parameter, but this API is tracked in InternalAPI.Unshipped.txt and may be consumed externally. Removing the parameter is a breaking change and will also desync the internal API baseline. Consider keeping the original signature and deriving the cache directory from the chosen base directory.
    public static IAnalyzerAssemblyLoader CreateShadowCopyAnalyzerAssemblyLoader()
    {
        var baseDirectory = Path.Combine(Path.GetTempPath(), "CodeAnalysis", "OmnisharpAnalyzerShadowCopies");
        var cacheDirectory = Path.Combine(Path.GetTempPath(), "CodeAnalysis", "OmnisharpAnalyzerShadowCopies-cache");
        return AnalyzerAssemblyLoader.CreateNonLockingLoader(baseDirectory, cacheDirectory);
    }

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:235

  • pruneCacheIfNeeded uses multiple LINQ operators (Where/OrderByDescending/Skip) over Directory.EnumerateFiles, which will allocate and sort the entire file list before deletion. Since this is compiler product code and may run on startup, consider using an explicit loop + sort to reduce allocations and make the cost more obvious.
                        // Permit up to 200 unlinked files (not hard-linked to a specific shadow loader directory).
                        // Delete the oldest files which exceed this limit.
                        const int maxUnlinkedCount = 200;
                        var filesToEvict = Directory.EnumerateFiles(CacheDirectory)
                            .Where(static file =>
                            {
                                Debug.Assert(PlatformInformation.IsWindows);
                                return FileUtilities.CountHardLinks(file) == 1;
                            })
                            .OrderByDescending(File.GetLastWriteTimeUtc)
                            .Skip(maxUnlinkedCount);

                        foreach (var file in filesToEvict)
                        {
                            File.Delete(file);
                        }

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:339

  • copyFile takes originalPath, but the call to linkFromCacheOrFallbackToCopy uses the outer originalFilePath instead. They are currently the same value, but using the parameter makes the helper less error-prone if the surrounding code changes.
                // The shadow copy should only copy files that exist. For files that don't exist, this best
                // emulates not having the shadow copy layer
                if (File.Exists(originalPath))
                {
                    linkFromCacheOrFallbackToCopy(originalFilePath, shadowCopyPath);
                    ClearReadOnlyFlagOnFile(new FileInfo(shadowCopyPath));
                }

src/Compilers/Core/CodeAnalysisTest/ShadowCopyAnalyzerPathResolverTests.cs:26

  • The resolver now takes an explicit cacheDirectory and contains new cache/hard-link behavior (including the fallback path when hard links fail). The existing tests validate basic shadow copying but don't appear to cover the cache behavior or a "hard links unsupported" scenario. Consider adding focused tests that (1) verify a second shadow copy reuses a cached entry when possible, and (2) verify functionality when hard-link creation always fails.
    public ShadowCopyAnalyzerPathResolverTests()
    {
        TempRoot = new TempRoot();
        PathResolver = new ShadowCopyAnalyzerPathResolver(TempRoot.CreateDirectory().Path, TempRoot.CreateDirectory().Path);
    }

…nto shadow-hard-link

# Conflicts:
#	src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs
Copilot AI review requested due to automatic review settings August 5, 2026 23:00
@RikkiGibson

Copy link
Copy Markdown
Member Author

/pr-val ee53d7f

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

View PR Validation Run triggered by @RikkiGibson

Parameters
  • Validation Type: pr-val
  • Pipeline ID: 8972
  • Pipeline Version: main
  • PR Number: 84765
  • Commit SHA: ee53d7f9b9a66baaf2c4dd76b5d2b3c815fec537
  • Source Branch: shadow-hard-link
  • Target Branch: main
  • Build ID: 14885313

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Suppressed comments (8)

src/Workspaces/Core/Portable/Workspace/Host/Metadata/IAnalyzerAssemblyLoaderProvider.cs:74

  • Same indentation issue as above in the non-NET branch: the cache path argument should be aligned with the other arguments for readability/consistency.
    public IAnalyzerAssemblyLoaderInternal CreateNewShadowCopyLoader()
        => this.WrapLoader(AnalyzerAssemblyLoader.CreateNonLockingLoader(
                Path.Combine(Path.GetTempPath(), nameof(Roslyn), "AnalyzerAssemblyLoader"),
            Path.Combine(Path.GetTempPath(), nameof(Roslyn), "AnalyzerAssemblyLoader-cache"),
                pathResolvers: default));

src/Compilers/Core/Portable/FileSystem/FileUtilities.cs:13

  • The newly added Windows-specific using directives are unused in this file, which adds unnecessary dependencies and may introduce warnings-as-errors in some builds. Remove the unused imports.
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.Win32.SafeHandles;

src/Workspaces/Core/Portable/Workspace/Host/Metadata/IAnalyzerAssemblyLoaderProvider.cs:58

  • The second argument line is mis-indented, which makes this call harder to read and inconsistent with the surrounding formatting. Align the cache path argument with the other arguments.

This issue also appears on line 70 of the same file.

        => this.WrapLoader(AnalyzerAssemblyLoader.CreateNonLockingLoader(
                Path.Combine(Path.GetTempPath(), nameof(Roslyn), "AnalyzerAssemblyLoader"),
            Path.Combine(Path.GetTempPath(), nameof(Roslyn), "AnalyzerAssemblyLoader-cache"),
                _assemblyPathResolvers,
                _assemblyResolvers));

src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.cs:408

  • windowsCachePath was added to the NET build signature but the XML doc comment wasn't updated. Please document this parameter (as is done in the non-NET build) so callers understand the same-volume requirement and the effect on caching.
        /// <param name="windowsShadowPath">A shadow copy path will be created on Windows and this value 
        /// will be the base directory where shadow copy assemblies are stored. </param>
        internal static IAnalyzerAssemblyLoaderInternal CreateNonLockingLoader(
            string windowsShadowPath,
            string windowsCachePath,

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:345

  • Inside copyFile, the existence check uses the originalPath parameter, but the subsequent cache/copy operation uses the outer originalFilePath variable. They happen to be the same today, but using the parameter avoids accidental divergence if this helper is reused/refactored.
                if (File.Exists(originalPath))
                {
                    linkFromCacheOrFallbackToCopy(originalFilePath, shadowCopyPath);
                    ClearReadOnlyFlagOnFile(new FileInfo(shadowCopyPath));

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:490

  • TryCreateHardLink's parameter names imply the same semantics/order as File.CreateHardLink(path, pathToTarget) (where path is the new link), but the implementation treats the first argument as the existing file and the second as the new link. Renaming parameters to match actual semantics will prevent accidental misuse at call sites.
        private static bool TryCreateHardLink(string path, string pathToTarget)
        {
            return CreateHardLink(pathToTarget, path, IntPtr.Zero);

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:450

  • The cache key includes the original file name plus additional metadata, which can easily exceed Windows' per-path-segment limits (e.g. long analyzer DLL names). That would turn caching into a potential functional failure (path too long / invalid file name). Consider using a fixed-length key (hash + mvid + length + extension) instead of embedding the original file name.
            // Key format: (original filename) + (file path hash) + (mvid) + (original file length)

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:354

  • New cache/hard-link behavior is introduced here (cache hit, cache miss, hard-link success, hard-link failure fallback, and cache pruning), but there are no dedicated unit tests exercising these branches. Adding targeted tests (Windows-only where needed) would help prevent regressions in the caching/fallback semantics.
            // Optimization for antivirus scanning on Windows:
            // - Shadow copied files are hard-linked to/from a cache directory if possible.
            // - We continue to use per-session 'ShadowDirectory' for ease of implementing correct loading semantics and cleanup.
            // - Hard linking a file from the cache instead of copying it is empirically observed to reduce time spent running AV scans when loading assemblies.
            void linkFromCacheOrFallbackToCopy(string originalPath, string shadowCopyPath)
            {

Comment thread src/Features/ExternalAccess/OmniSharp/Analyzers/OmnisharpAnalyzerLoaderFactory.cs Outdated
Comment on lines +407 to +408
string windowsShadowPath,
string windowsCachePath,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have one base path and then build cache and shadow off of that?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See also #84765 (comment)

My concern about changing the layout of the windowsShadowPath was, if we don't also change its location at the same time, then we could have old and new versions of this type both messing with the same windowsShadowPath at the same time. Potentially old versions attempting to clean up the wrong things, etc.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if that's the aces, can't we have one path and then just change the default we use for the shadwo path to avoid conflicts?

Comment on lines +197 to +198
if (!PlatformInformation.IsWindows)
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There also isn't a benefit to using shadow copy at all on Linux. That's why this type isn't used there. Basically this entire type is a windows specific artifact, nowhere else do we do sub-feature checknig for windows.


if (lockTaken)
{
// Permit up to 200 unlinked files (not hard-linked to a specific shadow loader directory).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 200 files?

@RikkiGibson RikkiGibson Aug 6, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I arbitrarily decided a few hundred is a decent amount.

I'll look at whether we can just expose stats here, or leverage existing stats to make a better decision (how many distinct analyzer dlls did we resolve through this). Then we could just try building various things and see what numbers we get. How many do we load, how much do we end up pruning, and how much disk utilization, if we build large solution A then B then C all on the same machine.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if it's arbitrary let's document why. That way future devs know what they're working with if they get evidence to change this.

Debug.Assert(PlatformInformation.IsWindows);
return (file, fileInformationOpt: TryGetWindowsFileInformation(file));
})
.Where(static pair => pair.fileInformationOpt is { NumberOfLinks: 1 })

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if another instance of this type is in another process racing to create links to these files in parallel? Even if this is safe discuss this in comments: both for future devs and future AI to take into consideration when making changes.

}

static void copyFile(string originalPath, string shadowCopyPath)
void copyFile(string originalPath, string shadowCopyPath)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep this static and explicitly pass state because it's super easy to capture the wrong value here create good looking but incorrect code.

Comment on lines +355 to +359
if (!PlatformInformation.IsWindows)
{
File.Copy(originalPath, shadowCopyPath);
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This platform checking feels wrong. The caller passed a cache path and you're silently ignoring it here. Feel like the construction of the type should be specifying the usage.


private static string? TryGetCacheKey(string originalPath)
{
// Key format: (original filename) + (file path hash) + (mvid) + (original file length)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment doesn't match implementation which includes extension.

}
}

private static string HashToHex(string value)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static string HashToHex(string value)
private static string HashToHex(ReadOnlySpan<char> value)

@RikkiGibson

Copy link
Copy Markdown
Member Author

Test insertion is passing CloudBuild+RPS+Speedometer with neither regressions or improvements. https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/766898

Copilot AI review requested due to automatic review settings August 6, 2026 21:27
@RikkiGibson

RikkiGibson commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

I added some benchmark numbers to the PR description which give some evidence that hard linking+loading is faster than copy+loading.

I also had my LLM write some additional benchmarks offline for following scenarios. These felt more "throwaway/exploratory" to me, so I didn't push them.

  • Copy a file, wait, then begin iteration and load assembly into ALC. The point was to investigate whether copying initiates some async scan, which loading the assembly is blocked on. Results indicated the cost is paid on first load, and waiting before initiating the load doesn't make the load complete faster.
  • Copy a file and load it once in setup. Then load it a 2nd time in iteration. This was meant to demonstrate whether the scan is amortized when loading files. Results indicated that yes the scan is amortized.

I don't find it super clear why the dll events are entirely missing from the "with cache" case in the defender performance report. Or, why the events are listed as 'OnClose'. It doesn't seem like the scan is occurring as part of the file copy, but, it's hard to be sure.

What seems clear from what we can measure though is that hard linking is helping here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

Copilot AI review requested due to automatic review settings August 6, 2026 23:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

Copilot AI review requested due to automatic review settings August 7, 2026 03:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/Compilers/Core/Portable/FileSystem/FileUtilities.cs:13

  • The added interop/platform-related using directives appear to be unused in this file (no references to Marshal, SupportedOSPlatform, or SafeFileHandle). Unused usings commonly produce build warnings and can be treated as errors in this repo; please remove them.
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.Win32.SafeHandles;

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:462

  • TryCreateHardLink's parameter names imply the same semantics as File.CreateHardLink(path, pathToTarget), but the P/Invoke call currently swaps them (CreateHardLink(pathToTarget, path, ...)). This makes the helper very easy to misuse and contradicts the doc link/comment. Rename the parameters to reflect the actual semantics (existing file path vs. new link path) and pass them to the Win32 API in that order.
        /// <summary>Create a hard link to a file.</summary>
        /// <seealso href="https://learn.microsoft.com/en-us/dotnet/api/system.io.file.createhardlink?view=net-11.0" />
        private static bool TryCreateHardLink(string path, string pathToTarget)
        {
            return CreateHardLink(pathToTarget, path, IntPtr.Zero);

            // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createhardlinkw
            [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
        }

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:234

  • The FILETIME-to-Int64 conversion used for cache eviction ordering sign-extends dwHighDateTime because it’s an int. Treat the high DWORD as unsigned to avoid incorrect ordering once the high bit is set.
                                var creationTime = pair.fileInformationOpt!.Value.CreationTime;
                                return (long)creationTime.dwHighDateTime << 32 | (uint)creationTime.dwLowDateTime;
                            })

src/Tools/Benchmarks/AssemblyFileOperationBenchmarks.cs:32

  • GlobalSetup throws on non-Windows. This benchmark project is validated in BenchmarkDotNet "Dry" mode (see eng/validate-benchmarks.ps1), and this exception will fail validation on non-Windows machines even though only the hard-link scenarios are Windows-specific. Consider making the hard-link benchmarks gracefully no-op or fall back to copy on non-Windows so the suite remains runnable cross-platform.
        if (!OperatingSystem.IsWindows())
        {
            throw new PlatformNotSupportedException("Hard-link creation is benchmarked through the Windows API.");
        }

src/Compilers/Core/CodeAnalysisTest/ShadowCopyAnalyzerPathResolverTests.cs:10

  • System.Runtime.InteropServices is imported but not used in this test file. Please remove it to avoid unused-using warnings.
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

Copilot AI review requested due to automatic review settings August 7, 2026 04:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:457

  • TryCreateHardLink(string path, string pathToTarget) reads like File.CreateHardLink(path, pathToTarget), but the implementation and call sites treat the first parameter as the existing file and the second as the new link path. Renaming the parameters (and updating the summary) would make the contract explicit and reduce the chance of accidental misuse.
        /// <summary>Create a hard link to a file.</summary>
        /// <seealso href="https://learn.microsoft.com/en-us/dotnet/api/system.io.file.createhardlink?view=net-11.0" />
        private static bool TryCreateHardLink(string path, string pathToTarget)
        {
            return CreateHardLink(pathToTarget, path, IntPtr.Zero);

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:353

  • The new cache/hard-link behavior is a significant behavioral change (cache keying, hard-link fallback, and pruning). There are unit tests for basic shadow copying, but nothing here asserts that the cache is populated and reused (or that we gracefully fall back when hard links are unsupported). Consider adding a Windows-only test that (1) resolves the same analyzer path through two resolvers with the same base directory and asserts the cache file exists and is hard-linked (e.g., link count > 1 / file index matches), and (2) exercises the cache-pruning path with a small max limit to ensure it doesn’t throw.
            // Optimization for antivirus scanning on Windows:
            // - Shadow copied files are hard-linked to/from a cache directory if possible.
            // - We continue to use per-session 'ShadowDirectory' for ease of implementing correct loading semantics and cleanup.
            // - Hard linking a file from the cache instead of copying it is empirically observed to reduce time spent running AV scans when loading assemblies.
            static void linkFromCacheOrFallbackToCopy(ShadowCopyAnalyzerPathResolver @this, string originalPath, string shadowCopyPath)

src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerPathResolver.cs:233

  • The FILETIME ordering key sign-extends dwHighDateTime (an int) when converting to long, which can mis-order cache entries and evict the wrong files. Cast to uint before widening so the comparison is based on the unsigned 64-bit FILETIME value.

This issue also appears in the following locations of the same file:

  • line 349
  • line 453
                                return (long)creationTime.dwHighDateTime << 32 | (uint)creationTime.dwLowDateTime;

src/Compilers/Core/Portable/FileSystem/FileUtilities.cs:13

  • These using directives appear to be unused in this file (System.Runtime.InteropServices, System.Runtime.Versioning, and Microsoft.Win32.SafeHandles). If warnings are treated as errors, this can break the build; otherwise it adds noise. Please remove them.
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.Win32.SafeHandles;

src/Compilers/Core/CodeAnalysisTest/ShadowCopyAnalyzerPathResolverTests.cs:10

  • System.Linq and System.Runtime.InteropServices are unused in this test file. Removing them avoids unnecessary warnings/noise.
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

optimize shadow copy from .nuget/packages

4 participants