Skip to content

Conversation

@jbaublitz
Copy link
Member

@jbaublitz jbaublitz commented Jun 30, 2025

Summary by CodeRabbit

  • New Features

    • Added optional non-blocking read/write accessors that yield currently available pools or None when none are available.
    • Introduced availability-aware guard types with iteration, conversions to dynamic views, and helpers to access entries by id/name.
  • Performance

    • Timers and event flows now prefer availability-based access to reduce contention and improve responsiveness.
  • Documentation

    • Documented availability-driven semantics and return behavior for the new non-blocking accessors.

@jbaublitz jbaublitz added this to the v3.9.0 milestone Jun 30, 2025
@jbaublitz jbaublitz self-assigned this Jun 30, 2025
@jbaublitz jbaublitz force-pushed the issue-stratisd-3837 branch 2 times, most recently from 1aba76f to a3240b1 Compare June 30, 2025 15:01
@jbaublitz jbaublitz moved this to In Progress in 2025June Jun 30, 2025
@packit-as-a-service
Copy link

Congratulations! One of the builds has completed. 🍾

You can install the built RPMs by following these steps:

  • sudo yum install -y dnf-plugins-core on RHEL 8
  • sudo dnf install -y dnf-plugins-core on Fedora
  • dnf copr enable packit/stratis-storage-stratisd-3871
  • And now you can install the packages.

Please note that the RPMs should be used only in a testing environment.

@jbaublitz jbaublitz force-pushed the issue-stratisd-3837 branch 3 times, most recently from 11c0059 to 111b2ac Compare July 3, 2025 18:23
@mulkieran mulkieran removed this from 2025June Jul 8, 2025
@mulkieran mulkieran moved this to In Progress in 2025July Jul 8, 2025
@jbaublitz jbaublitz force-pushed the issue-stratisd-3837 branch from 111b2ac to 652d5e5 Compare July 22, 2025 14:01
@jbaublitz jbaublitz force-pushed the issue-stratisd-3837 branch from 652d5e5 to 59a6f57 Compare August 5, 2025 20:26
@packit-as-a-service
Copy link

Congratulations! One of the builds has completed. 🍾

You can install the built RPMs by following these steps:

  • sudo yum install -y dnf-plugins-core on RHEL 8
  • sudo dnf install -y dnf-plugins-core on Fedora
  • dnf copr enable packit/stratis-storage-stratisd-3871-copr_pull
  • And now you can install the packages.

Please note that the RPMs should be used only in a testing environment.

@mulkieran mulkieran removed this from 2025July Aug 8, 2025
@mulkieran mulkieran moved this to In Progress in 2025August Aug 8, 2025
@jbaublitz jbaublitz force-pushed the issue-stratisd-3837 branch from 59a6f57 to af59887 Compare August 20, 2025 02:51
@coderabbitai
Copy link

coderabbitai bot commented Aug 20, 2025

Walkthrough

Adds non-blocking "available" lock acquisition paths and guards (read_all_available / write_all_available, AllLockReadAvailableGuard / AllLockWriteAvailableGuard); exposes optional Engine methods available_pools() / available_pools_mut(); SimEngine and StratEngine implement/use them; timers/event handlers early-return when none available.

Changes

Cohort / File(s) Summary
Engine trait: optional pool accessors
src/engine/engine.rs
Adds async optional accessors available_pools() / available_pools_mut() returning Option<AllLockReadAvailableGuard<PoolUuid, dyn Pool>> / Option<AllLockWriteAvailableGuard<PoolUuid, dyn Pool>>; imports updated to include available guard types.
SimEngine: forward available access
src/engine/sim_engine/engine.rs
Implements available_pools() / available_pools_mut() by calling read_all_available() / write_all_available() and converting guards to dyn via into_dyn(); returns None when no available pools.
StratEngine: available guards + timers
src/engine/strat_engine/engine.rs
Adds inherent available_pools() / available_pools_mut() (returning available guards of AnyPool); updates timer/event paths to use write_all_available() and early-return empty when None; bridges Engine trait methods to dyn guards with into_dyn().
Locking subsystem: non-blocking availability + guards
src/engine/structures/lock.rs
Adds read_all_available() / write_all_available() on AllOrSomeLock; introduces AllLockReadAvailableGuard / AllLockWriteAvailableGuard, iterator types, get/get_mut helpers, Into<Vec<SomeLock...Guard>> conversions, into_dyn() conversions, Drop semantics, availability futures, and unsafe Send/Sync impls.
Public re-exports
src/engine/structures/mod.rs
Re-exports AllLockReadAvailableGuard and AllLockWriteAvailableGuard from lock alongside existing lock types.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Caller
  participant E as Engine
  participant L as AllOrSomeLock
  participant G as AvailableGuard

  C->>E: available_pools()
  E->>L: read_all_available()
  alt some available
    L-->>E: Some(AllLockReadAvailableGuard)
    E-->>C: Some(G.into_dyn())
    note right of G `#a2d2ff`: non-blocking — iterate/inspect
  else none available
    L-->>E: None
    E-->>C: None
  end
Loading
sequenceDiagram
  autonumber
  participant T as Timer (StratEngine)
  participant S as StratEngine
  participant L as AllOrSomeLock
  participant G as WriteAvailableGuard

  T->>S: fs/pool event tick
  S->>L: write_all_available()
  alt Some available
    L-->>S: Some(AllLockWriteAvailableGuard)
    S->>G: convert/iterate -> SomeLockWriteGuard[]
    S-->>T: process available pools
  else None
    L-->>S: None
    S-->>T: return early (no work)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Pay extra attention to: src/engine/structures/lock.rs (new guard correctness, Drop and unsafe Send/Sync impls), conversions into_dyn() and Into<Vec<...>> impls, and the bridged Engine trait implementations in strat_engine and sim_engine.

Poem

I thump my paw: "Locks light and spare,"
I peek the warren, find who's there.
If none are free, I bound away—
If some are free, we hop and play.
A rabbit's cheer for guards that don't delay. 🐇

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'API to acquire available locks' clearly and concisely summarizes the main change: adding new API methods to acquire locks on available resources without blocking.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/engine/structures/lock.rs (3)

360-386: Doc correctness: “readable” vs “writable” and API intent

  • Line 374: The comment for get_available_write() says “readable”; it should be “writable”.
  • The functions’ behavior looks correct: you atomically determine availability while holding the mutexes and add per-element locks.

Apply this diff:

-    /// Get a list of all readable pools in the current lock.
+    /// Get a list of all writable pools in the current lock.
 fn get_available_write<T>(&mut self, inner: &mut Table<U, T>) -> Table<U, *mut T> {

607-614: Doc correctness: “read” vs “write”

The write_all_available() method’s doc comment says “Issue a read...”; it should say “Issue a write...”.

Apply this diff:

-    /// Issue a read on all available elements.
+    /// Issue a write on all available elements.
 pub async fn write_all_available(&self) -> Option<AllLockWriteAvailableGuard<U, T>> {

1121-1127: Doc correctness: wrong guard provenance

The doc says “Guard returned by AllRead future.” This type is returned by AllReadAvailable.

Apply this diff:

-/// Guard returned by AllRead future.
+/// Guard returned by AllReadAvailable future.
 pub struct AllLockReadAvailableGuard<U: AsUuid, T: ?Sized>(
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 07a4825 and af59887.

📒 Files selected for processing (5)
  • src/engine/engine.rs (2 hunks)
  • src/engine/sim_engine/engine.rs (2 hunks)
  • src/engine/strat_engine/engine.rs (3 hunks)
  • src/engine/structures/lock.rs (6 hunks)
  • src/engine/structures/mod.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-20T02:37:22.447Z
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3651
File: src/dbus_api/pool/pool_3_9/methods.rs:173-176
Timestamp: 2025-08-20T02:37:22.447Z
Learning: The encrypt_pool, reencrypt_pool, and decrypt_pool methods in src/dbus_api/pool/pool_3_9/methods.rs have intentionally different lock handling patterns due to different workflow requirements, and this is not an inconsistency that needs to be fixed.

Applied to files:

  • src/engine/sim_engine/engine.rs
  • src/engine/engine.rs
  • src/engine/strat_engine/engine.rs
🧬 Code Graph Analysis (4)
src/engine/sim_engine/engine.rs (2)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (745-747)
  • pools_mut (210-212)
  • pools_mut (749-751)
  • available_pools_mut (214-218)
  • available_pools_mut (753-755)
src/engine/engine.rs (2)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (745-747)
  • pools_mut (210-212)
  • pools_mut (749-751)
  • available_pools_mut (214-218)
  • available_pools_mut (753-755)
src/engine/structures/lock.rs (1)
src/engine/structures/table.rs (12)
  • default (52-57)
  • is_empty (174-176)
  • next (71-75)
  • next (94-98)
  • next (117-121)
  • get_by_uuid (212-216)
  • get_by_name (205-209)
  • iter (182-186)
  • len (178-180)
  • get_mut_by_uuid (227-231)
  • get_mut_by_name (219-224)
  • iter_mut (188-192)
src/engine/strat_engine/engine.rs (2)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
🔇 Additional comments (14)
src/engine/structures/lock.rs (2)

577-584: LGTM: read_all_available API

Non-blocking discovery returning Option is clean, and the guard semantics match the rest of the design.


1093-1120: LGTM: AllReadAvailable future should not wait

Non-blocking polling that returns None if no entries are available is consistent with the API. Holding both mutexes while materializing the available set prevents races.

src/engine/engine.rs (3)

22-25: LGTM: re-exports for available guard types

Publicly exposing AllLockReadAvailableGuard and AllLockWriteAvailableGuard through structures looks correct.


512-515: API addition: available_pools

The Option-guard API surface is clear and consistent with non-blocking semantics. No concerns.


519-522: API addition: available_pools_mut

Matches the read side and integrates cleanly with the rest of the trait.

src/engine/structures/mod.rs (1)

10-13: LGTM: export available guard types

Re-export and ordering are fine; no functional changes beyond public surface exposure.

src/engine/strat_engine/engine.rs (5)

37-40: LGTM: imports include available guards

Imports are complete and consistent with the new APIs.


206-209: LGTM: inherent available_pools

Directly forwards to AllOrSomeLock::read_all_available(); matches AnyPool usage.


214-219: LGTM: inherent available_pools_mut

Directly forwards to AllOrSomeLock::write_all_available().


745-747: LGTM: Engine::available_pools implementation

Pattern matches existing pools() implementation; into_dyn conversion ensures dyn Pool guard.


753-755: LGTM: Engine::available_pools_mut implementation

Pattern matches existing pools_mut() implementation; into_dyn conversion ensures dyn Pool guard.

src/engine/sim_engine/engine.rs (3)

23-26: LGTM: imports include available guards

Consistent with the new surface.


305-308: LGTM: Engine::available_pools (sim) implementation

Non-blocking mapping with into_dyn; mirrors read_all() path.


313-315: LGTM: Engine::available_pools_mut (sim) implementation

Non-blocking mapping with into_dyn; mirrors write_all() path.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/engine/strat_engine/engine.rs (3)

345-348: Mirror the timer improvement for filesystem events; add a log for visibility

Same rationale as pool_evented_timer: non-blocking behavior is appropriate for the timer path. Consider a debug log when the write-all lock is unavailable.

Apply this diff:

-            match self.pools.write_all_available().await {
-                Some(g) => g.into(),
-                None => return HashMap::default(),
-            };
+            if let Some(g) = self.pools.write_all_available().await {
+                g.into()
+            } else {
+                debug!("Skipping fs_evented_timer iteration: pools write lock unavailable");
+                return HashMap::default();
+            };

751-753: Avoid potential ambiguity by calling the inherent method via UFCS

Inside the Engine impl, calling self.available_pools() can be momentarily confusing since the trait method shares the same name. Using UFCS clarifies that the inherent method returning AnyPool is being called.

Apply this diff:

-        self.available_pools().await.map(|l| l.into_dyn())
+        StratEngine::available_pools(self).await.map(|l| l.into_dyn())

759-761: Same UFCS clarification for the mutable available accessor

Consistent with the read variant, prefer UFCS to clearly target the inherent method.

Apply this diff:

-        self.available_pools_mut().await.map(|l| l.into_dyn())
+        StratEngine::available_pools_mut(self).await.map(|l| l.into_dyn())
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between af59887 and 2de15f5.

📒 Files selected for processing (1)
  • src/engine/strat_engine/engine.rs (5 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-20T02:37:22.447Z
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3651
File: src/dbus_api/pool/pool_3_9/methods.rs:173-176
Timestamp: 2025-08-20T02:37:22.447Z
Learning: The encrypt_pool, reencrypt_pool, and decrypt_pool methods in src/dbus_api/pool/pool_3_9/methods.rs have intentionally different lock handling patterns due to different workflow requirements, and this is not an inconsistency that needs to be fixed.

Applied to files:

  • src/engine/strat_engine/engine.rs
🧬 Code Graph Analysis (1)
src/engine/strat_engine/engine.rs (2)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (30)
  • GitHub Check: testing-farm:fedora-43-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-43-x86_64:local
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-42-x86_64:local
  • GitHub Check: testing-farm:fedora-41-x86_64:local
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:local
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-43-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-42-x86_64:local
  • GitHub Check: testing-farm:fedora-43-x86_64:local
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:local
  • GitHub Check: testing-farm:fedora-41-x86_64:local
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: tests-with-testing-repo (master)
  • GitHub Check: stratis-cli-checks
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
🔇 Additional comments (4)
src/engine/strat_engine/engine.rs (4)

37-40: Imports: availability-aware guards are correctly exposed for use

The addition of AllLockReadAvailableGuard and AllLockWriteAvailableGuard matches the new non-blocking API usage below and aligns with the public re-exports described.


206-208: New read-availability accessor is consistent and minimal

available_pools() correctly mirrors pools() while respecting the non-blocking semantics via read_all_available().


214-218: New write-availability accessor is consistent and minimal

available_pools_mut() correctly mirrors pools_mut() while respecting the non-blocking semantics via write_all_available().


311-314: Add lightweight debug log & confirm conversion exists

Switching to write_all_available() with an early return avoids timer stalls. Consider emitting a low-level debug log to help diagnose persistent contention, and ensure that the Into<Vec<SomeLockWriteGuard<…>>> impl exists for the returned guard.

File: src/engine/strat_engine/engine.rs (lines 311–314)

Apply this diff:

-            match self.pools.write_all_available().await {
-                Some(g) => g.into(),
-                None => return HashMap::default(),
-            };
+            if let Some(g) = self.pools.write_all_available().await {
+                g.into()
+            } else {
+                debug!("Skipping pool_evented_timer iteration: pools write lock unavailable");
+                return HashMap::default();
+            };

Please verify:

  • write_all_available() returns an AllLockWriteAvailableGuard<…>
  • There’s an impl From<AllLockWriteAvailableGuard<…>> for Vec<SomeLockWriteGuard<…>> (or equivalent Into<…>)
  • All necessary methods (write_all_available, any conversion helpers) are correctly defined and in scope

@jbaublitz jbaublitz force-pushed the issue-stratisd-3837 branch from 2de15f5 to bd968a2 Compare August 20, 2025 12:30
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/engine/structures/lock.rs (2)

1489-1508: Critical: Into<Vec> has invalid invariants and will double-release locks

Two issues:

  • Asserts assume no per-element locks are held, but this guard already holds per-element write locks. Also, verifying read_locked/write_locked emptiness is too strong; only global state should be asserted.
  • Without disabling the guard’s Drop after conversion, the available guard will attempt to release locks that are now owned by the per-element guards, causing panics (remove_write_lock() on a UUID already removed) or corrupt state. Unlike reads, writes are tracked as a set, so re-adding does not refcount.

Apply this diff:

-impl<U, T> Into<Vec<SomeLockWriteGuard<U, T>>> for AllLockWriteAvailableGuard<U, T>
+impl<U, T> Into<Vec<SomeLockWriteGuard<U, T>>> for AllLockWriteAvailableGuard<U, T>
 where
     U: AsUuid,
 {
-    fn into(self) -> Vec<SomeLockWriteGuard<U, T>> {
+    fn into(mut self) -> Vec<SomeLockWriteGuard<U, T>> {
         let mut lock_record = self.0.lock().expect("Mutex only locked internally");
-        assert!(lock_record.read_locked.is_empty());
-        assert!(lock_record.write_locked.is_empty());
-        assert_eq!(lock_record.all_read_locked, 0);
+        // No global all-read or all-write locks should be set at this point.
+        assert_eq!(lock_record.all_read_locked, 0);
+        assert!(!lock_record.all_write_locked);
+
+        // Transfer responsibility for unlocking to the per-element guards.
+        // For writes, the lock_record uses a set (no refcounting); therefore,
+        // the available guard must not also try to remove these on Drop.
+        self.2 = false;
 
         self.1
             .iter()
             .map(|(n, u, t)| {
                 (
                     *u,
                     SomeLockWriteGuard(Arc::clone(&self.0), *u, n.clone(), *t, true),
                 )
             })
             .map(|(u, guard)| {
                 lock_record.add_write_lock(u, None);
                 guard
             })
             .collect::<Vec<_>>()
     }
 }

This aligns invariants to global state and prevents double-removal by disabling the available guard’s Drop after handing off to per-element guards.


1618-1632: Critical: Dropping AllLockWriteAvailableGuard removes the wrong lock type

Currently calls remove_write_all_lock(), but this guard represents per-element write locks acquired via add_write_lock(). This will panic/corrupt state and defeat the available-lock semantics.

Apply this diff:

 impl<U, T> Drop for AllLockWriteAvailableGuard<U, T>
 where
     U: AsUuid,
     T: ?Sized,
 {
     fn drop(&mut self) {
-        trace!("Dropping all write lock");
+        trace!("Dropping write locks for available subset");
         if self.2 {
             let mut lock_record = self.0.lock().expect("Mutex only locked internally");
-            lock_record.remove_write_all_lock();
+            // Remove the per-element write locks acquired in get_available_write()
+            for u in self.iter().map(|(_, u, _)| *u) {
+                lock_record.remove_write_lock(&u);
+            }
             lock_record.wake();
         }
-        trace!("All write lock dropped");
+        trace!("Write locks for available subset dropped");
     }
 }
🧹 Nitpick comments (5)
src/engine/structures/lock.rs (4)

360-372: Available-read acquisition logic looks sound; minor doc nit

The non-blocking discovery and per-element read-lock acquisition match the intended semantics (no waiting, no interaction with the wait queue, and respecting conflicts_with_woken/already_acquired). Nit: "pools" in the docstring is a bit misleading in this generic structure.

Apply this diff to make the comment more general:

-    /// Get a list of all readable pools in the current lock.
+    /// Get a list of all readable entries in the current lock.

374-386: Docstring typo: should say "writable" (not "readable")

This function collects writable entries; the docstring currently says "readable".

-    /// Get a list of all readable pools in the current lock.
+    /// Get a list of all writable entries in the current lock.

607-614: Docstring says "read" but this is write_all_available()

Small correction for clarity.

-    /// Issue a read on all available elements.
+    /// Issue a write on all available elements.

1229-1245: Adjust log messages to reflect "available subset" semantics

This guard releases per-element locks on a subset, not a global "all read" lock. Logging should reflect that to aid diagnostics.

-        trace!("Dropping all read lock");
+        trace!("Dropping read locks for available subset");
         if self.2 {
             let mut lock_record = self.0.lock().expect("Mutex only locked internally");
             for u in self.iter().map(|(_, u, _)| *u) {
                 lock_record.remove_read_lock(u);
             }
             lock_record.wake();
         }
-        trace!("All read lock dropped");
+        trace!("Read locks for available subset dropped");
src/engine/strat_engine/engine.rs (1)

751-761: Disambiguate inherent vs trait method calls for clarity

These wrapper methods inside the Engine impl call inherent methods with the same names. It works today, but is easy to misread as self-recursion. Prefer explicit inherent calls.

Apply this diff:

-    async fn available_pools(&self) -> Option<AllLockReadAvailableGuard<PoolUuid, dyn Pool>> {
-        self.available_pools().await.map(|l| l.into_dyn())
+    async fn available_pools(&self) -> Option<AllLockReadAvailableGuard<PoolUuid, dyn Pool>> {
+        StratEngine::available_pools(self).await.map(|l| l.into_dyn())
     }
@@
-    async fn pools_mut(&self) -> AllLockWriteGuard<PoolUuid, dyn Pool> {
-        self.pools_mut().await.into_dyn()
+    async fn pools_mut(&self) -> AllLockWriteGuard<PoolUuid, dyn Pool> {
+        StratEngine::pools_mut(self).await.into_dyn()
     }
@@
-    async fn available_pools_mut(&self) -> Option<AllLockWriteAvailableGuard<PoolUuid, dyn Pool>> {
-        self.available_pools_mut().await.map(|l| l.into_dyn())
+    async fn available_pools_mut(&self) -> Option<AllLockWriteAvailableGuard<PoolUuid, dyn Pool>> {
+        StratEngine::available_pools_mut(self).await.map(|l| l.into_dyn())
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2de15f5 and bd968a2.

📒 Files selected for processing (5)
  • src/engine/engine.rs (2 hunks)
  • src/engine/sim_engine/engine.rs (2 hunks)
  • src/engine/strat_engine/engine.rs (5 hunks)
  • src/engine/structures/lock.rs (6 hunks)
  • src/engine/structures/mod.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/engine/sim_engine/engine.rs
  • src/engine/structures/mod.rs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-20T02:37:22.477Z
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3651
File: src/dbus_api/pool/pool_3_9/methods.rs:173-176
Timestamp: 2025-08-20T02:37:22.477Z
Learning: The encrypt_pool, reencrypt_pool, and decrypt_pool methods in src/dbus_api/pool/pool_3_9/methods.rs have intentionally different lock handling patterns due to different workflow requirements, and this is not an inconsistency that needs to be fixed.

Applied to files:

  • src/engine/strat_engine/engine.rs
🧬 Code Graph Analysis (3)
src/engine/engine.rs (2)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (751-753)
  • pools_mut (210-212)
  • pools_mut (755-757)
  • available_pools_mut (214-218)
  • available_pools_mut (759-761)
src/engine/structures/lock.rs (1)
src/engine/structures/table.rs (12)
  • default (52-57)
  • is_empty (174-176)
  • next (71-75)
  • next (94-98)
  • next (117-121)
  • get_by_uuid (212-216)
  • get_by_name (205-209)
  • iter (182-186)
  • len (178-180)
  • get_mut_by_uuid (227-231)
  • get_mut_by_name (219-224)
  • iter_mut (188-192)
src/engine/strat_engine/engine.rs (2)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (29)
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: testing-farm:fedora-43-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:local
  • GitHub Check: testing-farm:fedora-43-x86_64:local
  • GitHub Check: testing-farm:fedora-41-x86_64:local
  • GitHub Check: testing-farm:fedora-42-x86_64:local
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: stratis-cli-checks
  • GitHub Check: checks (make -f Makefile test, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile check-typos, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-utils, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile clippy, 1.89.0, clippy)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile build-min, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile test, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile stratisd-tools, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile build, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile build-no-ipc, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-utils, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-no-ipc, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile clippy, 1.89.0, clippy)
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.89.0, cargo)
🔇 Additional comments (11)
src/engine/structures/lock.rs (4)

577-584: LGTM: Non-blocking read_all_available()

The API is consistent with the rest of the lock surface and properly wraps the AllReadAvailable future.


1093-1120: LGTM: AllReadAvailable future behaves correctly

  • Never waits, as intended.
  • Correctly respects conflicts_with_woken and already_acquired indirectly via get_available_read().
  • Returns None when no entries are available.

1121-1169: LGTM: AllLockReadAvailableGuard API surface mirrors read-all guard

The guard type, Into<Vec<SomeLockReadGuard<…>>>, into_dyn(), iterator types, and Send/Sync impls are consistent with existing patterns.


1450-1476: LGTM: AllWriteAvailable future is non-blocking and correct

  • Never waits; uses get_available_write() with proper conflict checks.
  • Returns None when nothing is available.
src/engine/engine.rs (3)

22-25: LGTM: Publicly importing available guard types

Brings the new availability-aware guard types into the Engine module scope as expected.


512-515: LGTM: Non-blocking available_pools API

The trait addition is clear and matches the new lock APIs. Returning Option to indicate “no available pools right now” is appropriate.


519-522: LGTM: Non-blocking available_pools_mut API

Symmetric with the read variant; aligns with usage in SimEngine/StratEngine.

src/engine/strat_engine/engine.rs (4)

37-40: LGTM: Importing available guard types for StratEngine

Makes the availability-aware guard types available for both public API and Engine impl wrappers.


206-219: LGTM: Public available_pools / available_pools_mut on StratEngine

These inherent methods cleanly expose the non-blocking variants and match the underlying lock API.


311-314: Timer path: Correct switch to availability-aware write guards

This avoids blocking in the timer thread when nothing is immediately writable. Note: correctness depends on fixing the available-write guard Into/Drop semantics in lock.rs to avoid double-removal/panics.

If you apply the lock.rs fixes, this path should be safe. Without them, it'll panic under contention. No further changes required here.


345-348: Timer path: fs_evented uses availability-aware write guards appropriately

Same note as for pool_evented_timer regarding reliance on correct Into/Drop behavior of available-write guards.

No action here after lock.rs fix.

@jbaublitz jbaublitz force-pushed the issue-stratisd-3837 branch 3 times, most recently from a430229 to 42f8b7d Compare August 26, 2025 14:51
@jbaublitz jbaublitz force-pushed the issue-stratisd-3837 branch from 42f8b7d to 90192ee Compare August 26, 2025 15:00
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/engine/structures/lock.rs (1)

1484-1507: Write-available conversion/drop balance is correct

As with read, into() adds one write lock per entry and Drop removes one, leaving each SomeLockWriteGuard with a single write lock. This fixes the earlier bug of removing a global write-all lock. Nice.

Also applies to: 1616-1632

🧹 Nitpick comments (11)
src/engine/structures/lock.rs (9)

374-386: Doc fix: “readable” -> “writable”

The docstring says “readable” but this helper returns writable entries. Recommend correcting to avoid confusion.

-    /// Get a list of all readable pools in the current lock.
+    /// Get a list of all writable pools in the current lock.

607-614: Doc fix: write_all_available() describes a read

The docstring says “Issue a read…” but this issues a write across available elements. Please correct.

-    /// Issue a read on all available elements.
+    /// Issue a write on all available elements.

1093-1120: AllReadAvailable: “never wait” path is sound; tighten docs

The future does a single pass under the mutexes and returns immediately. Good. The guard doc, however, mentions the wrong future.

Suggested doc tweak:

-/// Guard returned by AllRead future.
+/// Guard returned by AllReadAvailable future.

If helpful, I can add unit tests that:

  • verify None when all entries conflict or when conflicts exist in woken,
  • verify subset selection with mixed conflicts,
  • verify wake() is triggered on drop and counters are balanced after Into<Vec<_>>.
    Do you want me to draft these?

Also applies to: 1121-1127


1128-1150: Conversion/drop balance is correct; add a clarifying comment

Converting an AllLockReadAvailableGuard into Vec increments per-entry read counts once, and the guard’s Drop decrements once — net result: each SomeLockReadGuard holds one read lock. Consider an inline comment to document this intentional “add then drop” pattern to prevent future regressions.

Example comment to append above the map that increments:

+            // Note: get_available_read() already added one read lock per entry.
+            // We add one more here so that after this guard drops (and removes one),
+            // each returned SomeLockReadGuard retains exactly one read lock.

Also applies to: 1228-1245


1450-1476: AllWriteAvailable: implementation matches design; fix guard doc

The write-available future mirrors the read path and avoids waiting; looks good. The guard doc mentions the wrong future.

-/// Guard returned by AllWrite future.
+/// Guard returned by AllWriteAvailable future.

Also applies to: 1477-1483


1527-1540: Align Send/Sync bounds style with other guards

Other guards use T: ?Sized + Send/Sync; here it’s T: Send and T: Sync. Functionally fine, but for consistency and clarity (T is declared ?Sized at the type), mirror the existing pattern.

-unsafe impl<U, T> Send for AllLockWriteAvailableGuard<U, T>
+unsafe impl<U, T> Send for AllLockWriteAvailableGuard<U, T>
 where
     U: AsUuid + Send,
-    T: Send,
+    T: ?Sized + Send,
 {
 }
 
-unsafe impl<U, T> Sync for AllLockWriteAvailableGuard<U, T>
+unsafe impl<U, T> Sync for AllLockWriteAvailableGuard<U, T>
 where
     U: AsUuid + Sync,
-    T: Sync,
+    T: ?Sized + Sync,
 {
 }

1233-1244: Trace messages: clarify “available subset” vs “all”

The drop traces still say “Dropping all … lock” for the available-guard drops. Suggest refining the messages to avoid confusion during debugging.

-        trace!("Dropping all read lock");
+        trace!("Dropping read locks for available subset");
 ...
-        trace!("All read lock dropped");
+        trace!("Read locks for available subset dropped");
-        trace!("Dropping all write lock");
+        trace!("Dropping write locks for available subset");
 ...
-        trace!("All write lock dropped");
+        trace!("Write locks for available subset dropped");

Also applies to: 1621-1631


374-386: Optional: assert “no waiter manipulation” for available futures (debug-only)

Available paths are documented as “should never wait.” A lightweight debug assertion could validate that we didn’t enqueue a waiter while building the available set.

Example (behind debug assertions) right after computing the available table:

         let available =
             lock_record.get_available_write(unsafe { inner.get().as_mut() }.expect("Not null"));
+        debug_assert!(
+            !lock_record.waiting.iter().any(|w| matches!(w.ty, WaitType::AllWrite | WaitType::SomeWrite(_))),
+            "Available write should not enqueue waiters"
+        );

Same pattern could be used for read. Totally optional.

Also applies to: 607-614


360-372: Micro: avoid repeating the WaitType construction in filters

You construct WaitType twice per iteration (already_acquired and conflicts_with_woken). Cache it to shave a tiny bit of work and improve readability.

-        for (n, u, p) in inner.iter() {
-            if !self.already_acquired(&WaitType::SomeRead(*u))
-                && !self.conflicts_with_woken(&WaitType::SomeRead(*u))
+        for (n, u, p) in inner.iter() {
+            let wt = WaitType::SomeRead(*u);
+            if !self.already_acquired(&wt) && !self.conflicts_with_woken(&wt)
             {
                 self.add_read_lock(*u, None);
                 table.insert(n.clone(), *u, p as *const T);
             }
         }
-        for (n, u, p) in inner.iter_mut() {
-            if !self.already_acquired(&WaitType::SomeWrite(*u))
-                && !self.conflicts_with_woken(&WaitType::SomeWrite(*u))
+        for (n, u, p) in inner.iter_mut() {
+            let wt = WaitType::SomeWrite(*u);
+            if !self.already_acquired(&wt) && !self.conflicts_with_woken(&wt)
             {
                 self.add_write_lock(*u, None);
                 table.insert(n.clone(), *u, p as *mut T);
             }
         }

Also applies to: 374-386

src/engine/strat_engine/engine.rs (2)

311-314: Timer now uses availability-aware writes; consider a debug trace on skip

Early-returning an empty map when no pools are available is fine. A low-level debug log can aid observability during event droughts or heavy contention.

-            match self.pools.write_all_available().await {
-                Some(g) => g.into(),
-                None => return HashMap::default(),
-            };
+            match self.pools.write_all_available().await {
+                Some(g) => g.into(),
+                None => {
+                    debug!("pool_evented_timer: no pools available for write at this tick");
+                    return HashMap::default();
+                }
+            };

345-348: Same observability nit for fs_evented_timer

Mirror the optional debug trace here as well.

-            match self.pools.write_all_available().await {
-                Some(g) => g.into(),
-                None => return HashMap::default(),
-            };
+            match self.pools.write_all_available().await {
+                Some(g) => g.into(),
+                None => {
+                    debug!("fs_evented_timer: no pools available for write at this tick");
+                    return HashMap::default();
+                }
+            };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a430229 and 90192ee.

📒 Files selected for processing (5)
  • src/engine/engine.rs (2 hunks)
  • src/engine/sim_engine/engine.rs (2 hunks)
  • src/engine/strat_engine/engine.rs (5 hunks)
  • src/engine/structures/lock.rs (6 hunks)
  • src/engine/structures/mod.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/engine/engine.rs
  • src/engine/structures/mod.rs
  • src/engine/sim_engine/engine.rs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-20T02:37:22.477Z
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3651
File: src/dbus_api/pool/pool_3_9/methods.rs:173-176
Timestamp: 2025-08-20T02:37:22.477Z
Learning: The encrypt_pool, reencrypt_pool, and decrypt_pool methods in src/dbus_api/pool/pool_3_9/methods.rs have intentionally different lock handling patterns due to different workflow requirements, and this is not an inconsistency that needs to be fixed.

Applied to files:

  • src/engine/strat_engine/engine.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (30)
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-43-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-41-x86_64:local
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:local
  • GitHub Check: testing-farm:fedora-43-x86_64:local
  • GitHub Check: testing-farm:fedora-42-x86_64:local
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: checks (make -f Makefile fmt-ci, 1.89.0, rustfmt)
  • GitHub Check: python-based-tests
  • GitHub Check: stratis-cli-checks
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: checks (make -f Makefile build, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile test, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile build-min, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-min-no-systemd, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile docs-ci, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-no-ipc, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile build-no-ipc, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-utils, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile stratisd-tools, 1.89.0, cargo)
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.89.0, cargo)
  • GitHub Check: checks_with_tang_should_fail (TANG_URL=localhost make -f Makefile test-clevis-loop-should-fail-ro...
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.89.0, cargo)
🔇 Additional comments (6)
src/engine/structures/lock.rs (2)

360-372: Non-blocking availability discovery looks correct

The available-read scan respects both already-acquired and woken-conflict checks and acquires per-element read locks without touching the waiter queue. Matches the “should never wait” contract.


577-584: API addition: read_all_available() aligns with semantics

This exposes the availability surface as an Option without queuing; the “no wait” behavior is clear and appropriate for callers that should not block.

src/engine/strat_engine/engine.rs (4)

206-209: Add available_pools(): straightforward wrapper

Good addition; mirrors read_all() with non-blocking semantics and returns Option as expected.


214-218: Add available_pools_mut(): straightforward wrapper

Matches write_all_available() behavior and keeps the Option surface to signal “none available.”


751-754: Engine::available_pools dyn wrapper is correct

Mapping to dyn via into_dyn() yields the expected trait-object guard. LGTM.


759-762: Engine::available_pools_mut dyn wrapper is correct

Symmetric to the read side; no issues spotted.

@jbaublitz jbaublitz force-pushed the issue-stratisd-3837 branch from 90192ee to 824de39 Compare August 26, 2025 19:25
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (3)
src/engine/structures/lock.rs (3)

607-613: Fix incorrect documentation comment.

This is a duplicate of a previous review comment, but the documentation still says "read" when it should say "write".

Apply this diff:

-    /// Issue a read on all available elements.
+    /// Issue a write on all available elements.
     pub async fn write_all_available(&self) -> Option<AllLockWriteAvailableGuard<U, T>> {

1121-1126: Fix incorrect documentation comment.

This is identified as a duplicate of a previous review comment.

Apply this diff:

-/// Guard returned by AllRead future.
+/// Guard returned by AllReadAvailable future.
 pub struct AllLockReadAvailableGuard<U: AsUuid, T: ?Sized>(

1477-1482: Fix incorrect documentation comment.

This is identified as a duplicate of a previous review comment.

Apply this diff:

-/// Guard returned by AllWrite future.
+/// Guard returned by AllWriteAvailable future.
 pub struct AllLockWriteAvailableGuard<U: AsUuid, T: ?Sized>(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 90192ee and 824de39.

📒 Files selected for processing (5)
  • src/engine/engine.rs (2 hunks)
  • src/engine/sim_engine/engine.rs (2 hunks)
  • src/engine/strat_engine/engine.rs (5 hunks)
  • src/engine/structures/lock.rs (6 hunks)
  • src/engine/structures/mod.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/engine/structures/mod.rs
  • src/engine/strat_engine/engine.rs
  • src/engine/engine.rs
  • src/engine/sim_engine/engine.rs
🧰 Additional context used
🧬 Code graph analysis (1)
src/engine/structures/lock.rs (1)
src/engine/structures/table.rs (11)
  • default (52-57)
  • next (71-75)
  • next (94-98)
  • next (117-121)
  • get_by_uuid (212-216)
  • get_by_name (205-209)
  • iter (182-186)
  • len (178-180)
  • get_mut_by_uuid (227-231)
  • get_mut_by_name (219-224)
  • iter_mut (188-192)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-43-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:local
  • GitHub Check: testing-farm:fedora-43-x86_64:local
  • GitHub Check: testing-farm:fedora-41-x86_64:local
  • GitHub Check: testing-farm:fedora-42-x86_64:local
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: checks (make -f Makefile test, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile build, 1.89.0, cargo)
🔇 Additional comments (5)
src/engine/structures/lock.rs (5)

360-372: LGTM! Well-structured available read discovery method.

The logic correctly filters pools to only include those that are not already acquired or conflicted with woken requests, and properly acquires per-element read locks for the available subset.


1228-1244: LGTM! Correct Drop implementation for available read guards.

The Drop implementation correctly removes per-element read locks (not a global read-all lock) and properly wakes waiting tasks. This addresses the bug from previous reviews.


1616-1632: LGTM! Correct Drop implementation for available write guards.

The Drop implementation correctly removes per-element write locks (not a global write-all lock) and properly wakes waiting tasks. This also addresses the bug from previous reviews.


1128-1150: Verify the Into implementation invariants.

The assertion assert!(!lock_record.all_write_locked); is correct since available guards shouldn't conflict with global write locks. However, the absence of checks for read_locked and write_locked maps suggests this was addressed from previous reviews.


1484-1507: LGTM! Correct Into implementation for write available guards.

The invariants only check global locks (all_write_locked and all_read_locked) which is appropriate since per-element locks can coexist with available subsets.

@mulkieran mulkieran removed this from 2025August Sep 2, 2025
@mulkieran mulkieran moved this to In Progress in 2025September Sep 2, 2025
@jbaublitz jbaublitz closed this Sep 4, 2025
@github-project-automation github-project-automation bot moved this from In Progress to Done in 2025September Sep 4, 2025
@jbaublitz jbaublitz reopened this Sep 4, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
src/engine/structures/lock.rs (4)

374-386: Fix incorrect doc comment for get_available_write

This helper enumerates writable entries and acquires per-element write locks; the doc says “readable”.

Apply:

-    /// Get a list of all readable pools in the current lock.
+    /// Get a list of all writable pools in the current lock.
     fn get_available_write<T>(&mut self, inner: &mut Table<U, T>) -> Table<U, *mut T> {

607-614: Fix incorrect doc comment for write_all_available

Doc says “read on all available elements”; this function acquires write locks.

Apply:

-    /// Issue a read on all available elements.
+    /// Issue a write on all available elements.
     pub async fn write_all_available(&self) -> Option<AllLockWriteAvailableGuard<U, T>> {

1093-1119: Document AllReadAvailable future semantics

Clarify non-blocking behavior and return value.

Apply:

-/// Future returned by AllOrSomeLock::read_all_available().
-///
-/// Should never wait.
+/// Future returned by AllOrSomeLock::read_all_available().
+///
+/// Never waits: polls lock state and immediately returns
+/// - Some(AllLockReadAvailableGuard) if at least one entry can be read now, acquiring
+///   per-element read locks for those entries; or
+/// - None if no entries are currently readable without waiting.
 struct AllReadAvailable<U: AsUuid, T>(AllOrSomeLock<U, T>);

1449-1475: Document AllWriteAvailable future semantics

Mirror the read variant’s explanation.

Apply:

-/// Future returned by AllOrSomeLock::write_all_available().
-///
-/// Should never wait.
+/// Future returned by AllOrSomeLock::write_all_available().
+///
+/// Never waits: polls lock state and immediately returns
+/// - Some(AllLockWriteAvailableGuard) if at least one entry can be written now, acquiring
+///   per-element write locks for those entries; or
+/// - None if no entries are currently writable without waiting.
 struct AllWriteAvailable<U: AsUuid, T>(AllOrSomeLock<U, T>);
🧹 Nitpick comments (6)
src/engine/structures/lock.rs (6)

577-584: Tighten trace logs to reflect Option result

Logs currently claim locks were acquired even when returning None.

Apply:

     pub async fn read_all_available(&self) -> Option<AllLockReadAvailableGuard<U, T>> {
         trace!("Acquiring read lock on all available pools");
-        let guard = AllReadAvailable(self.clone()).await;
-        trace!("All read lock on all available pools acquired");
+        let guard = AllReadAvailable(self.clone()).await;
+        if guard.is_some() {
+            trace!("Acquired read locks on available pools");
+        } else {
+            trace!("No pools currently available for read");
+        }
         guard
     }
@@
     pub async fn write_all_available(&self) -> Option<AllLockWriteAvailableGuard<U, T>> {
         trace!("Acquiring write lock on all available pools");
-        let guard = AllWriteAvailable(self.clone()).await;
-        trace!("All write lock on all available pools acquired");
+        let guard = AllWriteAvailable(self.clone()).await;
+        if guard.is_some() {
+            trace!("Acquired write locks on available pools");
+        } else {
+            trace!("No pools currently available for write");
+        }
         guard
     }

Also applies to: 607-614


1121-1127: Fix guard doc to reference AllReadAvailable

The guard is produced by the available variant, not AllRead.

Apply:

-/// Guard returned by AllRead future.
+/// Guard returned by the AllReadAvailable future.
 pub struct AllLockReadAvailableGuard<U: AsUuid, T: ?Sized>(

1229-1243: Make drop logs specific to available-subset guards

Avoid “all read/write lock” phrasing which implies global locks.

Apply:

-        trace!("Dropping all read lock");
+        trace!("Dropping read locks for available subset");
@@
-        trace!("All read lock dropped");
+        trace!("Read locks for available subset dropped");
-        trace!("Dropping all write lock");
+        trace!("Dropping write locks for available subset");
@@
-        trace!("All write lock dropped");
+        trace!("Write locks for available subset dropped");

Also applies to: 1616-1632


1128-1150: Optional: avoid double-counting when converting available read guard into per-element guards

Current implementation increments each per-element read lock again and relies on this guard’s Drop to decrement once. It’s correct but harder to reason about. Transfer ownership instead.

Apply:

-impl<U, T> Into<Vec<SomeLockReadGuard<U, T>>> for AllLockReadAvailableGuard<U, T>
+impl<U, T> Into<Vec<SomeLockReadGuard<U, T>>> for AllLockReadAvailableGuard<U, T>
 where
     U: AsUuid,
 {
-    fn into(self) -> Vec<SomeLockReadGuard<U, T>> {
-        let mut lock_record = self.0.lock().expect("Mutex only acquired internally");
-        assert!(!lock_record.all_write_locked);
-
-        self.1
-            .iter()
-            .map(|(n, u, t)| {
-                (
-                    *u,
-                    SomeLockReadGuard(Arc::clone(&self.0), *u, n.clone(), *t as *const _, true),
-                )
-            })
-            .map(|(u, guard)| {
-                lock_record.add_read_lock(u, None);
-                guard
-            })
-            .collect::<Vec<_>>()
-    }
+    fn into(mut self) -> Vec<SomeLockReadGuard<U, T>> {
+        // Transfer existing per-element read locks to individual guards.
+        self.2 = false; // prevent this guard's Drop from removing the locks
+        self.1
+            .iter()
+            .map(|(n, u, t)| {
+                SomeLockReadGuard(Arc::clone(&self.0), *u, n.clone(), *t as *const _, true)
+            })
+            .collect()
+    }
 }

1477-1482: Fix guard doc to reference AllWriteAvailable

Aligns with the producing future.

Apply:

-/// Guard returned by AllWrite future.
+/// Guard returned by the AllWriteAvailable future.
 pub struct AllLockWriteAvailableGuard<U: AsUuid, T: ?Sized>(

1484-1507: Optional: avoid double-counting when converting available write guard into per-element guards

Same pattern as read: currently increments each write lock again and relies on Drop to subtract once.

Apply:

 impl<U, T> Into<Vec<SomeLockWriteGuard<U, T>>> for AllLockWriteAvailableGuard<U, T>
 where
     U: AsUuid,
 {
-    fn into(self) -> Vec<SomeLockWriteGuard<U, T>> {
-        let mut lock_record = self.0.lock().expect("Mutex only locked internally");
-        assert!(!lock_record.all_write_locked);
-        assert_eq!(lock_record.all_read_locked, 0);
-
-        self.1
-            .iter()
-            .map(|(n, u, t)| {
-                (
-                    *u,
-                    SomeLockWriteGuard(Arc::clone(&self.0), *u, n.clone(), *t, true),
-                )
-            })
-            .map(|(u, guard)| {
-                lock_record.add_write_lock(u, None);
-                guard
-            })
-            .collect::<Vec<_>>()
-    }
+    fn into(mut self) -> Vec<SomeLockWriteGuard<U, T>> {
+        // Transfer existing per-element write locks to individual guards.
+        self.2 = false; // prevent this guard's Drop from removing the locks
+        self.1
+            .iter()
+            .map(|(n, u, t)| SomeLockWriteGuard(Arc::clone(&self.0), *u, n.clone(), *t, true))
+            .collect()
+    }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 824de39 and a29f6b1.

📒 Files selected for processing (5)
  • src/engine/engine.rs (2 hunks)
  • src/engine/sim_engine/engine.rs (2 hunks)
  • src/engine/strat_engine/engine.rs (5 hunks)
  • src/engine/structures/lock.rs (6 hunks)
  • src/engine/structures/mod.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/engine/engine.rs
  • src/engine/structures/mod.rs
  • src/engine/strat_engine/engine.rs
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3898
File: src/engine/engine.rs:533-541
Timestamp: 2025-08-28T19:38:49.745Z
Learning: In the lock implementation in src/engine/structures/lock.rs, the upgrade_pool operation gets priority over other queued lock operations because it passes AtomicBool::new(true) to add_waiter(), which causes the waiter to be added to the front of the queue via push_front() rather than push_back() like other operations.
📚 Learning: 2025-08-28T19:38:49.745Z
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3898
File: src/engine/engine.rs:533-541
Timestamp: 2025-08-28T19:38:49.745Z
Learning: In the lock implementation in src/engine/structures/lock.rs, the upgrade_pool operation gets priority over other queued lock operations because it passes AtomicBool::new(true) to add_waiter(), which causes the waiter to be added to the front of the queue via push_front() rather than push_back() like other operations.

Applied to files:

  • src/engine/sim_engine/engine.rs
📚 Learning: 2025-08-20T02:37:22.477Z
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3651
File: src/dbus_api/pool/pool_3_9/methods.rs:173-176
Timestamp: 2025-08-20T02:37:22.477Z
Learning: The encrypt_pool, reencrypt_pool, and decrypt_pool methods in src/dbus_api/pool/pool_3_9/methods.rs have intentionally different lock handling patterns due to different workflow requirements, and this is not an inconsistency that needs to be fixed.

Applied to files:

  • src/engine/sim_engine/engine.rs
  • src/engine/structures/lock.rs
🧬 Code graph analysis (2)
src/engine/sim_engine/engine.rs (2)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (751-753)
  • pools_mut (210-212)
  • pools_mut (755-757)
  • available_pools_mut (214-218)
  • available_pools_mut (759-761)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/structures/lock.rs (1)
src/engine/structures/table.rs (11)
  • default (52-57)
  • next (71-75)
  • next (94-98)
  • next (117-121)
  • get_by_uuid (212-216)
  • get_by_name (205-209)
  • iter (182-186)
  • len (178-180)
  • get_mut_by_uuid (227-231)
  • get_mut_by_name (219-224)
  • iter_mut (188-192)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (29)
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-43-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-42-x86_64:local
  • GitHub Check: testing-farm:fedora-41-x86_64:local
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:local
  • GitHub Check: testing-farm:fedora-43-x86_64:local
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-utils, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile clippy, 1.89.0, clippy)
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile build-min, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile test, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile build-no-ipc, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile stratisd-tools, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile build, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-utils, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-min-no-systemd, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-min, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile clippy, 1.89.0, clippy)
  • GitHub Check: checks_with_tang_should_fail (TANG_URL=localhost make -f Makefile test-clevis-loop-should-fail-ro...
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.89.0, cargo)
🔇 Additional comments (5)
src/engine/sim_engine/engine.rs (3)

23-26: Imports extended correctly for available guards — LGTM

The new available guard types are imported alongside existing ones. No concerns.


305-307: available_pools wiring looks correct — LGTM

Delegates to read_all_available() and converts to dyn via into_dyn(). Matches Engine trait.


313-315: available_pools_mut wiring looks correct — LGTM

Delegates to write_all_available() and converts to dyn via into_dyn(). Matches Engine trait.

src/engine/structures/lock.rs (2)

360-372: get_available_read helper — LGTM

Reads are filtered against current acquisitions and woken conflicts; per-element locks are recorded. Looks correct.


922-922: Doc typo fix (read_all) — LGTM

Reference now correctly points to read_all().

@mulkieran mulkieran moved this from Done to Done(1) in 2025September Sep 8, 2025
@jbaublitz jbaublitz force-pushed the issue-stratisd-3837 branch 3 times, most recently from 68b1ff0 to 5ec3f32 Compare September 11, 2025 14:06
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
src/engine/sim_engine/engine.rs (1)

313-315: available_pools_mut implementation — LGTM; same verification applies

Mirrors the read variant.

src/engine/structures/lock.rs (3)

607-613: Fix copy/paste in doc: this issues a write, not a read

Doc should say “write”; also mirror the read-available wording.

-    /// Issue a read on all available elements.
+    /// Issue a write on all available elements.
+    ///
+    /// Returns `None` if no elements are currently available; never waits.

1093-1119: Document the AllReadAvailable future behavior

Expand beyond “Should never wait” to describe readiness and return value.

-/// Future returned by AllOrSomeLock::read_all_available().
-///
-/// Should never wait.
+/// Future returned by AllOrSomeLock::read_all_available().
+///
+/// This future never waits: it polls synchronously and returns immediately.
+/// Returns `None` when no elements are currently available for reading,
+/// otherwise returns a guard containing the available subset.
 struct AllReadAvailable<U: AsUuid, T>(AllOrSomeLock<U, T>);

1454-1475: Document the AllWriteAvailable future behavior

Match the read-available docs.

-/// Future returned by AllOrSomeLock::write_all_available().
-///
-/// Should never wait.
+/// Future returned by AllOrSomeLock::write_all_available().
+///
+/// This future never waits: it polls synchronously and returns immediately.
+/// Returns `None` when no elements are currently available for writing,
+/// otherwise returns a guard containing the available subset.
 struct AllWriteAvailable<U: AsUuid, T>(AllOrSomeLock<U, T>);
🧹 Nitpick comments (14)
src/engine/engine.rs (2)

512-515: Clarify Option semantics in docs

Spell out when None vs Some is returned to avoid ambiguity for callers.

-    /// Get all pools belonging to this engine that are currently available and will not block on
-    /// locking.
+    /// Try to acquire non-blocking read access to currently available pools.
+    /// Returns Some(...) if at least one pool could be locked immediately; None otherwise.

519-522: Clarify Option semantics in docs (mutable variant)

Mirror the read variant’s clarification.

-    /// Get mutable references to all pools belonging to this engine that are currently available
-    /// and will not block on locking.
+    /// Try to acquire non-blocking write access to currently available pools.
+    /// Returns Some(...) if at least one pool could be locked immediately; None otherwise.
src/engine/strat_engine/engine.rs (4)

311-314: Timer: add trace on skipped iteration when no locks available

Helps diagnose timer starvation without spamming logs.

-            match self.pools.write_all_available().await {
-                Some(g) => g.into(),
-                None => return HashMap::default(),
-            };
+            match self.pools.write_all_available().await {
+                Some(g) => g.into(),
+                None => {
+                    trace!("No pools available for write lock; skipping pool_evented_timer cycle");
+                    return HashMap::default();
+                }
+            };

345-348: Timer: add trace on skipped iteration when no locks available (fs variant)

Same rationale as pool timer.

-            match self.pools.write_all_available().await {
-                Some(g) => g.into(),
-                None => return HashMap::default(),
-            };
+            match self.pools.write_all_available().await {
+                Some(g) => g.into(),
+                None => {
+                    trace!("No pools available for write lock; skipping fs_evented_timer cycle");
+                    return HashMap::default();
+                }
+            };

751-753: Disambiguate inherent vs trait method to avoid perceived recursion

Fully qualify to make it obvious this calls the inherent helper, not the trait method.

-        self.available_pools().await.map(|l| l.into_dyn())
+        StratEngine::available_pools(self).await.map(|l| l.into_dyn())

759-761: Disambiguate inherent vs trait method (mutable variant)

Same readability improvement as above.

-        self.available_pools_mut().await.map(|l| l.into_dyn())
+        StratEngine::available_pools_mut(self).await.map(|l| l.into_dyn())
src/engine/structures/lock.rs (8)

360-372: Doc: clarify that locks are acquired

The helper acquires per-element read locks; make that explicit in the doc.

-    /// Get a list of all readable pools in the current lock.
+    /// Get a list of all pools currently available for reading and acquire read locks for them.

374-386: Doc: “writable” spelling and semantics

Prefer “writable” and note that write locks are acquired.

-    /// Get a list of all writeable pools in the current lock.
+    /// Get a list of all pools currently available for writing and acquire write locks for them.

577-583: API doc: mention return None and never-wait semantics

Make the behavior explicit.

-    /// Issue a read on all available elements.
+    /// Issue a read on all available elements.
+    ///
+    /// Returns `None` if no elements are currently available; never waits.

1121-1127: Correct guard doc: it’s for AllReadAvailable

Update the doc to reference the right future and RAII behavior.

-/// Guard returned by AllRead future.
+/// Guard returned by the `AllReadAvailable` future (RAII; releases per-element
+/// read locks for the available subset on drop).

1128-1150: Simplify lock transfer; avoid extra add/remove cycle

Mirror the write-available path: set self.2 = false and do not increment per-element read counts again.

-impl<U, T> Into<Vec<SomeLockReadGuard<U, T>>> for AllLockReadAvailableGuard<U, T>
+impl<U, T> Into<Vec<SomeLockReadGuard<U, T>>> for AllLockReadAvailableGuard<U, T>
 where
     U: AsUuid,
 {
-    fn into(self) -> Vec<SomeLockReadGuard<U, T>> {
-        let mut lock_record = self.0.lock().expect("Mutex only acquired internally");
-        assert!(!lock_record.all_write_locked);
-
-        self.1
-            .iter()
-            .map(|(n, u, t)| {
-                (
-                    *u,
-                    SomeLockReadGuard(Arc::clone(&self.0), *u, n.clone(), *t as *const _, true),
-                )
-            })
-            .map(|(u, guard)| {
-                lock_record.add_read_lock(u, None);
-                guard
-            })
-            .collect::<Vec<_>>()
-    }
+    fn into(mut self) -> Vec<SomeLockReadGuard<U, T>> {
+        let lock_record = self.0.lock().expect("Mutex only acquired internally");
+        assert!(!lock_record.all_write_locked);
+        drop(lock_record);
+
+        // Transfer ownership of the existing per-element read locks to the new guards.
+        self.2 = false;
+        self.1
+            .iter()
+            .map(|(n, u, t)| SomeLockReadGuard(Arc::clone(&self.0), *u, n.clone(), *t, true))
+            .collect::<Vec<_>>()
+    }
 }

1228-1244: Trace messages: clarify “available subset”

These drops release per-element read locks, not a global “all read” lock.

-        trace!("Dropping all read lock");
+        trace!("Dropping read locks for available subset");
 ...
-        trace!("All read lock dropped");
+        trace!("Read locks for available subset dropped");

1477-1483: Correct guard doc: it’s for AllWriteAvailable

Update to the correct origin and RAII note.

-/// Guard returned by AllWrite future.
+/// Guard returned by the `AllWriteAvailable` future (RAII; releases per-element
+/// write locks for the available subset on drop).

1609-1625: Trace messages: clarify “available subset”

Align with write-available semantics.

-        trace!("Dropping all write lock");
+        trace!("Dropping write locks for available subset");
 ...
-        trace!("All write lock dropped");
+        trace!("Write locks for available subset dropped");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e96bca and 5ec3f32.

📒 Files selected for processing (5)
  • src/engine/engine.rs (2 hunks)
  • src/engine/sim_engine/engine.rs (2 hunks)
  • src/engine/strat_engine/engine.rs (5 hunks)
  • src/engine/structures/lock.rs (6 hunks)
  • src/engine/structures/mod.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-20T02:37:22.477Z
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3651
File: src/dbus_api/pool/pool_3_9/methods.rs:173-176
Timestamp: 2025-08-20T02:37:22.477Z
Learning: The encrypt_pool, reencrypt_pool, and decrypt_pool methods in src/dbus_api/pool/pool_3_9/methods.rs have intentionally different lock handling patterns due to different workflow requirements, and this is not an inconsistency that needs to be fixed.

Applied to files:

  • src/engine/sim_engine/engine.rs
  • src/engine/strat_engine/engine.rs
  • src/engine/structures/lock.rs
📚 Learning: 2025-08-28T19:38:49.745Z
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3898
File: src/engine/engine.rs:533-541
Timestamp: 2025-08-28T19:38:49.745Z
Learning: In the lock implementation in src/engine/structures/lock.rs, the upgrade_pool operation gets priority over other queued lock operations because it passes AtomicBool::new(true) to add_waiter(), which causes the waiter to be added to the front of the queue via push_front() rather than push_back() like other operations.

Applied to files:

  • src/engine/strat_engine/engine.rs
🧬 Code graph analysis (4)
src/engine/engine.rs (2)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (751-753)
  • pools_mut (210-212)
  • pools_mut (755-757)
  • available_pools_mut (214-218)
  • available_pools_mut (759-761)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
src/engine/sim_engine/engine.rs (2)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (751-753)
  • pools_mut (210-212)
  • pools_mut (755-757)
  • available_pools_mut (214-218)
  • available_pools_mut (759-761)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/strat_engine/engine.rs (2)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
src/engine/structures/lock.rs (1)
src/engine/structures/table.rs (11)
  • default (52-57)
  • next (71-75)
  • next (94-98)
  • next (117-121)
  • get_by_uuid (212-216)
  • get_by_name (205-209)
  • iter (182-186)
  • len (178-180)
  • get_mut_by_uuid (227-231)
  • get_mut_by_name (219-224)
  • iter_mut (188-192)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-43-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-41-x86_64:local
  • GitHub Check: stratis-cli-checks
  • GitHub Check: stratis-min-cli-checks
  • GitHub Check: checks (make -f Makefile fmt-ci, 1.89.0, rustfmt)
  • GitHub Check: python-based-tests
  • GitHub Check: checks (make -f Makefile check-typos, 1.89.0, cargo)
  • GitHub Check: tests-with-testing-repo (master)
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:local
  • GitHub Check: testing-farm:fedora-42-x86_64:local
  • GitHub Check: testing-farm:fedora-43-x86_64:local
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: checks (make -f Makefile build, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile clippy, 1.89.0, clippy)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-min-no-systemd, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-utils, 1.89.0, cargo)
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.89.0, cargo)
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: checks_with_tang_should_fail (TANG_URL=localhost make -f Makefile test-clevis-loop-should-fail-ro...
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.89.0, cargo)
🔇 Additional comments (8)
src/engine/structures/mod.rs (1)

10-12: Public re-exports for Available guards — LGTM

Re-exports look correct and match downstream usage in engine/sim_engine/strat_engine.

src/engine/engine.rs (1)

22-25: Importing Available guard types — LGTM

Imports align with the new non-blocking API.

src/engine/sim_engine/engine.rs (2)

23-26: Importing Available guard types — LGTM

Consistent with the trait changes.


305-307: available_pools implementation — LGTM; into_dyn on available guards verified

into_dyn impls exist for AllLockReadAvailableGuard (src/engine/structures/lock.rs:1157) and AllLockWriteAvailableGuard (src/engine/structures/lock.rs:1507); callers match the blocking variant.

src/engine/strat_engine/engine.rs (3)

37-40: Importing Available guard types — LGTM

Matches usage throughout this file.


206-208: Non-blocking read accessor (inherent) — LGTM

Simple forwarding to read_all_available().


214-218: Non-blocking write accessor (inherent) — LGTM

Simple forwarding to write_all_available().

src/engine/structures/lock.rs (1)

1109-1111: No change required — Table::is_empty() exists.

Defined at src/engine/structures/table.rs:174 (returns self.items.is_empty()); leave the calls in src/engine/structures/lock.rs (1109–1111 and 1465–1466) unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/engine/structures/lock.rs (1)

607-613: Doc mismatch: says “read” but this is the write path.

Fix the method doc to avoid confusion. (This mirrors earlier feedback.)

-    /// Issue a read on all available elements.
+    /// Issue a write on all available elements.
🧹 Nitpick comments (7)
src/engine/structures/lock.rs (7)

374-386: Spelling: “writable” (not “writeable”).

Update the doc comment for clarity.

-    /// Get a list of all writeable pools in the current lock.
+    /// Get a list of all writable pools in the current lock.

1093-1119: Document the “never wait” behavior and return semantics.

Expand the future’s docs to state it polls immediately, never registers a waker, and returns None when no pools are available.

-/// Future returned by AllOrSomeLock::read_all_available().
-///
-/// Should never wait.
+/// Future returned by AllOrSomeLock::read_all_available().
+///
+/// This future never waits: it polls the lock state once and immediately
+/// returns `Poll::Ready`. It does not register a waker.
+/// Returns `Some(AllLockReadAvailableGuard)` if at least one pool is
+/// currently readable without blocking; otherwise returns `None`.

1121-1127: Incorrect guard doc header.

This guard is returned by AllReadAvailable, not AllRead.

-/// Guard returned by AllRead future.
+/// Guard returned by the `AllReadAvailable` future.

1228-1244: Trace messages are misleading for “available” guard.

These are per-element read locks, not an “all read lock”.

-        trace!("Dropping all read lock");
+        trace!("Dropping read locks for available subset");
 ...
-        trace!("All read lock dropped");
+        trace!("Read locks for available subset dropped");

1449-1475: Add analogous docs for write-available future.

Mirror the read variant’s details.

-/// Future returned by AllOrSomeLock::write_all_available().
-///
-/// Should never wait.
+/// Future returned by AllOrSomeLock::write_all_available().
+///
+/// This future never waits: it polls the lock state once and immediately
+/// returns `Poll::Ready`. It does not register a waker.
+/// Returns `Some(AllLockWriteAvailableGuard)` if at least one pool is
+/// currently writable without blocking; otherwise returns `None`.

1477-1483: Incorrect guard doc header.

This is returned by AllWriteAvailable, not AllWrite.

-/// Guard returned by AllWrite future.
+/// Guard returned by the `AllWriteAvailable` future.

1609-1625: Trace messages are misleading for “available” write guard.

Use wording consistent with per-element lock release.

-        trace!("Dropping all write lock");
+        trace!("Dropping write locks for available subset");
 ...
-        trace!("All write lock dropped");
+        trace!("Write locks for available subset dropped");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ec3f32 and 9bbb291.

📒 Files selected for processing (5)
  • src/engine/engine.rs (2 hunks)
  • src/engine/sim_engine/engine.rs (2 hunks)
  • src/engine/strat_engine/engine.rs (5 hunks)
  • src/engine/structures/lock.rs (6 hunks)
  • src/engine/structures/mod.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/engine/structures/mod.rs
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3898
File: src/engine/engine.rs:533-541
Timestamp: 2025-08-28T19:38:49.745Z
Learning: In the lock implementation in src/engine/structures/lock.rs, the upgrade_pool operation gets priority over other queued lock operations because it passes AtomicBool::new(true) to add_waiter(), which causes the waiter to be added to the front of the queue via push_front() rather than push_back() like other operations.
📚 Learning: 2025-08-20T02:37:22.477Z
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3651
File: src/dbus_api/pool/pool_3_9/methods.rs:173-176
Timestamp: 2025-08-20T02:37:22.477Z
Learning: The encrypt_pool, reencrypt_pool, and decrypt_pool methods in src/dbus_api/pool/pool_3_9/methods.rs have intentionally different lock handling patterns due to different workflow requirements, and this is not an inconsistency that needs to be fixed.

Applied to files:

  • src/engine/engine.rs
  • src/engine/strat_engine/engine.rs
  • src/engine/structures/lock.rs
  • src/engine/sim_engine/engine.rs
📚 Learning: 2025-08-28T19:38:49.745Z
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3898
File: src/engine/engine.rs:533-541
Timestamp: 2025-08-28T19:38:49.745Z
Learning: In the lock implementation in src/engine/structures/lock.rs, the upgrade_pool operation gets priority over other queued lock operations because it passes AtomicBool::new(true) to add_waiter(), which causes the waiter to be added to the front of the queue via push_front() rather than push_back() like other operations.

Applied to files:

  • src/engine/strat_engine/engine.rs
  • src/engine/sim_engine/engine.rs
🧬 Code graph analysis (4)
src/engine/engine.rs (2)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (759-761)
  • pools_mut (210-212)
  • pools_mut (763-765)
  • available_pools_mut (214-218)
  • available_pools_mut (767-769)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
src/engine/strat_engine/engine.rs (2)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/sim_engine/engine.rs (4)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
  • default (47-53)
src/engine/structures/lock.rs (1)
src/engine/structures/table.rs (11)
  • default (52-57)
  • next (71-75)
  • next (94-98)
  • next (117-121)
  • get_by_uuid (212-216)
  • get_by_name (205-209)
  • iter (182-186)
  • len (178-180)
  • get_mut_by_uuid (227-231)
  • get_mut_by_name (219-224)
  • iter_mut (188-192)
src/engine/sim_engine/engine.rs (2)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (759-761)
  • pools_mut (210-212)
  • pools_mut (763-765)
  • available_pools_mut (214-218)
  • available_pools_mut (767-769)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (25)
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-43-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-42-x86_64:local
  • GitHub Check: testing-farm:fedora-41-x86_64:local
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:local
  • GitHub Check: testing-farm:fedora-43-x86_64:local
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: tests-with-testing-repo (master)
  • GitHub Check: python-based-tests
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile stratisd-tools, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-min-no-systemd, 1.89.0, cargo)
  • GitHub Check: checks_with_tang_should_fail (TANG_URL=localhost make -f Makefile test-clevis-loop-should-fail-ro...
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.89.0, cargo)
  • GitHub Check: checks (make -f Makefile build, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-min-no-systemd, 1.89.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-no-ipc, 1.89.0, cargo)
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.89.0, cargo)
🔇 Additional comments (19)
src/engine/structures/lock.rs (6)

360-372: Algorithm is correct; naming is clear.

Non-blocking readable-subset discovery with conflict checks is sound.


577-583: LGTM: read_all_available() semantics.

Immediate, non-waiting behavior with Option guard is appropriate.


1128-1149: LGTM: conversion preserves per-element semantics.

Asserts avoid global-write conflicts; per-element read locks are transferred correctly.


1152-1168: LGTM: into_dyn() handoff.

Disabling the original guard before recreating with dyn pointers is correct.


1184-1198: LGTM: iterator yields safe shared refs.

Unsafe blocks are minimal and justified.


1488-1500: LGTM: write-available conversion.

Global-lock assertions are correct; ownership transfer to per-element guards is sound.

src/engine/engine.rs (3)

22-25: Public API surface: imports look right.

New guard types are correctly re-exported for trait signatures.


512-515: Non-blocking accessor is well-scoped.

Returning Option cleanly distinguishes “no available pools” without blocking callers.


519-522: Mutable non-blocking accessor mirrors read variant.

Consistent semantics; good.

src/engine/sim_engine/engine.rs (3)

23-26: Imports and type usage consistent with new API.


305-307: LGTM: forwards to read_all_available().

into_dyn() handoff is correct.


313-315: LGTM: forwards to write_all_available().

into_dyn() handoff is correct.

src/engine/strat_engine/engine.rs (7)

37-40: Imports align with available-guard usage.


206-208: Expose read-available for AnyPool.

Matches Engine trait and keeps blocking behavior out of fast paths.


214-218: Expose write-available for AnyPool.

Symmetric with read side; good.


311-314: Timer path: avoid blocking when busy.

Early return on None is a sensible fallback; conversion to per-element guards is correct.


345-348: Filesystem timer mirrors pool timer.

Consistent, non-blocking behavior.


759-761: Engine impl: dyn conversion via inherent method.

Name resolution calls the inherent available_pools(); no recursion. All good.


767-769: Engine impl: mutable available pools dyn conversion.

Symmetric and correct.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (8)
src/engine/structures/lock.rs (8)

374-386: Nit: spelling in doc

Use “writable” instead of “writeable”.

-    /// Get a list of all writeable pools in the current lock.
+    /// Get a list of all writable pools in the current lock.
     fn get_available_write<T>(&mut self, inner: &mut Table<U, T>) -> Table<U, *mut T> {

1093-1096: Improve AllReadAvailable docs

Clarify semantics: never waits; returns None when none available; Some(guard) otherwise.

-/// Future returned by AllOrSomeLock::read_all_available().
-///
-/// Should never wait.
+/// Future returned by AllOrSomeLock::read_all_available().
+///
+/// This future never waits: it immediately returns
+/// - `None` if no pools are currently readable, or
+/// - `Some(AllLockReadAvailableGuard<...>)` containing the readable subset.

1121-1127: Doc fix: wrong guard mentioned

This is the guard for the available variant.

-/// Guard returned by AllRead future.
+/// Guard returned by `AllReadAvailable` future.
 pub struct AllLockReadAvailableGuard<U: AsUuid, T: ?Sized>(

1128-1149: Simplify Into for AllLockReadAvailableGuard; avoid extra add_read_lock

Set self.2 = false and don’t increment read counts again; mirrors write-available variant and reduces counter churn.

-impl<U, T> Into<Vec<SomeLockReadGuard<U, T>>> for AllLockReadAvailableGuard<U, T>
+impl<U, T> Into<Vec<SomeLockReadGuard<U, T>>> for AllLockReadAvailableGuard<U, T>
 where
     U: AsUuid,
 {
-    fn into(self) -> Vec<SomeLockReadGuard<U, T>> {
-        let mut lock_record = self.0.lock().expect("Mutex only acquired internally");
+    fn into(mut self) -> Vec<SomeLockReadGuard<U, T>> {
+        let lock_record = self.0.lock().expect("Mutex only acquired internally");
         assert!(!lock_record.all_write_locked);
 
-        self.1
-            .iter()
-            .map(|(n, u, t)| {
-                (
-                    *u,
-                    SomeLockReadGuard(Arc::clone(&self.0), *u, n.clone(), *t as *const _, true),
-                )
-            })
-            .map(|(u, guard)| {
-                lock_record.add_read_lock(u, None);
-                guard
-            })
-            .collect::<Vec<_>>()
+        // Transfer ownership of per-element read locks to the returned guards.
+        self.2 = false;
+        self.1
+            .iter()
+            .map(|(n, u, t)| {
+                SomeLockReadGuard(Arc::clone(&self.0), *u, n.clone(), *t as *const _, true)
+            })
+            .collect()
     }
 }

1228-1244: Nit: trace message is misleading

This drops per-element read locks for an available subset, not a global “all read” lock.

-        trace!("Dropping all read lock");
+        trace!("Dropping read locks for available subset");
 ...
-        trace!("All read lock dropped");
+        trace!("Read locks for available subset dropped");

1449-1452: Improve AllWriteAvailable docs

Match the read variant’s clarification.

-/// Future returned by AllOrSomeLock::write_all_available().
-///
-/// Should never wait.
+/// Future returned by AllOrSomeLock::write_all_available().
+///
+/// This future never waits: it immediately returns
+/// - `None` if no pools are currently writable, or
+/// - `Some(AllLockWriteAvailableGuard<...>)` containing the writable subset.

1477-1483: Doc fix: wrong guard mentioned

Available variant, not “AllWrite”.

-/// Guard returned by AllWrite future.
+/// Guard returned by `AllWriteAvailable` future.
 pub struct AllLockWriteAvailableGuard<U: AsUuid, T: ?Sized>(

1609-1625: Nit: trace message is misleading

This drops per-element write locks, not a global “all write” lock.

-        trace!("Dropping all write lock");
+        trace!("Dropping write locks for available subset");
 ...
-        trace!("All write lock dropped");
+        trace!("Write locks for available subset dropped");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9bbb291 and 7b803c0.

📒 Files selected for processing (5)
  • src/engine/engine.rs (2 hunks)
  • src/engine/sim_engine/engine.rs (2 hunks)
  • src/engine/strat_engine/engine.rs (5 hunks)
  • src/engine/structures/lock.rs (6 hunks)
  • src/engine/structures/mod.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-20T02:37:22.477Z
Learnt from: jbaublitz
PR: stratis-storage/stratisd#3651
File: src/dbus_api/pool/pool_3_9/methods.rs:173-176
Timestamp: 2025-08-20T02:37:22.477Z
Learning: The encrypt_pool, reencrypt_pool, and decrypt_pool methods in src/dbus_api/pool/pool_3_9/methods.rs have intentionally different lock handling patterns due to different workflow requirements, and this is not an inconsistency that needs to be fixed.

Applied to files:

  • src/engine/engine.rs
  • src/engine/sim_engine/engine.rs
  • src/engine/strat_engine/engine.rs
  • src/engine/structures/lock.rs
🧬 Code graph analysis (4)
src/engine/engine.rs (2)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (760-762)
  • pools_mut (210-212)
  • pools_mut (764-766)
  • available_pools_mut (214-218)
  • available_pools_mut (768-770)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
src/engine/sim_engine/engine.rs (2)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (760-762)
  • pools_mut (210-212)
  • pools_mut (764-766)
  • available_pools_mut (214-218)
  • available_pools_mut (768-770)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/strat_engine/engine.rs (2)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
src/engine/structures/lock.rs (1)
src/engine/structures/table.rs (11)
  • default (52-57)
  • next (71-75)
  • next (94-98)
  • next (117-121)
  • get_by_uuid (212-216)
  • get_by_name (205-209)
  • iter (182-186)
  • len (178-180)
  • get_mut_by_uuid (227-231)
  • get_mut_by_name (219-224)
  • iter_mut (188-192)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
  • GitHub Check: python-based-tests
  • GitHub Check: checks (make -f Makefile fmt-ci, 1.90.0, rustfmt)
  • GitHub Check: stratis-min-cli-checks
  • GitHub Check: stratis-cli-checks
  • GitHub Check: tests-with-testing-repo (master)
  • GitHub Check: checks (make -f Makefile build, 1.90.0, cargo)
  • GitHub Check: checks (make -f Makefile build-no-ipc, 1.90.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build, 1.90.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-utils, 1.90.0, cargo)
  • GitHub Check: checks (make -f Makefile build-min, 1.90.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-no-ipc, 1.90.0, cargo)
  • GitHub Check: checks (make -f Makefile test, 1.90.0, cargo)
  • GitHub Check: checks (make -f Makefile clippy, 1.90.0, clippy)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-min, 1.90.0, cargo)
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.90.0, cargo)
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.90.0, cargo)
  • GitHub Check: checks_with_tang_should_fail (TANG_URL=localhost make -f Makefile test-clevis-loop-should-fail-ro...
🔇 Additional comments (16)
src/engine/structures/lock.rs (4)

360-372: LGTM on get_available_read()

Non-blocking scan + per‑element read lock acquisition looks correct.


577-584: LGTM: read_all_available() API

Non‑blocking behavior with Option guard is appropriate.


1484-1501: LGTM: write-available Into transfers ownership cleanly

Disabling drop and passing per-element write locks to SomeLockWriteGuard is correct.


607-614: Doc fix: write_all_available() says "read"

Change the doc to say “write”.

-    /// Issue a read on all available elements.
+    /// Issue a write on all available elements.
     pub async fn write_all_available(&self) -> Option<AllLockWriteAvailableGuard<U, T>> {
src/engine/structures/mod.rs (1)

10-12: LGTM: re-exports updated

New available guard types are correctly exposed.

src/engine/engine.rs (3)

22-25: LGTM: imports include available guard types

Consistent with new trait methods.


512-515: LGTM: available_pools API

Optional, non-blocking accessor fits semantics.


519-522: LGTM: available_pools_mut API

Optional, non-blocking accessor fits semantics.

src/engine/sim_engine/engine.rs (3)

23-25: LGTM: imports include available guard types


305-307: LGTM: available_pools forwards correctly and into_dyn()

No recursion risk here; uses underlying lock directly.


313-315: LGTM: available_pools_mut forwards correctly and into_dyn()

src/engine/strat_engine/engine.rs (5)

37-40: LGTM: imports include available guard types


206-209: LGTM: available_pools (inherent) delegates to lock


214-219: LGTM: available_pools_mut (inherent) delegates to lock


311-314: LGTM: pool_evented_timer uses non-blocking write guard

Early return on None avoids blocking the timer path.


345-348: LGTM: fs_evented_timer uses non-blocking write guard

Early return on None is appropriate.

@jbaublitz jbaublitz force-pushed the issue-stratisd-3837 branch from 7b803c0 to 8564b5e Compare October 16, 2025 14:26
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/engine/strat_engine/engine.rs (1)

756-758: Prefer UFCS to disambiguate inherent vs trait methods

Avoid potential confusion by calling inherent methods explicitly.

Apply:

-    async fn pools(&self) -> AllLockReadGuard<PoolUuid, dyn Pool> {
-        self.pools().await.into_dyn()
-    }
+    async fn pools(&self) -> AllLockReadGuard<PoolUuid, dyn Pool> {
+        StratEngine::pools(self).await.into_dyn()
+    }

-    async fn available_pools(&self) -> Option<AllLockReadAvailableGuard<PoolUuid, dyn Pool>> {
-        self.available_pools().await.map(|l| l.into_dyn())
-    }
+    async fn available_pools(&self) -> Option<AllLockReadAvailableGuard<PoolUuid, dyn Pool>> {
+        StratEngine::available_pools(self).await.map(|l| l.into_dyn())
+    }

-    async fn pools_mut(&self) -> AllLockWriteGuard<PoolUuid, dyn Pool> {
-        self.pools_mut().await.into_dyn()
-    }
+    async fn pools_mut(&self) -> AllLockWriteGuard<PoolUuid, dyn Pool> {
+        StratEngine::pools_mut(self).await.into_dyn()
+    }

-    async fn available_pools_mut(&self) -> Option<AllLockWriteAvailableGuard<PoolUuid, dyn Pool>> {
-        self.available_pools_mut().await.map(|l| l.into_dyn())
-    }
+    async fn available_pools_mut(&self) -> Option<AllLockWriteAvailableGuard<PoolUuid, dyn Pool>> {
+        StratEngine::available_pools_mut(self).await.map(|l| l.into_dyn())
+    }

Also applies to: 760-762, 764-766, 768-770

🧹 Nitpick comments (1)
src/engine/structures/lock.rs (1)

607-614: Fix doc inaccuracies and clarify “available” futures/guards

  • Correct “read”→“write” in write_all_available docs.
  • Name guards after the correct futures (…Available).
  • Briefly explain the “never wait” semantics.

Apply:

@@
-    /// Issue a read on all available elements.
+    /// Issue a write on all available elements.
     pub async fn write_all_available(&self) -> Option<AllLockWriteAvailableGuard<U, T>> {
@@
-/// Future returned by AllOrSomeLock::read_all_available().
-///
-/// Should never wait.
+/// Future returned by AllOrSomeLock::read_all_available().
+///
+/// Polls immediately and never waits. Returns:
+/// - Some(AllLockReadAvailableGuard) if at least one entry is readable now
+/// - None if no entries are currently readable.
 struct AllReadAvailable<U: AsUuid, T>(AllOrSomeLock<U, T>);
@@
-/// Guard returned by AllRead future.
+/// Guard returned by the `read_all_available` future.
 pub struct AllLockReadAvailableGuard<U: AsUuid, T: ?Sized>(
@@
-/// Future returned by AllOrSomeLock::write_all_available().
-///
-/// Should never wait.
+/// Future returned by AllOrSomeLock::write_all_available().
+///
+/// Polls immediately and never waits. Returns:
+/// - Some(AllLockWriteAvailableGuard) if at least one entry is writable now
+/// - None if no entries are currently writable.
 struct AllWriteAvailable<U: AsUuid, T>(AllOrSomeLock<U, T>);
@@
-/// Guard returned by AllWrite future.
+/// Guard returned by the `write_all_available` future.
 pub struct AllLockWriteAvailableGuard<U: AsUuid, T: ?Sized>(

Also applies to: 1093-1119, 1121-1127, 1449-1455, 1477-1483

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b803c0 and 8564b5e.

📒 Files selected for processing (5)
  • src/engine/engine.rs (2 hunks)
  • src/engine/sim_engine/engine.rs (2 hunks)
  • src/engine/strat_engine/engine.rs (5 hunks)
  • src/engine/structures/lock.rs (6 hunks)
  • src/engine/structures/mod.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/engine/engine.rs
🧰 Additional context used
🧬 Code graph analysis (3)
src/engine/sim_engine/engine.rs (2)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (760-762)
  • pools_mut (210-212)
  • pools_mut (764-766)
  • available_pools_mut (214-218)
  • available_pools_mut (768-770)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/strat_engine/engine.rs (2)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
src/engine/structures/lock.rs (1)
src/engine/structures/table.rs (11)
  • default (52-57)
  • next (71-75)
  • next (94-98)
  • next (117-121)
  • get_by_uuid (212-216)
  • get_by_name (205-209)
  • iter (182-186)
  • len (178-180)
  • get_mut_by_uuid (227-231)
  • get_mut_by_name (219-224)
  • iter_mut (188-192)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (22)
  • GitHub Check: testing-farm:fedora-43-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-41-x86_64:local
  • GitHub Check: testing-farm:fedora-43-x86_64:local
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:local
  • GitHub Check: testing-farm:fedora-42-x86_64:local
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: checks (make -f Makefile build-min, 1.90.0, cargo)
  • GitHub Check: checks (make -f Makefile build-no-ipc, 1.90.0, cargo)
  • GitHub Check: checks (make -f Makefile build-no-ipc, 1.90.0, cargo)
  • GitHub Check: checks (make -f Makefile build, 1.90.0, cargo)
  • GitHub Check: checks (make -f Makefile build, 1.90.0, cargo)
  • GitHub Check: checks (make -f Makefile test, 1.90.0, cargo)
  • GitHub Check: checks (make -f Makefile clippy, 1.90.0, clippy)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-min-no-systemd, 1.90.0, cargo)
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.90.0, cargo)
  • GitHub Check: checks (make -f Makefile clippy, 1.90.0, clippy)
  • GitHub Check: checks_with_tang_should_fail (TANG_URL=localhost make -f Makefile test-clevis-loop-should-fail-ro...
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.90.0, cargo)
🔇 Additional comments (9)
src/engine/structures/mod.rs (1)

10-12: Re-exports look good

New available-guard types are correctly surfaced alongside existing guards.

src/engine/sim_engine/engine.rs (2)

23-26: Imports updated correctly

Available-guard types are imported from structures; matches new API.


305-307: Non-blocking available accessors LGTM

Forwarding to read_all_available/write_all_available and converting to dyn is correct.

Also applies to: 313-315

src/engine/strat_engine/engine.rs (3)

37-40: Imports updated correctly

Available-guard types added; consistent with module changes.


206-209: Inherent available methods LGTM*

They correctly forward to read_all_available/write_all_available.

Also applies to: 214-219


311-314: Timer path uses available write guards correctly

Early-return on None prevents blocking and maintains per-element write locks via into(); good.

Also applies to: 345-348

src/engine/structures/lock.rs (3)

360-372: Available lock acquisition logic LGTM

Non-conflicting scan + per-element lock accounting is sound for both read and write paths; futures never wait as intended.

Also applies to: 374-386, 577-584


1228-1245: Drop semantics for available guards are correct

Per-element lock removal on drop matches acquisition; wake() usage is appropriate.

Also applies to: 1609-1625


1484-1500: Conversion to per-element write guards LGTM

Suppressing drop and not double-counting locks is correct.

@jbaublitz jbaublitz force-pushed the issue-stratisd-3837 branch from 8564b5e to 270c5d1 Compare November 5, 2025 01:32
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/engine/structures/lock.rs (2)

374-386: Fix documentation comment.

Apply this diff to correct the spelling:

-    /// Get a list of all writeable pools in the current lock.
+    /// Get a list of all writable pools in the current lock.
     fn get_available_write<T>(&mut self, inner: &mut Table<U, T>) -> Table<U, *mut T> {

607-613: Fix incorrect documentation.

The doc comment incorrectly states "read" when it should say "write".

Apply this diff:

-    /// Issue a read on all available elements.
+    /// Issue a write on all available elements.
     pub async fn write_all_available(&self) -> Option<AllLockWriteAvailableGuard<U, T>> {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8564b5e and 270c5d1.

📒 Files selected for processing (5)
  • src/engine/engine.rs (2 hunks)
  • src/engine/sim_engine/engine.rs (2 hunks)
  • src/engine/strat_engine/engine.rs (5 hunks)
  • src/engine/structures/lock.rs (6 hunks)
  • src/engine/structures/mod.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-20T02:37:22.477Z
Learnt from: jbaublitz
Repo: stratis-storage/stratisd PR: 3651
File: src/dbus_api/pool/pool_3_9/methods.rs:173-176
Timestamp: 2025-08-20T02:37:22.477Z
Learning: The encrypt_pool, reencrypt_pool, and decrypt_pool methods in src/dbus_api/pool/pool_3_9/methods.rs have intentionally different lock handling patterns due to different workflow requirements, and this is not an inconsistency that needs to be fixed.

Applied to files:

  • src/engine/sim_engine/engine.rs
  • src/engine/engine.rs
  • src/engine/strat_engine/engine.rs
  • src/engine/structures/lock.rs
📚 Learning: 2025-09-05T14:32:42.528Z
Learnt from: jbaublitz
Repo: stratis-storage/stratisd PR: 3898
File: src/engine/sim_engine/pool.rs:934-948
Timestamp: 2025-09-05T14:32:42.528Z
Learning: In src/engine/sim_engine/pool.rs, the start_encrypt_pool method intentionally creates a SizedKeyMemory with 1 byte allocated but 0 length reported - this is to avoid NULL pointer issues while simulating an empty key slice in the sim engine.

Applied to files:

  • src/engine/sim_engine/engine.rs
  • src/engine/engine.rs
🧬 Code graph analysis (4)
src/engine/sim_engine/engine.rs (2)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (760-762)
  • pools_mut (210-212)
  • pools_mut (764-766)
  • available_pools_mut (214-218)
  • available_pools_mut (768-770)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/engine.rs (2)
src/engine/strat_engine/engine.rs (6)
  • available_pools (206-208)
  • available_pools (760-762)
  • pools_mut (210-212)
  • pools_mut (764-766)
  • available_pools_mut (214-218)
  • available_pools_mut (768-770)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
src/engine/strat_engine/engine.rs (2)
src/engine/engine.rs (3)
  • available_pools (514-514)
  • pools_mut (517-517)
  • available_pools_mut (521-521)
src/engine/sim_engine/engine.rs (3)
  • available_pools (305-307)
  • pools_mut (309-311)
  • available_pools_mut (313-315)
src/engine/structures/lock.rs (1)
src/engine/structures/table.rs (11)
  • default (52-57)
  • next (71-75)
  • next (94-98)
  • next (117-121)
  • get_by_uuid (212-216)
  • get_by_name (205-209)
  • iter (182-186)
  • len (178-180)
  • get_mut_by_uuid (227-231)
  • get_mut_by_name (219-224)
  • iter_mut (188-192)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:cockpit
  • GitHub Check: testing-farm:fedora-43-x86_64:local
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:local
  • GitHub Check: testing-farm:fedora-41-x86_64:local
  • GitHub Check: testing-farm:fedora-42-x86_64:local
  • GitHub Check: rpm-build:fedora-43-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-42-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-rawhide-x86_64:copr_pull
  • GitHub Check: rpm-build:fedora-41-x86_64:copr_pull
  • GitHub Check: checks (make -f Makefile build-no-ipc, 1.91.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build, 1.91.0, cargo)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-min-no-systemd, 1.91.0, cargo)
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.91.0, cargo)
  • GitHub Check: python-based-tests
  • GitHub Check: stratis-min-cli-checks
  • GitHub Check: shell-checks
  • GitHub Check: stratis-cli-checks
  • GitHub Check: checks (make -f Makefile fmt-ci, 1.91.0, rustfmt)
  • GitHub Check: checks (PROFILEDIR=debug make -f Makefile build-min, 1.91.0, cargo)
  • GitHub Check: checks (make -f Makefile clippy, 1.91.0, clippy)
  • GitHub Check: checks_with_tang_should_fail (TANG_URL=localhost make -f Makefile test-clevis-loop-should-fail-ro...
  • GitHub Check: checks_with_udev (RUST_LOG=stratisd=debug make -f Makefile test-loop-root, 1.91.0, cargo)
🔇 Additional comments (17)
src/engine/structures/mod.rs (1)

10-12: LGTM!

The re-exports for the new availability guard types are correctly placed and follow the existing pattern.

src/engine/sim_engine/engine.rs (2)

305-307: LGTM!

The available_pools implementation correctly forwards to read_all_available and converts to the dynamic trait object, following the same pattern as the existing pools method.


313-315: LGTM!

The available_pools_mut implementation correctly forwards to write_all_available and converts to the dynamic trait object, consistent with the existing pools_mut method.

src/engine/engine.rs (2)

512-514: LGTM!

The trait method signature and documentation clearly communicate the non-blocking availability-based semantics.


519-521: LGTM!

The trait method signature and documentation are clear and consistent with the read variant.

src/engine/strat_engine/engine.rs (6)

206-208: LGTM!

The inherent method correctly delegates to the underlying lock's read_all_available method.


214-218: LGTM!

The inherent method correctly delegates to the underlying lock's write_all_available method.


311-314: LGTM!

The timer-based pool event handler correctly uses write_all_available to avoid blocking and early-returns an empty map when no pools are available. This prevents unnecessary waiting in the timer thread.


345-348: LGTM!

The timer-based filesystem event handler correctly uses write_all_available with the same early-return pattern, maintaining consistency with the pool event handler.


760-762: LGTM!

The trait implementation correctly calls the inherent method (Rust prioritizes inherent methods over trait methods for concrete types) and converts from AnyPool to dyn Pool. This is the intended bridging pattern.


768-770: LGTM!

The trait implementation follows the same correct pattern as available_pools, bridging from the concrete AnyPool type to the trait object.

src/engine/structures/lock.rs (6)

360-372: LGTM!

The helper correctly identifies available pools for reading by checking that they don't conflict with existing locks or woken waiters, and pre-acquires read locks for each available pool.


577-583: LGTM!

The method correctly provides non-blocking access to all currently available pools for reading.


1093-1119: LGTM!

The future correctly implements synchronous polling (never waits) and returns None when no pools are available or Some(guard) when at least one pool is available for reading.


1121-1244: LGTM!

The guard implementation correctly:

  • Provides iteration and lookup methods
  • Converts to dynamic trait objects via into_dyn
  • Releases per-element read locks in Drop (lines 1237-1239)

1449-1475: LGTM!

The write variant follows the same synchronous polling pattern as the read variant and correctly acquires per-element write locks.


1477-1625: LGTM!

The write guard implementation correctly:

  • Acquires per-element write locks via get_available_write
  • Provides mutation methods and iteration
  • Properly converts to Vec<SomeLockWriteGuard> without duplicating locks (sets self.2 = false)
  • Releases per-element write locks in Drop (lines 1618-1620)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

No open projects
Status: Done(1)

Development

Successfully merging this pull request may close these issues.

1 participant