Fix ServiceStack not calling EndRequest method#8466
Conversation
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8466) 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 (8466) - mean (74ms) : 70, 79
master - mean (74ms) : 70, 79
section Bailout
This PR (8466) - mean (77ms) : 75, 79
master - mean (78ms) : 74, 81
section CallTarget+Inlining+NGEN
This PR (8466) - mean (1,086ms) : 1021, 1151
master - mean (1,074ms) : 1032, 1116
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 (8466) - mean (115ms) : 110, 121
master - mean (116ms) : 109, 122
section Bailout
This PR (8466) - mean (115ms) : 113, 118
master - mean (115ms) : 111, 119
section CallTarget+Inlining+NGEN
This PR (8466) - mean (801ms) : 775, 826
master - mean (793ms) : 771, 814
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8466) - mean (102ms) : 96, 109
master - mean (103ms) : 97, 109
section Bailout
This PR (8466) - mean (102ms) : 99, 105
master - mean (102ms) : 97, 106
section CallTarget+Inlining+NGEN
This PR (8466) - mean (945ms) : 906, 984
master - mean (938ms) : 906, 970
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8466) - mean (103ms) : 97, 109
master - mean (99ms) : 95, 104
section Bailout
This PR (8466) - mean (103ms) : 98, 109
master - mean (102ms) : 97, 107
section CallTarget+Inlining+NGEN
This PR (8466) - mean (829ms) : 789, 868
master - mean (825ms) : 775, 875
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 (8466) - mean (211ms) : 204, 217
master - mean (206ms) : 202, 210
section Bailout
This PR (8466) - mean (214ms) : 210, 217
master - mean (210ms) : 205, 214
section CallTarget+Inlining+NGEN
This PR (8466) - mean (1,239ms) : 1196, 1282
master - mean (1,206ms) : 1168, 1243
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 (8466) - mean (305ms) : 297, 313
master - mean (299ms) : 293, 305
section Bailout
This PR (8466) - mean (307ms) : 300, 314
master - mean (300ms) : 295, 304
section CallTarget+Inlining+NGEN
This PR (8466) - mean (1,029ms) : 1002, 1055
master - mean (997ms) : 962, 1031
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8466) - mean (299ms) : 292, 307
master - mean (294ms) : 286, 302
section Bailout
This PR (8466) - mean (299ms) : 292, 306
master - mean (293ms) : 288, 298
section CallTarget+Inlining+NGEN
This PR (8466) - mean (1,181ms) : 1154, 1208
master - mean (1,176ms) : 1138, 1213
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8466) - mean (301ms) : 296, 306
master - mean (293ms) : 285, 300
section Bailout
This PR (8466) - mean (302ms) : 297, 306
master - mean (293ms) : 287, 298
section CallTarget+Inlining+NGEN
This PR (8466) - mean (1,095ms) : 989, 1201
master - mean (1,064ms) : 997, 1131
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
andrewlock
left a comment
There was a problem hiding this comment.
Great, thanks! I think an integration test would be nice but I'm not sure it's entirely necessary if you've manually verified this solves the issue, given this all makes sense and is a pretty small change 🙂
|
|
||
| if (sender is HttpApplication app && | ||
| app.Context.Items[_httpContextScopeKey] is Scope scope) | ||
| app.Context.Items[_httpContextScopeKey] is ScopeContainer container) |
There was a problem hiding this comment.
I was initially concerned that there's a subtle change in behaviour here, in that previously scope is guaranteed non-null, whereas now it's technically not guaranteed to be non null. However, given that (currently) when we call new ScopeContainer(scope, proxyScope), the scope is guaranteed non-null, I think this isn't an issue 🙂
BenchmarksBenchmark execution time: 2026-04-16 16:58:38 Comparing candidate commit 16b8076 in PR branch Found 0 performance improvements and 1 performance regressions! Performance is the same for 26 metrics, 0 unstable metrics, 87 known flaky benchmarks.
|
| /// <summary> | ||
| /// This key is only present when inferred proxy spans are enabled AND necessary proxy headers were present. | ||
| /// </summary> | ||
| /// <see cref="ConfigurationKeys.FeatureFlags.InferredProxySpansEnabled"/> |
There was a problem hiding this comment.
I guess my only nitpick is that it may be beneficial to retain this specific comment maybe below, but not a big deal IMO
Summary of changes
Wrap scopes stored in
HttpContext.Itemsin a non-disposableScopeContainerto prevent premature disposal by third-party frameworks.Reason for change
Fixes APMS-19184. ServiceStack's
ServiceStackHost.OnEndRequestruns before IIS'sEndRequestpipeline event and iterates through allHttpContext.Itemsvalues, disposing anything that implementsIDisposable— including ourScope. This causedSpan.Finish()to run beforeTracingHttpModule.OnEndRequestcould set the resource name and status code, resulting in spans withresource: aspnet.requestand nohttp.status_code.Implementation details
ScopeContainernested class that holds both the request scope and the optional inferred proxy scope. It does not implementIDisposable, so ServiceStack'sRelease()(which doesinstance as IDisposable) skips it.HttpContext.Itemsentries (_httpContextScopeKeyand_httpContextProxyScopeKey) into a singleScopeContainerentry.OnEndRequestandOnErrorto unwrap from the container.Test coverage
Reproduced locally with a ServiceStack 5.4 sample application on IIS Express. The test confirms that
aspnet.requestspans now get proper resource names and status codes.Other details
This also protects against any other framework that disposes
IDisposableobjects fromHttpContext.Itemsduring request cleanup.