@@ -8,8 +8,6 @@ namespace StackExchange.Redis
88 /// </summary>
99 public static class RedisCancellationExtensions
1010 {
11- private static readonly AsyncLocal < CancellationContext ? > _context = new ( ) ;
12-
1311 /// <summary>
1412 /// Sets an ambient cancellation token that will be used for all Redis operations
1513 /// in the current async context until the returned scope is disposed.
@@ -27,9 +25,7 @@ public static class RedisCancellationExtensions
2725 /// </code>
2826 /// </example>
2927 public static IDisposable WithCancellation ( this IRedisAsync redis , CancellationToken cancellationToken )
30- {
31- return new CancellationScope ( cancellationToken , null ) ;
32- }
28+ => new CancellationScope ( cancellationToken , null ) ;
3329
3430 /// <summary>
3531 /// Sets an ambient timeout that will be used for all Redis operations
@@ -47,9 +43,7 @@ public static IDisposable WithCancellation(this IRedisAsync redis, CancellationT
4743 /// </code>
4844 /// </example>
4945 public static IDisposable WithTimeout ( this IRedisAsync redis , TimeSpan timeout )
50- {
51- return new CancellationScope ( default , timeout ) ;
52- }
46+ => new CancellationScope ( CancellationToken . None , timeout ) ;
5347
5448 /// <summary>
5549 /// Sets both an ambient cancellation token and timeout that will be used for all Redis operations
@@ -71,66 +65,35 @@ public static IDisposable WithCancellationAndTimeout(
7165 this IRedisAsync redis ,
7266 CancellationToken cancellationToken ,
7367 TimeSpan timeout )
74- {
75- return new CancellationScope ( cancellationToken , timeout ) ;
76- }
68+ => new CancellationScope ( cancellationToken , timeout ) ;
7769
7870 /// <summary>
7971 /// Gets the effective cancellation token for the current async context,
8072 /// combining any ambient cancellation token and timeout.
8173 /// </summary>
8274 /// <returns>The effective cancellation token, or CancellationToken.None if no ambient context is set.</returns>
8375 internal static CancellationToken GetEffectiveCancellationToken ( )
84- {
85- var context = _context . Value ;
86- return context ? . GetEffectiveToken ( ) ?? default ;
87- }
76+ => _context . Value ? . Token ?? CancellationToken . None ;
8877
8978 /// <summary>
9079 /// Gets the current cancellation context for diagnostic purposes.
9180 /// </summary>
9281 /// <returns>The current context, or null if no ambient context is set.</returns>
93- internal static CancellationContext ? GetCurrentContext ( ) => _context . Value ;
82+ internal static object ? GetCurrentScope ( ) => _context . Value ;
9483
95- /// <summary>
96- /// Represents the cancellation context for Redis operations.
97- /// </summary>
98- internal record CancellationContext ( CancellationToken Token , TimeSpan ? Timeout )
99- {
100- /// <summary>
101- /// Gets the effective cancellation token, combining the explicit token with any timeout.
102- /// </summary>
103- /// <returns>A cancellation token that will be cancelled when either the explicit token is cancelled or the timeout expires.</returns>
104- public CancellationToken GetEffectiveToken ( )
105- {
106- if ( ! Timeout . HasValue ) return Token ;
107-
108- var timeoutSource = new CancellationTokenSource ( Timeout . Value ) ;
109- return Token . CanBeCanceled
110- ? CancellationTokenSource . CreateLinkedTokenSource ( Token , timeoutSource . Token ) . Token
111- : timeoutSource . Token ;
112- }
113-
114- /// <summary>
115- /// Gets a string representation of this context for debugging.
116- /// </summary>
117- public override string ToString ( )
118- {
119- var parts = new System . Collections . Generic . List < string > ( ) ;
120- if ( Token . CanBeCanceled ) parts . Add ( $ "Token: { ( Token . IsCancellationRequested ? "Cancelled" : "Active" ) } ") ;
121- if ( Timeout . HasValue ) parts . Add ( $ "Timeout: { Timeout . Value . TotalMilliseconds } ms") ;
122- return parts . Count > 0 ? string . Join ( ", " , parts ) : "None" ;
123- }
124- }
84+ private static readonly AsyncLocal < CancellationScope ? > _context = new ( ) ;
12585
12686 /// <summary>
12787 /// A disposable scope that manages the ambient cancellation context.
12888 /// </summary>
12989 private sealed class CancellationScope : IDisposable
13090 {
131- private readonly CancellationContext ? _previous ;
91+ private readonly CancellationTokenSource ? _ownedSource ;
92+ private readonly CancellationScope ? _previous ;
13293 private bool _disposed ;
13394
95+ public CancellationToken Token { get ; }
96+
13497 /// <summary>
13598 /// Creates a new cancellation scope with the specified token and timeout.
13699 /// </summary>
@@ -139,7 +102,42 @@ private sealed class CancellationScope : IDisposable
139102 public CancellationScope ( CancellationToken cancellationToken , TimeSpan ? timeout )
140103 {
141104 _previous = _context . Value ;
142- _context . Value = new CancellationContext ( cancellationToken , timeout ) ;
105+ if ( timeout . HasValue )
106+ {
107+ // has a timeout
108+ if ( cancellationToken . CanBeCanceled )
109+ {
110+ // need both timeout and cancellation; but we can avoid some layers if
111+ // we're already doomed
112+ if ( cancellationToken . IsCancellationRequested )
113+ {
114+ Token = cancellationToken ;
115+ }
116+ else
117+ {
118+ _ownedSource = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
119+ _ownedSource . CancelAfter ( timeout . GetValueOrDefault ( ) ) ;
120+ Token = _ownedSource . Token ;
121+ }
122+ }
123+ else
124+ {
125+ // just a timeout
126+ _ownedSource = new CancellationTokenSource ( timeout . GetValueOrDefault ( ) ) ;
127+ Token = _ownedSource . Token ;
128+ }
129+ }
130+ else if ( cancellationToken . CanBeCanceled )
131+ {
132+ // nice and simple, just a CT
133+ Token = cancellationToken ;
134+ }
135+ else
136+ {
137+ Token = CancellationToken . None ;
138+ }
139+
140+ _context . Value = this ;
143141 }
144142
145143 /// <summary>
@@ -149,8 +147,13 @@ public void Dispose()
149147 {
150148 if ( ! _disposed )
151149 {
152- _context . Value = _previous ;
153150 _disposed = true ;
151+ if ( ReferenceEquals ( _context . Value , this ) )
152+ {
153+ // reinstate the previous context
154+ _context . Value = _previous ;
155+ }
156+ _ownedSource ? . Dispose ( ) ;
154157 }
155158 }
156159 }
0 commit comments