mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-11 23:56:53 +00:00
fix(object): Fix concurrent request hang issue in S3 range read workloads (#2251)
Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -180,3 +180,265 @@ pub const ENV_OBJECT_GET_SKIP_BITROT_VERIFY: &str = "RUSTFS_OBJECT_GET_SKIP_BITR
|
||||
|
||||
/// Default: bitrot verification is enabled on GetObject reads (do not skip).
|
||||
pub const DEFAULT_OBJECT_GET_SKIP_BITROT_VERIFY: bool = false;
|
||||
|
||||
// =============================================================================
|
||||
// Concurrent Request Fix - Timeout and Backpressure Configuration
|
||||
// =============================================================================
|
||||
|
||||
/// Environment variable for GetObject request timeout in seconds.
|
||||
///
|
||||
/// When a GetObject request exceeds this duration, it will be cancelled
|
||||
/// and return a 504 Gateway Timeout error. This prevents requests from
|
||||
/// hanging indefinitely due to deadlocks or resource exhaustion.
|
||||
///
|
||||
/// Default: 30 seconds (can be overridden by `RUSTFS_OBJECT_GET_TIMEOUT`).
|
||||
/// Set to 0 to disable timeout (not recommended for production).
|
||||
pub const ENV_OBJECT_GET_TIMEOUT: &str = "RUSTFS_OBJECT_GET_TIMEOUT";
|
||||
|
||||
/// Default GetObject request timeout in seconds.
|
||||
///
|
||||
/// This value balances between allowing large object transfers to complete
|
||||
/// and preventing indefinite hangs. For 20-26MB objects with concurrent
|
||||
/// range reads, 30 seconds should be sufficient under normal conditions.
|
||||
pub const DEFAULT_OBJECT_GET_TIMEOUT: u64 = 30;
|
||||
|
||||
/// Environment variable for disk read operation timeout in seconds.
|
||||
///
|
||||
/// Individual disk read operations that exceed this duration will be
|
||||
/// cancelled and treated as failures. This helps detect slow or hung
|
||||
/// disks without waiting indefinitely.
|
||||
///
|
||||
/// Default: 10 seconds (can be overridden by `RUSTFS_OBJECT_DISK_READ_TIMEOUT`).
|
||||
pub const ENV_OBJECT_DISK_READ_TIMEOUT: &str = "RUSTFS_OBJECT_DISK_READ_TIMEOUT";
|
||||
|
||||
/// Default disk read timeout in seconds.
|
||||
pub const DEFAULT_OBJECT_DISK_READ_TIMEOUT: u64 = 10;
|
||||
|
||||
/// Environment variable for duplex pipe buffer size in bytes.
|
||||
///
|
||||
/// The duplex pipe connects the disk read task to the HTTP response stream.
|
||||
/// A larger buffer reduces backpressure but increases memory usage.
|
||||
/// For large objects (20-26MB), a 4MB buffer provides good throughput.
|
||||
///
|
||||
/// Default: 4194304 (4 MB, can be overridden by `RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE`).
|
||||
/// Minimum recommended: 1048576 (1 MB).
|
||||
pub const ENV_OBJECT_DUPLEX_BUFFER_SIZE: &str = "RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE";
|
||||
|
||||
/// Default duplex buffer size: 4 MB.
|
||||
///
|
||||
/// This is 4x larger than the original 1 MB buffer, providing better
|
||||
/// handling of large objects and reducing backpressure-related hangs.
|
||||
pub const DEFAULT_OBJECT_DUPLEX_BUFFER_SIZE: usize = 4 * 1024 * 1024;
|
||||
|
||||
/// Environment variable for I/O buffer size in bytes.
|
||||
///
|
||||
/// This controls the buffer size used for individual I/O operations.
|
||||
/// A larger buffer improves throughput for sequential reads but may
|
||||
/// increase latency for small random reads.
|
||||
///
|
||||
/// Default: 131072 (128 KB, can be overridden by `RUSTFS_OBJECT_IO_BUFFER_SIZE`).
|
||||
pub const ENV_OBJECT_IO_BUFFER_SIZE: &str = "RUSTFS_OBJECT_IO_BUFFER_SIZE";
|
||||
|
||||
/// Default I/O buffer size: 128 KB.
|
||||
pub const DEFAULT_OBJECT_IO_BUFFER_SIZE: usize = 128 * 1024;
|
||||
|
||||
/// Environment variable to enable/disable lock optimization.
|
||||
///
|
||||
/// When enabled, read locks are released immediately after metadata
|
||||
/// is read, rather than being held for the entire data transfer.
|
||||
/// This significantly reduces lock contention under high concurrency.
|
||||
///
|
||||
/// Default: true (enabled, can be overridden by `RUSTFS_OBJECT_LOCK_OPTIMIZATION_ENABLE`).
|
||||
pub const ENV_OBJECT_LOCK_OPTIMIZATION_ENABLE: &str = "RUSTFS_OBJECT_LOCK_OPTIMIZATION_ENABLE";
|
||||
|
||||
/// Default: lock optimization is enabled.
|
||||
pub const DEFAULT_OBJECT_LOCK_OPTIMIZATION_ENABLE: bool = true;
|
||||
|
||||
/// Environment variable to enable/disable priority-based I/O scheduling.
|
||||
///
|
||||
/// When enabled, smaller requests (< 1MB) are given higher priority
|
||||
/// than larger requests (> 10MB), preventing "starvation" of small
|
||||
/// requests by large ones.
|
||||
///
|
||||
/// Default: true (enabled, can be overridden by `RUSTFS_OBJECT_PRIORITY_SCHEDULING_ENABLE`).
|
||||
pub const ENV_OBJECT_PRIORITY_SCHEDULING_ENABLE: &str = "RUSTFS_OBJECT_PRIORITY_SCHEDULING_ENABLE";
|
||||
|
||||
/// Default: priority scheduling is enabled.
|
||||
pub const DEFAULT_OBJECT_PRIORITY_SCHEDULING_ENABLE: bool = true;
|
||||
|
||||
/// Environment variable to enable/disable deadlock detection.
|
||||
///
|
||||
/// When enabled, the system monitors active requests and detects
|
||||
/// potential deadlock situations (circular lock wait chains).
|
||||
/// This has some performance overhead and is intended for debugging.
|
||||
///
|
||||
/// Default: false (disabled, can be overridden by `RUSTFS_OBJECT_DEADLOCK_DETECTION_ENABLE`).
|
||||
pub const ENV_OBJECT_DEADLOCK_DETECTION_ENABLE: &str = "RUSTFS_OBJECT_DEADLOCK_DETECTION_ENABLE";
|
||||
|
||||
/// Default: deadlock detection is disabled for performance.
|
||||
pub const DEFAULT_OBJECT_DEADLOCK_DETECTION_ENABLE: bool = false;
|
||||
|
||||
/// Environment variable for deadlock detection check interval in seconds.
|
||||
///
|
||||
/// How often the deadlock detector analyzes the lock wait graph.
|
||||
/// More frequent checks detect deadlocks faster but use more CPU.
|
||||
///
|
||||
/// Default: 5 seconds (can be overridden by `RUSTFS_OBJECT_DEADLOCK_CHECK_INTERVAL`).
|
||||
pub const ENV_OBJECT_DEADLOCK_CHECK_INTERVAL: &str = "RUSTFS_OBJECT_DEADLOCK_CHECK_INTERVAL";
|
||||
|
||||
/// Default deadlock check interval: 5 seconds.
|
||||
pub const DEFAULT_OBJECT_DEADLOCK_CHECK_INTERVAL: u64 = 5;
|
||||
|
||||
/// Environment variable for deadlock detection hang threshold in seconds.
|
||||
///
|
||||
/// Requests that have been running longer than this threshold are
|
||||
/// considered "potentially hung" and included in deadlock analysis.
|
||||
///
|
||||
/// Default: 10 seconds (can be overridden by `RUSTFS_OBJECT_DEADLOCK_HANG_THRESHOLD`).
|
||||
pub const ENV_OBJECT_DEADLOCK_HANG_THRESHOLD: &str = "RUSTFS_OBJECT_DEADLOCK_HANG_THRESHOLD";
|
||||
|
||||
/// Default hang threshold: 10 seconds.
|
||||
pub const DEFAULT_OBJECT_DEADLOCK_HANG_THRESHOLD: u64 = 10;
|
||||
|
||||
/// Environment variable for backpressure high watermark percentage.
|
||||
///
|
||||
/// When buffer usage exceeds this percentage, the system enters
|
||||
/// "high watermark" state and may apply backpressure to producers.
|
||||
///
|
||||
/// Default: 80 (80%, can be overridden by `RUSTFS_OBJECT_BACKPRESSURE_HIGH_WATERMARK`).
|
||||
pub const ENV_OBJECT_BACKPRESSURE_HIGH_WATERMARK: &str = "RUSTFS_OBJECT_BACKPRESSURE_HIGH_WATERMARK";
|
||||
|
||||
/// Default high watermark: 80%.
|
||||
pub const DEFAULT_OBJECT_BACKPRESSURE_HIGH_WATERMARK: u32 = 80;
|
||||
|
||||
/// Environment variable for backpressure low watermark percentage.
|
||||
///
|
||||
/// When buffer usage drops below this percentage after being in
|
||||
/// high watermark state, backpressure is released.
|
||||
///
|
||||
/// Default: 50 (50%, can be overridden by `RUSTFS_OBJECT_BACKPRESSURE_LOW_WATERMARK`).
|
||||
pub const ENV_OBJECT_BACKPRESSURE_LOW_WATERMARK: &str = "RUSTFS_OBJECT_BACKPRESSURE_LOW_WATERMARK";
|
||||
|
||||
/// Default low watermark: 50%.
|
||||
pub const DEFAULT_OBJECT_BACKPRESSURE_LOW_WATERMARK: u32 = 50;
|
||||
|
||||
/// Environment variable for lock acquisition timeout in seconds.
|
||||
///
|
||||
/// When a lock cannot be acquired within this duration, the operation
|
||||
/// will fail with a timeout error. This prevents indefinite waiting
|
||||
/// for locks that may never be released due to deadlocks.
|
||||
///
|
||||
/// Default: 5 seconds (can be overridden by `RUSTFS_OBJECT_LOCK_ACQUIRE_TIMEOUT`).
|
||||
pub const ENV_OBJECT_LOCK_ACQUIRE_TIMEOUT: &str = "RUSTFS_OBJECT_LOCK_ACQUIRE_TIMEOUT";
|
||||
|
||||
/// Default lock acquisition timeout: 5 seconds.
|
||||
pub const DEFAULT_OBJECT_LOCK_ACQUIRE_TIMEOUT: u64 = 5;
|
||||
|
||||
// ============================================================================
|
||||
// I/O priority scheduling configuration
|
||||
// ============================================================================
|
||||
|
||||
/// Environment variable for I/O high priority size threshold in bytes.
|
||||
///
|
||||
/// Requests smaller than this threshold are classified as high priority.
|
||||
/// High priority requests are processed first to prevent starvation of small requests.
|
||||
///
|
||||
/// Default: 1048576 (1 MB, can be overridden by `RUSTFS_OBJECT_IO_HIGH_PRIORITY_SIZE_THRESHOLD`).
|
||||
pub const ENV_OBJECT_IO_HIGH_PRIORITY_SIZE_THRESHOLD: &str = "RUSTFS_OBJECT_IO_HIGH_PRIORITY_SIZE_THRESHOLD";
|
||||
|
||||
/// Default high priority size threshold: 1 MB.
|
||||
pub const DEFAULT_OBJECT_IO_HIGH_PRIORITY_SIZE_THRESHOLD: usize = 1024 * 1024;
|
||||
|
||||
/// Environment variable for I/O low priority size threshold in bytes.
|
||||
///
|
||||
/// Requests larger than this threshold are classified as low priority.
|
||||
/// Low priority requests are processed last to avoid blocking small requests.
|
||||
///
|
||||
/// Default: 104857600 (100 MB, can be overridden by `RUSTFS_OBJECT_IO_LOW_PRIORITY_SIZE_THRESHOLD`).
|
||||
pub const ENV_OBJECT_IO_LOW_PRIORITY_SIZE_THRESHOLD: &str = "RUSTFS_OBJECT_IO_LOW_PRIORITY_SIZE_THRESHOLD";
|
||||
|
||||
/// Default low priority size threshold: 100 MB.
|
||||
pub const DEFAULT_OBJECT_IO_LOW_PRIORITY_SIZE_THRESHOLD: usize = 100 * 1024 * 1024;
|
||||
|
||||
/// Environment variable for high priority queue capacity.
|
||||
///
|
||||
/// Maximum number of requests that can be queued in the high priority queue.
|
||||
///
|
||||
/// Default: 32 (can be overridden by `RUSTFS_OBJECT_IO_QUEUE_HIGH_CAPACITY`).
|
||||
pub const ENV_OBJECT_IO_QUEUE_HIGH_CAPACITY: &str = "RUSTFS_OBJECT_IO_QUEUE_HIGH_CAPACITY";
|
||||
|
||||
/// Default high priority queue capacity: 32.
|
||||
pub const DEFAULT_OBJECT_IO_QUEUE_HIGH_CAPACITY: usize = 32;
|
||||
|
||||
/// Environment variable for normal priority queue capacity.
|
||||
///
|
||||
/// Maximum number of requests that can be queued in the normal priority queue.
|
||||
///
|
||||
/// Default: 64 (can be overridden by `RUSTFS_OBJECT_IO_QUEUE_NORMAL_CAPACITY`).
|
||||
pub const ENV_OBJECT_IO_QUEUE_NORMAL_CAPACITY: &str = "RUSTFS_OBJECT_IO_QUEUE_NORMAL_CAPACITY";
|
||||
|
||||
/// Default normal priority queue capacity: 64.
|
||||
pub const DEFAULT_OBJECT_IO_QUEUE_NORMAL_CAPACITY: usize = 64;
|
||||
|
||||
/// Environment variable for low priority queue capacity.
|
||||
///
|
||||
/// Maximum number of requests that can be queued in the low priority queue.
|
||||
///
|
||||
/// Default: 16 (can be overridden by `RUSTFS_OBJECT_IO_QUEUE_LOW_CAPACITY`).
|
||||
pub const ENV_OBJECT_IO_QUEUE_LOW_CAPACITY: &str = "RUSTFS_OBJECT_IO_QUEUE_LOW_CAPACITY";
|
||||
|
||||
/// Default low priority queue capacity: 16.
|
||||
pub const DEFAULT_OBJECT_IO_QUEUE_LOW_CAPACITY: usize = 16;
|
||||
|
||||
/// Environment variable for starvation prevention check interval in milliseconds.
|
||||
///
|
||||
/// How often the system checks for starving low-priority requests.
|
||||
/// When a low-priority request has been waiting longer than the starvation threshold,
|
||||
/// it is promoted to normal priority.
|
||||
///
|
||||
/// Default: 100 ms (can be overridden by `RUSTFS_OBJECT_IO_STARVATION_PREVENTION_INTERVAL`).
|
||||
pub const ENV_OBJECT_IO_STARVATION_PREVENTION_INTERVAL: &str = "RUSTFS_OBJECT_IO_STARVATION_PREVENTION_INTERVAL";
|
||||
|
||||
/// Default starvation prevention interval: 100 ms.
|
||||
pub const DEFAULT_OBJECT_IO_STARVATION_PREVENTION_INTERVAL: u64 = 100;
|
||||
|
||||
/// Environment variable for starvation threshold in seconds.
|
||||
///
|
||||
/// Maximum time a low-priority request can wait before being promoted to normal priority.
|
||||
/// This prevents indefinite starvation of low-priority requests.
|
||||
///
|
||||
/// Default: 5 seconds (can be overridden by `RUSTFS_OBJECT_IO_STARVATION_THRESHOLD_SECS`).
|
||||
pub const ENV_OBJECT_IO_STARVATION_THRESHOLD_SECS: &str = "RUSTFS_OBJECT_IO_STARVATION_THRESHOLD_SECS";
|
||||
|
||||
/// Default starvation threshold: 5 seconds.
|
||||
pub const DEFAULT_OBJECT_IO_STARVATION_THRESHOLD_SECS: u64 = 5;
|
||||
|
||||
/// Environment variable for load sampling window size.
|
||||
///
|
||||
/// Number of recent samples used to calculate I/O load metrics.
|
||||
///
|
||||
/// Default: 100 samples (can be overridden by `RUSTFS_OBJECT_IO_LOAD_SAMPLE_WINDOW`).
|
||||
pub const ENV_OBJECT_IO_LOAD_SAMPLE_WINDOW: &str = "RUSTFS_OBJECT_IO_LOAD_SAMPLE_WINDOW";
|
||||
|
||||
/// Default load sampling window: 100 samples.
|
||||
pub const DEFAULT_OBJECT_IO_LOAD_SAMPLE_WINDOW: usize = 100;
|
||||
|
||||
/// Environment variable for high load wait time threshold in milliseconds.
|
||||
///
|
||||
/// When average wait time exceeds this threshold, the system is considered to be under high load.
|
||||
///
|
||||
/// Default: 50 ms (can be overridden by `RUSTFS_OBJECT_IO_LOAD_HIGH_THRESHOLD_MS`).
|
||||
pub const ENV_OBJECT_IO_LOAD_HIGH_THRESHOLD_MS: &str = "RUSTFS_OBJECT_IO_LOAD_HIGH_THRESHOLD_MS";
|
||||
|
||||
/// Default high load threshold: 50 ms.
|
||||
pub const DEFAULT_OBJECT_IO_LOAD_HIGH_THRESHOLD_MS: u64 = 50;
|
||||
|
||||
/// Environment variable for low load wait time threshold in milliseconds.
|
||||
///
|
||||
/// When average wait time is below this threshold, the system is considered to be under low load.
|
||||
///
|
||||
/// Default: 10 ms (can be overridden by `RUSTFS_OBJECT_IO_LOAD_LOW_THRESHOLD_MS`).
|
||||
pub const ENV_OBJECT_IO_LOAD_LOW_THRESHOLD_MS: &str = "RUSTFS_OBJECT_IO_LOAD_LOW_THRESHOLD_MS";
|
||||
|
||||
/// Default low load threshold: 10 ms.
|
||||
pub const DEFAULT_OBJECT_IO_LOAD_LOW_THRESHOLD_MS: u64 = 10;
|
||||
|
||||
@@ -116,6 +116,9 @@ faster-hex = { workspace = true }
|
||||
ratelimit = { workspace = true }
|
||||
aws-smithy-http-client.workspace = true
|
||||
|
||||
# Observability and Metrics
|
||||
metrics = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
criterion = { workspace = true, features = ["html_reports"] }
|
||||
|
||||
+149
-25
@@ -121,6 +121,21 @@ use uuid::Uuid;
|
||||
|
||||
pub const DEFAULT_READ_BUFFER_SIZE: usize = MI_B; // 1 MiB = 1024 * 1024;
|
||||
pub const MAX_PARTS_COUNT: usize = 10000;
|
||||
|
||||
/// Get the duplex buffer size from environment variable or use default.
|
||||
///
|
||||
/// This function reads `RUSTFS_DUPLEX_BUFFER_SIZE` environment variable
|
||||
/// to allow runtime configuration of the duplex pipe buffer size.
|
||||
/// A larger buffer (e.g., 4MB) helps prevent backpressure-related hangs
|
||||
/// when reading large objects (20-26MB) under high concurrency.
|
||||
///
|
||||
/// Default: 4MB (4 * 1024 * 1024 bytes)
|
||||
pub fn get_duplex_buffer_size() -> usize {
|
||||
rustfs_utils::get_env_usize(
|
||||
rustfs_config::ENV_OBJECT_DUPLEX_BUFFER_SIZE,
|
||||
rustfs_config::DEFAULT_OBJECT_DUPLEX_BUFFER_SIZE,
|
||||
)
|
||||
}
|
||||
const DISK_ONLINE_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
const DISK_HEALTH_CACHE_TTL: Duration = Duration::from_millis(750);
|
||||
|
||||
@@ -136,7 +151,72 @@ mod write;
|
||||
/// Get lock acquire timeout from environment variable RUSTFS_LOCK_ACQUIRE_TIMEOUT (in seconds)
|
||||
/// Defaults to 30 seconds if not set or invalid
|
||||
pub fn get_lock_acquire_timeout() -> Duration {
|
||||
Duration::from_secs(rustfs_utils::get_env_u64("RUSTFS_LOCK_ACQUIRE_TIMEOUT", 5))
|
||||
Duration::from_secs(rustfs_utils::get_env_u64(
|
||||
rustfs_config::ENV_OBJECT_LOCK_ACQUIRE_TIMEOUT,
|
||||
rustfs_config::DEFAULT_OBJECT_LOCK_ACQUIRE_TIMEOUT,
|
||||
))
|
||||
}
|
||||
|
||||
/// Check if lock optimization is enabled.
|
||||
/// When enabled, read locks are released after metadata read instead of
|
||||
/// being held for the entire data transfer duration.
|
||||
pub fn is_lock_optimization_enabled() -> bool {
|
||||
rustfs_utils::get_env_bool(
|
||||
rustfs_config::ENV_OBJECT_LOCK_OPTIMIZATION_ENABLE,
|
||||
rustfs_config::DEFAULT_OBJECT_LOCK_OPTIMIZATION_ENABLE,
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if deadlock detection is enabled.
|
||||
/// When enabled, lock operations are recorded for deadlock analysis.
|
||||
pub fn is_deadlock_detection_enabled() -> bool {
|
||||
rustfs_utils::get_env_bool(
|
||||
rustfs_config::ENV_OBJECT_DEADLOCK_DETECTION_ENABLE,
|
||||
rustfs_config::DEFAULT_OBJECT_DEADLOCK_DETECTION_ENABLE,
|
||||
)
|
||||
}
|
||||
|
||||
/// Record a lock acquisition for deadlock detection.
|
||||
/// This records detailed lock information for deadlock analysis.
|
||||
/// Returns the lock_id for later release tracking.
|
||||
#[inline]
|
||||
fn record_lock_acquire(bucket: &str, object: &str, lock_type: &str) -> String {
|
||||
let lock_id = format!("{}:{}", bucket, object);
|
||||
|
||||
if !is_deadlock_detection_enabled() {
|
||||
return lock_id;
|
||||
}
|
||||
|
||||
let request_id = format!("get-{}-{}", bucket, object);
|
||||
let resource = format!("{}/{}", bucket, object);
|
||||
|
||||
// Log with structured fields for analysis
|
||||
debug!(
|
||||
request_id = %request_id,
|
||||
lock_id = %lock_id,
|
||||
lock_type = %lock_type,
|
||||
resource = %resource,
|
||||
"Lock acquired for deadlock tracking"
|
||||
);
|
||||
|
||||
lock_id
|
||||
}
|
||||
|
||||
/// Record a lock release for deadlock detection.
|
||||
#[inline]
|
||||
fn record_lock_release(bucket: &str, object: &str, lock_id: &str, lock_type: &str) {
|
||||
if !is_deadlock_detection_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let request_id = format!("get-{}-{}", bucket, object);
|
||||
|
||||
debug!(
|
||||
request_id = %request_id,
|
||||
lock_id = %lock_id,
|
||||
lock_type = %lock_type,
|
||||
"Lock released for deadlock tracking"
|
||||
);
|
||||
}
|
||||
|
||||
fn build_tiered_decommission_file_info(
|
||||
@@ -451,20 +531,44 @@ impl ObjectIO for SetDisks {
|
||||
h: HeaderMap,
|
||||
opts: &ObjectOptions,
|
||||
) -> Result<GetObjectReader> {
|
||||
// Check if lock optimization is enabled
|
||||
// When enabled, read locks are released after metadata read
|
||||
let lock_optimization_enabled = is_lock_optimization_enabled();
|
||||
|
||||
// Acquire a shared read-lock early to protect read consistency
|
||||
let read_lock_guard = if !opts.no_lock {
|
||||
Some(
|
||||
self.new_ns_lock(bucket, object)
|
||||
.await?
|
||||
.get_read_lock(get_lock_acquire_timeout())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::other(format!(
|
||||
"Failed to acquire read lock: {}",
|
||||
self.format_lock_error_from_error(bucket, object, "read", &e)
|
||||
))
|
||||
})?,
|
||||
)
|
||||
let acquire_start = Instant::now();
|
||||
|
||||
// Record lock wait for deadlock detection
|
||||
if is_deadlock_detection_enabled() {
|
||||
debug!(
|
||||
lock_id = format!("{}:{}", bucket, object),
|
||||
lock_type = "read",
|
||||
resource = format!("{}/{}", bucket, object),
|
||||
"Waiting for read lock"
|
||||
);
|
||||
}
|
||||
|
||||
let guard = self
|
||||
.new_ns_lock(bucket, object)
|
||||
.await?
|
||||
.get_read_lock(get_lock_acquire_timeout())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::other(format!(
|
||||
"Failed to acquire read lock: {}",
|
||||
self.format_lock_error_from_error(bucket, object, "read", &e)
|
||||
))
|
||||
})?;
|
||||
|
||||
// Record lock acquisition for deadlock detection
|
||||
let _lock_id = record_lock_acquire(bucket, object, "read");
|
||||
|
||||
// Record lock statistics
|
||||
metrics::counter!("rustfs.lock.acquire.total", "type" => "read").increment(1);
|
||||
metrics::histogram!("rustfs.lock.acquire.duration.seconds").record(acquire_start.elapsed().as_secs_f64());
|
||||
|
||||
Some(guard)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -512,7 +616,28 @@ impl ObjectIO for SetDisks {
|
||||
return Ok(gr);
|
||||
}
|
||||
|
||||
let (rd, wd) = tokio::io::duplex(DEFAULT_READ_BUFFER_SIZE);
|
||||
// Lock optimization: release read lock after metadata read if enabled
|
||||
// This reduces lock contention by not holding the lock during data transfer
|
||||
let read_lock_guard = if lock_optimization_enabled {
|
||||
// Record lock release for deadlock detection
|
||||
if read_lock_guard.is_some() {
|
||||
let lock_id = format!("{}:{}", bucket, object);
|
||||
record_lock_release(bucket, object, &lock_id, "read");
|
||||
|
||||
// Record early lock release statistics
|
||||
metrics::counter!("rustfs.lock.release.early.total", "type" => "read").increment(1);
|
||||
}
|
||||
// Explicitly drop the lock guard to release the lock early
|
||||
drop(read_lock_guard);
|
||||
debug!(bucket, object, "Lock optimization: released read lock after metadata read");
|
||||
None
|
||||
} else {
|
||||
read_lock_guard
|
||||
};
|
||||
|
||||
let duplex_buffer_size = get_duplex_buffer_size();
|
||||
let (rd, wd) = tokio::io::duplex(duplex_buffer_size);
|
||||
debug!(bucket, object, duplex_buffer_size, "Created duplex pipe for object data transfer");
|
||||
|
||||
let (reader, offset, length) = GetObjectReader::new(Box::new(rd), range, &object_info, opts, &h)?;
|
||||
|
||||
@@ -523,9 +648,10 @@ impl ObjectIO for SetDisks {
|
||||
let pool_index = self.pool_index;
|
||||
let skip_verify = opts.skip_verify_bitrot;
|
||||
// Move the read-lock guard into the task so it lives for the duration of the read
|
||||
// Note: when lock optimization is enabled, read_lock_guard is None
|
||||
// let _guard_to_hold = _read_lock_guard; // moved into closure below
|
||||
tokio::spawn(async move {
|
||||
let _guard = read_lock_guard; // keep guard alive until task ends
|
||||
let _guard = read_lock_guard; // keep guard alive until task ends (None if optimization enabled)
|
||||
let mut writer = wd;
|
||||
if let Err(e) = Self::get_object_with_fileinfo(
|
||||
&bucket,
|
||||
@@ -751,7 +877,7 @@ impl ObjectIO for SetDisks {
|
||||
pfi.metadata = user_defined.clone();
|
||||
if is_inline_buffer {
|
||||
if let Some(writer) = writers[i].take() {
|
||||
pfi.data = Some(writer.into_inline_data().map(bytes::Bytes::from).unwrap_or_default());
|
||||
pfi.data = Some(writer.into_inline_data().map(Bytes::from).unwrap_or_default());
|
||||
}
|
||||
|
||||
pfi.set_inline_data();
|
||||
@@ -1093,7 +1219,7 @@ impl ObjectOperations for SetDisks {
|
||||
let mut unique_objects: HashSet<String> = HashSet::new();
|
||||
for dobj in &objects {
|
||||
if unique_objects.insert(dobj.object_name.clone()) {
|
||||
batch = batch.add_write_lock(rustfs_lock::ObjectKey::new(bucket, dobj.object_name.clone()));
|
||||
batch = batch.add_write_lock(ObjectKey::new(bucket, dobj.object_name.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2757,8 +2883,7 @@ impl MultipartOperations for SetDisks {
|
||||
// Build a lookup map for O(1) part resolution instead of O(n) find() in the loop
|
||||
// This optimizes from O(n^2) to O(n) when processing many parts
|
||||
use std::collections::HashMap;
|
||||
let part_lookup: HashMap<usize, &rustfs_filemeta::ObjectPartInfo> =
|
||||
curr_fi.parts.iter().map(|part| (part.number, part)).collect();
|
||||
let part_lookup: HashMap<usize, &ObjectPartInfo> = curr_fi.parts.iter().map(|part| (part.number, part)).collect();
|
||||
|
||||
for (i, p) in uploaded_parts.iter().enumerate() {
|
||||
let Some(ext_part) = part_lookup.get(&p.part_num) else {
|
||||
@@ -3445,12 +3570,11 @@ async fn disks_with_all_parts(
|
||||
if (meta.data.is_some() || meta.size == 0) && !meta.parts.is_empty() {
|
||||
if let Some(data) = &meta.data {
|
||||
let checksum_info = meta.erasure.get_checksum_info(meta.parts[0].number);
|
||||
let checksum_algo =
|
||||
if meta.uses_legacy_checksum && checksum_info.algorithm == rustfs_utils::HashAlgorithm::HighwayHash256S {
|
||||
rustfs_utils::HashAlgorithm::HighwayHash256SLegacy
|
||||
} else {
|
||||
checksum_info.algorithm
|
||||
};
|
||||
let checksum_algo = if meta.uses_legacy_checksum && checksum_info.algorithm == HashAlgorithm::HighwayHash256S {
|
||||
HashAlgorithm::HighwayHash256SLegacy
|
||||
} else {
|
||||
checksum_info.algorithm
|
||||
};
|
||||
let data_len = data.len();
|
||||
let verify_err = bitrot_verify(
|
||||
Box::new(Cursor::new(data.clone())),
|
||||
|
||||
Reference in New Issue
Block a user