Skip to content

Commit ddd2f31

Browse files
Fix a threading issue in ExceptionDiagnoser #1736 (#2182)
When a Benchmark spawns multiple threads that throw Exceptions, FirstChanceException event handler is called concurrently. It must count Exceptions in a thread-safe way. Note: do not use lock keyword because that will influence Monitor lock contention count of ThreadingDiagnoser.
1 parent ee81250 commit ddd2f31

File tree

2 files changed

+24
-3
lines changed

2 files changed

+24
-3
lines changed

src/BenchmarkDotNet/Engines/ExceptionsStats.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
using System;
22
using System.Runtime.ExceptionServices;
3+
using System.Threading;
34

45
namespace BenchmarkDotNet.Engines
56
{
67
internal class ExceptionsStats
78
{
89
internal const string ResultsLinePrefix = "// Exceptions: ";
910

10-
internal ulong ExceptionsCount { get; private set; }
11+
private long exceptionsCount;
12+
13+
internal long ExceptionsCount { get => exceptionsCount; }
1114

1215
public void StartListening()
1316
{
@@ -21,7 +24,7 @@ public void Stop()
2124

2225
private void OnFirstChanceException(object sender, FirstChanceExceptionEventArgs e)
2326
{
24-
ExceptionsCount++;
27+
Interlocked.Increment(ref exceptionsCount);
2528
}
2629

2730
public static string ToOutputLine(double exceptionCount) => $"{ResultsLinePrefix} {exceptionCount}";

tests/BenchmarkDotNet.IntegrationTests/ExceptionDiagnoserTests.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System;
99
using System.Collections.Generic;
1010
using System.Linq;
11+
using System.Threading.Tasks;
1112
using Xunit;
1213

1314
namespace BenchmarkDotNet.IntegrationTests
@@ -24,7 +25,8 @@ public void ExceptionCountIsAccurate()
2425
AssertStats(summary, new Dictionary<string, (string metricName, double expectedValue)>
2526
{
2627
{ nameof(ExceptionCount.DoNothing), ("ExceptionFrequency", 0.0) },
27-
{ nameof(ExceptionCount.ThrowOneException), ("ExceptionFrequency", 1.0) }
28+
{ nameof(ExceptionCount.ThrowOneException), ("ExceptionFrequency", 1.0) },
29+
{ nameof(ExceptionCount.ThrowFromMultipleThreads), ("ExceptionFrequency", 100.0) }
2830
});
2931
}
3032

@@ -42,6 +44,22 @@ public void ThrowOneException()
4244

4345
[Benchmark]
4446
public void DoNothing() { }
47+
48+
[Benchmark]
49+
public async Task ThrowFromMultipleThreads()
50+
{
51+
void ThrowMultipleExceptions()
52+
{
53+
for (int i = 0; i < 10; i++)
54+
{
55+
ThrowOneException();
56+
}
57+
}
58+
59+
var tasks = Enumerable.Range(1, 10).Select(
60+
i => Task.Run(ThrowMultipleExceptions));
61+
await Task.WhenAll(tasks);
62+
}
4563
}
4664

4765
private IConfig CreateConfig()

0 commit comments

Comments
 (0)