diff --git a/crates/ecstore/src/set_disk/ctx.rs b/crates/ecstore/src/set_disk/ctx.rs new file mode 100644 index 000000000..9c601232f --- /dev/null +++ b/crates/ecstore/src/set_disk/ctx.rs @@ -0,0 +1,100 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Borrow context for the `SetDisks` split (tracking #815, phase P0 #816). +//! +//! `SetDisks` stays the single owner of the shared set state — topology/config +//! (immutable after construction), `disks`, the locker trio, and the moka +//! caches. Operation-family service units introduced in later phases borrow +//! that state through [`SetDisksCtx`] instead of copying it, so no state is +//! duplicated as trait impls move out. +//! +//! This module only establishes the borrow handle. It moves no trait impl and +//! changes no runtime behavior. + +use super::*; + +/// Lightweight, `Copy` handle borrowing the shared [`SetDisks`] core state. +/// +/// Accessors return references tied to the borrowed core's lifetime, so an +/// operation-family unit can read topology, disks, and lockers without holding +/// its own copy. Anything not yet exposed through a typed accessor is reachable +/// via [`SetDisksCtx::core`]. +#[derive(Clone, Copy)] +pub(crate) struct SetDisksCtx<'a> { + core: &'a SetDisks, +} + +impl<'a> SetDisksCtx<'a> { + pub(crate) fn new(core: &'a SetDisks) -> Self { + Self { core } + } + + /// The borrowed core, for state not yet fronted by a typed accessor. + pub(crate) fn core(&self) -> &'a SetDisks { + self.core + } + + // --- Immutable topology / config (fixed after construction) --- + + pub(crate) fn set_index(&self) -> usize { + self.core.set_index + } + + pub(crate) fn pool_index(&self) -> usize { + self.core.pool_index + } + + pub(crate) fn set_drive_count(&self) -> usize { + self.core.set_drive_count + } + + pub(crate) fn default_parity_count(&self) -> usize { + self.core.default_parity_count + } + + pub(crate) fn set_endpoints(&self) -> &'a [Endpoint] { + &self.core.set_endpoints + } + + pub(crate) fn format(&self) -> &'a FormatV3 { + &self.core.format + } + + pub(crate) fn locker_owner(&self) -> &'a str { + &self.core.locker_owner + } + + // --- Shared mutable state (behind their own synchronization) --- + + pub(crate) fn disks(&self) -> &'a Arc>>> { + &self.core.disks + } + + // --- Locker trio --- + + pub(crate) fn lockers(&self) -> &'a [Arc] { + &self.core.lockers + } +} + +impl SetDisks { + /// Borrow this set's shared core state through a lightweight handle. + /// + /// Foundation for the operation-family split (#815 / #816): service units + /// take a [`SetDisksCtx`] rather than owning duplicated state. + pub(crate) fn ctx(&self) -> SetDisksCtx<'_> { + SetDisksCtx::new(self) + } +} diff --git a/crates/ecstore/src/set_disk/list.rs b/crates/ecstore/src/set_disk/list.rs index 2fba03a7b..2cb19fb2f 100644 --- a/crates/ecstore/src/set_disk/list.rs +++ b/crates/ecstore/src/set_disk/list.rs @@ -12,12 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::ctx::SetDisksCtx; use super::*; impl SetDisks { #[tracing::instrument(skip(self))] pub async fn delete_all(&self, bucket: &str, prefix: &str) -> Result<()> { - let disks = self.disks.read().await; + ListOperations::new(self.ctx()).delete_all(bucket, prefix).await + } +} + +/// List/prefix maintenance operations, borrowing the `SetDisks` core state +/// through [`SetDisksCtx`]. +/// +/// First validation of the borrow pattern for the SetDisks split (#816). The +/// behavior here is byte-for-byte identical to the previous inherent +/// `SetDisks::delete_all`; only state access moves from `self` to the borrow +/// handle. +pub(crate) struct ListOperations<'a> { + ctx: SetDisksCtx<'a>, +} + +impl<'a> ListOperations<'a> { + pub(crate) fn new(ctx: SetDisksCtx<'a>) -> Self { + Self { ctx } + } + + pub(crate) async fn delete_all(&self, bucket: &str, prefix: &str) -> Result<()> { + let disks = self.ctx.disks().read().await; let disks = disks.clone(); diff --git a/crates/ecstore/src/set_disk/lock.rs b/crates/ecstore/src/set_disk/lock.rs index d7f748822..00090d767 100644 --- a/crates/ecstore/src/set_disk/lock.rs +++ b/crates/ecstore/src/set_disk/lock.rs @@ -654,4 +654,67 @@ mod tests { drop(temp_dirs); } + + // SetDisks split P0 (#816): the borrow handle must mirror the core state and + // the List operation family must run identically through it. + #[tokio::test] + async fn set_disks_ctx_mirrors_core_and_drives_list_operations() { + use crate::set_disk::list::ListOperations; + + let disk_count = 4; + let format = FormatV3::new(1, disk_count); + + let mut temp_dirs = Vec::with_capacity(disk_count); + let mut endpoints = Vec::with_capacity(disk_count); + let mut disks = Vec::with_capacity(disk_count); + + for disk_idx in 0..disk_count { + let (temp_dir, endpoint, disk) = make_formatted_local_disk(disk_idx, &format).await; + temp_dirs.push(temp_dir); + endpoints.push(endpoint); + disks.push(Some(disk)); + } + + let set_disks = SetDisks::new( + "test-owner".to_string(), + Arc::new(RwLock::new(disks)), + disk_count, + disk_count / 2, + 0, + 0, + endpoints.clone(), + format, + Vec::new(), + ) + .await; + + // The handle reads through to the same core state, not a copy. + let ctx = set_disks.ctx(); + assert_eq!(ctx.locker_owner(), "test-owner"); + assert_eq!(ctx.set_drive_count(), disk_count); + assert_eq!(ctx.default_parity_count(), disk_count / 2); + assert_eq!(ctx.set_index(), 0); + assert_eq!(ctx.pool_index(), 0); + assert_eq!(ctx.set_endpoints().len(), endpoints.len()); + assert!(ctx.lockers().is_empty()); + assert!( + Arc::ptr_eq(ctx.disks(), &set_disks.disks), + "ctx must borrow the core disks, not clone them" + ); + assert!(std::ptr::eq(ctx.core(), &*set_disks), "ctx must borrow the core, not clone it"); + assert!(std::ptr::eq(ctx.format(), &set_disks.format), "ctx must borrow the core format"); + + // The List family runs through the borrow handle with unchanged + // behavior: delete_all reports success even when the prefix is absent. + ListOperations::new(set_disks.ctx()) + .delete_all("nonexistent-bucket", "nonexistent-prefix") + .await + .expect("delete_all via borrow handle should succeed"); + set_disks + .delete_all("nonexistent-bucket", "nonexistent-prefix") + .await + .expect("delete_all public entry should stay in sync"); + + drop(temp_dirs); + } } diff --git a/crates/ecstore/src/set_disk/mod.rs b/crates/ecstore/src/set_disk/mod.rs index 82f8f9d04..790685cb7 100644 --- a/crates/ecstore/src/set_disk/mod.rs +++ b/crates/ecstore/src/set_disk/mod.rs @@ -443,6 +443,7 @@ const DEFAULT_RUSTFS_GET_METADATA_VERSION_EARLY_STOP_ENABLE: bool = false; static OBJECT_LOCK_DIAG_ENABLED: OnceLock = OnceLock::new(); +mod ctx; mod heal; mod list; mod lock;