From d7b5a3fa20ba51ad78dda8b291216207933359f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Fri, 17 Jul 2026 10:54:00 +0200 Subject: [PATCH] Complete graph submissions on critical exceptions Route critical graph scheduler exceptions through the build manager's thread-exception path so submissions complete and the fatal error is surfaced instead of hanging. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 62a2178b-372e-479c-a717-ed6e7af95980 --- .../ProjectCache/ProjectCacheTests.cs | 35 +++++++++++++++++++ .../BackEnd/BuildManager/BuildManager.cs | 20 ++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs b/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs index 74e5a5bf932..047918b5c92 100644 --- a/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs +++ b/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs @@ -1342,6 +1342,41 @@ public void EngineShouldHandleExceptionsFromCachePluginViaGraphBuild(ErrorLocati } } + [Fact] + public async Task CriticalExceptionFromGraphBuildCompletesSubmission() + { + const string exceptionMessage = "Critical exception from graph build"; + + var project = _env.CreateFile( + "1.proj", + """ + + + + """); + var graph = new ProjectGraph(project.Path); + var cache = new DelegatingMockCache( + (_, _, _) => throw new InternalErrorException(exceptionMessage)); + var buildParameters = new BuildParameters + { + ProjectCacheDescriptor = ProjectCacheDescriptor.FromInstance(cache), + ShutdownInProcNodeOnBuildFinish = true + }; + var requestData = new GraphBuildRequestData(graph, ["Build"]); + + Task buildTask = Task.Run( + () => + { + using var buildManager = new BuildManager(); + return Should.Throw(() => buildManager.Build(buildParameters, requestData)); + }); + + Task completedTask = await Task.WhenAny(buildTask, Task.Delay(TimeSpan.FromSeconds(10))); + + completedTask.ShouldBeSameAs(buildTask, "Graph builds should complete after a critical exception."); + (await buildTask).Message.ShouldContain(exceptionMessage); + } + [Fact] public void EndBuildShouldGetCalledOnceWhenItThrowsExceptionsFromGraphBuilds() { diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 4b69ec5a6e2..7753a5fafd2 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -1772,9 +1772,9 @@ private void ExecuteSubmission(GraphBuildSubmission submission) { ExecuteGraphBuildScheduler(submission); } - catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex)) + catch (Exception ex) { - HandleSubmissionException(submission, ex); + HandleGraphSubmissionException(submission, ex); } }, _executionCancellationTokenSource!.Token, @@ -1783,13 +1783,25 @@ private void ExecuteSubmission(GraphBuildSubmission submission) } } // The handling of submission exception needs to be done outside of the lock - catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex)) + catch (Exception ex) { - HandleSubmissionException(submission, ex); + HandleGraphSubmissionException(submission, ex); throw; } } + private void HandleGraphSubmissionException(GraphBuildSubmission submission, Exception ex) + { + if (ExceptionHandling.IsCriticalException(ex)) + { + OnThreadException(ex); + } + else + { + HandleSubmissionException(submission, ex); + } + } + /// /// Creates the traversal and metaproject instances necessary to represent the solution and populates new configurations with them. ///