fix(dsm): use TaskScheduler.Default for ProcessQueueLoop to avoid deadlock#8767
Conversation
…dlock on custom schedulers When DataStreamsWriter.Initialize() is called from a thread whose TaskScheduler.Current is a non-default scheduler (e.g. MassTransit's LimitedConcurrencyLevelTaskScheduler), Task.Factory.StartNew inherits that scheduler. LongRunning is only honored by the built-in ThreadPoolTaskScheduler — custom schedulers ignore the hint and simply queue the task, so ProcessQueueLoop never gets its own OS thread and deadlocks against the scheduler's own concurrency limit. Passing TaskScheduler.Default explicitly bypasses TaskScheduler.Current and ensures ProcessQueueLoop always starts on a dedicated thread pool thread. Reproduces as: await IPublishEndpoint.Publish hangs after the first call on classic ASP.NET/IIS when using MassTransit 5.x + RabbitMQ with DSM enabled (APMS-19454). Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8767) and master. ✅ No regressions detected - check the details below Full Metrics ComparisonFakeDbCommand
HttpMessageHandler
Comparison explanationExecution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:
Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard. Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph). Duration chartsFakeDbCommand (.NET Framework 4.8)gantt
title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8767) - mean (75ms) : 71, 80
master - mean (75ms) : 71, 79
section Bailout
This PR (8767) - mean (79ms) : 75, 83
master - mean (78ms) : 74, 81
section CallTarget+Inlining+NGEN
This PR (8767) - mean (1,117ms) : 1068, 1167
master - mean (1,112ms) : 1059, 1164
FakeDbCommand (.NET Core 3.1)gantt
title Execution time (ms) FakeDbCommand (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8767) - mean (117ms) : 111, 123
master - mean (115ms) : 111, 120
section Bailout
This PR (8767) - mean (115ms) : 112, 118
master - mean (115ms) : 113, 118
section CallTarget+Inlining+NGEN
This PR (8767) - mean (790ms) : 767, 813
master - mean (794ms) : 768, 820
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8767) - mean (104ms) : 99, 110
master - mean (104ms) : 100, 108
section Bailout
This PR (8767) - mean (104ms) : 102, 106
master - mean (102ms) : 99, 105
section CallTarget+Inlining+NGEN
This PR (8767) - mean (953ms) : 911, 994
master - mean (954ms) : 905, 1003
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8767) - mean (101ms) : 96, 105
master - mean (101ms) : 97, 105
section Bailout
This PR (8767) - mean (104ms) : 98, 109
master - mean (103ms) : 99, 107
section CallTarget+Inlining+NGEN
This PR (8767) - mean (826ms) : 775, 877
master - mean (828ms) : 785, 871
HttpMessageHandler (.NET Framework 4.8)gantt
title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8767) - mean (199ms) : 193, 205
master - mean (200ms) : 195, 205
section Bailout
This PR (8767) - mean (203ms) : 197, 208
master - mean (204ms) : 198, 209
section CallTarget+Inlining+NGEN
This PR (8767) - mean (1,200ms) : 1153, 1247
master - mean (1,200ms) : 1161, 1240
HttpMessageHandler (.NET Core 3.1)gantt
title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8767) - mean (286ms) : 278, 293
master - mean (286ms) : 276, 296
section Bailout
This PR (8767) - mean (287ms) : 281, 292
master - mean (287ms) : 279, 295
section CallTarget+Inlining+NGEN
This PR (8767) - mean (959ms) : 937, 980
master - mean (962ms) : 944, 980
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8767) - mean (275ms) : 269, 281
master - mean (278ms) : 271, 285
section Bailout
This PR (8767) - mean (275ms) : 268, 283
master - mean (279ms) : 273, 285
section CallTarget+Inlining+NGEN
This PR (8767) - mean (1,149ms) : 1105, 1193
master - mean (1,156ms) : 1122, 1190
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8767) - mean (275ms) : 269, 281
master - mean (280ms) : 273, 287
section Bailout
This PR (8767) - mean (276ms) : 269, 283
master - mean (278ms) : 271, 285
section CallTarget+Inlining+NGEN
This PR (8767) - mean (1,035ms) : 990, 1080
master - mean (1,044ms) : 995, 1092
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BenchmarksBenchmark execution time: 2026-06-08 19:10:49 Comparing candidate commit 78524de in PR branch Found 0 performance improvements and 1 performance regressions! Performance is the same for 71 metrics, 0 unstable metrics, 60 known flaky benchmarks, 66 flaky benchmarks without significant changes.
|
andrewlock
left a comment
There was a problem hiding this comment.
Thanks, we should likely fix all the cases where we're using this overload, so I'll do a follow up fix
## Summary of changes Uses `TaskScheduler.Default` when calling `Task.Factory.StartNew` ## Reason for change When calling `Task.Factory.StartNew`, if you don't explicitly provide a `TaskScheduler`, it inherits `TaskScheduler.Current` from the calling thread. If there's a custom scheduler, this can cause problems (e.g. same as #8767). That PR fixed the issue for DataStreamsMonitoring, this PR fixes it everywhere else. ## Implementation details - Had 🤖 look for all the cases where we were missing this - _Technically_ there are some `ContinueWith` that should have it too, but in all of the _other_ cases not covered by this PR, those are simply logging on faulted, so it didn't seem worth the hassle - Updated the vendoring code to handle our changes to Statsdclient too - Note that `Task.Run` ignores `TaskScheduler.Current` so it's not a problem ## Test coverage Covered by existing tests, and not worth creating a regression for IMO
…dlock (#8767) ## Summary Fixes a deadlock where DSM is initialized with the ambient `TaskScheduler`, which may not honor`TaskCreationOptions.LongRunning` or have capacity to run the task. Fix by supplying the default task scheduler. The scenario to reproduce this was: Calling `await IPublishEndpoint.Publish<T>()` hangs on classic ASP.NET/IIS with MassTransit 5.x + RabbitMQ + DSM enabled. Disabling DSM or disabling RabbitMQ removed the deadlock. More detailed notes: - `DataStreamsWriter.Initialize()` calls `Task.Factory.StartNew(ProcessQueueLoop, TaskCreationOptions.LongRunning)` without specifying a `TaskScheduler`. - `Task.Factory.StartNew` inherits `TaskScheduler.Current` from the calling thread. When `Initialize()` is triggered from within a third-party task scheduler (i.e. MassTransit's `LimitedConcurrencyLevelTaskScheduler`), `ProcessQueueLoop` is posted to that scheduler instead of spawning a dedicated OS thread. If for example, the scheduler has zero concurrency, then `ProcessQueueLoop` takes it and blocks indefinitely. - Fix is to pass `TaskScheduler.Default` explicitly. Testing: - Manual. I am not sure if we have a good way of verifying this behavior in automated tests Co-authored-by: Claude Sonnet 4.6 <[email protected]>
## Summary of changes Uses `TaskScheduler.Default` when calling `Task.Factory.StartNew` ## Reason for change When calling `Task.Factory.StartNew`, if you don't explicitly provide a `TaskScheduler`, it inherits `TaskScheduler.Current` from the calling thread. If there's a custom scheduler, this can cause problems (e.g. same as #8767). That PR fixed the issue for DataStreamsMonitoring, this PR fixes it everywhere else. ## Implementation details - Had 🤖 look for all the cases where we were missing this - _Technically_ there are some `ContinueWith` that should have it too, but in all of the _other_ cases not covered by this PR, those are simply logging on faulted, so it didn't seem worth the hassle - Updated the vendoring code to handle our changes to Statsdclient too - Note that `Task.Run` ignores `TaskScheduler.Current` so it's not a problem ## Test coverage Covered by existing tests, and not worth creating a regression for IMO
Summary
Fixes a deadlock where DSM is initialized with the ambient
TaskScheduler, which may not honorTaskCreationOptions.LongRunningor have capacity to run the task. Fix by supplying the default task scheduler.The scenario to reproduce this was:
Calling
await IPublishEndpoint.Publish<T>()hangs on classic ASP.NET/IIS with MassTransit 5.x + RabbitMQ + DSM enabled. Disabling DSM or disabling RabbitMQ removed the deadlock.More detailed notes:
DataStreamsWriter.Initialize()callsTask.Factory.StartNew(ProcessQueueLoop, TaskCreationOptions.LongRunning)without specifying aTaskScheduler.Task.Factory.StartNewinheritsTaskScheduler.Currentfrom the calling thread. WhenInitialize()is triggered from within a third-party task scheduler (i.e. MassTransit'sLimitedConcurrencyLevelTaskScheduler),ProcessQueueLoopis posted to that scheduler instead of spawning a dedicated OS thread. If for example, the scheduler has zero concurrency, thenProcessQueueLooptakes it and blocks indefinitely.TaskScheduler.Defaultexplicitly.Testing: