mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-25 21:46:50 +00:00
fix(scanner): scope long walk timeouts (#4376)
* fix(scanner): scope long walk timeouts * fix(scanner): bound IAM config walks --------- Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
@@ -29,6 +30,7 @@ use rustfs_filemeta::FileInfo;
|
|||||||
|
|
||||||
pub const DEFAULT_FREE_VERSION_RECOVERY_LIMIT: usize = 1_000;
|
pub const DEFAULT_FREE_VERSION_RECOVERY_LIMIT: usize = 1_000;
|
||||||
const DEFAULT_FREE_VERSION_RECOVERY_SCAN_LIMIT: usize = 10_000;
|
const DEFAULT_FREE_VERSION_RECOVERY_SCAN_LIMIT: usize = 10_000;
|
||||||
|
const BACKGROUND_WALKDIR_TIMEOUT: Duration = Duration::from_secs(60);
|
||||||
|
|
||||||
type ObjectInfoOrErr = StorageObjectInfoOrErr<ObjectInfo, crate::error::Error>;
|
type ObjectInfoOrErr = StorageObjectInfoOrErr<ObjectInfo, crate::error::Error>;
|
||||||
type WalkOptions = StorageWalkOptions<fn(&FileInfo) -> bool>;
|
type WalkOptions = StorageWalkOptions<fn(&FileInfo) -> bool>;
|
||||||
@@ -222,7 +224,8 @@ async fn list_tier_free_versions(
|
|||||||
limit: walk_scan_limit,
|
limit: walk_scan_limit,
|
||||||
marker: object_marker,
|
marker: object_marker,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}
|
||||||
|
.with_walkdir_timeouts(BACKGROUND_WALKDIR_TIMEOUT),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ use tokio::time::Duration as TokioDuration;
|
|||||||
use tokio_util::io::ReaderStream;
|
use tokio_util::io::ReaderStream;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::{debug, error, instrument, trace, warn};
|
use tracing::{debug, error, instrument, trace, warn};
|
||||||
|
|
||||||
|
const BACKGROUND_WALKDIR_TIMEOUT: TokioDuration = TokioDuration::from_secs(60);
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
const EVENT_RESYNC_STATUS_UPDATE_SKIPPED: &str = "replication_resync_status_update_skipped";
|
const EVENT_RESYNC_STATUS_UPDATE_SKIPPED: &str = "replication_resync_status_update_skipped";
|
||||||
@@ -645,7 +647,13 @@ impl ReplicationResyncer {
|
|||||||
|
|
||||||
if let Err(err) = storage
|
if let Err(err) = storage
|
||||||
.clone()
|
.clone()
|
||||||
.walk(cancellation_token.clone(), &opts.bucket, "", tx.clone(), WalkOptions::default())
|
.walk(
|
||||||
|
cancellation_token.clone(),
|
||||||
|
&opts.bucket,
|
||||||
|
"",
|
||||||
|
tx.clone(),
|
||||||
|
WalkOptions::default().with_walkdir_timeouts(BACKGROUND_WALKDIR_TIMEOUT),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
error!(
|
error!(
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ async fn take_fallback_candidate<T>(fallback_items: &Arc<TokioMutex<VecDeque<T>>
|
|||||||
fallback_items.lock().await.pop_front()
|
fallback_items.lock().await.pop_front()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn duration_millis(duration: Duration) -> u64 {
|
||||||
|
u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) enum TestReaderBehavior {
|
pub(crate) enum TestReaderBehavior {
|
||||||
@@ -117,6 +121,8 @@ pub struct ListPathRawOptions {
|
|||||||
pub report_not_found: bool,
|
pub report_not_found: bool,
|
||||||
pub per_disk_limit: i32,
|
pub per_disk_limit: i32,
|
||||||
pub skip_walkdir_total_timeout: bool,
|
pub skip_walkdir_total_timeout: bool,
|
||||||
|
pub walkdir_timeout: Option<Duration>,
|
||||||
|
pub walkdir_stall_timeout: Option<Duration>,
|
||||||
pub agreed: Option<AgreedFn>,
|
pub agreed: Option<AgreedFn>,
|
||||||
pub partial: Option<PartialFn>,
|
pub partial: Option<PartialFn>,
|
||||||
pub finished: Option<FinishedFn>,
|
pub finished: Option<FinishedFn>,
|
||||||
@@ -146,6 +152,8 @@ impl Clone for ListPathRawOptions {
|
|||||||
report_not_found: self.report_not_found,
|
report_not_found: self.report_not_found,
|
||||||
per_disk_limit: self.per_disk_limit,
|
per_disk_limit: self.per_disk_limit,
|
||||||
skip_walkdir_total_timeout: self.skip_walkdir_total_timeout,
|
skip_walkdir_total_timeout: self.skip_walkdir_total_timeout,
|
||||||
|
walkdir_timeout: self.walkdir_timeout,
|
||||||
|
walkdir_stall_timeout: self.walkdir_stall_timeout,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
test_reader_behaviors: self.test_reader_behaviors.clone(),
|
test_reader_behaviors: self.test_reader_behaviors.clone(),
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -259,6 +267,8 @@ async fn list_path_raw_inner(
|
|||||||
forward_to: opts_clone.forward_to.clone(),
|
forward_to: opts_clone.forward_to.clone(),
|
||||||
limit: opts_clone.per_disk_limit,
|
limit: opts_clone.per_disk_limit,
|
||||||
skip_total_timeout: opts_clone.skip_walkdir_total_timeout,
|
skip_total_timeout: opts_clone.skip_walkdir_total_timeout,
|
||||||
|
timeout_ms: opts_clone.walkdir_timeout.map(duration_millis),
|
||||||
|
stall_timeout_ms: opts_clone.walkdir_stall_timeout.map(duration_millis),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -419,6 +429,8 @@ async fn list_path_raw_inner(
|
|||||||
forward_to: opts_clone.forward_to.clone(),
|
forward_to: opts_clone.forward_to.clone(),
|
||||||
limit: opts_clone.per_disk_limit,
|
limit: opts_clone.per_disk_limit,
|
||||||
skip_total_timeout: opts_clone.skip_walkdir_total_timeout,
|
skip_total_timeout: opts_clone.skip_walkdir_total_timeout,
|
||||||
|
timeout_ms: opts_clone.walkdir_timeout.map(duration_millis),
|
||||||
|
stall_timeout_ms: opts_clone.walkdir_stall_timeout.map(duration_millis),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
&mut wr,
|
&mut wr,
|
||||||
@@ -485,10 +497,19 @@ async fn list_path_raw_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let revjob = spawn(async move {
|
let revjob = spawn(async move {
|
||||||
#[cfg(test)]
|
let peek_timeout = opts
|
||||||
let peek_timeout = opts.peek_timeout.unwrap_or_else(get_drive_walkdir_stall_timeout);
|
.walkdir_stall_timeout
|
||||||
#[cfg(not(test))]
|
.or({
|
||||||
let peek_timeout = get_drive_walkdir_stall_timeout();
|
#[cfg(test)]
|
||||||
|
{
|
||||||
|
opts.peek_timeout
|
||||||
|
}
|
||||||
|
#[cfg(not(test))]
|
||||||
|
{
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(get_drive_walkdir_stall_timeout);
|
||||||
let mut errs: Vec<Option<DiskError>> = Vec::with_capacity(readers.len());
|
let mut errs: Vec<Option<DiskError>> = Vec::with_capacity(readers.len());
|
||||||
for _ in 0..readers.len() {
|
for _ in 0..readers.len() {
|
||||||
errs.push(None);
|
errs.push(None);
|
||||||
|
|||||||
@@ -2018,14 +2018,14 @@ impl DiskAPI for RemoteDisk {
|
|||||||
|
|
||||||
let disk = self.disk_ref().await;
|
let disk = self.disk_ref().await;
|
||||||
let body = serde_json::to_vec(&opts)?;
|
let body = serde_json::to_vec(&opts)?;
|
||||||
let stall_timeout = get_drive_walkdir_stall_timeout();
|
let stall_timeout = opts.stall_timeout_duration().unwrap_or_else(get_drive_walkdir_stall_timeout);
|
||||||
let bucket = opts.bucket.clone();
|
let bucket = opts.bucket.clone();
|
||||||
let base_dir = opts.base_dir.clone();
|
let base_dir = opts.base_dir.clone();
|
||||||
let disk_for_log = disk.clone();
|
let disk_for_log = disk.clone();
|
||||||
let timeout_duration = if opts.skip_total_timeout {
|
let timeout_duration = if opts.skip_total_timeout {
|
||||||
Duration::ZERO
|
Duration::ZERO
|
||||||
} else {
|
} else {
|
||||||
get_drive_walkdir_timeout()
|
opts.timeout_duration().unwrap_or_else(get_drive_walkdir_timeout)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.execute_with_timeout_for_op_and_health_action(
|
self.execute_with_timeout_for_op_and_health_action(
|
||||||
@@ -3771,6 +3771,34 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_remote_disk_walk_dir_uses_per_request_stall_timeout() {
|
||||||
|
let transport = RecordingInternodeDataTransport::default();
|
||||||
|
let remote_disk = new_remote_disk_with_transport(Arc::new(transport.clone())).await;
|
||||||
|
let opts = WalkDirOptions {
|
||||||
|
bucket: "bucket".to_string(),
|
||||||
|
base_dir: "prefix".to_string(),
|
||||||
|
recursive: true,
|
||||||
|
stall_timeout_ms: Some(60_000),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut writer = Vec::new();
|
||||||
|
|
||||||
|
remote_disk
|
||||||
|
.walk_dir(opts, &mut writer)
|
||||||
|
.await
|
||||||
|
.expect("walk_dir should be sent through configured data transport");
|
||||||
|
|
||||||
|
let calls = transport.calls();
|
||||||
|
assert_eq!(calls.len(), 1);
|
||||||
|
match &calls[0] {
|
||||||
|
RecordedTransportCall::WalkDir(request) => {
|
||||||
|
assert_eq!(request.stall_timeout, Some(Duration::from_secs(60)));
|
||||||
|
}
|
||||||
|
other => panic!("expected walk-dir transport call, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_remote_disk_walk_dir_retries_once_on_retryable_transport_error() {
|
async fn test_remote_disk_walk_dir_retries_once_on_retryable_transport_error() {
|
||||||
let transport = RetryingWalkDirInternodeDataTransport::with_steps(vec![
|
let transport = RetryingWalkDirInternodeDataTransport::with_steps(vec![
|
||||||
|
|||||||
@@ -1221,7 +1221,7 @@ impl DiskAPI for LocalDiskWrapper {
|
|||||||
let timeout_duration = if opts.skip_total_timeout {
|
let timeout_duration = if opts.skip_total_timeout {
|
||||||
Duration::ZERO
|
Duration::ZERO
|
||||||
} else {
|
} else {
|
||||||
get_drive_walkdir_timeout()
|
opts.timeout_duration().unwrap_or_else(get_drive_walkdir_timeout)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.track_disk_health_with_op_and_timeout_action(
|
self.track_disk_health_with_op_and_timeout_action(
|
||||||
@@ -1545,6 +1545,52 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drive_walkdir_timeout_uses_default_when_unset() {
|
||||||
|
temp_env::with_var_unset(rustfs_config::ENV_DRIVE_WALKDIR_TIMEOUT_SECS, || {
|
||||||
|
temp_env::with_var_unset(rustfs_config::ENV_DRIVE_MAX_TIMEOUT_DURATION, || {
|
||||||
|
temp_env::with_var_unset(rustfs_config::ENV_DRIVE_TIMEOUT_PROFILE, || {
|
||||||
|
assert_eq!(
|
||||||
|
get_drive_walkdir_timeout(),
|
||||||
|
Duration::from_secs(rustfs_config::DEFAULT_DRIVE_WALKDIR_TIMEOUT_SECS)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drive_walkdir_stall_timeout_uses_default_when_unset() {
|
||||||
|
temp_env::with_var_unset(rustfs_config::ENV_DRIVE_WALKDIR_STALL_TIMEOUT_SECS, || {
|
||||||
|
temp_env::with_var_unset(rustfs_config::ENV_DRIVE_MAX_TIMEOUT_DURATION, || {
|
||||||
|
temp_env::with_var_unset(rustfs_config::ENV_DRIVE_TIMEOUT_PROFILE, || {
|
||||||
|
assert_eq!(
|
||||||
|
get_drive_walkdir_stall_timeout(),
|
||||||
|
Duration::from_secs(rustfs_config::DEFAULT_DRIVE_WALKDIR_STALL_TIMEOUT_SECS)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drive_walkdir_timeout_prefers_canonical_over_legacy() {
|
||||||
|
temp_env::with_var(rustfs_config::ENV_DRIVE_WALKDIR_TIMEOUT_SECS, Some("11"), || {
|
||||||
|
temp_env::with_var(rustfs_config::ENV_DRIVE_MAX_TIMEOUT_DURATION, Some("17"), || {
|
||||||
|
assert_eq!(get_drive_walkdir_timeout(), Duration::from_secs(11));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drive_walkdir_stall_timeout_prefers_canonical_over_legacy() {
|
||||||
|
temp_env::with_var(rustfs_config::ENV_DRIVE_WALKDIR_STALL_TIMEOUT_SECS, Some("13"), || {
|
||||||
|
temp_env::with_var(rustfs_config::ENV_DRIVE_MAX_TIMEOUT_DURATION, Some("17"), || {
|
||||||
|
assert_eq!(get_drive_walkdir_stall_timeout(), Duration::from_secs(13));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn object_disk_read_timeout_uses_default_when_unset() {
|
fn object_disk_read_timeout_uses_default_when_unset() {
|
||||||
temp_env::with_var_unset(rustfs_config::ENV_OBJECT_DISK_READ_TIMEOUT, || {
|
temp_env::with_var_unset(rustfs_config::ENV_OBJECT_DISK_READ_TIMEOUT, || {
|
||||||
@@ -1795,6 +1841,50 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn walk_dir_uses_per_request_timeout_before_env_default() {
|
||||||
|
temp_env::async_with_vars([(rustfs_config::ENV_DRIVE_WALKDIR_TIMEOUT_SECS, Some("60"))], async {
|
||||||
|
let dir = tempfile::tempdir().expect("temp dir should be created");
|
||||||
|
let endpoint =
|
||||||
|
Endpoint::try_from(dir.path().to_str().expect("temp dir should be valid UTF-8")).expect("endpoint should parse");
|
||||||
|
let disk = Arc::new(LocalDisk::new(&endpoint, false).await.expect("local disk should be created"));
|
||||||
|
let wrapper = LocalDiskWrapper::new(disk, false);
|
||||||
|
let bucket = "test-bucket";
|
||||||
|
let object = "test-object";
|
||||||
|
|
||||||
|
wrapper.make_volume(bucket).await.expect("bucket should be created");
|
||||||
|
|
||||||
|
let mut file_info = FileInfo::new(&format!("{bucket}/{object}"), 1, 0);
|
||||||
|
file_info.volume = bucket.to_string();
|
||||||
|
file_info.name = object.to_string();
|
||||||
|
file_info.mod_time = Some(::time::OffsetDateTime::now_utc());
|
||||||
|
file_info.erasure.index = 1;
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.write_metadata("", bucket, object, file_info)
|
||||||
|
.await
|
||||||
|
.expect("object metadata should be written");
|
||||||
|
|
||||||
|
let mut writer = PendingWriter;
|
||||||
|
let result = wrapper
|
||||||
|
.walk_dir(
|
||||||
|
WalkDirOptions {
|
||||||
|
bucket: bucket.to_string(),
|
||||||
|
recursive: true,
|
||||||
|
timeout_ms: Some(10),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&mut writer,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(result.expect_err("walk_dir should use per-request timeout"), DiskError::Timeout);
|
||||||
|
assert_eq!(wrapper.runtime_state(), RuntimeDriveHealthState::Online);
|
||||||
|
assert!(!wrapper.health.is_faulty());
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn walk_dir_skip_total_timeout_keeps_stream_pending() {
|
async fn walk_dir_skip_total_timeout_keeps_stream_pending() {
|
||||||
temp_env::async_with_vars([(rustfs_config::ENV_DRIVE_WALKDIR_TIMEOUT_SECS, Some("1"))], async {
|
temp_env::async_with_vars([(rustfs_config::ENV_DRIVE_WALKDIR_TIMEOUT_SECS, Some("1"))], async {
|
||||||
|
|||||||
@@ -813,6 +813,24 @@ pub struct WalkDirOptions {
|
|||||||
// Skip the wrapper-level total timeout for long streaming walks.
|
// Skip the wrapper-level total timeout for long streaming walks.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub skip_total_timeout: bool,
|
pub skip_total_timeout: bool,
|
||||||
|
|
||||||
|
// Override the wrapper-level total timeout for long background walks.
|
||||||
|
#[serde(default)]
|
||||||
|
pub timeout_ms: Option<u64>,
|
||||||
|
|
||||||
|
// Override the remote stream stall timeout for long background walks.
|
||||||
|
#[serde(default)]
|
||||||
|
pub stall_timeout_ms: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalkDirOptions {
|
||||||
|
pub fn timeout_duration(&self) -> Option<std::time::Duration> {
|
||||||
|
self.timeout_ms.map(std::time::Duration::from_millis)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stall_timeout_duration(&self) -> Option<std::time::Duration> {
|
||||||
|
self.stall_timeout_ms.map(std::time::Duration::from_millis)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
@@ -1046,6 +1064,8 @@ mod tests {
|
|||||||
limit: 100,
|
limit: 100,
|
||||||
disk_id: "disk-123".to_string(),
|
disk_id: "disk-123".to_string(),
|
||||||
skip_total_timeout: false,
|
skip_total_timeout: false,
|
||||||
|
timeout_ms: Some(10_000),
|
||||||
|
stall_timeout_ms: Some(20_000),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(opts.bucket, "test-bucket");
|
assert_eq!(opts.bucket, "test-bucket");
|
||||||
@@ -1058,6 +1078,8 @@ mod tests {
|
|||||||
assert_eq!(opts.limit, 100);
|
assert_eq!(opts.limit, 100);
|
||||||
assert_eq!(opts.disk_id, "disk-123");
|
assert_eq!(opts.disk_id, "disk-123");
|
||||||
assert!(!opts.skip_total_timeout);
|
assert!(!opts.skip_total_timeout);
|
||||||
|
assert_eq!(opts.timeout_duration(), Some(std::time::Duration::from_secs(10)));
|
||||||
|
assert_eq!(opts.stall_timeout_duration(), Some(std::time::Duration::from_secs(20)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test DeleteOptions structure
|
/// Test DeleteOptions structure
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ use std::sync::{
|
|||||||
Arc,
|
Arc,
|
||||||
atomic::{AtomicU64, Ordering},
|
atomic::{AtomicU64, Ordering},
|
||||||
};
|
};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
use tokio::io::duplex;
|
use tokio::io::duplex;
|
||||||
use tokio::sync::broadcast::{self};
|
use tokio::sync::broadcast::{self};
|
||||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||||
@@ -70,6 +70,10 @@ const MAX_OBJECT_LIST: i32 = 1000;
|
|||||||
|
|
||||||
const METACACHE_SHARE_PREFIX: bool = false;
|
const METACACHE_SHARE_PREFIX: bool = false;
|
||||||
|
|
||||||
|
fn duration_millis(duration: Duration) -> u64 {
|
||||||
|
u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
|
||||||
|
}
|
||||||
|
|
||||||
type ListObjectsInfo = StorageListObjectsInfo<ObjectInfo>;
|
type ListObjectsInfo = StorageListObjectsInfo<ObjectInfo>;
|
||||||
type ListObjectsV2Info = StorageListObjectsV2Info<ObjectInfo>;
|
type ListObjectsV2Info = StorageListObjectsV2Info<ObjectInfo>;
|
||||||
type ListObjectVersionsInfo = StorageListObjectVersionsInfo<ObjectInfo>;
|
type ListObjectVersionsInfo = StorageListObjectVersionsInfo<ObjectInfo>;
|
||||||
@@ -252,6 +256,8 @@ pub struct ListPathOptions {
|
|||||||
pub set_idx: Option<usize>,
|
pub set_idx: Option<usize>,
|
||||||
pub cursor_source: Option<ListSourceMode>,
|
pub cursor_source: Option<ListSourceMode>,
|
||||||
pub cursor_generation: Option<String>,
|
pub cursor_generation: Option<String>,
|
||||||
|
pub walkdir_timeout: Option<Duration>,
|
||||||
|
pub walkdir_stall_timeout: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const MARKER_TAG_VERSION: &str = "v2";
|
const MARKER_TAG_VERSION: &str = "v2";
|
||||||
@@ -2488,6 +2494,8 @@ struct ListingSupplementOptions {
|
|||||||
forward_to: Option<String>,
|
forward_to: Option<String>,
|
||||||
per_disk_limit: i32,
|
per_disk_limit: i32,
|
||||||
skip_total_timeout: bool,
|
skip_total_timeout: bool,
|
||||||
|
walkdir_timeout: Option<Duration>,
|
||||||
|
walkdir_stall_timeout: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ListingSupplement {
|
struct ListingSupplement {
|
||||||
@@ -2618,6 +2626,8 @@ async fn read_fallback_listing_disk(
|
|||||||
forward_to: options.forward_to,
|
forward_to: options.forward_to,
|
||||||
limit: options.per_disk_limit,
|
limit: options.per_disk_limit,
|
||||||
skip_total_timeout: options.skip_total_timeout,
|
skip_total_timeout: options.skip_total_timeout,
|
||||||
|
timeout_ms: options.walkdir_timeout.map(duration_millis),
|
||||||
|
stall_timeout_ms: options.walkdir_stall_timeout.map(duration_millis),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
&mut wr,
|
&mut wr,
|
||||||
@@ -4224,6 +4234,8 @@ impl ECStore {
|
|||||||
forward_to: opts.marker.clone(),
|
forward_to: opts.marker.clone(),
|
||||||
per_disk_limit: bounded_usize_to_i32(opts.limit),
|
per_disk_limit: bounded_usize_to_i32(opts.limit),
|
||||||
skip_total_timeout: false,
|
skip_total_timeout: false,
|
||||||
|
walkdir_timeout: opts.walkdir_timeout,
|
||||||
|
walkdir_stall_timeout: opts.walkdir_stall_timeout,
|
||||||
},
|
},
|
||||||
fallback_disks.clone(),
|
fallback_disks.clone(),
|
||||||
claim_tracker.clone(),
|
claim_tracker.clone(),
|
||||||
@@ -4244,6 +4256,8 @@ impl ECStore {
|
|||||||
forward_to: opts.marker.clone(),
|
forward_to: opts.marker.clone(),
|
||||||
min_disks: raw_min_disks,
|
min_disks: raw_min_disks,
|
||||||
per_disk_limit: bounded_usize_to_i32(opts.limit),
|
per_disk_limit: bounded_usize_to_i32(opts.limit),
|
||||||
|
walkdir_timeout: opts.walkdir_timeout,
|
||||||
|
walkdir_stall_timeout: opts.walkdir_stall_timeout,
|
||||||
agreed: Some(Box::new(move |entry: MetaCacheEntry| {
|
agreed: Some(Box::new(move |entry: MetaCacheEntry| {
|
||||||
Box::pin({
|
Box::pin({
|
||||||
let value = tx1.clone();
|
let value = tx1.clone();
|
||||||
@@ -5364,6 +5378,8 @@ impl Sets {
|
|||||||
forward_to: opts.marker.clone(),
|
forward_to: opts.marker.clone(),
|
||||||
per_disk_limit: bounded_usize_to_i32(opts.limit),
|
per_disk_limit: bounded_usize_to_i32(opts.limit),
|
||||||
skip_total_timeout: false,
|
skip_total_timeout: false,
|
||||||
|
walkdir_timeout: opts.walkdir_timeout,
|
||||||
|
walkdir_stall_timeout: opts.walkdir_stall_timeout,
|
||||||
},
|
},
|
||||||
fallback_disks.clone(),
|
fallback_disks.clone(),
|
||||||
claim_tracker.clone(),
|
claim_tracker.clone(),
|
||||||
@@ -5384,6 +5400,8 @@ impl Sets {
|
|||||||
forward_to: opts.marker.clone(),
|
forward_to: opts.marker.clone(),
|
||||||
min_disks: raw_min_disks,
|
min_disks: raw_min_disks,
|
||||||
per_disk_limit: bounded_usize_to_i32(opts.limit),
|
per_disk_limit: bounded_usize_to_i32(opts.limit),
|
||||||
|
walkdir_timeout: opts.walkdir_timeout,
|
||||||
|
walkdir_stall_timeout: opts.walkdir_stall_timeout,
|
||||||
agreed: Some(Box::new(move |entry: MetaCacheEntry| {
|
agreed: Some(Box::new(move |entry: MetaCacheEntry| {
|
||||||
Box::pin({
|
Box::pin({
|
||||||
let value = tx1.clone();
|
let value = tx1.clone();
|
||||||
@@ -5953,6 +5971,8 @@ impl SetDisks {
|
|||||||
incl_deleted: true,
|
incl_deleted: true,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
versioned: true,
|
versioned: true,
|
||||||
|
walkdir_timeout: opts.walkdir_timeout,
|
||||||
|
walkdir_stall_timeout: opts.walkdir_stall_timeout,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
entry_tx,
|
entry_tx,
|
||||||
@@ -6215,6 +6235,8 @@ impl SetDisks {
|
|||||||
forward_to: opts.marker.clone(),
|
forward_to: opts.marker.clone(),
|
||||||
per_disk_limit: limit,
|
per_disk_limit: limit,
|
||||||
skip_total_timeout: false,
|
skip_total_timeout: false,
|
||||||
|
walkdir_timeout: opts.walkdir_timeout,
|
||||||
|
walkdir_stall_timeout: opts.walkdir_stall_timeout,
|
||||||
},
|
},
|
||||||
fallback_disks.clone(),
|
fallback_disks.clone(),
|
||||||
claim_tracker.clone(),
|
claim_tracker.clone(),
|
||||||
@@ -6235,6 +6257,8 @@ impl SetDisks {
|
|||||||
forward_to: opts.marker,
|
forward_to: opts.marker,
|
||||||
min_disks: raw_min_disks,
|
min_disks: raw_min_disks,
|
||||||
per_disk_limit: limit,
|
per_disk_limit: limit,
|
||||||
|
walkdir_timeout: opts.walkdir_timeout,
|
||||||
|
walkdir_stall_timeout: opts.walkdir_stall_timeout,
|
||||||
agreed: Some(Box::new(move |entry: MetaCacheEntry| {
|
agreed: Some(Box::new(move |entry: MetaCacheEntry| {
|
||||||
Box::pin({
|
Box::pin({
|
||||||
let value = tx1.clone();
|
let value = tx1.clone();
|
||||||
@@ -6597,6 +6621,8 @@ mod test {
|
|||||||
forward_to: None,
|
forward_to: None,
|
||||||
per_disk_limit: 100,
|
per_disk_limit: 100,
|
||||||
skip_total_timeout: true,
|
skip_total_timeout: true,
|
||||||
|
walkdir_timeout: None,
|
||||||
|
walkdir_stall_timeout: None,
|
||||||
},
|
},
|
||||||
Arc::new(Vec::new()),
|
Arc::new(Vec::new()),
|
||||||
FallbackClaimTracker::default(),
|
FallbackClaimTracker::default(),
|
||||||
@@ -6611,6 +6637,8 @@ mod test {
|
|||||||
forward_to: None,
|
forward_to: None,
|
||||||
per_disk_limit: 0,
|
per_disk_limit: 0,
|
||||||
skip_total_timeout: false,
|
skip_total_timeout: false,
|
||||||
|
walkdir_timeout: None,
|
||||||
|
walkdir_stall_timeout: None,
|
||||||
},
|
},
|
||||||
Arc::new(Vec::new()),
|
Arc::new(Vec::new()),
|
||||||
FallbackClaimTracker::default(),
|
FallbackClaimTracker::default(),
|
||||||
@@ -6658,6 +6686,8 @@ mod test {
|
|||||||
forward_to: None,
|
forward_to: None,
|
||||||
per_disk_limit: 0,
|
per_disk_limit: 0,
|
||||||
skip_total_timeout: false,
|
skip_total_timeout: false,
|
||||||
|
walkdir_timeout: None,
|
||||||
|
walkdir_stall_timeout: None,
|
||||||
},
|
},
|
||||||
Arc::new(vec![disk]),
|
Arc::new(vec![disk]),
|
||||||
FallbackClaimTracker::default(),
|
FallbackClaimTracker::default(),
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ use tokio_util::sync::CancellationToken;
|
|||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
use crate::storage_api::object_store::{
|
use crate::storage_api::object_store::{
|
||||||
HTTPPreconditions, ListOperations as _, ObjectInfoOrErr as StorageObjectInfoOrErr, ObjectOperations,
|
HTTPPreconditions, ListOperations, ObjectInfoOrErr as StorageObjectInfoOrErr, ObjectOperations,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static IAM_CONFIG_PREFIX: LazyLock<String> = LazyLock::new(|| format!("{IAM_CONFIG_ROOT_PREFIX}/iam"));
|
pub static IAM_CONFIG_PREFIX: LazyLock<String> = LazyLock::new(|| format!("{IAM_CONFIG_ROOT_PREFIX}/iam"));
|
||||||
@@ -65,6 +65,7 @@ type IamObjectOptions = <IamStore as ObjectOperations>::ObjectOptions;
|
|||||||
const IAM_IDENTITY_FILE: &str = "identity.json";
|
const IAM_IDENTITY_FILE: &str = "identity.json";
|
||||||
const IAM_POLICY_FILE: &str = "policy.json";
|
const IAM_POLICY_FILE: &str = "policy.json";
|
||||||
const IAM_GROUP_MEMBERS_FILE: &str = "members.json";
|
const IAM_GROUP_MEMBERS_FILE: &str = "members.json";
|
||||||
|
const BACKGROUND_WALKDIR_TIMEOUT: Duration = Duration::from_secs(60);
|
||||||
|
|
||||||
fn get_user_identity_path(user: &str, user_type: UserType) -> String {
|
fn get_user_identity_path(user: &str, user_type: UserType) -> String {
|
||||||
let base_path: &str = match user_type {
|
let base_path: &str = match user_type {
|
||||||
@@ -414,10 +415,9 @@ impl ObjectStore {
|
|||||||
let path = prefix.to_owned();
|
let path = prefix.to_owned();
|
||||||
let sender_on_error = sender.clone();
|
let sender_on_error = sender.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(err) = store
|
let walk_opts =
|
||||||
.walk(ctx.clone(), Self::BUCKET_NAME, &path, tx, Default::default())
|
<IamStore as ListOperations>::WalkOptions::default().with_walkdir_timeouts(BACKGROUND_WALKDIR_TIMEOUT);
|
||||||
.await
|
if let Err(err) = store.walk(ctx.clone(), Self::BUCKET_NAME, &path, tx, walk_opts).await {
|
||||||
{
|
|
||||||
let reason = classify_iam_system_path_failure_reason(&err);
|
let reason = classify_iam_system_path_failure_reason(&err);
|
||||||
record_system_path_failure("iam_config", "walk", reason);
|
record_system_path_failure("iam_config", "walk", reason);
|
||||||
error!(
|
error!(
|
||||||
|
|||||||
@@ -718,13 +718,13 @@ mod tests {
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
type MetadataResult = SwiftResult<Option<HashMap<String, String>>>;
|
type MetadataResult = SwiftResult<Option<HashMap<String, String>>>;
|
||||||
|
type ResultQueue<T> = Mutex<VecDeque<T>>;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
struct MockExpirationObjectBackend {
|
struct MockExpirationObjectBackend {
|
||||||
expiring_objects: Mutex<Vec<ExpirationCandidate>>,
|
expiring_objects: Mutex<Vec<ExpirationCandidate>>,
|
||||||
metadata_results: Mutex<VecDeque<MetadataResult>>,
|
metadata_results: ResultQueue<MetadataResult>,
|
||||||
delete_results: Mutex<VecDeque<SwiftResult<()>>>,
|
delete_results: ResultQueue<SwiftResult<()>>,
|
||||||
deleted_objects: Mutex<Vec<(String, String, String)>>,
|
deleted_objects: Mutex<Vec<(String, String, String)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ const DATA_SCANNER_COMPACT_LEAST_OBJECT: usize = 500;
|
|||||||
const DATA_SCANNER_COMPACT_AT_CHILDREN: usize = 10000;
|
const DATA_SCANNER_COMPACT_AT_CHILDREN: usize = 10000;
|
||||||
const DATA_SCANNER_COMPACT_AT_FOLDERS: usize = DATA_SCANNER_COMPACT_AT_CHILDREN / 4;
|
const DATA_SCANNER_COMPACT_AT_FOLDERS: usize = DATA_SCANNER_COMPACT_AT_CHILDREN / 4;
|
||||||
const DATA_SCANNER_FORCE_COMPACT_AT_FOLDERS: usize = 250_000;
|
const DATA_SCANNER_FORCE_COMPACT_AT_FOLDERS: usize = 250_000;
|
||||||
|
const SCANNER_LIST_PATH_RAW_TIMEOUT: Duration = Duration::from_secs(60);
|
||||||
const DEFAULT_HEAL_OBJECT_SELECT_PROB: u32 = 1024;
|
const DEFAULT_HEAL_OBJECT_SELECT_PROB: u32 = 1024;
|
||||||
const ENV_DATA_USAGE_UPDATE_DIR_CYCLES: &str = "RUSTFS_DATA_USAGE_UPDATE_DIR_CYCLES";
|
const ENV_DATA_USAGE_UPDATE_DIR_CYCLES: &str = "RUSTFS_DATA_USAGE_UPDATE_DIR_CYCLES";
|
||||||
const ENV_HEAL_OBJECT_SELECT_PROB: &str = "RUSTFS_HEAL_OBJECT_SELECT_PROB";
|
const ENV_HEAL_OBJECT_SELECT_PROB: &str = "RUSTFS_HEAL_OBJECT_SELECT_PROB";
|
||||||
@@ -2397,6 +2398,8 @@ impl FolderScanner {
|
|||||||
recursive: true,
|
recursive: true,
|
||||||
report_not_found: true,
|
report_not_found: true,
|
||||||
min_disks: disks_quorum,
|
min_disks: disks_quorum,
|
||||||
|
walkdir_timeout: Some(SCANNER_LIST_PATH_RAW_TIMEOUT),
|
||||||
|
walkdir_stall_timeout: Some(SCANNER_LIST_PATH_RAW_TIMEOUT),
|
||||||
agreed: Some(Box::new(move |entry: MetaCacheEntry| {
|
agreed: Some(Box::new(move |entry: MetaCacheEntry| {
|
||||||
let entry_name = entry.name.clone();
|
let entry_name = entry.name.clone();
|
||||||
let agreed_tx = agreed_tx.clone();
|
let agreed_tx = agreed_tx.clone();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::replication::{
|
use crate::replication::{
|
||||||
ReplicationState, ReplicationStatusType, VersionPurgeStatusType, replication_statuses_map, version_purge_statuses_map,
|
ReplicationState, ReplicationStatusType, VersionPurgeStatusType, replication_statuses_map, version_purge_statuses_map,
|
||||||
@@ -238,6 +239,8 @@ pub struct WalkOptions<Filter> {
|
|||||||
pub versions_sort: WalkVersionsSortOrder,
|
pub versions_sort: WalkVersionsSortOrder,
|
||||||
pub limit: usize,
|
pub limit: usize,
|
||||||
pub include_free_versions: bool,
|
pub include_free_versions: bool,
|
||||||
|
pub walkdir_timeout: Option<Duration>,
|
||||||
|
pub walkdir_stall_timeout: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Filter> Default for WalkOptions<Filter> {
|
impl<Filter> Default for WalkOptions<Filter> {
|
||||||
@@ -250,10 +253,20 @@ impl<Filter> Default for WalkOptions<Filter> {
|
|||||||
versions_sort: WalkVersionsSortOrder::default(),
|
versions_sort: WalkVersionsSortOrder::default(),
|
||||||
limit: 0,
|
limit: 0,
|
||||||
include_free_versions: false,
|
include_free_versions: false,
|
||||||
|
walkdir_timeout: None,
|
||||||
|
walkdir_stall_timeout: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Filter> WalkOptions<Filter> {
|
||||||
|
pub fn with_walkdir_timeouts(mut self, timeout: Duration) -> Self {
|
||||||
|
self.walkdir_timeout = Some(timeout);
|
||||||
|
self.walkdir_stall_timeout = Some(timeout);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait ObjectIO: Send + Sync + fmt::Debug + 'static {
|
pub trait ObjectIO: Send + Sync + fmt::Debug + 'static {
|
||||||
type Error: std::error::Error + Send + Sync + 'static;
|
type Error: std::error::Error + Send + Sync + 'static;
|
||||||
|
|||||||
Reference in New Issue
Block a user