mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-22 20:36:38 +00:00
Feature/scanner performance optimization (#498)
* Refactor: reimplement scanner Signed-off-by: RustFS Developer <dandan@rustfs.com> * comment lock Signed-off-by: junxiang Mu <1948535941@qq.com> * remove dirty file Signed-off-by: junxiang Mu <1948535941@qq.com> * Fix: fix rebase * fix(scanner): Improve error handling and logging Signed-off-by: junxiang Mu <1948535941@qq.com> --------- Signed-off-by: RustFS Developer <dandan@rustfs.com> Signed-off-by: junxiang Mu <1948535941@qq.com> Co-authored-by: RustFS Developer <dandan@rustfs.com>
This commit is contained in:
@@ -686,7 +686,14 @@ pub async fn expire_transitioned_object(
|
||||
//transitionLogIf(ctx, err);
|
||||
}
|
||||
|
||||
let dobj = api.delete_object(&oi.bucket, &oi.name, opts).await?;
|
||||
let dobj = match api.delete_object(&oi.bucket, &oi.name, opts).await {
|
||||
Ok(obj) => obj,
|
||||
Err(e) => {
|
||||
error!("Failed to delete transitioned object {}/{}: {:?}", oi.bucket, oi.name, e);
|
||||
// Return the original object info if deletion fails
|
||||
oi.clone()
|
||||
}
|
||||
};
|
||||
|
||||
//defer auditLogLifecycle(ctx, *oi, ILMExpiry, tags, traceFn)
|
||||
|
||||
@@ -947,10 +954,14 @@ pub async fn apply_expiry_on_non_transitioned_objects(
|
||||
|
||||
//debug!("lc_event.action: {:?}", lc_event.action);
|
||||
//debug!("opts: {:?}", opts);
|
||||
let mut dobj = api
|
||||
.delete_object(&oi.bucket, &encode_dir_object(&oi.name), opts)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut dobj = match api.delete_object(&oi.bucket, &encode_dir_object(&oi.name), opts).await {
|
||||
Ok(obj) => obj,
|
||||
Err(e) => {
|
||||
error!("Failed to delete object {}/{}: {:?}", oi.bucket, oi.name, e);
|
||||
// Return the original object info if deletion fails
|
||||
oi.clone()
|
||||
}
|
||||
};
|
||||
//debug!("dobj: {:?}", dobj);
|
||||
if dobj.name.is_empty() {
|
||||
dobj = oi.clone();
|
||||
|
||||
@@ -439,6 +439,7 @@ impl Lifecycle for BucketLifecycleConfiguration {
|
||||
if date0.unix_timestamp() != 0
|
||||
&& (now.unix_timestamp() == 0 || now.unix_timestamp() > date0.unix_timestamp())
|
||||
{
|
||||
info!("eval_inner: expiration by date - date0={:?}", date0);
|
||||
events.push(Event {
|
||||
action: IlmAction::DeleteAction,
|
||||
rule_id: rule.id.clone().expect("err!"),
|
||||
@@ -473,7 +474,11 @@ impl Lifecycle for BucketLifecycleConfiguration {
|
||||
}*/
|
||||
events.push(event);
|
||||
}
|
||||
} else {
|
||||
info!("eval_inner: expiration.days is None");
|
||||
}
|
||||
} else {
|
||||
info!("eval_inner: rule.expiration is None");
|
||||
}
|
||||
|
||||
if obj.transition_status != TRANSITION_COMPLETE {
|
||||
@@ -619,6 +624,7 @@ impl LifecycleCalculate for Transition {
|
||||
|
||||
pub fn expected_expiry_time(mod_time: OffsetDateTime, days: i32) -> OffsetDateTime {
|
||||
if days == 0 {
|
||||
info!("expected_expiry_time: days=0, returning UNIX_EPOCH for immediate expiry");
|
||||
return OffsetDateTime::UNIX_EPOCH; // Return epoch time to ensure immediate expiry
|
||||
}
|
||||
let t = mod_time
|
||||
@@ -631,6 +637,7 @@ pub fn expected_expiry_time(mod_time: OffsetDateTime, days: i32) -> OffsetDateTi
|
||||
}
|
||||
}
|
||||
//t.Truncate(24 * hour)
|
||||
info!("expected_expiry_time: mod_time={:?}, days={}, result={:?}", mod_time, days, t);
|
||||
t
|
||||
}
|
||||
|
||||
|
||||
@@ -35,12 +35,12 @@ pub enum ServiceType {
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||
pub struct LatencyStat {
|
||||
curr: u64, // 当前延迟
|
||||
avg: u64, // 平均延迟
|
||||
max: u64, // 最大延迟
|
||||
curr: u64, // current latency
|
||||
avg: u64, // average latency
|
||||
max: u64, // maximum latency
|
||||
}
|
||||
|
||||
// 定义 BucketTarget 结构体
|
||||
// Define BucketTarget struct
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||
pub struct BucketTarget {
|
||||
#[serde(rename = "sourcebucket")]
|
||||
|
||||
@@ -152,7 +152,7 @@ pub struct ReplicationPool {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[repr(u8)] // 明确表示底层值为 u8
|
||||
#[repr(u8)] // Explicitly indicate underlying value is u8
|
||||
pub enum ReplicationType {
|
||||
#[default]
|
||||
UnsetReplicationType = 0,
|
||||
@@ -600,7 +600,7 @@ use super::bucket_targets::TargetClient;
|
||||
//use crate::storage;
|
||||
|
||||
// 模拟依赖的类型
|
||||
pub struct Context; // 用于代替 Go 的 `context.Context`
|
||||
pub struct Context; // Used to replace Go's `context.Context`
|
||||
#[derive(Default)]
|
||||
pub struct ReplicationStats;
|
||||
|
||||
@@ -1024,7 +1024,7 @@ impl ReplicationStatusType {
|
||||
matches!(self, ReplicationStatusType::Pending) // Adjust logic if needed
|
||||
}
|
||||
|
||||
// 从字符串构造 ReplicationStatusType 枚举
|
||||
// Construct ReplicationStatusType enum from string
|
||||
pub fn from(value: &str) -> Self {
|
||||
match value.to_uppercase().as_str() {
|
||||
"PENDING" => ReplicationStatusType::Pending,
|
||||
@@ -1053,13 +1053,13 @@ impl VersionPurgeStatusType {
|
||||
matches!(self, VersionPurgeStatusType::Empty)
|
||||
}
|
||||
|
||||
// 检查是否是 Pending(Pending 或 Failed 都算作 Pending 状态)
|
||||
// Check if it's Pending (both Pending and Failed are considered Pending status)
|
||||
pub fn is_pending(&self) -> bool {
|
||||
matches!(self, VersionPurgeStatusType::Pending | VersionPurgeStatusType::Failed)
|
||||
}
|
||||
}
|
||||
|
||||
// 从字符串实现转换(类似于 Go 的字符串比较)
|
||||
// Implement conversion from string (similar to Go's string comparison)
|
||||
impl From<&str> for VersionPurgeStatusType {
|
||||
fn from(value: &str) -> Self {
|
||||
match value.to_uppercase().as_str() {
|
||||
@@ -1233,12 +1233,12 @@ pub fn get_replication_action(oi1: &ObjectInfo, oi2: &ObjectInfo, op_type: &str)
|
||||
ReplicationAction::ReplicateNone
|
||||
}
|
||||
|
||||
/// 目标的复制决策结构
|
||||
/// Target replication decision structure
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReplicateTargetDecision {
|
||||
pub replicate: bool, // 是否进行复制
|
||||
pub synchronous: bool, // 是否是同步复制
|
||||
pub arn: String, // 复制目标的 ARN
|
||||
pub replicate: bool, // Whether to perform replication
|
||||
pub synchronous: bool, // Whether it's synchronous replication
|
||||
pub arn: String, // ARN of the replication target
|
||||
pub id: String, // ID
|
||||
}
|
||||
|
||||
@@ -1396,16 +1396,16 @@ pub struct ReplicatedTargetInfo {
|
||||
pub arn: String,
|
||||
pub size: i64,
|
||||
pub duration: Duration,
|
||||
pub replication_action: ReplicationAction, // 完整或仅元数据
|
||||
pub op_type: i32, // 传输类型
|
||||
pub replication_status: ReplicationStatusType, // 当前复制状态
|
||||
pub prev_replication_status: ReplicationStatusType, // 上一个复制状态
|
||||
pub version_purge_status: VersionPurgeStatusType, // 版本清理状态
|
||||
pub resync_timestamp: String, // 重同步时间戳
|
||||
pub replication_resynced: bool, // 是否重同步
|
||||
pub endpoint: String, // 目标端点
|
||||
pub secure: bool, // 是否安全连接
|
||||
pub err: Option<String>, // 错误信息
|
||||
pub replication_action: ReplicationAction, // Complete or metadata only
|
||||
pub op_type: i32, // Transfer type
|
||||
pub replication_status: ReplicationStatusType, // Current replication status
|
||||
pub prev_replication_status: ReplicationStatusType, // Previous replication status
|
||||
pub version_purge_status: VersionPurgeStatusType, // Version purge status
|
||||
pub resync_timestamp: String, // Resync timestamp
|
||||
pub replication_resynced: bool, // Whether resynced
|
||||
pub endpoint: String, // Target endpoint
|
||||
pub secure: bool, // Whether secure connection
|
||||
pub err: Option<String>, // Error information
|
||||
}
|
||||
|
||||
// 实现 ReplicatedTargetInfo 方法
|
||||
@@ -1418,12 +1418,12 @@ impl ReplicatedTargetInfo {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct DeletedObjectReplicationInfo {
|
||||
#[serde(flatten)] // 使用 `flatten` 将 `DeletedObject` 的字段展开到当前结构体
|
||||
#[serde(flatten)] // Use `flatten` to expand `DeletedObject` fields into current struct
|
||||
pub deleted_object: DeletedObject,
|
||||
|
||||
pub bucket: String,
|
||||
pub event_type: String,
|
||||
pub op_type: ReplicationType, // 假设 `replication.Type` 是 `ReplicationType` 枚举
|
||||
pub op_type: ReplicationType, // Assume `replication.Type` is `ReplicationType` enum
|
||||
pub reset_id: String,
|
||||
pub target_arn: String,
|
||||
}
|
||||
@@ -2040,22 +2040,22 @@ impl ReplicateObjectInfo {
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct DeletedObject {
|
||||
#[serde(rename = "DeleteMarker")]
|
||||
pub delete_marker: Option<bool>, // Go 中的 `bool` 转换为 Rust 中的 `Option<bool>` 以支持 `omitempty`
|
||||
pub delete_marker: Option<bool>, // Go's `bool` converted to Rust's `Option<bool>` to support `omitempty`
|
||||
|
||||
#[serde(rename = "DeleteMarkerVersionId")]
|
||||
pub delete_marker_version_id: Option<String>, // `omitempty` 转为 `Option<String>`
|
||||
pub delete_marker_version_id: Option<String>, // `omitempty` converted to `Option<String>`
|
||||
|
||||
#[serde(rename = "Key")]
|
||||
pub object_name: Option<String>, // 同样适用 `Option` 包含 `omitempty`
|
||||
pub object_name: Option<String>, // Similarly use `Option` to include `omitempty`
|
||||
|
||||
#[serde(rename = "VersionId")]
|
||||
pub version_id: Option<String>, // 同上
|
||||
pub version_id: Option<String>, // Same as above
|
||||
|
||||
// 以下字段未出现在 XML 序列化中,因此不需要 serde 标注
|
||||
// The following fields do not appear in XML serialization, so no serde annotation needed
|
||||
#[serde(skip)]
|
||||
pub delete_marker_mtime: DateTime<Utc>, // 自定义类型,需定义或引入
|
||||
pub delete_marker_mtime: DateTime<Utc>, // Custom type, needs definition or import
|
||||
#[serde(skip)]
|
||||
pub replication_state: ReplicationState, // 自定义类型,需定义或引入
|
||||
pub replication_state: ReplicationState, // Custom type, needs definition or import
|
||||
}
|
||||
|
||||
// 假设 `DeleteMarkerMTime` 和 `ReplicationState` 的定义如下:
|
||||
@@ -2446,8 +2446,8 @@ pub fn clone_mss(v: &HashMap<String, String>) -> HashMap<String, String> {
|
||||
pub fn get_must_replicate_options(
|
||||
user_defined: &HashMap<String, String>,
|
||||
user_tags: &str,
|
||||
status: ReplicationStatusType, // 假设 `status` 是字符串类型
|
||||
op: ReplicationType, // 假设 `op` 是字符串类型
|
||||
status: ReplicationStatusType, // Assume `status` is string type
|
||||
op: ReplicationType, // Assume `op` is string type
|
||||
opts: &ObjectOptions,
|
||||
) -> MustReplicateOptions {
|
||||
let mut meta = clone_mss(user_defined);
|
||||
|
||||
@@ -19,7 +19,7 @@ use tracing::error;
|
||||
|
||||
pub const MIN_COMPRESSIBLE_SIZE: usize = 4096;
|
||||
|
||||
// 环境变量名称,用于控制是否启用压缩
|
||||
// Environment variable name to control whether compression is enabled
|
||||
pub const ENV_COMPRESSION_ENABLED: &str = "RUSTFS_COMPRESSION_ENABLED";
|
||||
|
||||
// Some standard object extensions which we strictly dis-allow for compression.
|
||||
@@ -39,14 +39,14 @@ pub const STANDARD_EXCLUDE_COMPRESS_CONTENT_TYPES: &[&str] = &[
|
||||
];
|
||||
|
||||
pub fn is_compressible(headers: &http::HeaderMap, object_name: &str) -> bool {
|
||||
// 检查环境变量是否启用压缩,默认关闭
|
||||
// Check if compression is enabled via environment variable, default disabled
|
||||
if let Ok(compression_enabled) = env::var(ENV_COMPRESSION_ENABLED) {
|
||||
if compression_enabled.to_lowercase() != "true" {
|
||||
error!("Compression is disabled by environment variable");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 环境变量未设置时默认关闭
|
||||
// Default disabled when environment variable is not set
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ mod tests {
|
||||
|
||||
let headers = HeaderMap::new();
|
||||
|
||||
// 测试环境变量控制
|
||||
// Test environment variable control
|
||||
temp_env::with_var(ENV_COMPRESSION_ENABLED, Some("false"), || {
|
||||
assert!(!is_compressible(&headers, "file.txt"));
|
||||
});
|
||||
@@ -94,14 +94,14 @@ mod tests {
|
||||
|
||||
temp_env::with_var(ENV_COMPRESSION_ENABLED, Some("true"), || {
|
||||
let mut headers = HeaderMap::new();
|
||||
// 测试不可压缩的扩展名
|
||||
// Test non-compressible extensions
|
||||
headers.insert("content-type", "text/plain".parse().unwrap());
|
||||
assert!(!is_compressible(&headers, "file.gz"));
|
||||
assert!(!is_compressible(&headers, "file.zip"));
|
||||
assert!(!is_compressible(&headers, "file.mp4"));
|
||||
assert!(!is_compressible(&headers, "file.jpg"));
|
||||
|
||||
// 测试不可压缩的内容类型
|
||||
// Test non-compressible content types
|
||||
headers.insert("content-type", "video/mp4".parse().unwrap());
|
||||
assert!(!is_compressible(&headers, "file.txt"));
|
||||
|
||||
@@ -114,7 +114,7 @@ mod tests {
|
||||
headers.insert("content-type", "application/x-gzip".parse().unwrap());
|
||||
assert!(!is_compressible(&headers, "file.txt"));
|
||||
|
||||
// 测试可压缩的情况
|
||||
// Test compressible cases
|
||||
headers.insert("content-type", "text/plain".parse().unwrap());
|
||||
assert!(is_compressible(&headers, "file.txt"));
|
||||
assert!(is_compressible(&headers, "file.log"));
|
||||
|
||||
@@ -41,14 +41,14 @@ impl<R> ParallelReader<R>
|
||||
where
|
||||
R: AsyncRead + Unpin + Send + Sync,
|
||||
{
|
||||
// readers传入前应处理disk错误,确保每个reader达到可用数量的BitrotReader
|
||||
// Readers should handle disk errors before being passed in, ensuring each reader reaches the available number of BitrotReaders
|
||||
pub fn new(readers: Vec<Option<BitrotReader<R>>>, e: Erasure, offset: usize, total_length: usize) -> Self {
|
||||
let shard_size = e.shard_size();
|
||||
let shard_file_size = e.shard_file_size(total_length as i64) as usize;
|
||||
|
||||
let offset = (offset / e.block_size) * shard_size;
|
||||
|
||||
// 确保offset不超过shard_file_size
|
||||
// Ensure offset does not exceed shard_file_size
|
||||
|
||||
ParallelReader {
|
||||
readers,
|
||||
@@ -99,7 +99,7 @@ where
|
||||
}
|
||||
}) as std::pin::Pin<Box<dyn std::future::Future<Output = (usize, Result<Vec<u8>, Error>)> + Send>>
|
||||
} else {
|
||||
// reader是None时返回FileNotFound错误
|
||||
// Return FileNotFound error when reader is None
|
||||
Box::pin(async move { (i, Err(Error::FileNotFound)) })
|
||||
as std::pin::Pin<Box<dyn std::future::Future<Output = (usize, Result<Vec<u8>, Error>)> + Send>>
|
||||
};
|
||||
@@ -146,7 +146,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取数据块总长度
|
||||
/// Get the total length of data blocks
|
||||
fn get_data_block_len(shards: &[Option<Vec<u8>>], data_blocks: usize) -> usize {
|
||||
let mut size = 0;
|
||||
for shard in shards.iter().take(data_blocks).flatten() {
|
||||
@@ -156,7 +156,7 @@ fn get_data_block_len(shards: &[Option<Vec<u8>>], data_blocks: usize) -> usize {
|
||||
size
|
||||
}
|
||||
|
||||
/// 将编码块中的数据块写入目标,支持 offset 和 length
|
||||
/// Write data blocks from encoded blocks to target, supporting offset and length
|
||||
async fn write_data_blocks<W>(
|
||||
writer: &mut W,
|
||||
en_blocks: &[Option<Vec<u8>>],
|
||||
|
||||
@@ -48,7 +48,7 @@ use uuid::Uuid;
|
||||
pub struct ReedSolomonEncoder {
|
||||
data_shards: usize,
|
||||
parity_shards: usize,
|
||||
// 使用RwLock确保线程安全,实现Send + Sync
|
||||
// Use RwLock to ensure thread safety, implementing Send + Sync
|
||||
encoder_cache: std::sync::RwLock<Option<reed_solomon_simd::ReedSolomonEncoder>>,
|
||||
decoder_cache: std::sync::RwLock<Option<reed_solomon_simd::ReedSolomonDecoder>>,
|
||||
}
|
||||
@@ -98,7 +98,7 @@ impl ReedSolomonEncoder {
|
||||
fn encode_with_simd(&self, shards_vec: &mut [&mut [u8]]) -> io::Result<()> {
|
||||
let shard_len = shards_vec[0].len();
|
||||
|
||||
// 获取或创建encoder
|
||||
// Get or create encoder
|
||||
let mut encoder = {
|
||||
let mut cache_guard = self
|
||||
.encoder_cache
|
||||
@@ -107,10 +107,10 @@ impl ReedSolomonEncoder {
|
||||
|
||||
match cache_guard.take() {
|
||||
Some(mut cached_encoder) => {
|
||||
// 使用reset方法重置现有encoder以适应新的参数
|
||||
// Use reset method to reset existing encoder to adapt to new parameters
|
||||
if let Err(e) = cached_encoder.reset(self.data_shards, self.parity_shards, shard_len) {
|
||||
warn!("Failed to reset SIMD encoder: {:?}, creating new one", e);
|
||||
// 如果reset失败,创建新的encoder
|
||||
// If reset fails, create new encoder
|
||||
reed_solomon_simd::ReedSolomonEncoder::new(self.data_shards, self.parity_shards, shard_len)
|
||||
.map_err(|e| io::Error::other(format!("Failed to create SIMD encoder: {e:?}")))?
|
||||
} else {
|
||||
@@ -118,34 +118,34 @@ impl ReedSolomonEncoder {
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// 第一次使用,创建新encoder
|
||||
// First use, create new encoder
|
||||
reed_solomon_simd::ReedSolomonEncoder::new(self.data_shards, self.parity_shards, shard_len)
|
||||
.map_err(|e| io::Error::other(format!("Failed to create SIMD encoder: {e:?}")))?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 添加原始shards
|
||||
// Add original shards
|
||||
for (i, shard) in shards_vec.iter().enumerate().take(self.data_shards) {
|
||||
encoder
|
||||
.add_original_shard(shard)
|
||||
.map_err(|e| io::Error::other(format!("Failed to add shard {i}: {e:?}")))?;
|
||||
}
|
||||
|
||||
// 编码并获取恢复shards
|
||||
// Encode and get recovery shards
|
||||
let result = encoder
|
||||
.encode()
|
||||
.map_err(|e| io::Error::other(format!("SIMD encoding failed: {e:?}")))?;
|
||||
|
||||
// 将恢复shards复制到输出缓冲区
|
||||
// Copy recovery shards to output buffer
|
||||
for (i, recovery_shard) in result.recovery_iter().enumerate() {
|
||||
if i + self.data_shards < shards_vec.len() {
|
||||
shards_vec[i + self.data_shards].copy_from_slice(recovery_shard);
|
||||
}
|
||||
}
|
||||
|
||||
// 将encoder放回缓存(在result被drop后encoder自动重置,可以重用)
|
||||
drop(result); // 显式drop result,确保encoder被重置
|
||||
// Return encoder to cache (encoder is automatically reset after result is dropped, can be reused)
|
||||
drop(result); // Explicitly drop result to ensure encoder is reset
|
||||
|
||||
*self
|
||||
.encoder_cache
|
||||
@@ -157,7 +157,7 @@ impl ReedSolomonEncoder {
|
||||
|
||||
/// Reconstruct missing shards.
|
||||
pub fn reconstruct(&self, shards: &mut [Option<Vec<u8>>]) -> io::Result<()> {
|
||||
// 使用 SIMD 进行重构
|
||||
// Use SIMD for reconstruction
|
||||
let simd_result = self.reconstruct_with_simd(shards);
|
||||
|
||||
match simd_result {
|
||||
@@ -333,9 +333,9 @@ impl Erasure {
|
||||
// let shard_size = self.shard_size();
|
||||
// let total_size = shard_size * self.total_shard_count();
|
||||
|
||||
// 数据切片数量
|
||||
// Data shard count
|
||||
let per_shard_size = calc_shard_size(data.len(), self.data_shards);
|
||||
// 总需求大小
|
||||
// Total required size
|
||||
let need_total_size = per_shard_size * self.total_shard_count();
|
||||
|
||||
// Create a new buffer with the required total length for all shards
|
||||
@@ -972,28 +972,28 @@ mod tests {
|
||||
|
||||
assert_eq!(shards.len(), data_shards + parity_shards);
|
||||
|
||||
// 验证每个shard的大小足够大,适合SIMD优化
|
||||
// Verify that each shard is large enough for SIMD optimization
|
||||
for (i, shard) in shards.iter().enumerate() {
|
||||
println!("🔍 Shard {}: {} bytes ({}KB)", i, shard.len(), shard.len() / 1024);
|
||||
assert!(shard.len() >= 512, "Shard {} is too small for SIMD: {} bytes", i, shard.len());
|
||||
}
|
||||
|
||||
// 模拟数据丢失 - 丢失最大可恢复数量的shard
|
||||
// Simulate data loss - lose maximum recoverable number of shards
|
||||
let mut shards_opt: Vec<Option<Vec<u8>>> = shards.iter().map(|b| Some(b.to_vec())).collect();
|
||||
shards_opt[0] = None; // 丢失第1个数据shard
|
||||
shards_opt[2] = None; // 丢失第3个数据shard
|
||||
shards_opt[8] = None; // 丢失第3个奇偶shard (index 6+3-1=8)
|
||||
shards_opt[0] = None; // Lose 1st data shard
|
||||
shards_opt[2] = None; // Lose 3rd data shard
|
||||
shards_opt[8] = None; // Lose 3rd parity shard (index 6+3-1=8)
|
||||
|
||||
println!("💥 Simulated loss of 3 shards (max recoverable with 3 parity shards)");
|
||||
|
||||
// 解码恢复数据
|
||||
// Decode and recover data
|
||||
let start = std::time::Instant::now();
|
||||
erasure.decode_data(&mut shards_opt).unwrap();
|
||||
let decode_duration = start.elapsed();
|
||||
|
||||
println!("⏱️ Decoding completed in: {decode_duration:?}");
|
||||
|
||||
// 验证恢复的数据完整性
|
||||
// Verify recovered data integrity
|
||||
let mut recovered = Vec::new();
|
||||
for shard in shards_opt.iter().take(data_shards) {
|
||||
recovered.extend_from_slice(shard.as_ref().unwrap());
|
||||
|
||||
@@ -3271,18 +3271,18 @@ impl ObjectIO for SetDisks {
|
||||
opts: &ObjectOptions,
|
||||
) -> Result<GetObjectReader> {
|
||||
// Acquire a shared read-lock early to protect read consistency
|
||||
let mut _read_lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
if !opts.no_lock {
|
||||
let guard_opt = self
|
||||
.namespace_lock
|
||||
.rlock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
.await?;
|
||||
// let mut _read_lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
// if !opts.no_lock {
|
||||
// let guard_opt = self
|
||||
// .namespace_lock
|
||||
// .rlock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
// .await?;
|
||||
|
||||
if guard_opt.is_none() {
|
||||
return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
}
|
||||
_read_lock_guard = guard_opt;
|
||||
}
|
||||
// if guard_opt.is_none() {
|
||||
// return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
// }
|
||||
// _read_lock_guard = guard_opt;
|
||||
// }
|
||||
|
||||
let (fi, files, disks) = self
|
||||
.get_object_fileinfo(bucket, object, opts, true)
|
||||
@@ -3330,9 +3330,9 @@ impl ObjectIO for SetDisks {
|
||||
let set_index = self.set_index;
|
||||
let pool_index = self.pool_index;
|
||||
// Move the read-lock guard into the task so it lives for the duration of the read
|
||||
let _guard_to_hold = _read_lock_guard; // moved into closure below
|
||||
// let _guard_to_hold = _read_lock_guard; // moved into closure below
|
||||
tokio::spawn(async move {
|
||||
let _guard = _guard_to_hold; // keep guard alive until task ends
|
||||
// let _guard = _guard_to_hold; // keep guard alive until task ends
|
||||
if let Err(e) = Self::get_object_with_fileinfo(
|
||||
&bucket,
|
||||
&object,
|
||||
@@ -3361,18 +3361,18 @@ impl ObjectIO for SetDisks {
|
||||
let disks = self.disks.read().await;
|
||||
|
||||
// Acquire per-object exclusive lock via RAII guard. It auto-releases asynchronously on drop.
|
||||
let mut _object_lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
if !opts.no_lock {
|
||||
let guard_opt = self
|
||||
.namespace_lock
|
||||
.lock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
.await?;
|
||||
// let mut _object_lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
// if !opts.no_lock {
|
||||
// let guard_opt = self
|
||||
// .namespace_lock
|
||||
// .lock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
// .await?;
|
||||
|
||||
if guard_opt.is_none() {
|
||||
return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
}
|
||||
_object_lock_guard = guard_opt;
|
||||
}
|
||||
// if guard_opt.is_none() {
|
||||
// return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
// }
|
||||
// _object_lock_guard = guard_opt;
|
||||
// }
|
||||
|
||||
if let Some(http_preconditions) = opts.http_preconditions.clone() {
|
||||
if let Some(err) = self.check_write_precondition(bucket, object, opts).await {
|
||||
@@ -4156,17 +4156,17 @@ impl StorageAPI for SetDisks {
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn get_object_info(&self, bucket: &str, object: &str, opts: &ObjectOptions) -> Result<ObjectInfo> {
|
||||
// Acquire a shared read-lock to protect consistency during info fetch
|
||||
let mut _read_lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
if !opts.no_lock {
|
||||
let guard_opt = self
|
||||
.namespace_lock
|
||||
.rlock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
.await?;
|
||||
if guard_opt.is_none() {
|
||||
return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
}
|
||||
_read_lock_guard = guard_opt;
|
||||
}
|
||||
// let mut _read_lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
// if !opts.no_lock {
|
||||
// let guard_opt = self
|
||||
// .namespace_lock
|
||||
// .rlock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
// .await?;
|
||||
// if guard_opt.is_none() {
|
||||
// return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
// }
|
||||
// _read_lock_guard = guard_opt;
|
||||
// }
|
||||
|
||||
let (fi, _, _) = self
|
||||
.get_object_fileinfo(bucket, object, opts, false)
|
||||
@@ -4199,17 +4199,17 @@ impl StorageAPI for SetDisks {
|
||||
// TODO: nslock
|
||||
|
||||
// Guard lock for metadata update
|
||||
let mut _lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
if !opts.no_lock {
|
||||
let guard_opt = self
|
||||
.namespace_lock
|
||||
.lock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
.await?;
|
||||
if guard_opt.is_none() {
|
||||
return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
}
|
||||
_lock_guard = guard_opt;
|
||||
}
|
||||
// let mut _lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
// if !opts.no_lock {
|
||||
// let guard_opt = self
|
||||
// .namespace_lock
|
||||
// .lock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
// .await?;
|
||||
// if guard_opt.is_none() {
|
||||
// return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
// }
|
||||
// _lock_guard = guard_opt;
|
||||
// }
|
||||
|
||||
let disks = self.get_disks_internal().await;
|
||||
|
||||
@@ -4302,17 +4302,17 @@ impl StorageAPI for SetDisks {
|
||||
};
|
||||
|
||||
// Acquire write-lock early; hold for the whole transition operation scope
|
||||
let mut _lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
if !opts.no_lock {
|
||||
let guard_opt = self
|
||||
.namespace_lock
|
||||
.lock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
.await?;
|
||||
if guard_opt.is_none() {
|
||||
return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
}
|
||||
_lock_guard = guard_opt;
|
||||
}
|
||||
// let mut _lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
// if !opts.no_lock {
|
||||
// let guard_opt = self
|
||||
// .namespace_lock
|
||||
// .lock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
// .await?;
|
||||
// if guard_opt.is_none() {
|
||||
// return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
// }
|
||||
// _lock_guard = guard_opt;
|
||||
// }
|
||||
|
||||
let (mut fi, meta_arr, online_disks) = self.get_object_fileinfo(bucket, object, opts, true).await?;
|
||||
/*if err != nil {
|
||||
@@ -4431,17 +4431,17 @@ impl StorageAPI for SetDisks {
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn restore_transitioned_object(&self, bucket: &str, object: &str, opts: &ObjectOptions) -> Result<()> {
|
||||
// Acquire write-lock early for the restore operation
|
||||
let mut _lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
if !opts.no_lock {
|
||||
let guard_opt = self
|
||||
.namespace_lock
|
||||
.lock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
.await?;
|
||||
if guard_opt.is_none() {
|
||||
return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
}
|
||||
_lock_guard = guard_opt;
|
||||
}
|
||||
// let mut _lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
// if !opts.no_lock {
|
||||
// let guard_opt = self
|
||||
// .namespace_lock
|
||||
// .lock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
// .await?;
|
||||
// if guard_opt.is_none() {
|
||||
// return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
// }
|
||||
// _lock_guard = guard_opt;
|
||||
// }
|
||||
let set_restore_header_fn = async move |oi: &mut ObjectInfo, rerr: Option<Error>| -> Result<()> {
|
||||
if rerr.is_none() {
|
||||
return Ok(());
|
||||
@@ -4516,17 +4516,17 @@ impl StorageAPI for SetDisks {
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn put_object_tags(&self, bucket: &str, object: &str, tags: &str, opts: &ObjectOptions) -> Result<ObjectInfo> {
|
||||
// Acquire write-lock for tag update (metadata write)
|
||||
let mut _lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
if !opts.no_lock {
|
||||
let guard_opt = self
|
||||
.namespace_lock
|
||||
.lock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
.await?;
|
||||
if guard_opt.is_none() {
|
||||
return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
}
|
||||
_lock_guard = guard_opt;
|
||||
}
|
||||
// let mut _lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
// if !opts.no_lock {
|
||||
// let guard_opt = self
|
||||
// .namespace_lock
|
||||
// .lock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
// .await?;
|
||||
// if guard_opt.is_none() {
|
||||
// return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
// }
|
||||
// _lock_guard = guard_opt;
|
||||
// }
|
||||
let (mut fi, _, disks) = self.get_object_fileinfo(bucket, object, opts, false).await?;
|
||||
|
||||
fi.metadata.insert(AMZ_OBJECT_TAGGING.to_owned(), tags.to_owned());
|
||||
@@ -5177,19 +5177,19 @@ impl StorageAPI for SetDisks {
|
||||
// let disks = Self::shuffle_disks(&disks, &fi.erasure.distribution);
|
||||
|
||||
// Acquire per-object exclusive lock via RAII guard. It auto-releases asynchronously on drop.
|
||||
let mut _object_lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
// let mut _object_lock_guard: Option<rustfs_lock::LockGuard> = None;
|
||||
if let Some(http_preconditions) = opts.http_preconditions.clone() {
|
||||
if !opts.no_lock {
|
||||
let guard_opt = self
|
||||
.namespace_lock
|
||||
.lock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
.await?;
|
||||
// if !opts.no_lock {
|
||||
// let guard_opt = self
|
||||
// .namespace_lock
|
||||
// .lock_guard(object, &self.locker_owner, Duration::from_secs(5), Duration::from_secs(10))
|
||||
// .await?;
|
||||
|
||||
if guard_opt.is_none() {
|
||||
return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
}
|
||||
_object_lock_guard = guard_opt;
|
||||
}
|
||||
// if guard_opt.is_none() {
|
||||
// return Err(Error::other("can not get lock. please retry".to_string()));
|
||||
// }
|
||||
// _object_lock_guard = guard_opt;
|
||||
// }
|
||||
|
||||
if let Some(err) = self.check_write_precondition(bucket, object, opts).await {
|
||||
return Err(err);
|
||||
|
||||
@@ -28,8 +28,8 @@ use crate::error::{
|
||||
};
|
||||
use crate::global::{
|
||||
DISK_ASSUME_UNKNOWN_SIZE, DISK_FILL_FRACTION, DISK_MIN_INODES, DISK_RESERVE_FRACTION, GLOBAL_BOOT_TIME,
|
||||
GLOBAL_LOCAL_DISK_MAP, GLOBAL_LOCAL_DISK_SET_DRIVES, GLOBAL_TierConfigMgr, get_global_endpoints, is_dist_erasure,
|
||||
is_erasure_sd, set_global_deployment_id, set_object_layer,
|
||||
GLOBAL_LOCAL_DISK_MAP, GLOBAL_LOCAL_DISK_SET_DRIVES, GLOBAL_TierConfigMgr, get_global_deployment_id, get_global_endpoints,
|
||||
is_dist_erasure, is_erasure_sd, set_global_deployment_id, set_object_layer,
|
||||
};
|
||||
use crate::notification_sys::get_global_notification_sys;
|
||||
use crate::pools::PoolMeta;
|
||||
@@ -241,8 +241,11 @@ impl ECStore {
|
||||
decommission_cancelers,
|
||||
});
|
||||
|
||||
// 只有在全局部署ID尚未设置时才设置它
|
||||
if let Some(dep_id) = deployment_id {
|
||||
set_global_deployment_id(dep_id);
|
||||
if get_global_deployment_id().is_none() {
|
||||
set_global_deployment_id(dep_id);
|
||||
}
|
||||
}
|
||||
|
||||
let wait_sec = 5;
|
||||
|
||||
@@ -221,7 +221,7 @@ fn check_format_erasure_value(format: &FormatV3) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// load_format_erasure_all 读取所有 format.json
|
||||
// load_format_erasure_all reads all format.json files
|
||||
pub async fn load_format_erasure_all(disks: &[Option<DiskStore>], heal: bool) -> (Vec<Option<FormatV3>>, Vec<Option<DiskError>>) {
|
||||
let mut futures = Vec::with_capacity(disks.len());
|
||||
let mut datas = Vec::with_capacity(disks.len());
|
||||
|
||||
@@ -612,7 +612,7 @@ impl ECStore {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// 读所有
|
||||
// Read all
|
||||
async fn list_merged(
|
||||
&self,
|
||||
rx: B_Receiver<bool>,
|
||||
|
||||
Reference in New Issue
Block a user