Skip to content

Commit 67ea9c0

Browse files
Stop collecting disposables if changed token encountered (#66265)
* reproduce issue: collecting disposables even if composite token has alredy been changed * stop collecting disposables if composite token has already been changed * Update src/libraries/Microsoft.Extensions.Primitives/tests/CompositeChangeTokenTest.cs Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com> * Update src/libraries/Microsoft.Extensions.Primitives/tests/CompositeChangeTokenTest.cs Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com> Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
1 parent 5c0c317 commit 67ea9c0

File tree

2 files changed

+66
-0
lines changed

2 files changed

+66
-0
lines changed

src/libraries/Microsoft.Extensions.Primitives/src/CompositeChangeToken.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ private void EnsureCallbacksInitialized()
101101
if (ChangeTokens[i].ActiveChangeCallbacks)
102102
{
103103
IDisposable disposable = ChangeTokens[i].RegisterChangeCallback(_onChangeDelegate, this);
104+
if (_cancellationTokenSource.IsCancellationRequested)
105+
{
106+
disposable.Dispose();
107+
break;
108+
}
104109
_disposables.Add(disposable);
105110
}
106111
}

src/libraries/Microsoft.Extensions.Primitives/tests/CompositeChangeTokenTest.cs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,66 @@ public async Task RegisteredCallbackGetsInvokedExactlyOnce_WhenMultipleConcurren
148148
// Assert
149149
Assert.Equal(1, count);
150150
}
151+
152+
[Fact]
153+
public void ShouldNotCollectDisposablesIfChangedTokenEncountered()
154+
{
155+
// Arrange
156+
var firstCancellationTokenSource = new CancellationTokenSource();
157+
var secondCancellationTokenSource = new CancellationTokenSource();
158+
var thirdCancellationTokenSource = new CancellationTokenSource();
159+
var count = 0;
160+
var compositeChangeToken = new CompositeChangeToken(new List<IChangeToken> {
161+
new ProxyCancellationChangeToken(firstCancellationTokenSource.Token, disposing: () => count++),
162+
new ProxyCancellationChangeToken(secondCancellationTokenSource.Token, disposing: () => count++),
163+
new ProxyCancellationChangeToken(thirdCancellationTokenSource.Token, disposing: () => count++) });
164+
165+
// Act
166+
firstCancellationTokenSource.Cancel();
167+
compositeChangeToken.RegisterChangeCallback(_ => { }, null);
168+
secondCancellationTokenSource.Cancel();
169+
170+
// Assert
171+
Assert.Equal(1, count);
172+
}
173+
}
174+
175+
internal class ProxyCancellationChangeToken : IChangeToken
176+
{
177+
private readonly CancellationChangeToken _cancellationChangeToken;
178+
private readonly Action _disposing;
179+
180+
public ProxyCancellationChangeToken(CancellationToken cancellationToken, Action disposing)
181+
{
182+
_cancellationChangeToken = new CancellationChangeToken(cancellationToken);
183+
_disposing = disposing;
184+
}
185+
public bool ActiveChangeCallbacks => _cancellationChangeToken.ActiveChangeCallbacks;
186+
187+
public bool HasChanged => _cancellationChangeToken.HasChanged;
188+
189+
public IDisposable RegisterChangeCallback(Action<object?> callback, object? state)
190+
{
191+
IDisposable registration = _cancellationChangeToken.RegisterChangeCallback(callback, state);
192+
return new Registration(_disposing, registration);
193+
}
194+
195+
private class Registration : IDisposable
196+
{
197+
private readonly Action _disposing;
198+
private readonly IDisposable _registration;
199+
200+
public Registration(Action disposing, IDisposable registration)
201+
{
202+
_disposing = disposing;
203+
_registration = registration;
204+
}
205+
206+
public void Dispose()
207+
{
208+
_registration?.Dispose();
209+
_disposing();
210+
}
211+
}
151212
}
152213
}

0 commit comments

Comments
 (0)