[Debugger] Fix ConcurrentAdaptiveCache.GetOrAdd race that returned stale value#8584
Conversation
BenchmarksBenchmark execution time: 2026-05-07 15:38:15 Comparing candidate commit 0703ae0 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 27 metrics, 0 unstable metrics, 58 known flaky benchmarks, 29 flaky benchmarks without significant changes.
|
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8584) and master.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Metric | Master (Mean ± 95% CI) | Current (Mean ± 95% CI) | Change | Status |
|---|---|---|---|---|
| .NET Framework 4.8 - Baseline | ||||
| duration | 202.64 ± (203.29 - 204.97) ms | 212.94 ± (215.27 - 218.66) ms | +5.1% | ❌⬆️ |
| .NET Framework 4.8 - Bailout | ||||
| duration | 207.02 ± (207.21 - 208.46) ms | 217.38 ± (219.71 - 223.17) ms | +5.0% | ❌⬆️ |
| .NET Framework 4.8 - CallTarget+Inlining+NGEN | ||||
| duration | 1264.58 ± (1263.27 - 1269.26) ms | 1353.90 ± (1352.00 - 1362.09) ms | +7.1% | ❌⬆️ |
Full Metrics Comparison
FakeDbCommand
| Metric | Master (Mean ± 95% CI) | Current (Mean ± 95% CI) | Change | Status |
|---|---|---|---|---|
| .NET Framework 4.8 - Baseline | ||||
| duration | 74.77 ± (74.78 - 75.47) ms | 72.63 ± (72.59 - 72.91) ms | -2.9% | ✅ |
| .NET Framework 4.8 - Bailout | ||||
| duration | 79.43 ± (78.92 - 79.46) ms | 77.46 ± (77.32 - 77.68) ms | -2.5% | ✅ |
| .NET Framework 4.8 - CallTarget+Inlining+NGEN | ||||
| duration | 1125.90 ± (1125.54 - 1132.96) ms | 1138.11 ± (1136.96 - 1144.55) ms | +1.1% | ✅⬆️ |
| .NET Core 3.1 - Baseline | ||||
| process.internal_duration_ms | 22.69 ± (22.62 - 22.75) ms | 22.74 ± (22.68 - 22.79) ms | +0.2% | ✅⬆️ |
| process.time_to_main_ms | 87.16 ± (86.80 - 87.51) ms | 86.98 ± (86.68 - 87.29) ms | -0.2% | ✅ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 10.91 ± (10.90 - 10.91) MB | 10.94 ± (10.94 - 10.94) MB | +0.3% | ✅⬆️ |
| runtime.dotnet.threads.count | 12 ± (12 - 12) | 12 ± (12 - 12) | +0.0% | ✅ |
| .NET Core 3.1 - Bailout | ||||
| process.internal_duration_ms | 22.40 ± (22.36 - 22.44) ms | 22.84 ± (22.78 - 22.90) ms | +2.0% | ✅⬆️ |
| process.time_to_main_ms | 85.56 ± (85.35 - 85.76) ms | 90.12 ± (89.81 - 90.43) ms | +5.3% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 10.96 ± (10.95 - 10.96) MB | 10.97 ± (10.97 - 10.97) MB | +0.1% | ✅⬆️ |
| runtime.dotnet.threads.count | 13 ± (13 - 13) | 13 ± (13 - 13) | +0.0% | ✅ |
| .NET Core 3.1 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 204.40 ± (203.79 - 205.00) ms | 206.38 ± (205.73 - 207.03) ms | +1.0% | ✅⬆️ |
| process.time_to_main_ms | 566.93 ± (565.53 - 568.34) ms | 571.00 ± (569.40 - 572.60) ms | +0.7% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 49.80 ± (49.77 - 49.83) MB | 49.81 ± (49.78 - 49.83) MB | +0.0% | ✅⬆️ |
| runtime.dotnet.threads.count | 28 ± (28 - 28) | 28 ± (28 - 28) | +0.9% | ✅⬆️ |
| .NET 6 - Baseline | ||||
| process.internal_duration_ms | 21.53 ± (21.47 - 21.59) ms | 21.37 ± (21.32 - 21.43) ms | -0.7% | ✅ |
| process.time_to_main_ms | 75.75 ± (75.46 - 76.03) ms | 74.78 ± (74.51 - 75.05) ms | -1.3% | ✅ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 10.63 ± (10.62 - 10.63) MB | 10.64 ± (10.64 - 10.64) MB | +0.1% | ✅⬆️ |
| runtime.dotnet.threads.count | 10 ± (10 - 10) | 10 ± (10 - 10) | +0.0% | ✅ |
| .NET 6 - Bailout | ||||
| process.internal_duration_ms | 21.10 ± (21.06 - 21.13) ms | 21.55 ± (21.49 - 21.61) ms | +2.2% | ✅⬆️ |
| process.time_to_main_ms | 74.75 ± (74.58 - 74.93) ms | 77.47 ± (77.19 - 77.76) ms | +3.6% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 10.74 ± (10.74 - 10.74) MB | 10.75 ± (10.75 - 10.75) MB | +0.1% | ✅⬆️ |
| runtime.dotnet.threads.count | 11 ± (11 - 11) | 11 ± (11 - 11) | +0.0% | ✅ |
| .NET 6 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 353.37 ± (351.28 - 355.46) ms | 354.03 ± (351.80 - 356.25) ms | +0.2% | ✅⬆️ |
| process.time_to_main_ms | 563.86 ± (562.44 - 565.28) ms | 566.78 ± (565.36 - 568.20) ms | +0.5% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 50.97 ± (50.94 - 50.99) MB | 51.20 ± (51.18 - 51.22) MB | +0.5% | ✅⬆️ |
| runtime.dotnet.threads.count | 28 ± (28 - 28) | 28 ± (28 - 28) | +0.1% | ✅⬆️ |
| .NET 8 - Baseline | ||||
| process.internal_duration_ms | 19.83 ± (19.77 - 19.89) ms | 19.45 ± (19.41 - 19.48) ms | -1.9% | ✅ |
| process.time_to_main_ms | 75.12 ± (74.84 - 75.40) ms | 72.97 ± (72.81 - 73.12) ms | -2.9% | ✅ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 7.68 ± (7.68 - 7.69) MB | 7.66 ± (7.66 - 7.67) MB | -0.3% | ✅ |
| runtime.dotnet.threads.count | 10 ± (10 - 10) | 10 ± (10 - 10) | +0.0% | ✅ |
| .NET 8 - Bailout | ||||
| process.internal_duration_ms | 19.71 ± (19.65 - 19.77) ms | 19.80 ± (19.74 - 19.86) ms | +0.5% | ✅⬆️ |
| process.time_to_main_ms | 76.07 ± (75.79 - 76.34) ms | 76.26 ± (75.98 - 76.55) ms | +0.3% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 7.72 ± (7.72 - 7.73) MB | 7.72 ± (7.71 - 7.72) MB | -0.1% | ✅ |
| runtime.dotnet.threads.count | 11 ± (11 - 11) | 11 ± (11 - 11) | +0.0% | ✅ |
| .NET 8 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 280.42 ± (278.17 - 282.67) ms | 281.80 ± (279.17 - 284.43) ms | +0.5% | ✅⬆️ |
| process.time_to_main_ms | 521.49 ± (520.14 - 522.84) ms | 524.96 ± (523.68 - 526.23) ms | +0.7% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 37.86 ± (37.83 - 37.90) MB | 37.86 ± (37.83 - 37.89) MB | -0.0% | ✅ |
| runtime.dotnet.threads.count | 27 ± (27 - 27) | 27 ± (27 - 27) | +0.1% | ✅⬆️ |
HttpMessageHandler
| Metric | Master (Mean ± 95% CI) | Current (Mean ± 95% CI) | Change | Status |
|---|---|---|---|---|
| .NET Framework 4.8 - Baseline | ||||
| duration | 202.64 ± (203.29 - 204.97) ms | 212.94 ± (215.27 - 218.66) ms | +5.1% | ❌⬆️ |
| .NET Framework 4.8 - Bailout | ||||
| duration | 207.02 ± (207.21 - 208.46) ms | 217.38 ± (219.71 - 223.17) ms | +5.0% | ❌⬆️ |
| .NET Framework 4.8 - CallTarget+Inlining+NGEN | ||||
| duration | 1264.58 ± (1263.27 - 1269.26) ms | 1353.90 ± (1352.00 - 1362.09) ms | +7.1% | ❌⬆️ |
| .NET Core 3.1 - Baseline | ||||
| process.internal_duration_ms | 200.22 ± (199.53 - 200.92) ms | 211.57 ± (209.70 - 213.44) ms | +5.7% | ✅⬆️ |
| process.time_to_main_ms | 87.05 ± (86.71 - 87.39) ms | 94.51 ± (93.52 - 95.51) ms | +8.6% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 3 ± (3 - 3) | 3 ± (3 - 3) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 15.96 ± (15.94 - 15.97) MB | 15.84 ± (15.82 - 15.86) MB | -0.7% | ✅ |
| runtime.dotnet.threads.count | 20 ± (20 - 20) | 20 ± (20 - 20) | +0.9% | ✅⬆️ |
| .NET Core 3.1 - Bailout | ||||
| process.internal_duration_ms | 200.40 ± (199.56 - 201.25) ms | 214.68 ± (212.85 - 216.52) ms | +7.1% | ✅⬆️ |
| process.time_to_main_ms | 88.64 ± (88.27 - 89.02) ms | 95.67 ± (94.80 - 96.53) ms | +7.9% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 3 ± (3 - 3) | 3 ± (3 - 3) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 15.98 ± (15.96 - 15.99) MB | 15.86 ± (15.85 - 15.88) MB | -0.7% | ✅ |
| runtime.dotnet.threads.count | 21 ± (21 - 21) | 21 ± (21 - 21) | +0.6% | ✅⬆️ |
| .NET Core 3.1 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 386.01 ± (384.62 - 387.41) ms | 403.65 ± (400.52 - 406.77) ms | +4.6% | ✅⬆️ |
| process.time_to_main_ms | 583.59 ± (581.82 - 585.36) ms | 616.51 ± (613.28 - 619.74) ms | +5.6% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 3 ± (3 - 3) | 3 ± (3 - 3) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 58.59 ± (58.42 - 58.75) MB | 60.03 ± (59.91 - 60.15) MB | +2.5% | ✅⬆️ |
| runtime.dotnet.threads.count | 30 ± (30 - 30) | 30 ± (30 - 30) | +0.9% | ✅⬆️ |
| .NET 6 - Baseline | ||||
| process.internal_duration_ms | 204.83 ± (204.19 - 205.46) ms | 220.52 ± (218.53 - 222.50) ms | +7.7% | ✅⬆️ |
| process.time_to_main_ms | 75.41 ± (75.12 - 75.71) ms | 81.73 ± (80.96 - 82.50) ms | +8.4% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 16.24 ± (16.22 - 16.26) MB | 16.19 ± (16.16 - 16.21) MB | -0.3% | ✅ |
| runtime.dotnet.threads.count | 19 ± (19 - 19) | 20 ± (19 - 20) | +0.9% | ✅⬆️ |
| .NET 6 - Bailout | ||||
| process.internal_duration_ms | 205.14 ± (204.46 - 205.82) ms | 218.24 ± (216.48 - 220.00) ms | +6.4% | ✅⬆️ |
| process.time_to_main_ms | 76.82 ± (76.51 - 77.12) ms | 81.62 ± (80.94 - 82.30) ms | +6.3% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 16.29 ± (16.27 - 16.31) MB | 16.25 ± (16.23 - 16.27) MB | -0.3% | ✅ |
| runtime.dotnet.threads.count | 20 ± (20 - 20) | 21 ± (20 - 21) | +1.2% | ✅⬆️ |
| .NET 6 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 567.35 ± (564.70 - 569.99) ms | 581.07 ± (575.38 - 586.77) ms | +2.4% | ✅⬆️ |
| process.time_to_main_ms | 570.70 ± (569.50 - 571.90) ms | 598.14 ± (595.67 - 600.62) ms | +4.8% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 61.94 ± (61.90 - 61.97) MB | 62.01 ± (61.96 - 62.07) MB | +0.1% | ✅⬆️ |
| runtime.dotnet.threads.count | 31 ± (31 - 31) | 31 ± (31 - 31) | +0.4% | ✅⬆️ |
| .NET 8 - Baseline | ||||
| process.internal_duration_ms | 202.87 ± (202.26 - 203.49) ms | 213.13 ± (211.49 - 214.77) ms | +5.1% | ✅⬆️ |
| process.time_to_main_ms | 74.81 ± (74.54 - 75.07) ms | 78.83 ± (78.13 - 79.52) ms | +5.4% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 11.64 ± (11.63 - 11.66) MB | 11.51 ± (11.49 - 11.53) MB | -1.2% | ✅ |
| runtime.dotnet.threads.count | 19 ± (19 - 19) | 19 ± (19 - 19) | +0.7% | ✅⬆️ |
| .NET 8 - Bailout | ||||
| process.internal_duration_ms | 202.43 ± (201.72 - 203.14) ms | 211.33 ± (209.81 - 212.85) ms | +4.4% | ✅⬆️ |
| process.time_to_main_ms | 75.69 ± (75.44 - 75.95) ms | 79.34 ± (78.71 - 79.98) ms | +4.8% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 11.63 ± (11.62 - 11.65) MB | 11.60 ± (11.58 - 11.62) MB | -0.3% | ✅ |
| runtime.dotnet.threads.count | 20 ± (20 - 20) | 20 ± (20 - 20) | +0.8% | ✅⬆️ |
| .NET 8 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 495.65 ± (491.23 - 500.07) ms | 556.97 ± (548.97 - 564.97) ms | +12.4% | ✅⬆️ |
| process.time_to_main_ms | 531.02 ± (529.46 - 532.59) ms | 557.47 ± (555.19 - 559.74) ms | +5.0% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 51.66 ± (51.61 - 51.70) MB | 51.91 ± (51.83 - 51.99) MB | +0.5% | ✅⬆️ |
| runtime.dotnet.threads.count | 30 ± (30 - 30) | 30 ± (30 - 30) | +0.5% | ✅⬆️ |
Comparison explanation
Execution-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:
- Welch test with statistical test for significance of 5%
- Only results indicating a difference greater than 5% and 5 ms are considered.
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 charts
FakeDbCommand (.NET Framework 4.8)
gantt
title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8584) - mean (73ms) : 70, 75
master - mean (75ms) : 70, 80
section Bailout
This PR (8584) - mean (78ms) : 75, 80
master - mean (79ms) : 75, 83
section CallTarget+Inlining+NGEN
This PR (8584) - mean (1,141ms) : 1084, 1197
master - mean (1,129ms) : 1075, 1183
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 (8584) - mean (117ms) : 111, 123
master - mean (117ms) : 110, 124
section Bailout
This PR (8584) - mean (121ms) : 115, 126
master - mean (115ms) : 112, 118
section CallTarget+Inlining+NGEN
This PR (8584) - mean (815ms) : 785, 846
master - mean (808ms) : 777, 839
FakeDbCommand (.NET 6)
gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8584) - mean (103ms) : 97, 108
master - mean (104ms) : 99, 109
section Bailout
This PR (8584) - mean (106ms) : 101, 111
master - mean (102ms) : 98, 106
section CallTarget+Inlining+NGEN
This PR (8584) - mean (954ms) : 911, 996
master - mean (951ms) : 910, 991
FakeDbCommand (.NET 8)
gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8584) - mean (100ms) : 97, 104
master - mean (103ms) : 97, 109
section Bailout
This PR (8584) - mean (104ms) : 99, 110
master - mean (104ms) : 99, 109
section CallTarget+Inlining+NGEN
This PR (8584) - mean (840ms) : 785, 896
master - mean (834ms) : 791, 876
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 (8584) - mean (217ms) : 192, 242
master - mean (204ms) : 192, 217
section Bailout
This PR (8584) - mean (221ms) : crit, 196, 247
master - mean (208ms) : 199, 217
section CallTarget+Inlining+NGEN
This PR (8584) - mean (1,357ms) : crit, 1284, 1431
master - mean (1,266ms) : 1223, 1309
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 (8584) - mean (324ms) : 271, 377
master - mean (298ms) : 282, 315
section Bailout
This PR (8584) - mean (323ms) : crit, 280, 366
master - mean (299ms) : 283, 315
section CallTarget+Inlining+NGEN
This PR (8584) - mean (1,080ms) : crit, 1029, 1131
master - mean (1,010ms) : 987, 1033
HttpMessageHandler (.NET 6)
gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8584) - mean (317ms) : 272, 362
master - mean (290ms) : 276, 304
section Bailout
This PR (8584) - mean (317ms) : crit, 271, 363
master - mean (292ms) : 277, 307
section CallTarget+Inlining+NGEN
This PR (8584) - mean (1,224ms) : 1141, 1308
master - mean (1,171ms) : 1132, 1210
HttpMessageHandler (.NET 8)
gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8584) - mean (309ms) : 266, 351
master - mean (289ms) : 274, 305
section Bailout
This PR (8584) - mean (304ms) : 272, 337
master - mean (289ms) : 274, 305
section CallTarget+Inlining+NGEN
This PR (8584) - mean (1,152ms) : crit, 1035, 1269
master - mean (1,065ms) : 988, 1141
Summary of changes
ConcurrentAdaptiveCache.GetOrAddreturning a stale local value (default/null) when another thread populated the entry between the initialTryGetmiss and the write-lock acquisition.LastAccessed, increment hit count, notify the eviction policy, and return the cached value.ConcurrentAdaptiveCacheTeststhat exercises the double-check branch via a custom comparer.Reason for change
ConcurrentAdaptiveCache.GetOrAddcould observedefault(T)(ornullfor reference/nullable values) for a key that was already populated, causing incorrect cached results in debugger redaction, span code origin, and other consumers of this cache.Implementation details
ConcurrentAdaptiveCache.GetOrAdd, when the inner_cache.TryGetValue(key, out var item)succeeds under the write lock, returnitem.Valueand callitem.UpdateAccess(...),Interlocked.Increment(ref _hits), and_evictionPolicy.Access(key).TryGethit path so cache metadata (last accessed time, access count, eviction policy ordering, hit count) is consistent regardless of which path produced the hit.Add/AddOrUpdate.Test coverage
ConcurrentAdaptiveCacheTests