diff --git a/Cargo.lock b/Cargo.lock index ad2aa33d0..c979ec908 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1801,6 +1801,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + [[package]] name = "cbc" version = "0.1.2" @@ -2063,6 +2072,19 @@ dependencies = [ "unicode-width 0.2.2", ] +[[package]] +name = "compact_str" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79fcda08c33bb58b97008b2cdada6622500e949e060f5913361763121abd2416" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "static_assertions", + "zmij", +] + [[package]] name = "compression-codecs" version = "0.4.38" @@ -4226,9 +4248,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -4241,9 +4263,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -4251,15 +4273,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -4268,9 +4290,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-lite" @@ -4287,13 +4309,13 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -4309,21 +4331,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -4991,9 +5013,9 @@ dependencies = [ [[package]] name = "hotpath" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be80823867e0c9820c9237c38b21f9f4aa1ebb0db1f98ff25ac0b1d2c088a470" +checksum = "62e810bedda5a467ef5c9b5c8a20763fefebc89b63ef36f7ee44a143085204a2" dependencies = [ "arc-swap", "async-channel", @@ -5025,9 +5047,9 @@ dependencies = [ [[package]] name = "hotpath-macros" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61d1fb3ee80ae7b4743d29487665766ce5a1442e959521790e86317f89dcd5a3" +checksum = "01bdc59bfc1a9984bee2ff5da63b2f6fccbaa57cd9a4119d709524632bddf341" dependencies = [ "proc-macro2", "quote", @@ -5036,15 +5058,15 @@ dependencies = [ [[package]] name = "hotpath-macros-meta" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feede71fa226b0b5d523e58e7b0a1462935c0b8a00584a6669f45d564086209d" +checksum = "d9216e8a01abe1e1671c376dc8736fb1bf772d7a889538d25f9e1200120ced38" [[package]] name = "hotpath-meta" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424fe0a13105d3731f65237785f5b95c3e4b8bfae4a039d932f56192cd74afc0" +checksum = "f22a9d20435fb79511b19dae37b3607224cd98f342a410702d84657cc38fc72f" dependencies = [ "hotpath-macros-meta", ] @@ -5420,9 +5442,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9080b15e63775b9a2ac7dca720f7050a8b955e092ea0f6020a4a80f69998cdc0" +checksum = "d64d8ca234d152948ceaede1f419b6a83983a5ecccaac05fb337a809c96d3aa6" dependencies = [ "bitflags 2.13.1", "cfg-if", @@ -5896,18 +5918,18 @@ dependencies = [ [[package]] name = "liblzma" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45aec2360b3933207e27908049d8e4df4e476b58180afb1e56b2a4fb72efe4ba" +checksum = "2fe0a34ca854fd4f20c07f696fc8675aec78f87d88d29f5e10257a7490a1b2e1" dependencies = [ "liblzma-sys", ] [[package]] name = "liblzma-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a046c7f353ba30f810545151e04f63545833803f5b86ee3ddf1517247fe560a5" +checksum = "a0dad045e4b1b7b170be4b60b54b780cafb4490165461bac7d1cf7b703f61d5f" dependencies = [ "cc", "libc", @@ -6416,9 +6438,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.15" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9" dependencies = [ "async-lock", "crossbeam-channel", @@ -6747,9 +6769,9 @@ checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] @@ -7720,9 +7742,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" @@ -8074,9 +8096,9 @@ dependencies = [ [[package]] name = "pulldown-cmark-to-cmark" -version = "22.0.0" +version = "22.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" +checksum = "ab1ad36992cead65f02aa399a373a42730922f1525d988172634fdefdecb8a60" dependencies = [ "pulldown-cmark", ] @@ -8430,9 +8452,9 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.14.8" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" +checksum = "091e7a8e7d86e6feb87a27ce8e2cba29d49eff9507afeebefab7eeb2ca667fb4" dependencies = [ "aws-lc-rs", "pem", @@ -8837,9 +8859,9 @@ dependencies = [ [[package]] name = "russh" -version = "0.62.5" +version = "0.62.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c230e0ed9cbeb92fbad6c8848985d6df2a1464c0dc247a021abd666e9005e" +checksum = "b41043523e0edcbd4e31d00903e26f12994f63b21bae9904f7405c1ed92752a5" dependencies = [ "aes 0.9.2", "aws-lc-rs", @@ -9702,6 +9724,7 @@ name = "rustfs-lock" version = "1.0.0-rc.1" dependencies = [ "async-trait", + "compact_str", "crossbeam-queue", "futures", "hotpath", @@ -9712,7 +9735,6 @@ dependencies = [ "serde", "serde_json", "smallvec", - "smartstring", "thiserror 2.0.20", "tokio", "tonic", @@ -10546,9 +10568,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" dependencies = [ "aws-lc-rs", "ring", @@ -10928,9 +10950,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" dependencies = [ "base64 0.22.1", "bs58", @@ -10938,6 +10960,7 @@ dependencies = [ "hex", "indexmap 1.9.3", "indexmap 2.14.0", + "jiff", "schemars 0.9.0", "schemars 1.2.2", "serde_core", @@ -10948,9 +10971,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -11244,17 +11267,6 @@ dependencies = [ "serde", ] -[[package]] -name = "smartstring" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" -dependencies = [ - "autocfg", - "static_assertions", - "version_check", -] - [[package]] name = "snafu" version = "0.6.10" @@ -12807,9 +12819,9 @@ dependencies = [ [[package]] name = "whoami" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d" +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" dependencies = [ "libc", "libredox", diff --git a/Cargo.toml b/Cargo.toml index feae86db2..5cca4c433 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,10 +142,10 @@ async-recursion = "1.1.1" async-trait = "0.1.92" async-nats = { version = "0.50.0", default-features = false } axum = "0.8.9" -futures = "0.3.33" -futures-core = "0.3.33" +futures = "0.3.34" +futures-core = "0.3.34" futures-lite = "2.6.1" -futures-util = "0.3.33" +futures-util = "0.3.34" pollster = "1.0.1" pulsar = { default-features = false, version = "6.8.0" } lapin = { default-features = false, version = "4.10.0" } @@ -268,7 +268,7 @@ lz4 = "1.28.1" matchit = "0.9.2" md-5 = "0.11.0" mime_guess = "2.0.5" -moka = { version = "0.12.15" } +moka = { version = "0.12.16" } netif = "0.1.6" num_cpus = { version = "1.17.0" } nvml-wrapper = "0.12.1" @@ -294,7 +294,7 @@ serial_test = "4.0.1" shadow-rs = { default-features = false, version = "2.0.0" } siphasher = "1.0.3" smallvec = { version = "1.15.2" } -smartstring = "1.0.1" +compact_str = "0.10.0" snap = "1.1.2" starshard = { version = "2.2.2" } strum = { version = "0.28.0" } @@ -339,8 +339,8 @@ pyroscope = { version = "2.1.1" } libunftp = { version = "0.23.0" } unftp-core = "0.1.0" suppaftp = { version = "10.0.1" } -rcgen = { version = "0.14.8", default-features = false, features = ["aws_lc_rs", "crypto", "pem"] } -russh = { version = "0.62.5" } +rcgen = { version = "0.14.9", default-features = false, features = ["aws_lc_rs", "crypto", "pem"] } +russh = { version = "0.62.6" } russh-sftp = "2.4.0" # WebDAV @@ -349,7 +349,7 @@ dav-server = "0.11.0" # Performance Analysis and Memory Profiling mimalloc = { version = "0.1.52", git = "https://github.com/xonatius/mimalloc_rust.git", rev = "ce6338661179c8be22e516b00af7483f151485a7" } libmimalloc-sys = { version = "0.1.49", git = "https://github.com/xonatius/mimalloc_rust.git", rev = "ce6338661179c8be22e516b00af7483f151485a7", features = ["extended"] } -hotpath = { version = "0.23.1", default-features = false } +hotpath = { version = "0.23.2", default-features = false } # Snapshot testing for output format regression detection insta = { version = "1.48" } diff --git a/crates/ecstore/src/bucket/durability.rs b/crates/ecstore/src/bucket/durability.rs index 404c046de..1b3bc0176 100644 --- a/crates/ecstore/src/bucket/durability.rs +++ b/crates/ecstore/src/bucket/durability.rs @@ -64,10 +64,41 @@ impl BucketDurabilityConfig { } } +/// Default durability tier seeded into a newly created bucket's metadata +/// (rustfs/backlog#1811). `relaxed` aligns new buckets with MinIO's default +/// posture: object data is still fdatasynced, while xl.meta and directory-entry +/// fsyncs follow the relaxed durability gate. +pub const ENV_NEW_BUCKET_DURABILITY_MODE: &str = "RUSTFS_NEW_BUCKET_DURABILITY_MODE"; +pub const DEFAULT_NEW_BUCKET_DURABILITY_MODE: &str = BUCKET_DURABILITY_MODE_RELAXED; + +/// The `durability.json` bytes to seed into a freshly created bucket's metadata. +/// Empty means "no override" (the bucket then follows the global +/// `RUSTFS_DURABILITY_MODE`); otherwise the serialized chosen tier. Operators +/// can set `inherit` to disable the new-bucket override. Invalid values also +/// fail closed to inherit the global mode instead of seeding a surprising tier. +pub fn new_bucket_durability_config_json() -> Vec { + let raw = std::env::var(ENV_NEW_BUCKET_DURABILITY_MODE).unwrap_or_else(|_| DEFAULT_NEW_BUCKET_DURABILITY_MODE.to_string()); + let mode = raw.trim(); + if mode.eq_ignore_ascii_case("inherit") || mode.is_empty() || !BucketDurabilityConfig::is_valid_mode(mode) { + return Vec::new(); + } + serde_json::to_vec(&BucketDurabilityConfig::new(mode)).expect("BucketDurabilityConfig serialization cannot fail") +} + #[cfg(test)] mod tests { use super::*; + fn new_bucket_seeded_mode() -> Option { + let json = new_bucket_durability_config_json(); + if json.is_empty() { + return None; + } + serde_json::from_slice::(&json) + .expect("new-bucket durability config must serialize") + .normalized_mode() + } + #[test] fn valid_modes_are_recognized() { assert!(BucketDurabilityConfig::is_valid_mode("strict")); @@ -99,4 +130,33 @@ mod tests { let empty: BucketDurabilityConfig = serde_json::from_slice(b"{}").expect("deserialize empty"); assert_eq!(empty.normalized_mode(), None); } + + #[test] + fn new_bucket_default_seeds_relaxed_when_unset() { + temp_env::with_var_unset(ENV_NEW_BUCKET_DURABILITY_MODE, || { + assert_eq!(new_bucket_seeded_mode().as_deref(), Some(BUCKET_DURABILITY_MODE_RELAXED)); + }); + } + + #[test] + fn new_bucket_default_honors_explicit_tiers() { + for mode in [ + BUCKET_DURABILITY_MODE_STRICT, + BUCKET_DURABILITY_MODE_RELAXED, + BUCKET_DURABILITY_MODE_NONE, + ] { + temp_env::with_var(ENV_NEW_BUCKET_DURABILITY_MODE, Some(mode), || { + assert_eq!(new_bucket_seeded_mode().as_deref(), Some(mode)); + }); + } + } + + #[test] + fn new_bucket_default_can_inherit_global_mode() { + for mode in ["inherit", "", "bogus"] { + temp_env::with_var(ENV_NEW_BUCKET_DURABILITY_MODE, Some(mode), || { + assert_eq!(new_bucket_seeded_mode(), None); + }); + } + } } diff --git a/crates/ecstore/src/bucket/metadata.rs b/crates/ecstore/src/bucket/metadata.rs index 2b02231fd..6aef5b8bf 100644 --- a/crates/ecstore/src/bucket/metadata.rs +++ b/crates/ecstore/src/bucket/metadata.rs @@ -425,6 +425,15 @@ impl BucketMetadata { } } + /// Metadata for a physically new user bucket. Existing or fabricated legacy + /// metadata must use [`Self::new`] so upgrades do not rewrite their + /// durability posture. + pub fn new_with_default_durability(name: &str) -> Self { + let mut metadata = Self::new(name); + metadata.durability_config_json = super::durability::new_bucket_durability_config_json(); + metadata + } + pub fn save_file_path(&self) -> String { format!("{}/{}/{}", BUCKET_META_PREFIX, self.name.as_str(), BUCKET_METADATA_FILE) } @@ -1378,6 +1387,43 @@ mod test { assert_ne!(old.bucket_incarnation_id, new.bucket_incarnation_id); } + #[test] + fn regular_bucket_metadata_constructor_does_not_seed_durability() { + temp_env::with_var_unset(crate::bucket::durability::ENV_NEW_BUCKET_DURABILITY_MODE, || { + let metadata = BucketMetadata::new("legacy-or-fabricated"); + assert!(metadata.durability_config_json.is_empty()); + assert!(metadata.durability_config().is_none()); + }); + } + + #[test] + fn new_bucket_metadata_constructor_seeds_default_durability() { + temp_env::with_var_unset(crate::bucket::durability::ENV_NEW_BUCKET_DURABILITY_MODE, || { + let metadata = BucketMetadata::new_with_default_durability("new-user-bucket"); + assert_eq!( + metadata.durability_config().and_then(|cfg| cfg.normalized_mode()).as_deref(), + Some(crate::bucket::durability::BUCKET_DURABILITY_MODE_RELAXED) + ); + + let encoded = metadata.marshal_msg().expect("marshal metadata"); + let decoded = BucketMetadata::unmarshal(&encoded).expect("unmarshal metadata"); + assert_eq!(decoded.durability_config_json, metadata.durability_config_json); + assert_eq!( + decoded.durability_config().and_then(|cfg| cfg.normalized_mode()).as_deref(), + Some(crate::bucket::durability::BUCKET_DURABILITY_MODE_RELAXED) + ); + }); + } + + #[test] + fn new_bucket_metadata_constructor_can_inherit_global_durability() { + temp_env::with_var(crate::bucket::durability::ENV_NEW_BUCKET_DURABILITY_MODE, Some("inherit"), || { + let metadata = BucketMetadata::new_with_default_durability("strict-fleet-new-bucket"); + assert!(metadata.durability_config_json.is_empty()); + assert!(metadata.durability_config().is_none()); + }); + } + #[test] fn site_replication_config_updates_cannot_replace_bucket_incarnation() { let mut metadata = BucketMetadata::new("site-replication-update"); diff --git a/crates/ecstore/src/store/bucket.rs b/crates/ecstore/src/store/bucket.rs index 5ab25fef6..1fac01899 100644 --- a/crates/ecstore/src/store/bucket.rs +++ b/crates/ecstore/src/store/bucket.rs @@ -457,7 +457,13 @@ impl ECStore { None }; - let mut meta = existing_metadata.unwrap_or_else(|| BucketMetadata::new(bucket)); + let mut meta = existing_metadata.unwrap_or_else(|| { + if confirmed_missing && !is_meta_bucketname(bucket) { + BucketMetadata::new_with_default_durability(bucket) + } else { + BucketMetadata::new(bucket) + } + }); let existing_incarnation_is_authoritative = meta.bucket_incarnation_sidecar; if confirmed_missing || is_meta_bucketname(bucket) { meta.set_created(opts.created_at); @@ -1557,6 +1563,76 @@ mod tests { ); } + #[tokio::test(flavor = "multi_thread")] + #[serial] + async fn make_bucket_seeds_new_bucket_durability_override() { + temp_env::async_with_vars([(crate::bucket::durability::ENV_NEW_BUCKET_DURABILITY_MODE, None::<&str>)], async { + let (_disk_paths, ecstore) = setup_bucket_delete_test_env().await; + let bucket = format!("bucket-default-durability-{}", Uuid::new_v4().simple()); + + ecstore + .make_bucket(&bucket, &MakeBucketOptions::default()) + .await + .expect("new bucket should be created"); + + let metadata = metadata_sys::get_in(&ecstore.ctx, &bucket) + .await + .expect("metadata should load for the new bucket"); + assert_eq!( + metadata.durability_config().and_then(|cfg| cfg.normalized_mode()).as_deref(), + Some(crate::bucket::durability::BUCKET_DURABILITY_MODE_RELAXED) + ); + }) + .await; + } + + #[tokio::test(flavor = "multi_thread")] + #[serial] + async fn force_create_existing_bucket_keeps_durability_override() { + let (_disk_paths, ecstore) = setup_bucket_delete_test_env().await; + let bucket = format!("bucket-force-durability-{}", Uuid::new_v4().simple()); + + temp_env::async_with_vars([(crate::bucket::durability::ENV_NEW_BUCKET_DURABILITY_MODE, Some("inherit"))], async { + ecstore + .make_bucket(&bucket, &MakeBucketOptions::default()) + .await + .expect("plain bucket should be created without a durability override"); + }) + .await; + assert!( + metadata_sys::get_in(&ecstore.ctx, &bucket) + .await + .expect("metadata should load after initial create") + .durability_config() + .is_none(), + "test setup: the existing bucket must start without an override" + ); + + temp_env::async_with_vars([(crate::bucket::durability::ENV_NEW_BUCKET_DURABILITY_MODE, None::<&str>)], async { + ecstore + .make_bucket( + &bucket, + &MakeBucketOptions { + force_create: true, + lock_enabled: true, + ..Default::default() + }, + ) + .await + .expect("force create should update existing bucket metadata"); + }) + .await; + + let metadata = metadata_sys::get_in(&ecstore.ctx, &bucket) + .await + .expect("metadata should load after force create"); + assert!(metadata.lock_enabled, "force create sanity check: Object Lock should be enabled"); + assert!( + metadata.durability_config().is_none(), + "force create must not apply the new-bucket default to existing bucket metadata" + ); + } + /// `DeleteBucket`'s emptiness check is a raw disk scan (`has_xlmeta_files`), /// not an S3-level listing, so "the client drained the bucket" and "the /// bucket is deletable" are two different contracts. Nothing pinned the diff --git a/crates/lock/Cargo.toml b/crates/lock/Cargo.toml index dcaccf4a6..0b2b76bfe 100644 --- a/crates/lock/Cargo.toml +++ b/crates/lock/Cargo.toml @@ -57,7 +57,7 @@ thiserror.workspace = true parking_lot.workspace = true rand.workspace = true smallvec = { workspace = true, features = ["serde"] } -smartstring.workspace = true +compact_str.workspace = true crossbeam-queue = { workspace = true } [dev-dependencies] diff --git a/crates/lock/src/fast_lock/types.rs b/crates/lock/src/fast_lock/types.rs index d500061c8..8337971fe 100644 --- a/crates/lock/src/fast_lock/types.rs +++ b/crates/lock/src/fast_lock/types.rs @@ -13,8 +13,8 @@ // limitations under the License. use crate::fast_lock::guard::FastLockGuard; +use compact_str::CompactString; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use smartstring::SmartString; use std::hash::{Hash, Hasher}; use std::sync::Arc; use std::sync::OnceLock; @@ -143,15 +143,15 @@ impl ObjectKey { } } -/// Optimized object key using smart strings for better performance +/// Optimized object key using compact strings for better performance #[derive(Debug, Clone)] pub struct OptimizedObjectKey { /// Bucket name - uses inline storage for small strings - pub bucket: SmartString, + pub bucket: CompactString, /// Object name - uses inline storage for small strings - pub object: SmartString, + pub object: CompactString, /// Version - optional for latest version semantics - pub version: Option>, + pub version: Option, /// Cached hash to avoid recomputation hash_cache: OnceLock, } @@ -189,10 +189,7 @@ impl Ord for OptimizedObjectKey { } impl OptimizedObjectKey { - pub fn new( - bucket: impl Into>, - object: impl Into>, - ) -> Self { + pub fn new(bucket: impl Into, object: impl Into) -> Self { Self { bucket: bucket.into(), object: object.into(), @@ -202,9 +199,9 @@ impl OptimizedObjectKey { } pub fn with_version( - bucket: impl Into>, - object: impl Into>, - version: impl Into>, + bucket: impl Into, + object: impl Into, + version: impl Into, ) -> Self { Self { bucket: bucket.into(), @@ -232,9 +229,9 @@ impl OptimizedObjectKey { /// Convert from regular ObjectKey pub fn from_object_key(key: &ObjectKey) -> Self { Self { - bucket: SmartString::from(key.bucket.as_ref()), - object: SmartString::from(key.object.as_ref()), - version: key.version.as_ref().map(|v| SmartString::from(v.as_ref())), + bucket: CompactString::from(key.bucket.as_ref()), + object: CompactString::from(key.object.as_ref()), + version: key.version.as_ref().map(|v| CompactString::from(v.as_ref())), hash_cache: OnceLock::new(), } } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 58b92e725..72ee90552 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -908,6 +908,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -995,6 +1004,19 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +[[package]] +name = "compact_str" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79fcda08c33bb58b97008b2cdada6622500e949e060f5913361763121abd2416" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "static_assertions", + "zmij", +] + [[package]] name = "combine" version = "4.6.7" @@ -4341,6 +4363,7 @@ name = "rustfs-lock" version = "1.0.0-beta.11" dependencies = [ "async-trait", + "compact_str", "crossbeam-queue", "futures", "parking_lot", @@ -4349,7 +4372,6 @@ dependencies = [ "serde", "serde_json", "smallvec", - "smartstring", "thiserror 2.0.19", "tokio", "tonic", @@ -5134,17 +5156,6 @@ dependencies = [ "serde", ] -[[package]] -name = "smartstring" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" -dependencies = [ - "autocfg", - "static_assertions", - "version_check", -] - [[package]] name = "snap" version = "1.1.2"