enable out of process execution of inline tasks#11948
enable out of process execution of inline tasks#11948JanProvaznik merged 61 commits intodotnet:mainfrom
Conversation
AR-May
left a comment
There was a problem hiding this comment.
There is possibly another alternative we may mention, that is sending the code to the task host and compiling there, but I think it is worse than doing it in the main process, even if task hosts would be persistent.
The changes imo have to be behind a feature flag - as indeed they will affect performance in order to allow compatibility with multithreaded mode.
As for user defined custom task factories, I wonder how used the feature is. We should add any necessary API changes (if they are needed) so they customers can re-write them to work with multithreaded mode. But by default, it seems they would not work with task hosts, ie would not be compatible with multithreaded mode if their tasks are not thread safe.
|
"send the information about the task to the host process and do the older process there" is more what I was expecting--can you elaborate a bit more on the cons of that approach? |
|
|
For the benefits of suggested approach, I will add that even with sidecar taskhosts, compilation of the task assembly in the worst case would be needed on each of those. And compilation overhead is quite big - around 2 seconds according to @JanProvaznik's data. Having it compiled and then location cached in the main node minimizes that overhead. We are also nicely reusing current infrastructure - that is the minimal additional complexity mentioned by @JanProvaznik The drawback is obvious - instead of creating a temp file for maximum mere seconds we create it for the duration of the build. And if the process is killed, the temp files would not be cleaned up - that's the main risk. |
|
I'm not sure how severe the risk is, how often a nongraceful process exit happens and what would be the expectation of disk overuse when you're building on a dev machine X times per day... a maybe silly idea: probabilistic deep cleanup
I am very open to suggestions how to manage these files another way. data: |
|
new disposal idea thx @AR-May @rainersigwald : I have to test it if the OS API really works this way and the extent to which this works on *nix in the Factory initialization open a file handle for the compiled dll with a flag |
not so simple, because how we load in taskhost nodes now we use Assembly.Load and the DeleteOnClose requires that the file is opened with the FILE_SHARE_DELETE which Assembly.Load does not expose api for doing that. Possible workaround for that is detecting the "temporary dll file" situation and loading first to memory by opening with the flag and then loading from memory. Which needs more logic to pass the path... because the location of the in memory assembly is empty again. |
deb2b7a to
49f0950
Compare
49f0950 to
efc743e
Compare
|
complication: the references of the inline tasks have to be somehow loaded on the taskhost and they could be arbitrary dlls, this resolve reference logic is nontrivial LoC in each factory |
|
So the inline tasks can have references. I glossed over that initially thinking the resolution info would be in the dll so calling Assembly.Load on the dll with the task would be enough. Apparently it does not work that way, (also CodeTaskFactory and RoslynCodeTaskFactory do it differently). brainstorming how to proceed: I am inclined to start with 1. Though I am afraid of what more roadblocks I'll find. |
Some tests were failing when run from Visual Studio because they were running in a .NET 9 process launched as `TestHost.exe`, not in a `dotnet.exe` host. That caused TaskHost launching to try to run `TestHost.exe MSBuild.dll` which failed, as TestHost isn't a general-purpose launcher. Add another fallback to `GetCurrentHost` to fall back to the current shared framework's parent `dotnet.exe`, if we can find it.
…uild into kickroslyntasks
|
ready for another review after major changes @AR-May |
AR-May
left a comment
There was a problem hiding this comment.
Overall looks good to me. We will need to test the behavior with exp insertion.
Better to create two, one of them should additionally set forcing the tasks out of proc by default - let's see if we notice any errors in DDRITS.
|
I'm creating 2 exp insertions: |
in the last iteration the changes have been discussed and adjusted
Out-of-Process Execution of Inline Tasks
Summary
Enables inline MSBuild tasks (
RoslynCodeTaskFactory,CodeTaskFactory,XamlTaskFactory) to execute in TaskHost for multithreaded build support.Issue: #11914 | PR: #11948
The Problem
Inline tasks compiled to in-memory assemblies can't run out-of-process because:
Assembly.Locationis empty)The Solution
Compile inline tasks to temporary disk files when
MSBUILDFORCEINLINETASKFACTORIESOUTOFPROC=1:%TEMP%/<user>/InlineTaskTempDllSubPath/pid_<PID>/<guid>_inline_task.dll.loadmanifestfile with reference assembly pathsTaskHostTaskKey Implementation Details
IOutOfProcTaskFactory Interface
Marker interface in
src/Framework/IOutOfProcTaskFactory.csimplemented by all three built-in factories:Used by
TaskExecutionHostto distinguish built-in factories from custom ones (which fail with error).TaskFactoryUtilities Class
Shared utilities in
src/Shared/TaskFactoryUtilities.cs:GetTemporaryTaskAssemblyPath()- Creates process-specific temp pathLoadTaskAssembly(path)- Loads from bytes to avoid file lockingCreateLoadManifest(path, dirs)- Writes.loadmanifestfile with reference directoriesRegisterAssemblyResolveHandlersFromManifest(path)- TaskHost reads manifest and registers resolversCleanCurrentProcessInlineTaskDirectory()- Called inBuildManager.EndBuild()Assembly Caching
Factories cache compiled assemblies by (task name + source code + references):
Where
CachedAssemblyEntrystores both theAssemblyand the file path. Cache validates file still exists before reuse.Execution Flow in TaskExecutionHost
When
Traits.Instance.ForceTaskFactoryOutOfProc == true:IntrinsicTaskFactory(always in-proc)IOutOfProcTaskFactoryor errorGetAssemblyPath()TaskHostTaskwhich routes to out-of-process executionLoad Manifest Format
Plain text file
<assembly>.dll.loadmanifestwith one directory per line for assembly resolution.Configuration
Set
MSBUILDFORCEINLINETASKFACTORIESOUTOFPROC=1to enable.Trait defined in
src/Framework/Traits.cs:Important Notes
Performance When Enabled
Adds overhead: disk I/O (~4KB/task), TaskHost process creation, duplicate assembly loading. Mitigated by assembly caching and sidecar TaskHost (when available). Typical compilation: ~2s first time, then cached.
Performance When Disabled
Zero impact - feature is opt-in, no code paths change when off.
Testing
All existing inline task tests converted to
[Theory]withbool forceOutOfProcparameter, running in both modes.Limitations
Custom TaskFactory: Not supported with out-of-process execution (build error). Workaround: disable multithreaded mode or convert to standard assembly-based task.
File Cleanup: If MSBuild crashes, temp files (~4KB/task) remain in
%TEMP%/<user>/InlineTaskTempDllSubPath/pid_<PID>/. User can safely delete parent directory.Related Issues
/mtflag