fix(ecstore): make body-cache hook re-registrable + add e2e regressions ODC-21 (backlog#1126): the GET body-cache hook lived in a first-wins OnceLock. When AppContext is rebuilt (config reload, test re-init) a fresh ObjectDataCacheAdapter is constructed and re-registered, but the OnceLock kept ecstore's GET probe pointed at adapter #1 while every usecase-layer fill and invalidation targeted adapter #2 — silently degrading the feature to a 0% hit rate with no error, log, or metric, and stranding entries in the unreachable cache until their TTL. Replace the slot with RwLock<Option<Arc<dyn GetObjectBodyCacheHook>>> so re-registration atomically swaps to the newest adapter, and log at WARN when a swap replaces a *different* instance (Arc::ptr_eq). RwLock over ArcSwapOption because arc-swap's RefCnt is impl<T> (Sized, thin *mut T) and cannot hold an Arc<dyn Trait> without a sized newtype wrapper; the probe reads the slot once per full-object GET but only clones an Arc, negligible next to the metadata quorum fan-out already done before the probe. Add a test-only clear_get_object_body_cache_hook so tests register/unregister deterministically. With the hook now re-registrable, add true end-to-end regressions that drive get_object_reader (not the full_object_plaintext_len predicate) against a real erasure-coded, genuinely-compressed object via the blackbox make_local_set_disks harness, with a stand-in hook playing the app-layer cache (the injection point production uses; the adapter itself lives above ecstore). These close the gap the predicate-only tests left — a caller that opens a new shortcut serving the cached body directly, the original form of both P0s: - backlog#1108: a raw_data_movement_read must yield the STORED (compressed) bytes, never the cached plaintext. - backlog#1109: a compressed cache hit must publish the DECOMPRESSED length as object_info.size (the UploadPartCopy invariant), with the streamed length matching. - backlog#1146: a restore read (restore_request.days) must serve STORED bytes, not the cache. Mutation-verified each e2e test bites: dropping the raw_data_movement_read gate serves plaintext (fails #1108); removing the hit-site size republication publishes 2972 vs 660000 (fails #1109); dropping the restore gate serves plaintext (fails #1146). Co-authored-by: heihutu <[email protected]>
94 lines
2.8 KiB
Rust
94 lines
2.8 KiB
Rust
// 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.
|
|
|
|
/// Scope-based hotpath measurement for `#[async_trait]` methods, where
|
|
/// `#[cfg_attr(feature = "hotpath", hotpath::measure)]` would only time the boxed-future construction.
|
|
/// The guard records wall time from this statement until the enclosing
|
|
/// (desugared) async block completes, including early returns via `?`.
|
|
#[cfg(feature = "hotpath")]
|
|
#[macro_export]
|
|
macro_rules! hp_guard {
|
|
($label:expr) => {
|
|
let _hotpath_scope_guard = ::hotpath::functions::build_measurement_guard_sync($label, false);
|
|
};
|
|
}
|
|
|
|
#[cfg(not(feature = "hotpath"))]
|
|
#[macro_export]
|
|
macro_rules! hp_guard {
|
|
($label:expr) => {};
|
|
}
|
|
|
|
pub mod api;
|
|
mod bucket;
|
|
mod cache_value;
|
|
mod cluster;
|
|
mod config;
|
|
mod core;
|
|
mod data_movement;
|
|
mod data_usage;
|
|
mod diagnostics;
|
|
mod disk;
|
|
mod erasure;
|
|
mod error;
|
|
mod io_support;
|
|
pub(crate) mod layout;
|
|
mod object_api;
|
|
mod runtime;
|
|
mod services;
|
|
mod set_disk;
|
|
mod storage_api_contracts;
|
|
mod store;
|
|
|
|
// pub mod checksum;
|
|
mod client;
|
|
mod event;
|
|
|
|
use rustfs_concurrency::WorkloadAdmissionSnapshotProvider;
|
|
use std::sync::Arc;
|
|
|
|
pub type WorkloadAdmissionSnapshotProviderRef = Arc<dyn WorkloadAdmissionSnapshotProvider + Send + Sync>;
|
|
|
|
pub fn set_workload_admission_snapshot_provider(
|
|
provider: WorkloadAdmissionSnapshotProviderRef,
|
|
) -> std::result::Result<(), WorkloadAdmissionSnapshotProviderRef> {
|
|
runtime::sources::set_workload_admission_snapshot_provider(provider)
|
|
}
|
|
|
|
/// Request shutdown of all long-lived peer/disk background monitor tasks.
|
|
///
|
|
/// Call this during graceful shutdown, *before* the Tokio runtime is dropped, so
|
|
/// each monitor future (and the `tracing::Span` it holds) is dropped while the
|
|
/// runtime and tracing subscriber are still alive. This avoids the
|
|
/// thread-local-storage `on_close` panic that can otherwise abort the process
|
|
/// during worker-thread teardown (issue #4264). Idempotent and cheap.
|
|
pub fn shutdown_background_monitors() {
|
|
cluster::rpc::shutdown_background_monitors();
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod rio_tests {
|
|
#[test]
|
|
fn uses_expected_rio_backend() {
|
|
let expected = if cfg!(feature = "rio-v2") { "rio-v2" } else { "legacy-rio" };
|
|
assert_eq!(crate::io_support::rio::backend_name(), expected);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) mod ecstore_validation_blackbox;
|
|
|
|
#[cfg(test)]
|
|
pub(crate) mod test_metrics;
|