@@ -248,21 +248,28 @@ public void RequestCores_WorksWhenAutoEjectedInMultiThreadedMode()
248248 }
249249
250250 /// <summary>
251- /// Verifies that RequestCores when callbacks are disabled logs error MSB5022 and returns 0.
251+ /// Regression test for https://github.com/dotnet/msbuild/issues/13333
252+ /// When callbacks are not supported (cross-version OOP TaskHost), RequestCores must
253+ /// throw NotImplementedException (not log a build error and return 0).
254+ /// Real callers (MonoAOTCompiler, EmccCompile, ILStrip, EmitBundleBase) catch this
255+ /// exception and fall back to their own parallelism estimate. The previous behavior
256+ /// of logging BuildErrorEventArgs caused the build to fail silently.
252257 /// </summary>
253258 [ Fact ]
254- public void RequestCores_LogsErrorWhenCallbacksNotSupported ( )
259+ public void RequestCores_ThrowsNotImplementedWhenCallbacksNotSupported ( )
255260 {
256261 using TestEnvironment env = TestEnvironment . Create ( _output ) ;
257262
258- // Explicitly do NOT set MSBUILDENABLETASKHOSTCALLBACKS
263+ // Explicitly do NOT set MSBUILDENABLETASKHOSTCALLBACKS — callbacks should be disabled.
264+ // Use RequestCoresWithFallbackTask which catches NotImplementedException like real callers do.
259265 string projectContents = $@ "
260266<Project>
261- <UsingTask TaskName=""{ nameof ( RequestCoresTask ) } "" AssemblyFile=""{ typeof ( RequestCoresTask ) . Assembly . Location } "" TaskFactory=""TaskHostFactory"" />
267+ <UsingTask TaskName=""{ nameof ( RequestCoresWithFallbackTask ) } "" AssemblyFile=""{ typeof ( RequestCoresWithFallbackTask ) . Assembly . Location } "" TaskFactory=""TaskHostFactory"" />
262268 <Target Name=""Test"">
263- <{ nameof ( RequestCoresTask ) } CoreCount=""2"">
264- <Output PropertyName=""Result"" TaskParameter=""GrantedCores"" />
265- </{ nameof ( RequestCoresTask ) } >
269+ <{ nameof ( RequestCoresWithFallbackTask ) } CoreCount=""4"">
270+ <Output PropertyName=""GrantedResult"" TaskParameter=""GrantedCores"" />
271+ <Output PropertyName=""FellBack"" TaskParameter=""UsedFallback"" />
272+ </{ nameof ( RequestCoresWithFallbackTask ) } >
266273 </Target>
267274</Project>" ;
268275
@@ -274,9 +281,60 @@ public void RequestCores_LogsErrorWhenCallbacksNotSupported()
274281 new BuildParameters { MaxNodeCount = 4 , EnableNodeReuse = false , Loggers = [ logger ] } ,
275282 new BuildRequestData ( projectInstance , targetsToBuild : [ "Test" ] ) ) ;
276283
277- // MSB5022 error should be logged — the callback was not forwarded
278- logger . ErrorCount . ShouldBeGreaterThan ( 0 ) ;
279- logger . FullLog . ShouldContain ( "MSB5022" ) ;
284+ // Build must succeed — the task catches NotImplementedException and falls back.
285+ buildResult . OverallResult . ShouldBe ( BuildResultCode . Success ) ;
286+
287+ // No errors should be logged — NotImplementedException is caught by the task, not by MSBuild.
288+ logger . ErrorCount . ShouldBe ( 0 ) ;
289+
290+ // The task should have used its fallback path (NotImplementedException was thrown).
291+ logger . FullLog . ShouldContain ( "RequestCores threw NotImplementedException, using fallback" ) ;
292+
293+ // GrantedCores should be the task's own fallback (CoreCount), not 0.
294+ logger . FullLog . ShouldContain ( "GrantedCores = 4" ) ;
295+ }
296+
297+ /// <summary>
298+ /// Regression test for https://github.com/dotnet/msbuild/issues/13333
299+ /// When callbacks are not supported, the full caller pattern (RequestCores with catch,
300+ /// then skip ReleaseCores) must work. This matches MonoAOTCompiler/EmccCompile/ILStrip:
301+ /// try { cores = be9.RequestCores(N); }
302+ /// catch (NotImplementedException) { be9 = null; }
303+ /// finally { be9?.ReleaseCores(cores); }
304+ /// ReleaseCores must NOT be called when the fallback fires (be9 is nulled).
305+ /// </summary>
306+ [ Fact ]
307+ public void RequestAndReleaseCores_FallbackSkipsReleaseWhenCallbacksNotSupported ( )
308+ {
309+ using TestEnvironment env = TestEnvironment . Create ( _output ) ;
310+
311+ // Explicitly do NOT set MSBUILDENABLETASKHOSTCALLBACKS — callbacks should be disabled
312+ string projectContents = $@ "
313+ <Project>
314+ <UsingTask TaskName=""{ nameof ( RequestCoresWithFallbackTask ) } "" AssemblyFile=""{ typeof ( RequestCoresWithFallbackTask ) . Assembly . Location } "" TaskFactory=""TaskHostFactory"" />
315+ <Target Name=""Test"">
316+ <{ nameof ( RequestCoresWithFallbackTask ) } CoreCount=""4"" ReleaseAfter=""true"">
317+ <Output PropertyName=""GrantedResult"" TaskParameter=""GrantedCores"" />
318+ <Output PropertyName=""FellBack"" TaskParameter=""UsedFallback"" />
319+ </{ nameof ( RequestCoresWithFallbackTask ) } >
320+ </Target>
321+ </Project>" ;
322+
323+ TransientTestProjectWithFiles project = env . CreateTestProjectWithFiles ( projectContents ) ;
324+ ProjectInstance projectInstance = new ( project . ProjectFile ) ;
325+
326+ var logger = new MockLogger ( _output ) ;
327+ BuildResult buildResult = BuildManager . DefaultBuildManager . Build (
328+ new BuildParameters { MaxNodeCount = 4 , EnableNodeReuse = false , Loggers = [ logger ] } ,
329+ new BuildRequestData ( projectInstance , targetsToBuild : [ "Test" ] ) ) ;
330+
331+ // Build must succeed.
332+ buildResult . OverallResult . ShouldBe ( BuildResultCode . Success ) ;
333+ logger . ErrorCount . ShouldBe ( 0 ) ;
334+
335+ // Fallback fired — ReleaseCores should have been skipped (be9 nulled in catch).
336+ logger . FullLog . ShouldContain ( "RequestCores threw NotImplementedException, using fallback" ) ;
337+ logger . FullLog . ShouldNotContain ( "ReleaseCores(" ) ;
280338 }
281339 }
282340}
0 commit comments