Skip to content

Commit 9bc6b44

Browse files
authored
Fix race in ExpireAfter when item is removed or updated before expiration fires (#1076)
The expiration timer callback now checks that the item still exists and still has a pending expiration before attempting removal. Prevents InvalidOperationException when items are concurrently removed or updated between when the expiration was scheduled and when it fires. Also uses |= to accumulate expiration changes correctly.
1 parent 8db4806 commit 9bc6b44

1 file changed

Lines changed: 13 additions & 6 deletions

File tree

src/DynamicData/Cache/Internal/ExpireAfter.ForSource.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,18 @@ private void OnEditingSource(ISourceUpdater<TObject, TKey> updater)
161161
{
162162
_expirationDueTimesByKey.Remove(proposedExpiration.Key);
163163

164-
_removedItemsBuffer.Add(new(
165-
key: proposedExpiration.Key,
166-
value: updater.Lookup(proposedExpiration.Key).Value));
167-
168-
updater.RemoveKey(proposedExpiration.Key);
164+
// The item may have been removed or updated by another thread between when
165+
// this expiration was scheduled and when it fired. Check that the item is
166+
// still present and still has an expiration before removing it.
167+
var lookup = updater.Lookup(proposedExpiration.Key);
168+
if (lookup.HasValue && _timeSelector.Invoke(lookup.Value) is not null)
169+
{
170+
_removedItemsBuffer.Add(new(
171+
key: proposedExpiration.Key,
172+
value: lookup.Value));
173+
174+
updater.RemoveKey(proposedExpiration.Key);
175+
}
169176
}
170177
}
171178
_proposedExpirationsQueue.RemoveRange(0, proposedExpirationIndex);
@@ -273,7 +280,7 @@ private void OnSourceNext(IChangeSet<TObject, TKey> changes)
273280
{
274281
if (_timeSelector.Invoke(change.Current) is { } expireAfter)
275282
{
276-
haveExpirationsChanged = TrySetExpiration(
283+
haveExpirationsChanged |= TrySetExpiration(
277284
key: change.Key,
278285
dueTime: now + expireAfter);
279286
}

0 commit comments

Comments
 (0)