mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-23 20:59:05 +00:00
ca8d4c2ea7348810b196cac85b28df1275041786
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
acce8b2253 |
fix(lock): let waiters hear releases and let acquisition succeed past registered waiters (#5670)
* fix(lock): let waiters hear releases and let acquisition succeed past registered waiters Same-key write contention scaled superlinearly with writer count: 8 concurrent conditional PUTs on one key cost ~340-460 ms, 16 cost ~700 ms, 32 cost ~5 s, against ~4 ms per uncontended write and ~10 ms actual lock holds (measured via RUSTFS_OBJECT_LOCK_DIAG at 1 ms thresholds). Outcomes were always correct; the cost was pure waiting. Two coupled defects in fast_lock caused it: 1. The slow path's early retries slept without subscribing to anything. notify_writer()/notify_readers() are gated on the waiter counters, which a sleeper never increments, so a release during the backoff woke nobody. The lock sat free while every loser slept out its full backoff, and the ladder compounded: successive acquires landed at the cumulative ladder offsets (10+20+40+80+100... ms). 2. try_acquire_exclusive demanded the entire packed state word be zero, including the readers_waiting/writers_waiting counter bits. A lock with registered waiters could be acquired by no one - including the waiters themselves, each blocked by the others' registration - so contended acquisition only succeeded in windows where every waiter happened to be unregistered. This is also why (1) could not be fixed by simply registering the sleepers: registration alone deadlocks acquisition until the acquire deadline. try_acquire_shared already masks correctly and preserves the counter bits in its CAS; the exclusive path now mirrors it. The fix: mask the acquisition CAS to ownership bits only (writer flag, active readers), and turn the early-retry sleep into a notification wait bounded by the same backoff, so a release wakes a waiter immediately while the bound still protects against lost or stolen wakeups exactly as NOTIFY_WAIT_CAP does for the post-retry wait. With both changes, 8 concurrent same-key CAS writers resolve in 17-29 ms (was 340-460 ms) and 32 resolve in 20-53 ms (was ~5 s), with per-racer cost now decreasing in N. Outcomes remain exactly one winner, N-1 precondition failures, zero errors at every width. cargo test -p rustfs-lock passes 113/113 at pristine-parity runtime, including test_concurrent_write_lock_contention, which previously only passed because sleepers were invisible to it. * test(lock): pin both halves of the waiter-starvation fix The fix commit touched only production files, so reverting either half left the suite green: test_concurrent_write_lock_contention only waits for five writers to finish and never asserts that acquisition happens before the backoff ladder runs out. Three tests, one per revert: * exclusive_acquisition_ignores_registered_waiters (state.rs) - a free lock with registered waiters must be acquirable, and the CAS must preserve the counters. Fails against the all-zero `expected`. * early_retry_registers_as_waiter (shard.rs) - a waiter in the early-retry backoff must appear in the writer waiter count within the ~750ms early-retry phase, since notify_writer/notify_readers are gated on those counters. Fails against a bare `sleep`, which registers nowhere. * contended_writers_drain_promptly_after_release (tests.rs) - 16 same-key writers, all registered behind one holder, must drain within 1s of the release rather than sit out their 5s acquire deadlines. Fails against the all-zero `expected` end to end. Wakeup latency is deliberately not asserted anywhere. NOTIFY_POOL is a process-global of 128 Notify slots shared by every lock, so a waiter in a concurrently-running test can consume another's notify_one and push it to the end of its rung: a 24-key latency probe measured ~150us in isolation and ~92ms - a full unexpired rung - alongside the existing 64-key missed-wakeup test. That is the stolen wakeup NOTIFY_WAIT_CAP already exists to bound, and it makes any in-suite latency budget flaky. cargo test -p rustfs-lock: 116/116. Signed-off-by: Miguel Amador <miguel@amador.one> --------- Signed-off-by: Miguel Amador <miguel@amador.one> |
||
|
|
717cdd2abd |
fix(migration): decrypt MinIO IAM & server config on drop-in migration (#4358)
* fix(migration): decrypt MinIO IAM & server config on drop-in migration MinIO encrypts IAM identity/service-account files and the server config at rest with a key derived from the root credentials. The drop-in migration paths read those blobs from the legacy `.minio.sys` bucket and parsed them as plaintext JSON, so any encrypted blob failed to parse and was silently skipped with "incompatible format". This is why users migrating from MinIO kept their buckets/objects/policies but lost users and access keys (#2212). The IAM load path already knows how to decrypt these blobs (RustFS master keys plus MinIO-compatible legacy keys derived from the root credentials), but that logic lived behind a private method and was never used by the migration paths. Expose it as `rustfs_iam::try_decrypt_iam_blob` and inject it into both migration paths via a `LegacyBlobDecryptFn` callback (ecstore cannot depend on the IAM crate, so the closure is wired in the binary crate). When a blob cannot be decrypted the raw bytes are used as-is, preserving the previous plaintext-only behavior with no regression. Also improve object-layer migration observability without changing control flow: `try_migrate_format` now distinguishes "no legacy format" (a normal fresh install) from "legacy format present but incompatible", and the caller logs a loud error before initializing a fresh format that would leave the existing MinIO objects unreadable. Topology/version skip reasons are promoted from debug to warn. Fixes a pre-existing test isolation race by marking `test_recovery_falls_back_to_default_config_when_blob_stays_corrupt` serial, since it reads a process-wide env var toggled by a sibling test. Co-Authored-By: heihutu <heihutu@gmail.com> * fix(migration): box FormatV3 in LegacyFormatOutcome to satisfy clippy Co-Authored-By: heihutu <heihutu@gmail.com> * test(ecstore): stabilize concurrent multipart resend lock timeout concurrent_resend_same_part_commits_one_generation spawns 6 same-part resends whose cross-disk commits serialize on the per-uploadId commit lock. Under the full nextest suite the parallel disk load pushes those serialized commits past the small default lock-acquire timeout (5s), producing a spurious `Lock(Timeout ...)` unrelated to the property under test (observed on CI at 5.775s vs ~0.5s in isolation). Raise RUSTFS_OBJECT_LOCK_ACQUIRE_TIMEOUT to the production default (30s) for the concurrent-commit section via temp_env, so the regression guard reflects correctness (exactly one intact generation) rather than disk latency under CI load. The meaningful assertions are unchanged, and #[serial] keeps the process-wide env override isolated. Co-Authored-By: heihutu <heihutu@gmail.com> * fix(lock): bound fast-lock notification wait to prevent lost-wakeup stall The real cause of the concurrent_resend_same_part_commits_one_generation failures was a lost wakeup in the fast-lock slow path, not disk latency: raising the acquire timeout to 30s only delayed the failure (it then timed out at 30s), proving a genuine stall rather than overload. In acquire_lock_slow_path a waiter that reaches the notification phase did a single `timeout(remaining, wait_for_write())` spanning the whole acquire budget, and treated that wait's elapse as a hard `Timeout`. But the release path only notifies when `writer_waiters > 0`, so if the holder releases in the gap after the waiter's `try_acquire` fails and before it registers as a waiter, no notification (and no stored permit, since the pooled `Notify` is gated) is produced. The waiter then blocks until the deadline even though the lock is free and stays free — a spurious lock-acquire timeout. The shared process-wide notify pool makes it worse: a wakeup can be consumed by a waiter of a different lock hashing to the same slot. Bound each notification wait (NOTIFY_WAIT_CAP = 50ms) and, on elapse, loop back and re-`try_acquire` instead of returning `Timeout`; the deadline check at the top of the loop is the single source of truth for timing out. A lost/stolen wakeup now degrades to bounded re-polling (acquire within ~50ms of the lock becoming free) instead of stalling for the whole timeout. Correctness (mutual exclusion) is unchanged — acquisition still only happens via `try_acquire_*`. Add a regression test that reproduces the stall (holder + late waiter across many keys): it times out without the fix and passes in ~1s with it. Revert the earlier acquire-timeout workaround in the multipart test now that the underlying stall is fixed, so it runs under the default timeout again. Co-Authored-By: heihutu <heihutu@gmail.com> --------- Co-authored-by: heihutu <heihutu@gmail.com> |
||
|
|
a6878e8fce |
fix(runtime): remove startup panic fallbacks (#3754)
* fix(runtime): remove startup panic fallbacks * test(runtime): cover buffer profile fallback safety * test(runtime): reduce panic-style assertions * fix(runtime): expose fallible env config setup * test(runtime): simplify permit acquisition assertion * test(runtime): tighten operation helper assertions * fix(filemeta): stop panicking on invalid free version ids * fix(init): satisfy buffer profile clippy lints * fix(lock): harden fast lock config construction * chore(checks): refresh layer dependency baseline --------- Signed-off-by: houseme <housemecn@gmail.com> |
||
|
|
dce117840c |
refactor: NamespaceLock (nslock), AHM→Heal Crate, and Lock/Clippy Fixes (#1664)
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: weisd <2057561+weisd@users.noreply.github.com> Co-authored-by: houseme <housemecn@gmail.com> |