From d7da0b3584e4f50d0ced7cd2d42f150cd4364ce7 Mon Sep 17 00:00:00 2001 From: David Osipov Date: Sat, 14 Mar 2026 23:04:44 +0400 Subject: [PATCH] fix: log JoinErrors in reconnect_all; restore pub(crate) for EdgeConnectionsCacheEntry - pool_config.rs: replace silent .is_some() drain with while-let that logs JoinError, making panics in reconnect tasks visible in production logs. Add tokio regression test verifying panicking tasks yield JoinError. - runtime_edge.rs: revert EdgeConnectionsCacheEntry visibility from pub to pub(crate); the type is internal to the api module and must not be exported beyond crate scope. - Copilot issues for dashed lint names, unreachable!/clippy::panic, and tokio::rename atomicity were confirmed false positives via empirical cargo clippy runs and POSIX semantics analysis. --- src/api/runtime_edge.rs | 2 +- src/transport/middle_proxy/pool_config.rs | 25 ++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/api/runtime_edge.rs b/src/api/runtime_edge.rs index cc95fec..ff5b482 100644 --- a/src/api/runtime_edge.rs +++ b/src/api/runtime_edge.rs @@ -67,7 +67,7 @@ pub(super) struct RuntimeEdgeConnectionsSummaryData { } #[derive(Clone)] -pub struct EdgeConnectionsCacheEntry { +pub(crate) struct EdgeConnectionsCacheEntry { pub(super) expires_at: Instant, pub(super) payload: RuntimeEdgeConnectionsSummaryPayload, pub(super) generated_at_epoch_secs: u64, diff --git a/src/transport/middle_proxy/pool_config.rs b/src/transport/middle_proxy/pool_config.rs index eae17bb..be93962 100644 --- a/src/transport/middle_proxy/pool_config.rs +++ b/src/transport/middle_proxy/pool_config.rs @@ -147,7 +147,11 @@ impl MePool { if spawned == 0 { break; } - while join.join_next().await.is_some() {} + while let Some(result) = join.join_next().await { + if let Err(err) = result { + warn!(error = ?err, "reconnect task failed (panic or cancellation)"); + } + } } } } @@ -202,6 +206,25 @@ mod tests { ); } + // Regression: a panicking reconnect task must produce a catchable JoinError rather + // than being silently swallowed. If the while loop ever reverts to `.is_some()` + // the error is dropped; this test ensures the error EXISTS and can be observed. + #[tokio::test] + async fn panicking_reconnect_task_produces_join_error() { + let mut join: tokio::task::JoinSet<()> = tokio::task::JoinSet::new(); + join.spawn(async { panic!("simulated reconnect task panic") }); + let mut error_count = 0usize; + while let Some(result) = join.join_next().await { + if result.is_err() { + error_count += 1; + } + } + assert_eq!( + error_count, 1, + "exactly one JoinError must be emitted by the panicking task" + ); + } + // Verify that the batch-iteration logic never exceeds MAX_CONCURRENT_RECONNECTS // tasks per batch, regardless of how many writers exist. #[test]