Skip to content

Take the test case from #581 - #583

Merged
tatsuya6502 merged 8 commits into
mainfrom
chore/add-tests-from-pr581
Mar 15, 2026
Merged

tatsuya6502 merged 8 commits into
mainfrom
chore/add-tests-from-pr581

Conversation

@tatsuya6502

@tatsuya6502 tatsuya6502 commented Mar 15, 2026 •

Copy link
Copy Markdown
Member

Open with Devin

Summary by CodeRabbit

  • Tests
    • Added a unit test that reproduces a timing edge case in cache expiration where an item that has expired but not yet evicted could cause a newly created value to lose its expiration.
    • This verifies correct expiration/resurrection behavior across precise time sequences, preventing new values from persisting indefinitely after such accesses.

atrocities and others added 5 commits March 2, 2026 00:54
Exposed by #564, which sets a None expiration time when the
expire_after_update callback returns None. The underlying problem -
comparing non sequential timestamps, has existed for some time, but the
buggy behavior was introduced by that PR.

Reproduces a bug where `optionally_get_with` on an expired but not yet evicted
entry causes the new value's expiration to be cleared, making it never expire.

1. optionally_get_with misses at time 0, inserts value 1 with 2s expr
2. Housekeeping will happen at 0.3, 0.6, 0.9, 1.2, 1.5, 1.8s
2. optionally_get_with hits (entry not expired yet) at time 1.98s.
3. optionally_get_with misses (expired) at 2.01s
   - Since housekeeping has not happened, the entry is not evicted, meaning that this is
     an UPDATE operation in do_insert_with_hash.
   - In do_insert_with_hash, the 'current' time (ts) is _after_ the entry expiration
   - In expire_after_read_or_update (line 694) called from do_post_update_steps, the
     expiration time is before the current time, resulting in a None value
   - None is passed as the current_time to the default expire_after_update, which returns
     None
   - duration is now None and current_per_entry_exp_time is Some, resulting in no expiration
4. optionally_get_with HITS value 2 at time 12.01s, and indefinitely after that
Fix invalid timestamp comparison order for updates on unevicted expired entries
@coderabbitai

coderabbitai Bot commented Mar 15, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a unit test test_optionally_get_with_expired_entry_bug to src/sync/cache.rs that reproduces an edge case where optionally_get_with runs on an expired-but-not-yet-evicted entry using a custom Expiry and a mocked clock to advance timestamps.

Changes

Cohort / File(s) Summary
Cache expiration bug test
src/sync/cache.rs
Adds a 66-line unit test test_optionally_get_with_expired_entry_bug which sets a 2s expiry via a custom Expiry, uses a mocked clock to step time (t=0, ~1.98s, 2.01s, 12.01s), and verifies behavior when optionally_get_with updates an expired-but-still-present entry.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related issues

Possibly related PRs

Suggested labels

bug

Poem

🐰 I hop through ticks and tiny time bends,
A test uncovers how expiry ends.
I poke at clocks, I sniff the trace,
And find the bug in a quiet place. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Take the test case from #581' is vague and lacks specificity about the actual change being made in the PR. Consider revising the title to be more descriptive, such as 'Add regression test for optionally_get_with expired entry bug' to clearly communicate what the test validates.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/add-tests-from-pr581
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@tatsuya6502 tatsuya6502 added this to the v0.12.15 milestone Mar 15, 2026
@codecov

codecov Bot commented Mar 15, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.45%. Comparing base (0b002e0) to head (b09ca15).
⚠️ Report is 10 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #583      +/-   ##
==========================================
+ Coverage   93.33%   93.45%   +0.11%     
==========================================
  Files          44       44              
  Lines       17036    17073      +37     
==========================================
+ Hits        15901    15955      +54     
+ Misses       1135     1118      -17     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tatsuya6502
tatsuya6502 marked this pull request as ready for review March 15, 2026 11:12

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/sync/cache.rs (2)

5404-5405: Assert the exact expected value at Line 5405.

assert_ne!(..., Some(2)) is too permissive and can hide unrelated regressions. This scenario should deterministically return Some(3).

♻️ Proposed fix
-        let value = cache.optionally_get_with("key", next_value);
-        assert_ne!(value, Some(2), "Access at 12.01s should not still be 2");
+        let value = cache.optionally_get_with("key", next_value);
+        assert_eq!(
+            value,
+            Some(3),
+            "Access at 12.01s should miss expired value 2 and create value 3"
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sync/cache.rs` around lines 5404 - 5405, The assertion at the
optionally_get_with call is too permissive (assert_ne!(..., Some(2))) and should
assert the exact expected value; change the assertion for the result of
cache.optionally_get_with("key", next_value) to assert it equals Some(3) so the
test deterministically verifies the expected value returned by that call.

5335-5347: Simplify the long scenario comment to avoid stale internals.

The block currently encodes internal function-path assumptions and a fixed line reference, which will age quickly. Keep the test docs behavior-focused.

🧹 Suggested comment rewrite
-    /// 1. optionally_get_with misses at time 0, inserts value 1 with 2s expr
-    /// 2. Housekeeping will happen at 0.3, 0.6, 0.9, 1.2, 1.5, 1.8s
-    /// 2. optionally_get_with hits (entry not expired yet) at time 1.98s.
-    /// 3. optionally_get_with misses (expired) at 2.01s
-    ///    - Since housekeeping has not happened, the entry is not evicted, meaning that this is
-    ///      an UPDATE operation in do_insert_with_hash.
-    ///    - In do_insert_with_hash, the 'current' time (ts) is _after_ the entry expiration
-    ///    - In expire_after_read_or_update (line 694) called from do_post_update_steps, the
-    ///      expiration time is before the current time, resulting in a None value
-    ///    - None is passed as the current_time to the default expire_after_update, which returns
-    ///      None
-    ///    - duration is now None and current_per_entry_exp_time is Some, resulting in no expiration
-    /// 4. optionally_get_with HITS value 2 at time 12.01s, and indefinitely after that
+    /// 1. `optionally_get_with` misses at t=0 and inserts value 1 with 2s expiry.
+    /// 2. At t=1.98s, `optionally_get_with` still hits value 1.
+    /// 3. At t=2.01s (expired, not yet evicted), `optionally_get_with` should miss and create value 2.
+    /// 4. At t=12.01s, value 2 should also be expired; `optionally_get_with` should create a new value.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sync/cache.rs` around lines 5335 - 5347, The long scenario comment for
the test should be simplified to a behavior-focused description: remove internal
function-path assumptions, fixed line references, and implementation details
(e.g., do_insert_with_hash, do_post_update_steps, expire_after_read_or_update,
expire_after_update, current_per_entry_exp_time) and instead state the
observable sequence of events and expected outcomes for optionally_get_with
(miss, insert with 2s expiry, intermediate housekeeping timings, hit before
expiry, miss after expiry, and final behavior), keeping only what a reader needs
to understand the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/sync/cache.rs`:
- Around line 5404-5405: The assertion at the optionally_get_with call is too
permissive (assert_ne!(..., Some(2))) and should assert the exact expected
value; change the assertion for the result of cache.optionally_get_with("key",
next_value) to assert it equals Some(3) so the test deterministically verifies
the expected value returned by that call.
- Around line 5335-5347: The long scenario comment for the test should be
simplified to a behavior-focused description: remove internal function-path
assumptions, fixed line references, and implementation details (e.g.,
do_insert_with_hash, do_post_update_steps, expire_after_read_or_update,
expire_after_update, current_per_entry_exp_time) and instead state the
observable sequence of events and expected outcomes for optionally_get_with
(miss, insert with 2s expiry, intermediate housekeeping timings, hit before
expiry, miss after expiry, and final behavior), keeping only what a reader needs
to understand the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 47e42df9-5fdb-454d-8bd4-4dd27092e6be

📥 Commits

Reviewing files that changed from the base of the PR and between 4d9d5c4 and cb798c7.

📒 Files selected for processing (1)
  • src/sync/cache.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/sync/cache.rs (2)

5395-5396: Consider a more specific assertion to verify expected behavior.

The current assertion assert_ne!(value, Some(2)) correctly detects if the bug exists (stale value 2), but assert_eq!(value, Some(3)) would additionally verify that a new value was properly created with fresh expiry. This makes test failures more descriptive.

💡 Suggested improvement
         let value = cache.optionally_get_with("key", next_value);
-        assert_ne!(value, Some(2), "Access at 12.01s should not still be 2");
+        assert_eq!(value, Some(3), "Access at 12.01s should create value 3 since value 2 expired");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sync/cache.rs` around lines 5395 - 5396, Replace the non-specific
inequality check with an explicit equality check so the test verifies a fresh
value was created: in the call to cache.optionally_get_with("key", next_value)
assert that value == Some(3) instead of assert_ne!(value, Some(2)); update the
assertion in the test around the optionally_get_with invocation (refer to the
variables value and next_value and the optionally_get_with function) so failures
show the expected new value rather than just the absence of the stale value.

5340-5341: Redundant import: Clock is already imported at module level.

Clock is already imported at line 1884 (use crate::common::{time::Clock, HousekeeperConfig}), so this local import is unnecessary.

🧹 Suggested fix
     #[test]
     fn test_optionally_get_with_expired_entry_bug() {
-        use crate::common::time::Clock;
         use std::sync::atomic::{AtomicU32, Ordering};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sync/cache.rs` around lines 5340 - 5341, In
test_optionally_get_with_expired_entry_bug remove the redundant local import
`use crate::common::time::Clock;` since `Clock` is already imported at module
level (from `use crate::common::{time::Clock, HousekeeperConfig}`); simply
delete that local use statement so the test compiles without duplicate imports
and continues to reference `Clock` from the module-level import.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/sync/cache.rs`:
- Around line 5395-5396: Replace the non-specific inequality check with an
explicit equality check so the test verifies a fresh value was created: in the
call to cache.optionally_get_with("key", next_value) assert that value ==
Some(3) instead of assert_ne!(value, Some(2)); update the assertion in the test
around the optionally_get_with invocation (refer to the variables value and
next_value and the optionally_get_with function) so failures show the expected
new value rather than just the absence of the stale value.
- Around line 5340-5341: In test_optionally_get_with_expired_entry_bug remove
the redundant local import `use crate::common::time::Clock;` since `Clock` is
already imported at module level (from `use crate::common::{time::Clock,
HousekeeperConfig}`); simply delete that local use statement so the test
compiles without duplicate imports and continues to reference `Clock` from the
module-level import.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 84dfb81d-e8f7-4161-9393-109a29ccbac9

📥 Commits

Reviewing files that changed from the base of the PR and between cb798c7 and 31a6d37.

📒 Files selected for processing (1)
  • src/sync/cache.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/sync/cache.rs (1)

5394-5395: Strengthen the final assertion to an exact expected value.

At Line 5395, assert_ne!(value, Some(2)) is weaker than needed in a deterministic single-threaded flow. Prefer asserting Some(3) so the test guarantees exactly one new creation at the final step.

🎯 Proposed assertion update
-        let value = cache.optionally_get_with("key", next_value);
-        assert_ne!(value, Some(2), "Access at 12.01s should not still be 2");
+        let value = cache.optionally_get_with("key", next_value);
+        assert_eq!(value, Some(3), "Access at 12.01s should miss and return 3");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sync/cache.rs` around lines 5394 - 5395, Update the final assertion that
checks the result of cache.optionally_get_with("key", next_value): replace the
weak inequality check against Some(2) with an exact equality check for the
expected value Some(3) so the test verifies the precise result of the
single-threaded creation; locate the assertion that inspects the variable value
after calling optionally_get_with and change it to assert equality with Some(3).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/sync/cache.rs`:
- Around line 5394-5395: Update the final assertion that checks the result of
cache.optionally_get_with("key", next_value): replace the weak inequality check
against Some(2) with an exact equality check for the expected value Some(3) so
the test verifies the precise result of the single-threaded creation; locate the
assertion that inspects the variable value after calling optionally_get_with and
change it to assert equality with Some(3).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f387c032-b6e0-4a0d-a420-0a0cf11d89c5

📥 Commits

Reviewing files that changed from the base of the PR and between 31a6d37 and b09ca15.

📒 Files selected for processing (1)
  • src/sync/cache.rs

@tatsuya6502 tatsuya6502 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merging.

@tatsuya6502
tatsuya6502 merged commit 0370d64 into main Mar 15, 2026
43 checks passed
@tatsuya6502
tatsuya6502 deleted the chore/add-tests-from-pr581 branch March 15, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants