Skip to content

Fix two real double-release causes in CefClient/render_handler (issue #22) - #42

Merged
Thrameos merged 1 commit into
masterfrom
backport/cleanupbrowser-double-release
Sep 6, 2026
Merged

Thrameos merged 1 commit into
masterfrom
backport/cleanupbrowser-double-release

Conversation

@Thrameos

@Thrameos Thrameos commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • CefClient.cleanupBrowser()'s "close all" branch iterated browser_.values() directly while browser.close(true) synchronously re-entered and mutated the same map via onBeforeClose() -> cleanupBrowser() -> browser_.remove(). Snapshot into an ArrayList before iterating.
  • cleanupBrowser()'s handler-removal + super.dispose() block could re-enter once browser_ was already empty and isDisposed_ was already true, double-releasing each handler's native reference. Added a handlersRemoved_ guard so it only runs once.
  • RenderHandler::GetJNIScreenInfo() wrapped the caller-owned jScreenInfo parameter in a second ScopedJNIObjectLocal, causing a double DeleteLocalRef on the same jobject (undefined behavior per the JNI spec).

Ported from coverage/phase1-value-objects-phase2-handlers's 7290b2e, which found and confirmed all three live via that branch's JCEF_TRACE facility (tracing observed the exact same CefFocusHandler native pointer released twice, ~83ms apart, with no intervening AddRef). The trace calls and JCefTrace import are dropped here since master doesn't have that tracing facility yet (issue #26, not backported).

Closes #22 (partially -- the source commit's own message notes a second, distinct double-release mechanism, suspected in ClientHandler::GetHandler<T>()'s reentrant lazy-create cache, that remains open and unfixed).

Test plan

  • Native (ninja jcef) and Java (tools/compile.sh linux64) both build clean.
  • Local full-suite verification not completed this round -- the local dev VM hit a hard crash/hang unrelated to this diff (a pre-existing, already-tracked GH Release build: SIGSEGV in libc.so.6 during JVM shutdown after all tests pass #10 shutdown SIGSEGV reproduced repeatedly during other verification in the same session, compounding into VM-level resource exhaustion). Relying on this repo's CI (test job) to verify instead.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 26.31579% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.01%. Comparing base (be68fe1) to head (2a721f4).

Files with missing lines Patch % Lines
native/render_handler.cpp 13.33% 0 Missing and 13 partials ⚠️
java/org/cef/CefClient.java 75.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master      #42      +/-   ##
============================================
- Coverage     38.01%   38.01%   -0.01%     
  Complexity      735      735              
============================================
  Files           243      243              
  Lines         14863    14866       +3     
  Branches       2449     2449              
============================================
+ Hits           5650     5651       +1     
  Misses         8011     8011              
- Partials       1202     1204       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Thrameos added a commit that referenced this pull request Sep 5, 2026
preliminary update racing ahead of onBeforeDownload

CefDownloadItemTest.cancelDuringDownloadInvokesNCancel() flaked
intermittently in CI across multiple unrelated backport PRs (#38, #39, #41,
#42), always failing the same assertion: "onBeforeDownload was never
invoked". Root-caused by reproducing it locally (100% deterministic once a
low-risk, narrowly-scoped repro was possible -- see PR #44's run_tests.sh
fix, needed first to run just this one test without also running the whole
suite + leak-sweep) and adding temporary native fprintf instrumentation
(not included in this commit) at DownloadHandler::OnBeforeDownload/
OnDownloadUpdated and ClientHandler::GetDownloadHandler.

That instrumentation showed CEF can deliver a preliminary
OnDownloadUpdated() notification -- for a valid, real CefDownloadItem --
*before* OnBeforeDownload() has been dispatched to the handler at all. This
test's onDownloadUpdated() used to treat "not complete" on the very first
update it saw as "must actively cancel it", immediately calling
callback.cancel() and terminateTest() -- racing ahead of onBeforeDownload
and ending the test before it ever got a chance to run, which is exactly
what "onBeforeDownload was never invoked" captures.

Fix: ignore any onDownloadUpdated() notification that arrives before this
handler's own onBeforeDownload() has recorded its decision
(gotBeforeDownload[0]). Verified via direct reproduction: failed 3/3 in
isolation before the fix (including against a heavily-used local CEF
profile where it failed 100% of the time), passed cleanly every time after
(5+ consecutive solo runs, plus the full class together) with no change to
downloadCompletesAndWritesRealFile's behavior.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HBFmQs6JDypYP5qdC99yDq
1. CefClient.cleanupBrowser()'s "close all" branch iterated
   browser_.values() directly while browsers concurrently removed
   themselves from the same map (browser.close(true) synchronously
   triggers onBeforeClose() -> cleanupBrowser() -> browser_.remove()),
   risking ConcurrentModificationException. Snapshot into an ArrayList
   before iterating.

2. cleanupBrowser()'s remove*Handler() teardown block could re-enter
   once browser_ had already emptied out and isDisposed_ was already
   true, double-releasing each handler's native reference a second
   time. Added a handlersRemoved_ guard so the block only runs once.

3. RenderHandler::GetJNIScreenInfo() double-wrapped the caller-owned
   jScreenInfo parameter in a second ScopedJNIObjectLocal, causing a
   double DeleteLocalRef on the same jobject.

Ported from coverage/phase1-value-objects-phase2-handlers's 7290b2e,
which found and confirmed all three live via JCEF_TRACE (a tracing
facility that only exists on that branch, not master -- the trace
calls and the JCefTrace import are dropped here since they'd need
issue #26's not-yet-backported facility). See issue #22.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HBFmQs6JDypYP5qdC99yDq
@Thrameos
Thrameos force-pushed the backport/cleanupbrowser-double-release branch from 7e3c504 to 2a721f4 Compare September 5, 2026 21:23
@Thrameos Thrameos added the bug Something isn't working label Sep 6, 2026
@Thrameos
Thrameos merged commit d9acc85 into master Sep 6, 2026
8 of 9 checks passed
@Thrameos
Thrameos deleted the backport/cleanupbrowser-double-release branch September 6, 2026 03:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Intermittent SIGSEGV in SetCefForJNIObjectHelper::Release during normal CefClient teardown (Debug build)

1 participant