[CELEBORN-2371] Bound Spark batch-open client creation retries and stop them on interruption#3746
Conversation
There was a problem hiding this comment.
@sunchao, thanks — the direction is right and the tests are focused. The bound on the outer loop, propagating interruption through bootstrap, and closing the in-progress channel on interrupt are all good improvements. A few points worth addressing before merge (none are hard blockers, but the first two are worth a response):
-
The second per-host attempt is redundant. All locations grouped under a
hostPortshare the samehost:fetchPort(grouping keys byhostAndFetchPort, and thecreateClientlambda uses onlygetHost/getFetchPort), so attempt #2 re-targets the same worker. See the inline note.MAX=1would give the same result; if you keep 2, the "preserves same-worker fallback" comment is misleading. -
Interrupt handling is now inconsistent across
createClientcallers.findInterruptedExceptionmakesretryCreateClientset the interrupt flag and throw a bareInterruptedExceptionon a wrapped interrupt, but onlycreateClientsInParallelhas matchingcase ex: InterruptedExceptionhandling. The other callers —CelebornInputStream.createReaderWithRetry(catchesException, thenexcludeFailedFetchLocation+Uninterruptibles.sleepUninterruptiblywhich re-asserts the flag), the non-parallelmakeOpenStreamList, and the push/replicate paths — swallow it generically, so a cancellation gets recorded as a fetch/push failure (polluting shared cross-task exclusion state) and, because Netty'sawait()throws immediately when the flag is preset, cascades into fast-failing the remaining retries. Much of this cascade is pre-existing for direct interrupts; this PR widens it to wrapped ones. Worth either handlingInterruptedExceptionconsistently at these call sites, or noting the trade-off. -
Minor — worker-thread interrupt surfaces as
ExecutionException. When a workerrun()throwsInterruptedException(the new line-620 check, or a wrapped interrupt on the worker thread rethrown at line 628),futures.foreach(_.get())raisesExecutionException, which is not matched by thecase ex: InterruptedExceptionat ~642 — so it escapes without restoring the caller's interrupt status. Low impact, but the new code makes "InterruptedException out of run()" more reachable. -
Nit — reuse.
findInterruptedExceptionduplicates the cause-chain walk inMasterClient.findMasterNotLeaderException; GuavaThrowables.getCausalChain()(already imported here) or commons-lang3ExceptionUtils.throwableOfType/ a sharedExceptionUtilshelper would avoid a third hand-rolled copy (and could carry a cycle guard).
|
Follow-up on the two review-summary-only notes:
The full follow-up is in |
There was a problem hiding this comment.
@sunchao, thanks for the interruption/cancellation propagation changes. The core approach is sound: each interrupt check is placed at the top of its catch, before the excludeFailedFetchLocation / reportShuffleFetchFailure side effects; retryCreateClient now unwraps a wrapped InterruptedException via the cause chain; awaitWithChannelCleanup closes the channel and restores the interrupt flag before rethrowing; and the added channel cleanup on the unlimited-timeout (connectTimeoutMs <= 0) path fixes a real leak. The headOption change in the parallel path is safe — every location in a hostAndFetchPort group targets the identical getHost/getFetchPort endpoint, so walking the rest was only re-retrying the same host. The touched suites pass locally (CelebornInputStreamPeerFailoverTest 6, TransportClientFactorySuiteJ 9, TransportClientFactoryInterruptSuiteJ 1).
A few non-blocking notes below: one incompleteness (the sequential path isn't bounded), one design consideration (breadth of the interrupt classification), and a consistency/consolidation point on the three new interrupt-detection helpers.
|
@sunchao, could you please address above comments? |
03a6174 to
606f251
Compare
|
@SteNicholas sorry lost track of this. Updated the PR and addressed the comments. Please take another look! |
SteNicholas
left a comment
There was a problem hiding this comment.
LGTM. Thanks for updates.
Why are the changes needed?
CELEBORN-2371 follows up on the parallel Spark batch-open client creation added by #3692.
Batch-open locations are grouped by
host:fetchPort, but each worker task previously walked multiplePartitionLocationentries. Since everycreateClientinvocation already receivesTransportClientFactory's full retry budget, that outer loop could multiply connection latency without targeting a different endpoint.Cancellation also needs to terminate consistently. A direct or wrapped
InterruptedExceptionmust stop reader setup and fetch retry flow before it is recorded as a worker failure, added to shared exclusion state, retried against a peer, or reported as a shuffle fetch failure. The unlimited-timeout transport branch also needed explicit cleanup on cancelled or failed connection futures.What changes were proposed in this PR?
Keep one retry budget per worker endpoint
Each grouped
host:fetchPortnow uses one representative location for client creation.TransportClientFactoryremains the owner of connection retries, controlled by the existingceleborn.data.io.maxRetries; there is no second outer retry setting or multiplicative retry budget.Propagate cancellation through reader paths
CelebornShuffleReadernow uses a shared interrupt-aware client-creation helper in both sequential and parallel batch-open paths. It checks a pre-existing interrupt flag, restores the flag whencreateClientthrowsInterruptedException, and exits without invoking the ordinary failure callback.CelebornInputStreamnow detects interruption throughout reader creation, failed-stream cleanup, reconnect, and buffer-fill paths. It exits before exclusion, retry, peer failover, or shuffle-fetch-failure reporting. If reader cleanup itself fails while propagating cancellation, the cleanup failure is retained as a suppressed exception without masking the original interruption.Preserve interruption and cleanup in transport bootstrap
TransportClientFactoryuses Guava'sThrowables.getCausalChain()to detect wrappedInterruptedException, restores the interrupt flag, and stops retrying immediately. TCP-connect and TLS-handshake waits close the in-progress channel before propagating interruption. The unlimited-timeout connection path now also closes cancelled and failed channel futures explicitly.How was this PR tested?
Formatting was applied with:
The focused transport tests passed (11 tests):
The focused input-stream peer-failover and interruption tests passed (6 tests):
The Spark 3.5 reader suite passed (7 tests):
Spark 4.0 / Scala 2.13 production and test compilation also passed: