From 340af81a2f6cc8f0905577e8af8af10d66bfdf1a Mon Sep 17 00:00:00 2001 From: Raushan kumar Date: Sun, 7 Jun 2026 05:08:06 +0000 Subject: [PATCH 1/2] test(publish): add regression test for false deadlock in workspace publish --- tests/testsuite/publish.rs | 102 +++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/tests/testsuite/publish.rs b/tests/testsuite/publish.rs index 6145b3997fe..246a4091f31 100644 --- a/tests/testsuite/publish.rs +++ b/tests/testsuite/publish.rs @@ -3409,6 +3409,108 @@ fn timeout_waiting_for_publish() { .run(); } +#[cargo_test] +fn wait_for_workspace_publish() { + let arc: Arc> = Arc::new(Mutex::new(0)); + + let registry = registry::RegistryBuilder::new() + .http_api() + .http_index() + .add_responder("/index/1/c", move |req, server| { + let mut lock = arc.lock().unwrap(); + *lock += 1; + // 3 queries come from resolving `c` during packaging of `a` + // 3 more from the wait loop while `b` and `c` are being confirmed + // `c` becomes available on the 7th query, unblocking `a` + if *lock <= 6 { + server.not_found(req) + } else { + server.index(req) + } + }) + .build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["a", "b", "c"] + "#, + ) + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "1.0.0" + edition = "2015" + license = "MIT" + description = "a" + repository = "a" + + [dependencies] + b = { version = "1.0", path = "../b" } + c = { version = "1.0", path = "../c" } + "#, + ) + .file("a/src/lib.rs", "") + .file( + "b/Cargo.toml", + r#" + [package] + name = "b" + version = "1.0.0" + edition = "2015" + license = "MIT" + description = "b" + repository = "b" + "#, + ) + .file("b/src/lib.rs", "") + .file( + "c/Cargo.toml", + r#" + [package] + name = "c" + version = "1.0.0" + edition = "2015" + license = "MIT" + description = "c" + repository = "c" + "#, + ) + .file("c/src/lib.rs", "") + .build(); + + p.cargo("publish --workspace --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr_data(str![[r#" +[UPDATING] crates.io index +[PACKAGING] b v1.0.0 ([ROOT]/foo/b) +[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) +[PACKAGING] c v1.0.0 ([ROOT]/foo/c) +[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) +[PACKAGING] a v1.0.0 ([ROOT]/foo/a) +[UPDATING] crates.io index +[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) +[UPLOADING] b v1.0.0 ([ROOT]/foo/b) +[UPLOADED] b v1.0.0 to registry `crates-io` +[UPLOADING] c v1.0.0 ([ROOT]/foo/c) +[UPLOADED] c v1.0.0 to registry `crates-io` +[NOTE] waiting for b v1.0.0 or c v1.0.0 to be available at registry `crates-io`. + 1 remaining crate to be published +[PUBLISHED] b v1.0.0 at registry `crates-io` +[ERROR] no packages ready to publish but 1 packages remain in plan with 1 awaiting confirmation: a v1.0.0 +[NOTE] this is an unexpected cargo internal error +[NOTE] we would appreciate a bug report: https://github.com/rust-lang/cargo/issues/ +[NOTE] cargo [..] + +"#]]) + .run(); +} + #[cargo_test] fn timeout_waiting_for_dependency_publish() { // Publish doesn't happen within the timeout window. From ae3b65ddaeefa2cb8a51b2fc544b936e264dcb78 Mon Sep 17 00:00:00 2001 From: Raushan kumar Date: Sun, 7 Jun 2026 05:16:39 +0000 Subject: [PATCH 2/2] fix(publish): avoid false deadlock when to_confirm is non-empty --- src/cargo/ops/registry/publish.rs | 5 ++--- tests/testsuite/publish.rs | 14 +++++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/cargo/ops/registry/publish.rs b/src/cargo/ops/registry/publish.rs index 599b3be5453..c1a987c68bb 100644 --- a/src/cargo/ops/registry/publish.rs +++ b/src/cargo/ops/registry/publish.rs @@ -210,9 +210,8 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> { // upload. let mut ready = plan.take_ready(); - if ready.is_empty() { - // Circular dependencies are caught above, so this indicates a failure - // to progress, potentially due to a timeout while waiting for confirmations. + if ready.is_empty() && to_confirm.is_empty() { + // Cycles are caught above; reaching here means an unexpected stall. return Err(crate::util::internal(format!( "no packages ready to publish but {} packages remain in plan with {} awaiting confirmation: {}", plan.len(), diff --git a/tests/testsuite/publish.rs b/tests/testsuite/publish.rs index 246a4091f31..57082b96753 100644 --- a/tests/testsuite/publish.rs +++ b/tests/testsuite/publish.rs @@ -3485,7 +3485,7 @@ fn wait_for_workspace_publish() { p.cargo("publish --workspace --no-verify") .replace_crates_io(registry.index_url()) - .with_status(101) + .with_status(0) .with_stderr_data(str![[r#" [UPDATING] crates.io index [PACKAGING] b v1.0.0 ([ROOT]/foo/b) @@ -3502,10 +3502,14 @@ fn wait_for_workspace_publish() { [NOTE] waiting for b v1.0.0 or c v1.0.0 to be available at registry `crates-io`. 1 remaining crate to be published [PUBLISHED] b v1.0.0 at registry `crates-io` -[ERROR] no packages ready to publish but 1 packages remain in plan with 1 awaiting confirmation: a v1.0.0 -[NOTE] this is an unexpected cargo internal error -[NOTE] we would appreciate a bug report: https://github.com/rust-lang/cargo/issues/ -[NOTE] cargo [..] +[NOTE] waiting for c v1.0.0 to be available at registry `crates-io`. + 1 remaining crate to be published +[PUBLISHED] c v1.0.0 at registry `crates-io` +[UPLOADING] a v1.0.0 ([ROOT]/foo/a) +[UPLOADED] a v1.0.0 to registry `crates-io` +[NOTE] waiting for a v1.0.0 to be available at registry `crates-io` +[HELP] you may press ctrl-c to skip waiting; the crate should be available shortly +[PUBLISHED] a v1.0.0 at registry `crates-io` "#]]) .run();