Skip to content

Stop swallowing CancellationException in suspend code#70

Merged
nathanfallet merged 1 commit into
mainfrom
fix/cancellation-exception-swallowing
May 22, 2026
Merged

Stop swallowing CancellationException in suspend code#70
nathanfallet merged 1 commit into
mainfrom
fix/cancellation-exception-swallowing

Conversation

@nathanfallet
Copy link
Copy Markdown
Member

Summary

Several broad catch (e: Exception) blocks and runCatching {} calls in suspend functions were absorbing CancellationException. In Kotlin CancellationException is an Exception (and runCatching catches Throwable), so these blocks silently swallowed it and broke cooperative cancellation.

Why it matters: when the enclosing scope is cancelled (browser.stop()coroutineScope.cancel(), or a caller's withTimeout), the affected coroutines kept issuing CDP work / completed "successfully" instead of unwinding, and the WebSocket receive loop printed a spurious stack trace on every clean shutdown — classic "won't die" / noisy-shutdown symptoms.

Changes

Each affected site now rethrows CancellationException before the broad catch:

  • DefaultConnection.prepareHeadless / prepareExpertrunCatching {} converted to explicit try/catch
  • DefaultConnection.startListening — inner (emit) and outer (incoming loop) catches
  • DefaultTab.findElementsByTextresolveNode and element-build catches
  • DefaultBrowser.testConnection
  • DefaultElement.mouseMoveWithTrajectory viewport query

Non-cancellation behavior is preserved (other exceptions remain swallowed/logged exactly as before). Sites that already rethrow (querySelector/querySelectorAll, mouseClick cleanup) were left unchanged to keep the change scoped to the root cause.

Verification (red → green)

Added CancellationPropagationTest, which drives the real findElementsByText via a DefaultTab subclass with a stubbed callCommand (no browser):

  • RED (before fix): DOM.resolveNode throws CancellationException, which is swallowed; findElementsByText returns normally → assertFailsWith<CancellationException> fails.
  • GREEN (after fix): the cancellation propagates → test passes.

Full :core:jvmTest (real headless Chrome) and :opentelemetry:jvmTest pass with no regressions.

Test plan

  • ./gradlew :core:jvmTest --tests "*.CancellationPropagationTest" — red on unfixed code, green after fix
  • ./gradlew :core:jvmTest (real Chrome) — all pass
  • ./gradlew :opentelemetry:jvmTest — all pass

Several broad `catch (e: Exception)` blocks and `runCatching {}` calls in
suspend functions absorbed CancellationException (which is an Exception, and
runCatching catches Throwable). This broke cooperative cancellation: when the
enclosing scope was cancelled (browser.stop(), withTimeout), affected coroutines
kept running CDP work or completed "successfully" instead of unwinding, and the
WebSocket receive loop printed a spurious stack trace on every clean shutdown.

Each affected site now rethrows CancellationException before the broad catch:
- DefaultConnection.prepareHeadless/prepareExpert (runCatching -> try/catch)
- DefaultConnection.startListening (inner emit + outer incoming-loop catches)
- DefaultTab.findElementsByText (resolveNode + element-build catches)
- DefaultBrowser.testConnection
- DefaultElement.mouseMoveWithTrajectory viewport query

Non-cancellation behavior is preserved (other exceptions stay swallowed/logged
as before). Sites that already rethrow were left unchanged.

Verified red->green with CancellationPropagationTest, which drives the real
findElementsByText through a stubbed callCommand: before the fix the cancellation
is swallowed and the call returns normally (test fails); after, it propagates.
Full :core:jvmTest (real Chrome) and :opentelemetry:jvmTest pass.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 22, 2026

Codecov Report

❌ Patch coverage is 54.16667% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...n/dev/kdriver/core/connection/DefaultConnection.kt 54.16% 10 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@nathanfallet nathanfallet merged commit fea03a1 into main May 22, 2026
4 of 8 checks passed
@nathanfallet nathanfallet deleted the fix/cancellation-exception-swallowing branch May 22, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant