[BREAKING]: Normalizes ICacheClient IncrementAsync TTL behavior, Removes ListRemoveAsync ExpiresIn argument to align with all other Removes, Hybrid Cache Improvements#434
Conversation
Normalizes the behavior of `IncrementAsync` regarding TTL handling, aligning it with `SetAsync`. Previously, `IncrementAsync` preserved existing TTLs when `expiresIn` was null. Now, passing null removes the TTL, ensuring consistency across cache operations. This change simplifies the hybrid cache implementation and avoids potential inconsistencies between L1 and L2 caches. Removes the `expiresIn` parameter from the `ListRemoveAsync` method to simplify its usage, as expiration was not being correctly handled. Updates documentation and tests to reflect the changes in TTL behavior and the removal of the `expiresIn` parameter from `ListRemoveAsync`.
There was a problem hiding this comment.
Pull request overview
This PR introduces breaking changes to normalize TTL behavior across cache operations. The primary changes are:
- IncrementAsync TTL normalization: Changed behavior from preserving existing TTL when
expiresInis null to removing it, aligning withSetAsyncbehavior for consistency - ListRemoveAsync simplification: Removed the
expiresInparameter which was not properly implemented and caused confusion
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Foundatio/Caching/ICacheClient.cs | Updated interface: removed expiresIn from ListRemoveAsync, updated IncrementAsync documentation |
| src/Foundatio/Caching/InMemoryCacheClient.cs | Updated IncrementAsync to always set TTL instead of conditionally preserving it; updated ListRemoveAsync signature; reordered empty check in SetAllAsync |
| src/Foundatio/Caching/HybridCacheClient.cs | Simplified IncrementAsync local cache handling to leverage new TTL behavior; added expiration checks to SetIfHigher/SetIfLower methods; updated ListRemoveAsync signature |
| src/Foundatio/Caching/HybridAwareCacheClient.cs | Updated ListRemoveAsync to remove expiresIn parameter |
| src/Foundatio/Caching/ScopedCacheClient.cs | Updated ListRemoveAsync to remove expiresIn parameter |
| src/Foundatio/Caching/NullCacheClient.cs | Updated ListRemoveAsync signature |
| src/Foundatio/Extensions/CacheClientExtensions.cs | Updated extension methods to remove expiresIn from ListRemoveAsync calls |
| docs/guide/caching.md | Updated TTL behavior documentation to reflect IncrementAsync changes and ListRemoveAsync parameter removal |
| docs/guide/implementations/hybrid-cache.md | Updated synchronization strategy documentation for the new IncrementAsync behavior |
| src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs | Updated tests to validate new TTL behavior and ListRemoveAsync changes |
| src/Foundatio.TestHarness/Caching/HybridCacheClientTestBase.cs | Added tests for IncrementAsync with zero expiration; updated local cache count expectations |
| tests/Foundatio.Tests/Caching/*.cs | Renamed test methods from ListRemoveAsync_WithExpiration_SetsExpirationCorrectly to ListRemoveAsync_WithMultipleItems_RemovesCorrectly |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Improves hybrid cache efficiency by only publishing invalidation messages when the distributed cache is actually modified. This change prevents unnecessary network traffic and processing overhead by ensuring that invalidation messages are only sent when a key is removed, a value is updated, or a list is modified. Adds tests to verify that invalidation messages are not published when operations do not result in changes to the distributed cache.
Addresses an edge case in `IncrementAsync` where a result of 0 would cause inconsistencies between L1 and L2 caches. Updates `SetIfHigherAsync` and `SetIfLowerAsync` to correctly update L1 cache when the value is updated. Adds test cases for `SetIfHigherAsync` and `SetIfLowerAsync` with negative values. Improves cache consistency and correctness.
|
Regarding code comments: Edge Case:
|
Bug Fix:
|
| Scenario | Return Value |
|---|---|
| New key created | The value itself (can be negative!) |
| Existing key updated | Positive difference |
| No update | 0 |
The difference > 0 check incorrectly assumed the return value would always be positive for successful operations.
The Fix
Changed the condition from if (difference > 0) to if (difference != 0) in all four methods:
SetIfHigherAsync(double and long overloads)SetIfLowerAsync(double and long overloads)
This correctly interprets:
!= 0→ Operation succeeded (either new key created or existing key updated), sync to L1== 0→ No update occurred, remove from L1 since we don't know the actual value
Test Coverage
Extended the existing SetIfHigherAsync_WithFloatingPointDecimals_ComparesCorrectly and SetIfLowerAsync_WithFloatingPointDecimals_ComparesCorrectly tests to include negative value scenarios:
- Initialize new keys with negative values
- Update existing keys with more/less negative values
- Verify both double and long overloads handle negative values correctly
All 1696 tests pass with the fix in place.
This maintains the self-healing property of the hybrid cache while correctly handling the full numeric range including negative values.
Uses Math.Abs and double.Epsilon for more reliable comparisons when checking if the value was updated. This prevents issues caused by floating-point precision.
Removes unnecessary comments from caching examples to enhance clarity and readability.
Normalizes the behavior of
IncrementAsyncregarding TTL handling, aligning it withSetAsync. Previously,IncrementAsyncpreserved existing TTLs whenexpiresInwas null. Now, passing null removes the TTL, ensuring consistency across cache operations.This change simplifies the hybrid cache implementation and avoids potential inconsistencies between L1 and L2 caches.
Removes the
expiresInparameter from theListRemoveAsyncmethod to simplify its usage, as expiration was not being correctly handled.Updates documentation and tests to reflect the changes in TTL behavior and the removal of the
expiresInparameter fromListRemoveAsync.