fix: stabilize rebalance start and listing (#2961)

* fix: unblock empty chunked admin rebalance start

Some admin clients send empty POST requests with chunked framing and no explicit content length. The admin rebalance route carries no request body, so normalize that known route before downstream validation sees a missing length.

Constraint: Keep normalization scoped to known empty-body admin routes

Rejected: Relax transfer-encoding handling for S3 routes | broader protocol risk

Confidence: high

Scope-risk: narrow

Tested: cargo test -p rustfs --lib server::layer::tests::

Tested: cargo fmt --all --check

Tested: git diff --check

* fix: keep rebalance listing from stalling

Large object migration was awaited inside the listing callback, so metacache readers could stop being drained while drive walk operations were still writing listing data. Move entry migration into bounded background tasks and wait for them after each set listing completes.

Constraint: Preserve existing rebalance cancellation and first-error propagation

Rejected: Only increase drive walk timeouts | masks callback backpressure and still fails for slower migrations

Confidence: medium

Scope-risk: moderate

Tested: cargo test -p rustfs-ecstore rebalance_unit_tests

Tested: cargo test -p rustfs-ecstore cache_value::metacache_set::tests

Tested: cargo test -p rustfs --lib server::layer::tests::

Tested: cargo clippy -p rustfs-ecstore --lib --all-features -- -D warnings

Tested: cargo clippy -p rustfs-ecstore --tests --all-features -- -D warnings

Tested: cargo fmt --all --check

Tested: git diff --check

---------

Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: cxymds <Cxymds@qq.com>
This commit is contained in:
weisd
2026-05-16 13:17:01 +08:00
committed by GitHub
parent 6898e720dd
commit 9dcf8cb7ea
2 changed files with 105 additions and 30 deletions
+10 -9
View File
@@ -262,6 +262,7 @@ where
if should_force_zero_content_length_for_empty_body_route(&req) {
req.headers_mut()
.insert(http::header::CONTENT_LENGTH, HeaderValue::from_static("0"));
req.headers_mut().remove(http::header::TRANSFER_ENCODING);
}
let mut inner = self.inner.clone();
@@ -274,14 +275,14 @@ fn should_force_zero_content_length_for_empty_body_route<B>(req: &HttpRequest<B>
return false;
}
if req.headers().contains_key(http::header::TRANSFER_ENCODING) {
return false;
}
if is_empty_body_admin_path(req.method(), req.uri().path()) {
return true;
}
if req.headers().contains_key(http::header::TRANSFER_ENCODING) {
return false;
}
is_empty_body_s3_path(req.method(), req.uri())
}
@@ -1301,13 +1302,13 @@ mod tests {
}
#[tokio::test]
async fn empty_body_layer_preserves_admin_transfer_encoding_without_content_length() {
async fn empty_body_layer_normalizes_admin_chunked_request_without_content_length() {
let capture = HeaderCaptureService::default();
let headers = capture.headers();
let mut service = EmptyBodyContentLengthCompatLayer.layer(capture);
let request = Request::builder()
.method(Method::PUT)
.uri("/rustfs/admin/v3/set-group-status?group=test&status=enabled")
.method(Method::POST)
.uri(format!("{MINIO_ADMIN_V3_PREFIX}/rebalance/start"))
.header(http::header::TRANSFER_ENCODING, "chunked")
.body(())
.expect("request");
@@ -1315,8 +1316,8 @@ mod tests {
let _ = service.call(request).await.expect("service call");
let headers = headers.lock().expect("captured headers").take().expect("captured headers");
assert!(headers.get(http::header::CONTENT_LENGTH).is_none());
assert_eq!(headers.get(http::header::TRANSFER_ENCODING).unwrap(), "chunked");
assert_eq!(headers.get(http::header::CONTENT_LENGTH).unwrap(), "0");
assert!(headers.get(http::header::TRANSFER_ENCODING).is_none());
}
#[test]