mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-11 07:36:53 +00:00
refactor: converge storage io hot paths (#3029)
* refactor(issue-633): clarify layered io control policies * refactor(issue-633): consolidate timeout and deadlock layers * refactor(issue-633): align storage backpressure metadata * refactor(issue-633): unify storage backpressure transitions * refactor(issue-633): simplify watermark transition API * test(issue-633): add storage backpressure transition test * refactor(issue-633): align storage pipe meta shape * refactor(issue-633): enrich storage monitor metadata * refactor(issue-633): finalize storage backpressure convergence * refactor(issue-633): complete scheduler layer convergence * refactor(issue-633): reduce concurrency facade config duplication * refactor(issue-633): migrate storage callsites to final policy names * chore(issue-633): apply final pre-commit normalization * refactor(issue-633): unify timeout wrapper dynamic size path * refactor(issue-633): make concurrency policies copyable * refactor(issue-633): converge storage io hot paths * fix(issue-633): honor storage timeout min bound * fix(storage): avoid timeout calc panic on huge sizes * refactor(storage): consolidate timeout checks and test attrs * fix(storage): harden io scheduler core config mapping * refactor(storage): eliminate patch-on-patch patterns and dead code - Remove trivial accessor methods on ConcurrencyConfig that just return pub fields - Remove dead BackpressureEvent/BackpressureEventType types from concurrency crate - Fix io_schedule test using wrong constructor (from_core_config -> from_scheduler_config) - Update manager.rs to use config fields directly instead of removed accessors * fix: adopt review feedback for config guards * test: remove needless struct update defaults * fix: harden timeout policy and preserve api alias
This commit is contained in:
@@ -14,15 +14,17 @@
|
||||
|
||||
//! Backpressure management
|
||||
|
||||
use rustfs_io_core::{BackpressureMonitor as CoreBackpressureMonitor, BackpressureState};
|
||||
use rustfs_io_core::{
|
||||
BackpressureConfig as CoreBackpressureConfig, BackpressureMonitor as CoreBackpressureMonitor, BackpressureState,
|
||||
};
|
||||
use rustfs_io_metrics::backpressure_metrics;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tokio::io::{DuplexStream, duplex};
|
||||
|
||||
/// Backpressure configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BackpressureConfig {
|
||||
/// Facade policy for duplex-pipe watermark backpressure.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PipeBackpressurePolicy {
|
||||
/// Buffer size in bytes
|
||||
pub buffer_size: usize,
|
||||
/// High watermark percentage
|
||||
@@ -31,7 +33,7 @@ pub struct BackpressureConfig {
|
||||
pub low_watermark: u32,
|
||||
}
|
||||
|
||||
impl Default for BackpressureConfig {
|
||||
impl Default for PipeBackpressurePolicy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buffer_size: 4 * 1024 * 1024, // 4MB
|
||||
@@ -41,7 +43,7 @@ impl Default for BackpressureConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl BackpressureConfig {
|
||||
impl PipeBackpressurePolicy {
|
||||
/// Calculate high watermark threshold in bytes
|
||||
pub fn high_watermark_bytes(&self) -> usize {
|
||||
(self.buffer_size as u64 * self.high_watermark as u64 / 100) as usize
|
||||
@@ -51,42 +53,59 @@ impl BackpressureConfig {
|
||||
pub fn low_watermark_bytes(&self) -> usize {
|
||||
(self.buffer_size as u64 * self.low_watermark as u64 / 100) as usize
|
||||
}
|
||||
|
||||
/// Convert the facade policy into the reusable io-core admission-pressure config.
|
||||
///
|
||||
/// The concurrency layer still owns duplex buffer sizing, but the shared
|
||||
/// overload/admission primitive lives in `io-core`.
|
||||
pub fn to_core_config(&self) -> CoreBackpressureConfig {
|
||||
CoreBackpressureConfig {
|
||||
max_concurrent: 32,
|
||||
high_water_mark: self.high_watermark as f64 / 100.0,
|
||||
low_water_mark: self.low_watermark as f64 / 100.0,
|
||||
cooldown: std::time::Duration::from_millis(100),
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Backpressure manager
|
||||
pub struct BackpressureManager {
|
||||
config: BackpressureConfig,
|
||||
config: PipeBackpressurePolicy,
|
||||
core_config: CoreBackpressureConfig,
|
||||
monitor: Arc<CoreBackpressureMonitor>,
|
||||
}
|
||||
|
||||
impl BackpressureManager {
|
||||
/// Create a new backpressure manager
|
||||
pub fn new(buffer_size: usize, high_watermark: u32, low_watermark: u32) -> Self {
|
||||
let config = BackpressureConfig {
|
||||
Self::from_policy(PipeBackpressurePolicy {
|
||||
buffer_size,
|
||||
high_watermark,
|
||||
low_watermark,
|
||||
};
|
||||
|
||||
let core_config = rustfs_io_core::BackpressureConfig {
|
||||
max_concurrent: 32,
|
||||
high_water_mark: high_watermark as f64 / 100.0,
|
||||
low_water_mark: low_watermark as f64 / 100.0,
|
||||
cooldown: std::time::Duration::from_millis(100),
|
||||
enabled: true,
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new backpressure manager from the facade policy type.
|
||||
pub fn from_policy(config: PipeBackpressurePolicy) -> Self {
|
||||
let core_config = config.to_core_config();
|
||||
Self {
|
||||
config,
|
||||
core_config: core_config.clone(),
|
||||
monitor: Arc::new(CoreBackpressureMonitor::new(core_config)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the configuration
|
||||
pub fn config(&self) -> &BackpressureConfig {
|
||||
pub fn config(&self) -> &PipeBackpressurePolicy {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Get the derived io-core admission-pressure configuration.
|
||||
pub fn core_config(&self) -> &CoreBackpressureConfig {
|
||||
&self.core_config
|
||||
}
|
||||
|
||||
/// Get the monitor
|
||||
pub fn monitor(&self) -> Arc<CoreBackpressureMonitor> {
|
||||
self.monitor.clone()
|
||||
@@ -94,7 +113,7 @@ impl BackpressureManager {
|
||||
|
||||
/// Create a backpressure pipe
|
||||
pub fn create_pipe(&self) -> BackpressurePipe {
|
||||
BackpressurePipe::new(self.config.clone(), self.monitor.clone())
|
||||
BackpressurePipe::new(self.config, self.monitor.clone())
|
||||
}
|
||||
|
||||
/// Get current state
|
||||
@@ -112,13 +131,24 @@ impl BackpressureManager {
|
||||
pub struct BackpressurePipe {
|
||||
reader: DuplexStream,
|
||||
writer: DuplexStream,
|
||||
config: BackpressureConfig,
|
||||
config: PipeBackpressurePolicy,
|
||||
monitor: Arc<CoreBackpressureMonitor>,
|
||||
created_at: Instant,
|
||||
}
|
||||
|
||||
/// Shared pipe metadata snapshot for facade-level backpressure pipes.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct BackpressurePipeMeta {
|
||||
/// Configured duplex buffer capacity in bytes.
|
||||
pub buffer_capacity: usize,
|
||||
/// Current backpressure state reported by the shared core monitor.
|
||||
pub state: BackpressureState,
|
||||
/// Age of the pipe since creation.
|
||||
pub age: std::time::Duration,
|
||||
}
|
||||
|
||||
impl BackpressurePipe {
|
||||
fn new(config: BackpressureConfig, monitor: Arc<CoreBackpressureMonitor>) -> Self {
|
||||
fn new(config: PipeBackpressurePolicy, monitor: Arc<CoreBackpressureMonitor>) -> Self {
|
||||
let (reader, writer) = duplex(config.buffer_size);
|
||||
|
||||
Self {
|
||||
@@ -146,7 +176,7 @@ impl BackpressurePipe {
|
||||
}
|
||||
|
||||
/// Get the configuration
|
||||
pub fn config(&self) -> &BackpressureConfig {
|
||||
pub fn config(&self) -> &PipeBackpressurePolicy {
|
||||
&self.config
|
||||
}
|
||||
|
||||
@@ -160,6 +190,15 @@ impl BackpressurePipe {
|
||||
self.created_at.elapsed()
|
||||
}
|
||||
|
||||
/// Get a compact metadata snapshot for the pipe.
|
||||
pub fn meta(&self) -> BackpressurePipeMeta {
|
||||
BackpressurePipeMeta {
|
||||
buffer_capacity: self.config.buffer_size,
|
||||
state: self.state(),
|
||||
age: self.age(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if should apply backpressure
|
||||
pub fn should_apply_backpressure(&self) -> bool {
|
||||
let should = self.monitor.should_apply_backpressure();
|
||||
@@ -170,45 +209,26 @@ impl BackpressurePipe {
|
||||
}
|
||||
}
|
||||
|
||||
/// Backpressure event
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BackpressureEvent {
|
||||
/// Event timestamp
|
||||
pub timestamp: Instant,
|
||||
/// Event type
|
||||
pub event_type: BackpressureEventType,
|
||||
/// Buffer usage
|
||||
pub buffer_usage: usize,
|
||||
/// Buffer capacity
|
||||
pub buffer_capacity: usize,
|
||||
}
|
||||
|
||||
/// Backpressure event type
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum BackpressureEventType {
|
||||
/// High watermark reached
|
||||
HighWatermarkReached,
|
||||
/// High watermark exited
|
||||
HighWatermarkExited,
|
||||
/// Backpressure applied
|
||||
BackpressureApplied,
|
||||
/// Backpressure released
|
||||
BackpressureReleased,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_backpressure_config() {
|
||||
let config = BackpressureConfig::default();
|
||||
let config = PipeBackpressurePolicy::default();
|
||||
assert_eq!(config.buffer_size, 4 * 1024 * 1024);
|
||||
assert!(config.high_watermark > config.low_watermark);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backpressure_policy_to_core_config() {
|
||||
let policy = PipeBackpressurePolicy::default();
|
||||
let core = policy.to_core_config();
|
||||
assert_eq!(core.high_water_mark, policy.high_watermark as f64 / 100.0);
|
||||
assert_eq!(core.low_water_mark, policy.low_watermark as f64 / 100.0);
|
||||
assert!(core.enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backpressure_manager() {
|
||||
let manager = BackpressureManager::new(1024, 80, 50);
|
||||
@@ -220,5 +240,6 @@ mod tests {
|
||||
let manager = BackpressureManager::new(1024, 80, 50);
|
||||
let pipe = manager.create_pipe();
|
||||
assert_eq!(pipe.state(), BackpressureState::Normal);
|
||||
assert_eq!(pipe.meta().buffer_capacity, 1024);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
|
||||
//! Configuration for concurrency management
|
||||
|
||||
use crate::{
|
||||
backpressure::PipeBackpressurePolicy, deadlock::DeadlockMonitorPolicy, scheduler::SchedulerPolicy,
|
||||
timeout::TimeoutManagerPolicy,
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Feature flags for concurrency modules
|
||||
@@ -72,84 +76,39 @@ impl ConcurrencyFeatures {
|
||||
}
|
||||
}
|
||||
|
||||
/// Facade policy for lock manager behavior.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LockManagerPolicy {
|
||||
/// Enable lock optimization.
|
||||
pub enabled: bool,
|
||||
/// Lock acquisition timeout.
|
||||
pub acquire_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Default for LockManagerPolicy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
acquire_timeout: Duration::from_secs(5),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Main configuration for concurrency management
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ConcurrencyConfig {
|
||||
/// Feature flags
|
||||
pub features: ConcurrencyFeatures,
|
||||
|
||||
// Timeout configuration
|
||||
/// Default timeout duration
|
||||
pub default_timeout: Duration,
|
||||
/// Maximum timeout duration
|
||||
pub max_timeout: Duration,
|
||||
/// Enable dynamic timeout
|
||||
pub enable_dynamic_timeout: bool,
|
||||
|
||||
// Lock configuration
|
||||
/// Enable lock optimization
|
||||
pub enable_lock_optimization: bool,
|
||||
/// Lock acquisition timeout
|
||||
pub lock_acquire_timeout: Duration,
|
||||
|
||||
// Deadlock configuration
|
||||
/// Enable deadlock detection
|
||||
pub enable_deadlock_detection: bool,
|
||||
/// Deadlock check interval
|
||||
pub deadlock_check_interval: Duration,
|
||||
/// Hang threshold
|
||||
pub hang_threshold: Duration,
|
||||
|
||||
// Backpressure configuration
|
||||
/// Buffer size for backpressure
|
||||
pub backpressure_buffer_size: usize,
|
||||
/// High watermark percentage
|
||||
pub high_watermark: u32,
|
||||
/// Low watermark percentage
|
||||
pub low_watermark: u32,
|
||||
|
||||
// Scheduler configuration
|
||||
/// Base buffer size for I/O
|
||||
pub io_buffer_size: usize,
|
||||
/// Maximum buffer size
|
||||
pub max_buffer_size: usize,
|
||||
/// High priority size threshold
|
||||
pub high_priority_threshold: usize,
|
||||
/// Low priority size threshold
|
||||
pub low_priority_threshold: usize,
|
||||
}
|
||||
|
||||
impl Default for ConcurrencyConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
features: ConcurrencyFeatures::default(),
|
||||
|
||||
// Timeout defaults
|
||||
default_timeout: Duration::from_secs(30),
|
||||
max_timeout: Duration::from_secs(300),
|
||||
enable_dynamic_timeout: true,
|
||||
|
||||
// Lock defaults
|
||||
enable_lock_optimization: true,
|
||||
lock_acquire_timeout: Duration::from_secs(5),
|
||||
|
||||
// Deadlock defaults
|
||||
enable_deadlock_detection: false,
|
||||
deadlock_check_interval: Duration::from_secs(10),
|
||||
hang_threshold: Duration::from_secs(60),
|
||||
|
||||
// Backpressure defaults
|
||||
backpressure_buffer_size: 4 * 1024 * 1024, // 4MB
|
||||
high_watermark: 80,
|
||||
low_watermark: 50,
|
||||
|
||||
// Scheduler defaults
|
||||
io_buffer_size: 64 * 1024, // 64KB
|
||||
max_buffer_size: 4 * 1024 * 1024, // 4MB
|
||||
high_priority_threshold: 1024 * 1024, // 1MB
|
||||
low_priority_threshold: 10 * 1024 * 1024, // 10MB
|
||||
}
|
||||
}
|
||||
/// Timeout facade policy.
|
||||
pub timeout_policy: TimeoutManagerPolicy,
|
||||
/// Lock facade policy.
|
||||
pub lock_policy: LockManagerPolicy,
|
||||
/// Deadlock facade policy.
|
||||
pub deadlock_policy: DeadlockMonitorPolicy,
|
||||
/// Backpressure facade policy.
|
||||
pub backpressure_policy: PipeBackpressurePolicy,
|
||||
/// Scheduler facade policy.
|
||||
pub scheduler_policy: SchedulerPolicy,
|
||||
}
|
||||
|
||||
impl ConcurrencyConfig {
|
||||
@@ -161,25 +120,25 @@ impl ConcurrencyConfig {
|
||||
if let Ok(val) = std::env::var("RUSTFS_TIMEOUT_DEFAULT")
|
||||
&& let Ok(secs) = val.parse::<u64>()
|
||||
{
|
||||
config.default_timeout = Duration::from_secs(secs);
|
||||
config.timeout_policy.default_timeout = Duration::from_secs(secs);
|
||||
}
|
||||
|
||||
if let Ok(val) = std::env::var("RUSTFS_TIMEOUT_MAX")
|
||||
&& let Ok(secs) = val.parse::<u64>()
|
||||
{
|
||||
config.max_timeout = Duration::from_secs(secs);
|
||||
config.timeout_policy.max_timeout = Duration::from_secs(secs);
|
||||
}
|
||||
|
||||
if let Ok(val) = std::env::var("RUSTFS_BACKPRESSURE_BUFFER_SIZE")
|
||||
&& let Ok(size) = val.parse::<usize>()
|
||||
{
|
||||
config.backpressure_buffer_size = size;
|
||||
config.backpressure_policy.buffer_size = size;
|
||||
}
|
||||
|
||||
if let Ok(val) = std::env::var("RUSTFS_IO_BUFFER_SIZE")
|
||||
&& let Ok(size) = val.parse::<usize>()
|
||||
{
|
||||
config.io_buffer_size = size;
|
||||
config.scheduler_policy.base_buffer_size = size;
|
||||
}
|
||||
|
||||
config
|
||||
@@ -187,18 +146,25 @@ impl ConcurrencyConfig {
|
||||
|
||||
/// Validate configuration
|
||||
pub fn validate(&self) -> Result<(), ConfigError> {
|
||||
if self.default_timeout > self.max_timeout {
|
||||
if self.timeout_policy.default_timeout > self.timeout_policy.max_timeout {
|
||||
return Err(ConfigError::InvalidTimeout("default_timeout cannot exceed max_timeout".to_string()));
|
||||
}
|
||||
if self.timeout_policy.min_timeout > self.timeout_policy.max_timeout {
|
||||
return Err(ConfigError::InvalidTimeout("min_timeout cannot exceed max_timeout".to_string()));
|
||||
}
|
||||
|
||||
if self.high_watermark <= self.low_watermark || self.high_watermark > 100 {
|
||||
if self.backpressure_policy.high_watermark <= self.backpressure_policy.low_watermark
|
||||
|| self.backpressure_policy.high_watermark > 100
|
||||
{
|
||||
return Err(ConfigError::InvalidBackpressure(
|
||||
"high_watermark must be > low_watermark and <= 100".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if self.io_buffer_size > self.max_buffer_size {
|
||||
return Err(ConfigError::InvalidScheduler("io_buffer_size cannot exceed max_buffer_size".to_string()));
|
||||
if self.scheduler_policy.base_buffer_size > self.scheduler_policy.max_buffer_size {
|
||||
return Err(ConfigError::InvalidScheduler(
|
||||
"base_buffer_size cannot exceed max_buffer_size".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -235,8 +201,12 @@ mod tests {
|
||||
#[test]
|
||||
fn test_invalid_timeout() {
|
||||
let config = ConcurrencyConfig {
|
||||
default_timeout: Duration::from_secs(100),
|
||||
max_timeout: Duration::from_secs(50),
|
||||
timeout_policy: TimeoutManagerPolicy {
|
||||
default_timeout: Duration::from_secs(100),
|
||||
max_timeout: Duration::from_secs(50),
|
||||
enable_dynamic: true,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
assert!(
|
||||
@@ -245,6 +215,22 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_min_timeout() {
|
||||
let config = ConcurrencyConfig {
|
||||
timeout_policy: TimeoutManagerPolicy {
|
||||
min_timeout: Duration::from_secs(100),
|
||||
max_timeout: Duration::from_secs(50),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
assert!(
|
||||
config.validate().is_err(),
|
||||
"validate() should return an error when min_timeout > max_timeout"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_features() {
|
||||
let features = ConcurrencyFeatures::all();
|
||||
|
||||
@@ -14,15 +14,15 @@
|
||||
|
||||
//! Deadlock detection management
|
||||
|
||||
use rustfs_io_core::{DeadlockDetector as CoreDeadlockDetector, LockType};
|
||||
use rustfs_io_core::{DeadlockDetector as CoreDeadlockDetector, DeadlockDetectorConfig as CoreDeadlockConfig, LockType};
|
||||
use rustfs_io_metrics::deadlock_metrics;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Deadlock configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeadlockConfig {
|
||||
/// Facade policy for the concurrency-layer deadlock monitor.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DeadlockMonitorPolicy {
|
||||
/// Enable deadlock detection
|
||||
pub enabled: bool,
|
||||
/// Check interval
|
||||
@@ -31,7 +31,7 @@ pub struct DeadlockConfig {
|
||||
pub hang_threshold: Duration,
|
||||
}
|
||||
|
||||
impl Default for DeadlockConfig {
|
||||
impl Default for DeadlockMonitorPolicy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
@@ -41,9 +41,20 @@ impl Default for DeadlockConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl DeadlockMonitorPolicy {
|
||||
/// Convert the facade policy into the reusable io-core deadlock config.
|
||||
pub fn to_core_config(&self) -> CoreDeadlockConfig {
|
||||
CoreDeadlockConfig {
|
||||
enabled: self.enabled,
|
||||
detection_interval: self.check_interval,
|
||||
max_hold_time: self.hang_threshold,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deadlock manager
|
||||
pub struct DeadlockManager {
|
||||
config: DeadlockConfig,
|
||||
config: DeadlockMonitorPolicy,
|
||||
detector: Arc<CoreDeadlockDetector>,
|
||||
running: Arc<tokio::sync::Mutex<bool>>,
|
||||
}
|
||||
@@ -51,18 +62,16 @@ pub struct DeadlockManager {
|
||||
impl DeadlockManager {
|
||||
/// Create a new deadlock manager
|
||||
pub fn new(enabled: bool, check_interval: Duration, hang_threshold: Duration) -> Self {
|
||||
let config = DeadlockConfig {
|
||||
Self::from_policy(DeadlockMonitorPolicy {
|
||||
enabled,
|
||||
check_interval,
|
||||
hang_threshold,
|
||||
};
|
||||
|
||||
let core_config = rustfs_io_core::DeadlockDetectorConfig {
|
||||
enabled,
|
||||
detection_interval: check_interval,
|
||||
max_hold_time: hang_threshold,
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new deadlock manager from the facade policy type.
|
||||
pub fn from_policy(config: DeadlockMonitorPolicy) -> Self {
|
||||
let core_config = config.to_core_config();
|
||||
Self {
|
||||
config,
|
||||
detector: Arc::new(CoreDeadlockDetector::new(core_config)),
|
||||
@@ -71,7 +80,7 @@ impl DeadlockManager {
|
||||
}
|
||||
|
||||
/// Get the configuration
|
||||
pub fn config(&self) -> &DeadlockConfig {
|
||||
pub fn config(&self) -> &DeadlockMonitorPolicy {
|
||||
&self.config
|
||||
}
|
||||
|
||||
@@ -129,7 +138,11 @@ impl DeadlockManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Request tracker for tracking resources
|
||||
/// Lightweight compatibility wrapper for request-scoped deadlock bookkeeping.
|
||||
///
|
||||
/// This type intentionally stays minimal in the concurrency layer. Rich
|
||||
/// request-level lock/resource diagnostics belong to
|
||||
/// `rustfs::storage::deadlock_detector::RequestResourceTracker`.
|
||||
pub struct RequestTracker {
|
||||
request_id: String,
|
||||
description: String,
|
||||
@@ -174,6 +187,11 @@ impl RequestTracker {
|
||||
deadlock_metrics::record_lock_acquisition("read");
|
||||
}
|
||||
|
||||
/// Return a read-only view of tracked resource names.
|
||||
pub fn resources(&self) -> &HashMap<String, Vec<String>> {
|
||||
&self.resources
|
||||
}
|
||||
|
||||
/// Record a lock release
|
||||
pub fn record_lock_release(&mut self, lock_id: u64) {
|
||||
self.detector.record_release(lock_id);
|
||||
@@ -196,12 +214,24 @@ mod tests {
|
||||
assert!(!manager.config().enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deadlock_policy_to_core_config() {
|
||||
let policy = DeadlockMonitorPolicy::default();
|
||||
let core = policy.to_core_config();
|
||||
assert_eq!(core.enabled, policy.enabled);
|
||||
assert_eq!(core.detection_interval, policy.check_interval);
|
||||
assert_eq!(core.max_hold_time, policy.hang_threshold);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_request_tracker() {
|
||||
let manager = DeadlockManager::new(true, Duration::from_secs(10), Duration::from_secs(60));
|
||||
let tracker = manager.track_request("req-1".to_string(), "test request".to_string());
|
||||
let mut tracker = manager.track_request("req-1".to_string(), "test request".to_string());
|
||||
let lock_id = manager.register_lock(LockType::Mutex);
|
||||
tracker.record_lock_acquire(lock_id, "bucket/key".to_string());
|
||||
|
||||
assert_eq!(tracker.request_id(), "req-1");
|
||||
assert_eq!(tracker.description(), "test request");
|
||||
assert_eq!(tracker.resources().get("locks").map(Vec::len), Some(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,19 +128,19 @@ pub mod workers;
|
||||
|
||||
// Public module exports with feature gates
|
||||
#[cfg(feature = "timeout")]
|
||||
pub use timeout::{TimeoutConfig, TimeoutGuard, TimeoutManager};
|
||||
pub use timeout::{TimeoutGuard, TimeoutManager, TimeoutManagerPolicy};
|
||||
|
||||
#[cfg(feature = "lock")]
|
||||
pub use lock::{LockConfig, LockManager, LockScopeGuard, OptimizedLockGuard};
|
||||
|
||||
#[cfg(feature = "deadlock")]
|
||||
pub use deadlock::{DeadlockConfig, DeadlockManager, RequestTracker};
|
||||
pub use deadlock::{DeadlockManager, DeadlockMonitorPolicy, RequestTracker};
|
||||
|
||||
#[cfg(feature = "backpressure")]
|
||||
pub use backpressure::{BackpressureConfig, BackpressureManager, BackpressurePipe};
|
||||
pub use backpressure::{BackpressureManager, BackpressurePipe, PipeBackpressurePolicy};
|
||||
|
||||
#[cfg(feature = "scheduler")]
|
||||
pub use scheduler::{IoStrategy, SchedulerConfig, SchedulerManager};
|
||||
pub use scheduler::{IoStrategy, SchedulerManager, SchedulerPolicy};
|
||||
|
||||
// Configuration
|
||||
mod config;
|
||||
@@ -155,19 +155,19 @@ pub mod prelude {
|
||||
//! Prelude module for convenient imports
|
||||
|
||||
#[cfg(feature = "timeout")]
|
||||
pub use crate::timeout::{TimeoutConfig, TimeoutGuard, TimeoutManager};
|
||||
pub use crate::timeout::{TimeoutGuard, TimeoutManager, TimeoutManagerPolicy};
|
||||
|
||||
#[cfg(feature = "lock")]
|
||||
pub use crate::lock::{LockConfig, LockManager, LockScopeGuard, OptimizedLockGuard};
|
||||
|
||||
#[cfg(feature = "deadlock")]
|
||||
pub use crate::deadlock::{DeadlockConfig, DeadlockManager, RequestTracker};
|
||||
pub use crate::deadlock::{DeadlockManager, DeadlockMonitorPolicy, RequestTracker};
|
||||
|
||||
#[cfg(feature = "backpressure")]
|
||||
pub use crate::backpressure::{BackpressureConfig, BackpressureManager, BackpressurePipe};
|
||||
pub use crate::backpressure::{BackpressureManager, BackpressurePipe, PipeBackpressurePolicy};
|
||||
|
||||
#[cfg(feature = "scheduler")]
|
||||
pub use crate::scheduler::{IoStrategy, SchedulerConfig, SchedulerManager};
|
||||
pub use crate::scheduler::{IoStrategy, SchedulerManager, SchedulerPolicy};
|
||||
|
||||
pub use crate::{ConcurrencyConfig, ConcurrencyFeatures, ConcurrencyManager};
|
||||
}
|
||||
|
||||
@@ -85,39 +85,22 @@ impl ConcurrencyManager {
|
||||
|
||||
Self {
|
||||
#[cfg(feature = "timeout")]
|
||||
timeout: Arc::new(crate::timeout::TimeoutManager::new(
|
||||
config.default_timeout,
|
||||
config.max_timeout,
|
||||
config.enable_dynamic_timeout,
|
||||
)),
|
||||
timeout: Arc::new(crate::timeout::TimeoutManager::from_policy(config.timeout_policy)),
|
||||
|
||||
#[cfg(feature = "lock")]
|
||||
lock: Arc::new(crate::lock::LockManager::new(
|
||||
config.enable_lock_optimization,
|
||||
config.lock_acquire_timeout,
|
||||
config.lock_policy.enabled,
|
||||
config.lock_policy.acquire_timeout,
|
||||
)),
|
||||
|
||||
#[cfg(feature = "deadlock")]
|
||||
deadlock: Arc::new(crate::deadlock::DeadlockManager::new(
|
||||
config.enable_deadlock_detection,
|
||||
config.deadlock_check_interval,
|
||||
config.hang_threshold,
|
||||
)),
|
||||
deadlock: Arc::new(crate::deadlock::DeadlockManager::from_policy(config.deadlock_policy)),
|
||||
|
||||
#[cfg(feature = "backpressure")]
|
||||
backpressure: Arc::new(crate::backpressure::BackpressureManager::new(
|
||||
config.backpressure_buffer_size,
|
||||
config.high_watermark,
|
||||
config.low_watermark,
|
||||
)),
|
||||
backpressure: Arc::new(crate::backpressure::BackpressureManager::from_policy(config.backpressure_policy)),
|
||||
|
||||
#[cfg(feature = "scheduler")]
|
||||
scheduler: Arc::new(crate::scheduler::SchedulerManager::new(
|
||||
config.io_buffer_size,
|
||||
config.max_buffer_size,
|
||||
config.high_priority_threshold,
|
||||
config.low_priority_threshold,
|
||||
)),
|
||||
scheduler: Arc::new(crate::scheduler::SchedulerManager::from_policy(config.scheduler_policy)),
|
||||
|
||||
config,
|
||||
}
|
||||
@@ -244,7 +227,7 @@ impl ConcurrencyManager {
|
||||
pub async fn start(&self) {
|
||||
#[cfg(feature = "deadlock")]
|
||||
{
|
||||
if self.config.enable_deadlock_detection {
|
||||
if self.config.deadlock_policy.enabled {
|
||||
self.deadlock.start().await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ use rustfs_io_metrics::io_metrics;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Scheduler configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SchedulerConfig {
|
||||
/// Facade policy for the concurrency-layer scheduler manager.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SchedulerPolicy {
|
||||
/// Base buffer size
|
||||
pub base_buffer_size: usize,
|
||||
/// Maximum buffer size
|
||||
@@ -35,7 +35,7 @@ pub struct SchedulerConfig {
|
||||
pub low_priority_threshold: usize,
|
||||
}
|
||||
|
||||
impl Default for SchedulerConfig {
|
||||
impl Default for SchedulerPolicy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base_buffer_size: 64 * 1024, // 64KB
|
||||
@@ -46,9 +46,23 @@ impl Default for SchedulerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl SchedulerPolicy {
|
||||
/// Convert facade policy to io-core scheduler config.
|
||||
pub fn to_core_config(&self) -> rustfs_io_core::IoSchedulerConfig {
|
||||
rustfs_io_core::IoSchedulerConfig {
|
||||
base_buffer_size: self.base_buffer_size,
|
||||
max_buffer_size: self.max_buffer_size,
|
||||
high_priority_size_threshold: self.high_priority_threshold,
|
||||
low_priority_size_threshold: self.low_priority_threshold,
|
||||
..rustfs_io_core::IoSchedulerConfig::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scheduler manager
|
||||
pub struct SchedulerManager {
|
||||
config: SchedulerConfig,
|
||||
config: SchedulerPolicy,
|
||||
core_config: rustfs_io_core::IoSchedulerConfig,
|
||||
scheduler: Arc<CoreIoScheduler>,
|
||||
}
|
||||
|
||||
@@ -60,26 +74,35 @@ impl SchedulerManager {
|
||||
high_priority_threshold: usize,
|
||||
low_priority_threshold: usize,
|
||||
) -> Self {
|
||||
let config = SchedulerConfig {
|
||||
Self::from_policy(SchedulerPolicy {
|
||||
base_buffer_size,
|
||||
max_buffer_size,
|
||||
high_priority_threshold,
|
||||
low_priority_threshold,
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
let core_config = rustfs_io_core::IoSchedulerConfig::default();
|
||||
/// Create a scheduler manager from facade policy.
|
||||
pub fn from_policy(config: SchedulerPolicy) -> Self {
|
||||
let core_config = config.to_core_config();
|
||||
|
||||
Self {
|
||||
config,
|
||||
core_config: core_config.clone(),
|
||||
scheduler: Arc::new(CoreIoScheduler::new(core_config)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the configuration
|
||||
pub fn config(&self) -> &SchedulerConfig {
|
||||
pub fn config(&self) -> &SchedulerPolicy {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Get the derived io-core scheduler config.
|
||||
pub fn core_config(&self) -> &rustfs_io_core::IoSchedulerConfig {
|
||||
&self.core_config
|
||||
}
|
||||
|
||||
/// Get the scheduler
|
||||
pub fn scheduler(&self) -> Arc<CoreIoScheduler> {
|
||||
self.scheduler.clone()
|
||||
@@ -87,7 +110,7 @@ impl SchedulerManager {
|
||||
|
||||
/// Create an I/O strategy
|
||||
pub fn create_strategy(&self) -> IoStrategy {
|
||||
IoStrategy::new(self.config.clone(), self.scheduler.clone())
|
||||
IoStrategy::new(self.config, self.scheduler.clone())
|
||||
}
|
||||
|
||||
/// Calculate buffer size
|
||||
@@ -111,12 +134,12 @@ impl SchedulerManager {
|
||||
|
||||
/// I/O strategy
|
||||
pub struct IoStrategy {
|
||||
config: SchedulerConfig,
|
||||
config: SchedulerPolicy,
|
||||
scheduler: Arc<CoreIoScheduler>,
|
||||
}
|
||||
|
||||
impl IoStrategy {
|
||||
fn new(config: SchedulerConfig, scheduler: Arc<CoreIoScheduler>) -> Self {
|
||||
fn new(config: SchedulerPolicy, scheduler: Arc<CoreIoScheduler>) -> Self {
|
||||
Self { config, scheduler }
|
||||
}
|
||||
|
||||
@@ -191,7 +214,7 @@ impl IoStrategy {
|
||||
}
|
||||
|
||||
/// Get the configuration
|
||||
pub fn config(&self) -> &SchedulerConfig {
|
||||
pub fn config(&self) -> &SchedulerPolicy {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
@@ -202,10 +225,20 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_scheduler_config() {
|
||||
let config = SchedulerConfig::default();
|
||||
let config = SchedulerPolicy::default();
|
||||
assert!(config.base_buffer_size < config.max_buffer_size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scheduler_policy_to_core_config() {
|
||||
let policy = SchedulerPolicy::default();
|
||||
let core = policy.to_core_config();
|
||||
assert_eq!(core.base_buffer_size, policy.base_buffer_size);
|
||||
assert_eq!(core.max_buffer_size, policy.max_buffer_size);
|
||||
assert_eq!(core.high_priority_size_threshold, policy.high_priority_threshold);
|
||||
assert_eq!(core.low_priority_size_threshold, policy.low_priority_threshold);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scheduler_manager() {
|
||||
let manager = SchedulerManager::new(1024, 4096, 512, 2048);
|
||||
|
||||
@@ -14,60 +14,101 @@
|
||||
|
||||
//! Timeout management for operations
|
||||
|
||||
use rustfs_io_core::{TimeoutError, calculate_adaptive_timeout};
|
||||
use rustfs_io_core::{TimeoutConfig as CoreTimeoutConfig, TimeoutError, calculate_adaptive_timeout};
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
/// Timeout configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TimeoutConfig {
|
||||
/// Facade policy for the concurrency-layer timeout manager.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TimeoutManagerPolicy {
|
||||
/// Default timeout duration
|
||||
pub default_timeout: Duration,
|
||||
/// Maximum timeout duration
|
||||
pub max_timeout: Duration,
|
||||
/// Minimum timeout floor (prevents dynamic calculation from going too low).
|
||||
pub min_timeout: Duration,
|
||||
/// Enable dynamic timeout calculation
|
||||
pub enable_dynamic: bool,
|
||||
}
|
||||
|
||||
impl Default for TimeoutConfig {
|
||||
impl Default for TimeoutManagerPolicy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
default_timeout: Duration::from_secs(30),
|
||||
max_timeout: Duration::from_secs(300),
|
||||
min_timeout: Duration::from_secs(5),
|
||||
enable_dynamic: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeoutManagerPolicy {
|
||||
/// Convert the facade policy into the reusable io-core timeout configuration.
|
||||
///
|
||||
/// This keeps the concurrency layer explicitly wired to the shared core
|
||||
/// timeout primitives without changing the facade's public behavior.
|
||||
pub fn to_core_config(&self) -> CoreTimeoutConfig {
|
||||
CoreTimeoutConfig {
|
||||
base_timeout: self.default_timeout,
|
||||
timeout_per_mb: Duration::ZERO,
|
||||
max_timeout: self.max_timeout,
|
||||
min_timeout: self.min_timeout,
|
||||
get_object_timeout: self.default_timeout,
|
||||
put_object_timeout: self.max_timeout,
|
||||
list_objects_timeout: self.default_timeout,
|
||||
enable_dynamic_timeout: self.enable_dynamic,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Timeout manager
|
||||
pub struct TimeoutManager {
|
||||
config: TimeoutConfig,
|
||||
config: TimeoutManagerPolicy,
|
||||
core_config: CoreTimeoutConfig,
|
||||
}
|
||||
|
||||
impl TimeoutManager {
|
||||
/// Create a new timeout manager
|
||||
pub fn new(default_timeout: Duration, max_timeout: Duration, enable_dynamic: bool) -> Self {
|
||||
Self {
|
||||
config: TimeoutConfig {
|
||||
default_timeout,
|
||||
max_timeout,
|
||||
enable_dynamic,
|
||||
},
|
||||
}
|
||||
let min_timeout = default_timeout.min(max_timeout);
|
||||
Self::from_policy(TimeoutManagerPolicy {
|
||||
default_timeout,
|
||||
max_timeout,
|
||||
min_timeout,
|
||||
enable_dynamic,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new timeout manager from the facade policy type.
|
||||
pub fn from_policy(config: TimeoutManagerPolicy) -> Self {
|
||||
let config = TimeoutManagerPolicy {
|
||||
// Guard clamp(min, max) from panic when callers provide an
|
||||
// out-of-order policy (or very small max_timeout).
|
||||
min_timeout: config.min_timeout.min(config.max_timeout),
|
||||
..config
|
||||
};
|
||||
let core_config = config.to_core_config();
|
||||
Self { config, core_config }
|
||||
}
|
||||
|
||||
/// Get the configuration
|
||||
pub fn config(&self) -> &TimeoutConfig {
|
||||
pub fn config(&self) -> &TimeoutManagerPolicy {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Get the derived io-core timeout configuration.
|
||||
pub fn core_config(&self) -> &CoreTimeoutConfig {
|
||||
&self.core_config
|
||||
}
|
||||
|
||||
/// Calculate timeout for a given size
|
||||
pub fn calculate_timeout(&self, size: u64, _history: &[Duration]) -> Duration {
|
||||
if !self.config.enable_dynamic {
|
||||
return self.config.default_timeout;
|
||||
}
|
||||
|
||||
calculate_adaptive_timeout(self.config.default_timeout, None, 0, size).min(self.config.max_timeout)
|
||||
calculate_adaptive_timeout(self.core_config.base_timeout, None, 0, size)
|
||||
.clamp(self.core_config.min_timeout, self.core_config.max_timeout)
|
||||
}
|
||||
|
||||
/// Wrap an operation with timeout control
|
||||
@@ -87,7 +128,7 @@ impl TimeoutManager {
|
||||
|
||||
/// Create a timeout guard for manual timeout control
|
||||
pub fn create_guard(&self, timeout: Option<Duration>) -> TimeoutGuard {
|
||||
TimeoutGuard::new(timeout.unwrap_or(self.config.default_timeout))
|
||||
TimeoutGuard::new(timeout.unwrap_or(self.core_config.base_timeout))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,10 +175,41 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_timeout_config() {
|
||||
let config = TimeoutConfig::default();
|
||||
let config = TimeoutManagerPolicy::default();
|
||||
assert!(config.default_timeout < config.max_timeout);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timeout_policy_to_core_config() {
|
||||
let policy = TimeoutManagerPolicy::default();
|
||||
let core = policy.to_core_config();
|
||||
assert_eq!(core.base_timeout, policy.default_timeout);
|
||||
assert_eq!(core.max_timeout, policy.max_timeout);
|
||||
assert_eq!(core.min_timeout, policy.min_timeout);
|
||||
assert_eq!(core.get_object_timeout, policy.default_timeout);
|
||||
assert!(core.enable_dynamic_timeout);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timeout_manager_new_sanitizes_min_timeout_with_small_max_timeout() {
|
||||
let manager = TimeoutManager::new(Duration::from_secs(1), Duration::from_secs(1), true);
|
||||
let timeout = manager.calculate_timeout(1024, &[]);
|
||||
assert_eq!(timeout, Duration::from_secs(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timeout_manager_from_policy_sanitizes_min_timeout() {
|
||||
let manager = TimeoutManager::from_policy(TimeoutManagerPolicy {
|
||||
default_timeout: Duration::from_secs(30),
|
||||
max_timeout: Duration::from_secs(1),
|
||||
min_timeout: Duration::from_secs(5),
|
||||
enable_dynamic: true,
|
||||
});
|
||||
|
||||
assert_eq!(manager.config().min_timeout, Duration::from_secs(1));
|
||||
assert_eq!(manager.core_config().min_timeout, Duration::from_secs(1));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_wrap_operation_success() {
|
||||
let manager = TimeoutManager::new(Duration::from_secs(5), Duration::from_secs(10), true);
|
||||
|
||||
Reference in New Issue
Block a user