[Bug report] UBT environment variable XML fails with file access conflict when multiple UBT processes run

The behaviour of writing environment variable settings to a file that is the same for all processes is incorrect. Even if the code was changed to retry on failure, this would introduce a race condition by which two processes with different environment variables could result in one process accidentally using the settings of the other.

Instead, WriteEnvironmentXml should write to a memory stream, and inputFileLocations/temporaryInputFileLocations should be modified to accept both file paths and memory streams. This would ensure that processes from different Unreal Engine installations can’t interfere with each other when two UBT processes are running at the same time.

As an aside, the “GlobalSingleInstanceMutex.GetUniqueMutexForPath(“UnrealBuildTool_Mutex_XmlConfig”, FileReference.FromString(Assembly.GetExecutingAssembly().Location))” mutex that surrounds the UBT XML config read is also incorrect, since the assembly location is not part of the unique path for the environment XML path, and this mutex also doesn’t surround other calls to XmlConfig.ReadConfigFiles like the one inside PlatformExports (which is where the crash for the callstack above actually happens).

Steps to Reproduce

  • Set an environment variable prefixed with “UnrealBuildTool_”
  • Run multiple UBT processes at the same time
  • Observe a failure to read/write the “UnrealBuildTool.Env.BuildConfiguration.xml” file:

Unable to rename C:\WINDOWS\system32\config\systemprofile\AppData\Local\UnrealEngine\Intermediate\Build\UnrealBuildTool.Env.BuildConfiguration.xml to C:\WINDOWS\system32\config\systemprofile\AppData\Local\UnrealEngine\Intermediate\Build\UnrealBuildTool.Env.BuildConfiguration.xml.old

System.IO.IOException: The process cannot access the file 'C:\WINDOWS\system32\config\systemprofile\AppData\Local\UnrealEngine\Intermediate\Build\UnrealBuildTool.Env.BuildConfiguration.xml' because it is being used by another process.

at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)

at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable1 unixCreateMode)`

at System.IO.File.OpenHandle(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)

at System.IO.File.WriteToFile(String path, FileMode mode, String contents, Encoding encoding)

at EpicGames.Core.FileReference.WriteAllText(FileReference location, String contents, Encoding encoding) in D:\build\++UE5\Sync\Engine\Source\Programs\Shared\EpicGames.Core\FileReference.cs:line 709

at UnrealBuildTool.Utils.WriteFileIfChanged(FileItem FileItem, String Contents, StringComparison Comparison, ILogger Logger) in D:\build\++UE5\Sync\Engine\Source\Programs\UnrealBuildTool\System\Utils.cs:line 1905

at UnrealBuildTool.Utils.WriteFileIfChanged(FileReference Location, String Contents, StringComparison Comparison, ILogger Logger) in D:\build\++UE5\Sync\Engine\Source\Programs\UnrealBuildTool\System\Utils.cs:line 1705

at UnrealBuildTool.Utils.WriteFileIfChanged(FileReference Location, String Contents, ILogger Logger) in D:\build\++UE5\Sync\Engine\Source\Programs\UnrealBuildTool\System\Utils.cs:line 1693

at UnrealBuildTool.XmlConfig.WriteEnvironmentXml(FileReference location, ILogger logger) in D:\build\++UE5\Sync\Engine\Source\Programs\UnrealBuildTool\System\XmlConfig.cs:line 862

at UnrealBuildTool.XmlConfig.ReadConfigFiles(FileReference overrideCacheFile, DirectoryReference projectRootDirectory, ILogger logger) in D:\build\++UE5\Sync\Engine\Source\Programs\UnrealBuildTool\System\XmlConfig.cs:line 134

at UnrealBuildTool.PlatformExports.Initialize(String[] CommandLineArgs, ILogger Logger) in D:\build\++UE5\Sync\Engine\Source\Programs\UnrealBuildTool\System\PlatformExports.cs:line 155

at AutomationTool.Automation.ProcessAsync(ParsedCommandLine AutomationToolCommandLine, StartupTraceListener StartupListener, HashSet1 ScriptModuleAssemblies) in D:\build++UE5\Sync\Engine\Source\Programs\AutomationTool\AutomationUtils\Automation.cs:line 123`

Public jira for tracking will be here https://issues.unrealengine.com/issue/UE-355047, I’ll take a look soon