Skip to content

Commit d285e43

Browse files
authored
Use new Interlocked.Exchange/CompareExchange overloads that support bool values (#4585)
1 parent e58a8f1 commit d285e43

File tree

10 files changed

+244
-38
lines changed

10 files changed

+244
-38
lines changed

.editorconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ indent_size = 2
4040
# Sort using and Import directives with System.* appearing first
4141
dotnet_sort_system_directives_first = true
4242

43+
# Keep using directives at the top of the file, outside of the namespace
44+
csharp_using_directive_placement = outside_namespace
45+
4346
# Avoid "this." and "Me." if not necessary
4447
dotnet_style_qualification_for_field = false : warning
4548
dotnet_style_qualification_for_property = false : warning

src/Sentry.EntityFramework/SentryDatabaseLogging.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1+
using Sentry.Internal;
2+
13
namespace Sentry.EntityFramework;
24

35
/// <summary>
46
/// Sentry Database Logger
57
/// </summary>
68
internal static class SentryDatabaseLogging
79
{
8-
private static int Init;
10+
private static InterlockedBoolean Init;
911

1012
internal static SentryCommandInterceptor? UseBreadcrumbs(
1113
IQueryLogger? queryLogger = null,
1214
bool initOnce = true,
1315
IDiagnosticLogger? diagnosticLogger = null)
1416
{
15-
if (initOnce && Interlocked.Exchange(ref Init, 1) != 0)
17+
if (initOnce && Init.Exchange(true))
1618
{
1719
diagnosticLogger?.LogWarning("{0}.{1} was already executed.",
1820
nameof(SentryDatabaseLogging), nameof(UseBreadcrumbs));

src/Sentry.Profiling/SampleProfilerSession.cs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,12 @@ private SampleProfilerSession(SentryStopwatch stopwatch, EventPipeSession sessio
5656

5757
public TraceLog TraceLog => EventSource.TraceLog;
5858

59-
// default is false, set 1 for true.
60-
private static int _throwOnNextStartupForTests = 0;
59+
private static InterlockedBoolean _throwOnNextStartupForTests = false;
6160

6261
internal static bool ThrowOnNextStartupForTests
6362
{
64-
get { return Interlocked.CompareExchange(ref _throwOnNextStartupForTests, 1, 1) == 1; }
65-
set
66-
{
67-
if (value)
68-
Interlocked.CompareExchange(ref _throwOnNextStartupForTests, 1, 0);
69-
else
70-
Interlocked.CompareExchange(ref _throwOnNextStartupForTests, 0, 1);
71-
}
63+
get { return _throwOnNextStartupForTests; }
64+
set { _throwOnNextStartupForTests.Exchange(value); }
7265
}
7366

7467
public static SampleProfilerSession StartNew(IDiagnosticLogger? logger = null)
@@ -77,7 +70,7 @@ public static SampleProfilerSession StartNew(IDiagnosticLogger? logger = null)
7770
{
7871
var client = new DiagnosticsClient(Environment.ProcessId);
7972

80-
if (Interlocked.CompareExchange(ref _throwOnNextStartupForTests, 0, 1) == 1)
73+
if (_throwOnNextStartupForTests.CompareExchange(false, true) == true)
8174
{
8275
throw new Exception("Test exception");
8376
}

src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,11 @@ namespace Sentry.Profiling;
66
internal class SamplingTransactionProfilerFactory : IDisposable, ITransactionProfilerFactory
77
{
88
// We only allow a single profile so let's keep track of the current status.
9-
internal int _inProgress = FALSE;
9+
internal InterlockedBoolean _inProgress = false;
1010

1111
// Whether the session startup took longer than the given timeout.
1212
internal bool StartupTimedOut { get; }
1313

14-
private const int TRUE = 1;
15-
private const int FALSE = 0;
16-
1714
// Stop profiling after the given number of milliseconds.
1815
private const int TIME_LIMIT_MS = 30_000;
1916

@@ -50,20 +47,20 @@ public SamplingTransactionProfilerFactory(SentryOptions options, TimeSpan startu
5047
public ITransactionProfiler? Start(ITransactionTracer _, CancellationToken cancellationToken)
5148
{
5249
// Start a profiler if one wasn't running yet.
53-
if (!_errorLogged && Interlocked.Exchange(ref _inProgress, TRUE) == FALSE)
50+
if (!_errorLogged && !_inProgress.Exchange(true))
5451
{
5552
if (!_sessionTask.IsCompleted)
5653
{
5754
_options.LogWarning("Cannot start a sampling profiler, the session hasn't started yet.");
58-
_inProgress = FALSE;
55+
_inProgress = false;
5956
return null;
6057
}
6158

6259
if (!_sessionTask.IsCompletedSuccessfully)
6360
{
6461
_options.LogWarning("Cannot start a sampling profiler because the session startup has failed. This is a permanent error and no future transactions will be sampled.");
6562
_errorLogged = true;
66-
_inProgress = FALSE;
63+
_inProgress = false;
6764
return null;
6865
}
6966

@@ -72,13 +69,13 @@ public SamplingTransactionProfilerFactory(SentryOptions options, TimeSpan startu
7269
{
7370
return new SamplingTransactionProfiler(_options, _sessionTask.Result, TIME_LIMIT_MS, cancellationToken)
7471
{
75-
OnFinish = () => _inProgress = FALSE
72+
OnFinish = () => _inProgress = false
7673
};
7774
}
7875
catch (Exception e)
7976
{
8077
_options.LogError(e, "Failed to start a profiler session.");
81-
_inProgress = FALSE;
78+
_inProgress = false;
8279
}
8380
}
8481
return null;

src/Sentry/Ben.BlockingDetector/DetectBlockingSynchronizationContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ internal sealed class DetectBlockingSynchronizationContext : SynchronizationCont
1010

1111
internal int _isSuppressed;
1212

13-
internal void Suppress() => Interlocked.Exchange(ref _isSuppressed, _isSuppressed + 1);
14-
internal void Restore() => Interlocked.Exchange(ref _isSuppressed, _isSuppressed - 1);
13+
internal void Suppress() => Interlocked.Increment(ref _isSuppressed);
14+
internal void Restore() => Interlocked.Decrement(ref _isSuppressed);
1515

1616
public DetectBlockingSynchronizationContext(IBlockingMonitor monitor)
1717
{

src/Sentry/Internal/Hub.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,16 @@ internal class Hub : IHub, IDisposable
2424
private readonly MemoryMonitor? _memoryMonitor;
2525
#endif
2626

27-
private int _isPersistedSessionRecovered;
27+
private InterlockedBoolean _isPersistedSessionRecovered;
2828

2929
// Internal for testability
3030
internal ConditionalWeakTable<Exception, ISpan> ExceptionToSpanMap { get; } = new();
3131

3232
internal IInternalScopeManager ScopeManager { get; }
3333

34-
private int _isEnabled = 1;
35-
public bool IsEnabled => _isEnabled == 1;
34+
private InterlockedBoolean _isEnabled = true;
35+
36+
public bool IsEnabled => _isEnabled;
3637

3738
internal SentryOptions Options => _options;
3839

@@ -356,7 +357,7 @@ public TransactionContext ContinueTrace(
356357
public void StartSession()
357358
{
358359
// Attempt to recover persisted session left over from previous run
359-
if (Interlocked.Exchange(ref _isPersistedSessionRecovered, 1) != 1)
360+
if (_isPersistedSessionRecovered.Exchange(true) != true)
360361
{
361362
try
362363
{
@@ -835,7 +836,7 @@ public void Dispose()
835836
{
836837
_options.LogInfo("Disposing the Hub.");
837838

838-
if (Interlocked.Exchange(ref _isEnabled, 0) != 1)
839+
if (!_isEnabled.Exchange(false))
839840
{
840841
return;
841842
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#if NET9_0_OR_GREATER
2+
using TBool = System.Boolean;
3+
#else
4+
using TBool = System.Int32;
5+
#endif
6+
7+
namespace Sentry.Internal;
8+
9+
internal struct InterlockedBoolean
10+
{
11+
private volatile TBool _value;
12+
13+
[Browsable(false)]
14+
internal TBool ValueForTests => _value;
15+
16+
#if NET9_0_OR_GREATER
17+
private const TBool True = true;
18+
private const TBool False = false;
19+
#else
20+
private const TBool True = 1;
21+
private const TBool False = 0;
22+
#endif
23+
24+
public InterlockedBoolean() { }
25+
26+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
27+
public InterlockedBoolean(bool value) { _value = value ? True : False; }
28+
29+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
30+
public static implicit operator bool(InterlockedBoolean @this) => (@this._value != False);
31+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
32+
public static implicit operator InterlockedBoolean(bool @this) => new InterlockedBoolean(@this);
33+
34+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
35+
public bool Exchange(bool newValue)
36+
{
37+
TBool localNewValue = newValue ? True : False;
38+
39+
TBool localReturnValue = Interlocked.Exchange(ref _value, localNewValue);
40+
41+
return (localReturnValue != False);
42+
}
43+
44+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
45+
public bool CompareExchange(bool value, bool comparand)
46+
{
47+
TBool localValue = value ? True : False;
48+
TBool localComparand = comparand ? True : False;
49+
50+
TBool localReturnValue = Interlocked.CompareExchange(ref _value, localValue, localComparand);
51+
52+
return (localReturnValue != False);
53+
}
54+
}

src/Sentry/Threading/ScopedCountdownLock.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Sentry.Internal;
2+
13
namespace Sentry.Threading;
24

35
/// <summary>
@@ -13,12 +15,13 @@ namespace Sentry.Threading;
1315
internal sealed class ScopedCountdownLock : IDisposable
1416
{
1517
private readonly CountdownEvent _event;
16-
private volatile int _isEngaged;
18+
19+
private InterlockedBoolean _isEngaged;
1720

1821
internal ScopedCountdownLock()
1922
{
2023
_event = new CountdownEvent(1);
21-
_isEngaged = 0;
24+
_isEngaged = false;
2225
}
2326

2427
/// <summary>
@@ -31,13 +34,13 @@ internal ScopedCountdownLock()
3134
/// Gets the number of remaining <see cref="CounterScope"/> required to exit in order to set/signal the event while a <see cref="LockScope"/> is active.
3235
/// When <see langword="0"/> and while a <see cref="LockScope"/> is active, no more <see cref="CounterScope"/> can be entered.
3336
/// </summary>
34-
internal int Count => _isEngaged == 1 ? _event.CurrentCount : _event.CurrentCount - 1;
37+
internal int Count => _isEngaged ? _event.CurrentCount : _event.CurrentCount - 1;
3538

3639
/// <summary>
3740
/// Returns <see langword="true"/> when a <see cref="LockScope"/> is active and the event can be set/signaled by <see cref="Count"/> reaching <see langword="0"/>.
3841
/// Returns <see langword="false"/> when the <see cref="Count"/> can only reach the initial count of <see langword="1"/> when no <see cref="CounterScope"/> is active any longer.
3942
/// </summary>
40-
internal bool IsEngaged => _isEngaged == 1;
43+
internal bool IsEngaged => _isEngaged;
4144

4245
/// <summary>
4346
/// No <see cref="CounterScope"/> will be entered when the <see cref="Count"/> has reached <see langword="0"/>, or while the lock is engaged via an active <see cref="LockScope"/>.
@@ -79,7 +82,7 @@ private void ExitCounterScope()
7982
/// </remarks>
8083
internal LockScope TryEnterLockScope()
8184
{
82-
if (Interlocked.CompareExchange(ref _isEngaged, 1, 0) == 0)
85+
if (_isEngaged.CompareExchange(true, false) == false)
8386
{
8487
Debug.Assert(_event.CurrentCount >= 1);
8588
_ = _event.Signal(); // decrement the initial count of 1, so that the event can be set with the count reaching 0 when all entered 'CounterScope' instances have exited
@@ -94,7 +97,7 @@ private void ExitLockScope()
9497
Debug.Assert(_event.IsSet);
9598
_event.Reset(); // reset the signaled event to the initial count of 1, so that new 'CounterScope' instances can be entered again
9699

97-
if (Interlocked.CompareExchange(ref _isEngaged, 0, 1) != 1)
100+
if (_isEngaged.CompareExchange(false, true) != true)
98101
{
99102
Debug.Fail("The Lock should have not been disengaged without being engaged first.");
100103
}

src/Sentry/TransactionTracer.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ public class TransactionTracer : IBaseTracer, ITransactionTracer
1212
private readonly IHub _hub;
1313
private readonly SentryOptions? _options;
1414
private readonly Timer? _idleTimer;
15-
private long _cancelIdleTimeout;
1615
private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew();
1716

17+
private InterlockedBoolean _cancelIdleTimeout;
18+
1819
private readonly Instrumenter _instrumenter = Instrumenter.Sentry;
1920

2021
bool IBaseTracer.IsOtelInstrumenter => _instrumenter == Instrumenter.OpenTelemetry;
@@ -247,7 +248,7 @@ internal TransactionTracer(IHub hub, ITransactionContext context, TimeSpan? idle
247248
// Set idle timer only if an idle timeout has been provided directly
248249
if (idleTimeout.HasValue)
249250
{
250-
_cancelIdleTimeout = 1; // Timer will be cancelled once, atomically setting this back to 0
251+
_cancelIdleTimeout = true; // Timer will be cancelled once, atomically setting this back to false
251252
_idleTimer = new Timer(state =>
252253
{
253254
if (state is not TransactionTracer transactionTracer)
@@ -362,7 +363,7 @@ public void Clear()
362363
public void Finish()
363364
{
364365
_options?.LogDebug("Attempting to finish Transaction {0}.", SpanId);
365-
if (Interlocked.Exchange(ref _cancelIdleTimeout, 0) == 1)
366+
if (_cancelIdleTimeout.Exchange(false) == true)
366367
{
367368
_options?.LogDebug("Disposing of idle timer for Transaction {0}.", SpanId);
368369
_idleTimer?.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);

0 commit comments

Comments
 (0)