Conversation
…or_each Cover the previously untested branches: - removing a held entry via the in-use path (entry.remove()) - held entries without a value (skipped, kept until guard drop) - for_each visiting/skipping held entries after guard release - white-box tests for the defensive unheld-valueless state (refcnt == 0, no value), unreachable through the public API
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #45 +/- ##
==========================================
+ Coverage 99.29% 99.91% +0.62%
==========================================
Files 3 3
Lines 2272 2422 +150
==========================================
+ Hits 2256 2420 +164
+ Misses 16 2 -14 ☔ View full report in Codecov by Harness. |
There was a problem hiding this comment.
Pull request overview
Adds targeted tests to exercise previously uncovered branches in LockMap::retain and LruLockMap::{retain, for_each}, including behavior around held entries, valueless entries, and defensive internal states. This improves confidence in concurrent behavior and helps ensure the “in-use” code paths are covered.
Changes:
- Add tests ensuring held entries are removed via the in-use path (
entry.remove()) inretain. - Add tests verifying valueless held entries are skipped (and cleaned up on guard drop), and that
for_eachvisits held entries after guard release. - Add white-box tests that simulate the defensive “unheld + valueless” internal state and validate
retain/for_eachbehavior (and LRU list consistency forLruLockMap).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
src/lockmap.rs |
Adds new retain tests covering held-entry removal, valueless held-entry skipping, and defensive unheld-valueless state. |
src/lru_lockmap.rs |
Adds analogous retain tests plus for_each tests and an LRU-consistency assertion for defensive unheld-valueless removal. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let retainer = { | ||
| let map = map.clone(); | ||
| std::thread::spawn(move || map.retain(|_, v| *v % 2 == 0)) | ||
| }; | ||
|
|
||
| std::thread::sleep(std::time::Duration::from_millis(10)); | ||
| drop(held); | ||
| retainer.join().unwrap(); |
| let calls = Arc::new(AtomicU32::new(0)); | ||
| let retainer = { | ||
| let map = map.clone(); | ||
| let calls = calls.clone(); | ||
| std::thread::spawn(move || { | ||
| map.retain(|_, _| { | ||
| calls.fetch_add(1, Ordering::AcqRel); | ||
| false | ||
| }) | ||
| }) | ||
| }; | ||
|
|
||
| std::thread::sleep(std::time::Duration::from_millis(10)); | ||
| drop(held); | ||
| retainer.join().unwrap(); |
| if s.key == 1 { | ||
| // SAFETY: the shard lock is held and refcnt == 0, so no | ||
| // guard exists and none can be created concurrently. | ||
| unsafe { (*s.value.get()).take() }; | ||
| s.set_value_state(false); | ||
| } |
| let retainer = { | ||
| let cache = cache.clone(); | ||
| std::thread::spawn(move || cache.retain(|_, v| *v % 2 == 0)) | ||
| }; | ||
|
|
||
| std::thread::sleep(std::time::Duration::from_millis(10)); | ||
| drop(held); | ||
| retainer.join().unwrap(); |
| let calls = Arc::new(AtomicU32::new(0)); | ||
| let retainer = { | ||
| let cache = cache.clone(); | ||
| let calls = calls.clone(); | ||
| std::thread::spawn(move || { | ||
| cache.retain(|_, _| { | ||
| calls.fetch_add(1, Ordering::AcqRel); | ||
| false | ||
| }) | ||
| }) | ||
| }; | ||
|
|
||
| std::thread::sleep(std::time::Duration::from_millis(10)); | ||
| drop(held); | ||
| retainer.join().unwrap(); |
| if s.key == 1 { | ||
| // SAFETY: the shard lock is held and refcnt == 0, so no | ||
| // guard exists and none can be created concurrently. | ||
| unsafe { (*s.value.get()).take() }; | ||
| s.set_value_state(false); | ||
| } |
| let visitor = { | ||
| let cache = cache.clone(); | ||
| std::thread::spawn(move || { | ||
| let mut visited = Vec::new(); | ||
| cache.for_each(|k, v| visited.push((*k, *v))); | ||
| visited.sort(); | ||
| visited | ||
| }) | ||
| }; | ||
|
|
||
| std::thread::sleep(std::time::Duration::from_millis(10)); | ||
| drop(held); | ||
| assert_eq!(visitor.join().unwrap(), vec![(1, 10), (2, 20)]); |
| let visitor = { | ||
| let cache = cache.clone(); | ||
| std::thread::spawn(move || { | ||
| let mut visited = Vec::new(); | ||
| cache.for_each(|k, v| visited.push((*k, *v))); | ||
| visited | ||
| }) | ||
| }; | ||
|
|
||
| std::thread::sleep(std::time::Duration::from_millis(10)); | ||
| drop(held); | ||
| assert_eq!(visitor.join().unwrap(), vec![(1, 10)]); |
Cover the previously untested branches: