Skip to content

Display: an init() during the old EDT's teardown starts its own dispatch thread - #5694

Open
shai-almog wants to merge 6 commits into
masterfrom
fix/edt-handover-adoption
Open

Display: an init() during the old EDT's teardown starts its own dispatch thread#5694
shai-almog wants to merge 6 commits into
masterfrom
fix/edt-handover-adoption

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Fixes the intermittent build-test failure

FormTest timed out after 5000ms; edt=display-not-initialized

which has failed a different core-unittests class each time it appeared (ValidatorTest, AutoCompleteTextComponentTest) and never reproduced locally.

The bug

A thread that has left mainEDTLoop's loop stays isAlive() for the whole of its teardown -- Desktop.disposeAll() and impl.deinitialize(), either of which can block. Display.init() decided whether to start a dispatch thread on exactly that evidence, so an init() landing in that window adopts a thread on its way out and starts nothing.

The new generation then has no dispatch at all: everything it queues waits for ever, and isInitialized() stays false while codenameOneRunning stays true -- a state init() cannot repair, because it guards on that flag. Every test in the class times out.

The fix

The departing thread publishes the fact instead of leaving it to be inferred. edtDispatching is renounced inside the same monitor init() takes to decide adoption, so the two orderings become the two correct outcomes rather than a race:

  • the thread renounces first, and init() starts a dispatch thread of its own;
  • or init() gets there first -- and it has already set codenameOneRunning back to true, so the thread's next test keeps it in the loop, which is the adoption that is legitimately free.

Two things that look like tidying are load-bearing, and both are commented in the code:

  • The thread stays the recorded edt until the end. The teardown is meant to run as the EDT, so clearing edt early to dodge adoption -- the obvious first fix -- makes isEdt() false for exactly that call.
  • The final clear is guarded on identity. Once adoption stops, the two generations overlap by design, and clearing the field unconditionally disowned the live successor: isEdt() then answered false on the dispatch thread itself.

Why not in the harness

UITestBase and FormTestInterceptor each carry a recovery for this and both comments treat it as test infrastructure. They took ValidatorTest from twelve failures to one; neither can close the window. Their recoveries are left alone -- they also cover the unrelated half-initialised state DisplayRecoveryTest describes.

Verification

EdtHandoverTest holds the window open with an implementation that blocks inside deinitialize() rather than racing for it. Both tests fail on master in 10.8s and pass in 1.9s, and each half of the fix is proven alone: removing only the identity guard fails the second test and leaves the first passing.

Full core-unittests module 6142/6142; SpotBugs 0; PMD, copyright and control-character gates clean.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T14:49:18.352877Z 59a33a0 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 938c78d467

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/ui/Display.java
Comment thread CodenameOne/src/com/codename1/ui/Display.java Outdated
@shai-almog

shai-almog commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.14% (9061/99176 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.91% (46674/523952), branch 3.50% (1739/49639), complexity 3.49% (1849/52940), method 5.37% (1496/27852), class 10.81% (403/3727)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.14% (9061/99176 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.91% (46674/523952), branch 3.50% (1739/49639), complexity 3.49% (1849/52940), method 5.37% (1496/27852), class 10.81% (403/3727)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 202ms / native 94ms = 2.1x speedup
SIMD float-mul (64K x300) java 51ms / native 33ms = 1.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 41.000 ms
Base64 CN1 decode 52.000 ms
Base64 native encode 261.000 ms
Base64 encode ratio (CN1/native) 0.157x (84.3% faster)
Base64 native decode 194.000 ms
Base64 decode ratio (CN1/native) 0.268x (73.2% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog

shai-almog commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 811f7c9ed1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/ui/Display.java
@shai-almog

shai-almog commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 160 screenshots: 160 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 293 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 109ms / native 6ms = 18.1x speedup
SIMD float-mul (64K x300) java 108ms / native 4ms = 27.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 259.000 ms
Base64 CN1 decode 158.000 ms
Image encode benchmark iterations 100
Image createMask (SIMD off) 18.000 ms
Image createMask (SIMD on) 6.000 ms
Image createMask ratio (SIMD on/off) 0.333x (66.7% faster)
Image applyMask (SIMD off) 100.000 ms
Image applyMask (SIMD on) 69.000 ms
Image applyMask ratio (SIMD on/off) 0.690x (31.0% faster)
Image modifyAlpha (SIMD off) 102.000 ms
Image modifyAlpha (SIMD on) 93.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.912x (8.8% faster)
Image modifyAlpha removeColor (SIMD off) 98.000 ms
Image modifyAlpha removeColor (SIMD on) 80.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.816x (18.4% faster)

@shai-almog

shai-almog commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 383 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 87ms / native 5ms = 17.4x speedup
SIMD float-mul (64K x300) java 96ms / native 3ms = 32.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 233.000 ms
Base64 CN1 decode 132.000 ms
Base64 native encode 580.000 ms
Base64 encode ratio (CN1/native) 0.402x (59.8% faster)
Base64 native decode 241.000 ms
Base64 decode ratio (CN1/native) 0.548x (45.2% faster)
Base64 SIMD encode 70.000 ms
Base64 encode ratio (SIMD/CN1) 0.300x (70.0% faster)
Base64 SIMD decode 90.000 ms
Base64 decode ratio (SIMD/CN1) 0.682x (31.8% faster)
Base64 encode ratio (SIMD/native) 0.121x (87.9% faster)
Base64 decode ratio (SIMD/native) 0.373x (62.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 21.000 ms
Image createMask (SIMD on) 4.000 ms
Image createMask ratio (SIMD on/off) 0.190x (81.0% faster)
Image applyMask (SIMD off) 72.000 ms
Image applyMask (SIMD on) 56.000 ms
Image applyMask ratio (SIMD on/off) 0.778x (22.2% faster)
Image modifyAlpha (SIMD off) 89.000 ms
Image modifyAlpha (SIMD on) 50.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.562x (43.8% faster)
Image modifyAlpha removeColor (SIMD off) 58.000 ms
Image modifyAlpha removeColor (SIMD on) 60.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.034x (3.4% slower)

@shai-almog

shai-almog commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 1203 seconds

Build and Run Timing

Metric Duration
Simulator Boot 78000 ms
Simulator Boot (Run) 1000 ms
App Install 12000 ms
App Launch 2000 ms
Test Execution 392000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 64ms / native 3ms = 21.3x speedup
SIMD float-mul (64K x300) java 87ms / native 3ms = 29.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 191.000 ms
Base64 CN1 decode 113.000 ms
Base64 native encode 322.000 ms
Base64 encode ratio (CN1/native) 0.593x (40.7% faster)
Base64 native decode 233.000 ms
Base64 decode ratio (CN1/native) 0.485x (51.5% faster)
Base64 SIMD encode 49.000 ms
Base64 encode ratio (SIMD/CN1) 0.257x (74.3% faster)
Base64 SIMD decode 45.000 ms
Base64 decode ratio (SIMD/CN1) 0.398x (60.2% faster)
Base64 encode ratio (SIMD/native) 0.152x (84.8% faster)
Base64 decode ratio (SIMD/native) 0.193x (80.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.429x (57.1% faster)
Image applyMask (SIMD off) 43.000 ms
Image applyMask (SIMD on) 29.000 ms
Image applyMask ratio (SIMD on/off) 0.674x (32.6% faster)
Image modifyAlpha (SIMD off) 36.000 ms
Image modifyAlpha (SIMD on) 34.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.944x (5.6% faster)
Image modifyAlpha removeColor (SIMD off) 36.000 ms
Image modifyAlpha removeColor (SIMD on) 42.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.167x (16.7% slower)

shai-almog and others added 2 commits September 4, 2026 14:38
…tch thread

This is the bug behind the intermittent

    FormTest timed out after 5000ms; edt=display-not-initialized

that has failed a different core-unittests class each time it appeared --
ValidatorTest, AutoCompleteTextComponentTest -- and that never reproduced
locally, because the window is only open while a loaded machine is descheduling
a thread.

A thread that has left mainEDTLoop's loop stays isAlive() for the whole of its
teardown: Desktop.disposeAll() and impl.deinitialize(), either of which can
block. init() decided whether to start a dispatch thread on exactly that
evidence, so an init() landing in that window adopted a thread on its way out and
started nothing. The new generation then has no dispatch at all -- everything it
queues waits for ever and isInitialized() stays false while codenameOneRunning
stays true, a state init() cannot repair because it guards on that flag. Every
test in the class times out.

The fix publishes the fact instead of inferring it. edtDispatching is renounced
by the departing thread inside the same monitor init() takes to decide adoption,
so the two orderings become the two correct outcomes rather than a race: either
the thread renounces first and init() starts a dispatch thread, or init() gets
there first -- and it has already set codenameOneRunning back to true, so the
thread's next test keeps it in the loop, which is the adoption that is
legitimately free. Claimed in init() rather than by the new thread, because
between start() and its first instruction a thread is alive and not yet
dispatching.

Two things that look like tidying are load-bearing:

- The thread stays the recorded edt until the END. The teardown is meant to run
  AS the EDT -- Desktop.disposeAll() disposes windows on the thread their tree
  expects -- so clearing edt early to dodge adoption, the obvious first fix,
  makes isEdt() false for exactly that call.
- The final clear is now guarded on identity. Once adoption stops, the two
  generations overlap by design, and clearing the field unconditionally disowned
  the LIVE successor: isEdt() then answered false on the dispatch thread itself.

The harness has been patched for this twice, in UITestBase and
FormTestInterceptor, and both comments treat it as test infrastructure. They took
ValidatorTest from twelve failures to one; neither can close the window, because
the harness is not what is wrong. Their recoveries are left alone -- they also
cover the unrelated half-initialised state DisplayRecoveryTest describes.

EdtHandoverTest holds the window open with an implementation that blocks inside
deinitialize() rather than racing for it. Both tests fail on master in 10.8s and
pass in 1.9s, and each half is proven alone: removing only the identity guard
fails the second test and leaves the first passing.

Full core-unittests module green; SpotBugs, PMD, copyright and control-character
gates clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CompareObjectsWithEquals is on the PR quality gate's forbidden list, and it fails
the build for any finding. Identity is exactly what this comparison needs -- the
question is whether the departing thread is still the recorded EDT, and a Thread
that considered itself equal to another would still be a different thread -- so
it takes the marker the rest of the tree uses for the same situation rather than
a rewrite.

Found only because the identical construct in the continuity branch tripped the
check there. The gate run on this branch had covered SpotBugs and not the
forbidden PMD list, so this would have gone red in CI.

core-unittests: forbidden PMD 0, SpotBugs 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2207 seconds

Build and Run Timing

Metric Duration
Simulator Boot 86000 ms
Simulator Boot (Run) 1000 ms
App Install 19000 ms
App Launch 14000 ms
Test Execution 705000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 96ms / native 17ms = 5.6x speedup
SIMD float-mul (64K x300) java 75ms / native 3ms = 25.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 245.000 ms
Base64 CN1 decode 100.000 ms
Base64 native encode 757.000 ms
Base64 encode ratio (CN1/native) 0.324x (67.6% faster)
Base64 native decode 3327.000 ms
Base64 decode ratio (CN1/native) 0.030x (97.0% faster)
Base64 SIMD encode 52.000 ms
Base64 encode ratio (SIMD/CN1) 0.212x (78.8% faster)
Base64 SIMD decode 49.000 ms
Base64 decode ratio (SIMD/CN1) 0.490x (51.0% faster)
Base64 encode ratio (SIMD/native) 0.069x (93.1% faster)
Base64 decode ratio (SIMD/native) 0.015x (98.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 11.000 ms
Image createMask (SIMD on) 4.000 ms
Image createMask ratio (SIMD on/off) 0.364x (63.6% faster)
Image applyMask (SIMD off) 84.000 ms
Image applyMask (SIMD on) 82.000 ms
Image applyMask ratio (SIMD on/off) 0.976x (2.4% faster)
Image modifyAlpha (SIMD off) 51.000 ms
Image modifyAlpha (SIMD on) 63.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.235x (23.5% slower)
Image modifyAlpha removeColor (SIMD off) 90.000 ms
Image modifyAlpha removeColor (SIMD on) 218.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 2.422x (142.2% slower)

The adoption fix closed one of the two ways a Display generation ends up
reporting display-not-initialized. This closes the rest of them.

Now that an init() during the old thread's teardown starts a dispatch
thread of its own instead of adopting a departing one, the two
generations overlap by design -- and the teardown was still reading the
process-wide singletons rather than saying which generation it was
tearing down:

- impl is a single slot that init() overwrites, so `impl.deinitialize()`
  after the loop deinitialized the SUCCESSOR's implementation. That
  leaves isInitialized() false while codenameOneRunning stays true, a
  state init() cannot repair because it guards on that flag -- the same
  unrepairable half-up display, reached through the other door. The
  harness already describes this failure in UITestBase's own comment.
- Identity scoping is not enough on its own, because the implementation
  may be SHARED: UITestBase reuses one TestCodenameOneImplementation from
  class to class, so "tear down the one I served" is still the live one.
  The teardown now asks whether the implementation is IN SERVICE.
- Desktop is a process singleton with one window registry, so
  disposeAll() from a superseded thread closes the successor's windows,
  on the successor's dispatch thread.

Two more races in the same handover:

- The exception exit tested codenameOneRunning and cleared edtDispatching
  afterwards, which is the race keepDispatching() exists to close,
  reopened on the rarer path. It goes through the same helper now.
- init() claimed edtDispatching under the lock but recorded `edt` outside
  it. The two fields describe one thread, and both the next init() and
  the departing thread's own teardown read them together.

And the nondeterminism underneath all of it: the EDT's idle wait tested
only shouldEDTSleep(), so an idle EDT woken by deinitialize()'s notify
found itself still idle and waited again. "Closes down the EDT" only
actually did when something else happened to hand the thread work;
otherwise it stayed parked for ever, alive and flagged as dispatching.
Whether a deinitialize() ended a generation or left its thread to be
adopted decided itself on whether the display happened to be idle at that
instant. codenameOneRunning is part of that condition now.

EdtHandoverTest gains the two orderings the existing fixture could not
reach: it parks the departing thread inside Desktop.disposeAll(), which
is before the implementation to tear down is read, where parking inside
deinitialize() is after the receiver has already been resolved. Each new
test was verified to fail with only its own half of the fix reverted.

core-unittests 6144/6144; SpotBugs, PMD and Checkstyle 0. The handover
class ran 40 times under CPU load with no failure, where before the
idle-wait fix it failed roughly one run in twenty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog
shai-almog force-pushed the fix/edt-handover-adoption branch from 811f7c9 to 9c16f05 Compare September 4, 2026 12:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c16f05db3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/ui/Display.java
Comment thread CodenameOne/src/com/codename1/ui/Display.java Outdated
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Preview

The previous commit tried to make an overlapping teardown safe by asking,
before each step, whether it had been superseded. build-test (8) showed
why that cannot work: PreferencesTest lost all five of its tests to
display-not-initialized. The departing thread asked while it was still
the recorded EDT, was descheduled, and an init() completed in the gap --
so the deinitialize() that followed cleared the flag initImpl had just
set. A check and the call after it are two statements, and a thread being
descheduled between two statements is this entire class of failure.

The damage needs no distinct implementations to happen. UITestBase reuses
one TestCodenameOneImplementation from class to class and the Android
factory returns AndroidImplementation.getInstance(), so the departing
generation and its successor routinely hold the SAME object: identity
scoping cannot separate them, and neither can any other check made ahead
of the call.

So the two are ordered rather than guarded. The departing thread claims
the teardown inside the same monitor and at the same instant it renounces
edtDispatching -- provably before any init() can have begun, because
init()'s first act is to set codenameOneRunning true and the renunciation
happens with it false. init() waits for that claim to be released.
Generations no longer overlap, which is what makes the process-wide state
they share -- the impl slot, the implementation's initialized flag,
Desktop's single window registry -- safe to touch at all.

The wait is bounded. The teardown is the PORT's, and a port is entitled
to need a thread that may be the one calling init(): Android hands the
tail of its teardown to the activity UI thread, which is also where
init() is called from. Waiting indefinitely would turn a slow teardown
into a dead application, so on expiry the generations overlap exactly as
before and the superseded-checks in the teardown are what covers it. The
claim is also released on the exception exit, which runs no teardown at
all, and in a finally, so a throwing teardown cannot leave init() waiting
out the whole bound.

EdtHandoverTest asserts the invariant directly now: an init() racing a
parked teardown must NOT run to completion beside it. Verified by
deleting the wait, which fails that assertion and takes a second test
with it.

core-unittests 6144/6144; SpotBugs, PMD forbidden rules and Checkstyle
all 0, read out of this checkout's own reports.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e25a52c13

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/ui/Display.java
Comment thread CodenameOne/src/com/codename1/ui/Display.java Outdated
Review follow-up. One finding taken, two rejected with the reasoning put
where it will be read -- in the code, not in a PR thread.

Taken: two initializers parked in awaitPreviousTeardown() are released by
a single notifyAll, so both would wake, both find codenameOneRunning
false and both go on to build a generation, overwriting each other's impl
mid-initImpl(). init() always had that check-then-set unsynchronized, but
the wait is what synchronizes two callers into hitting it together. The
wait and the claim are now one critical section.

Rejected, at releaseTeardownClaim(): that the claim should cover
AndroidImplementation.deinitialize()'s asynchronous half. It reads two
statements too early. startContext() waits out deinitializingEdt and then
calls deinitialize() again FROM the UI thread, where it runs its Runnable
inline rather than posting it, and it does that before reinit() calls
instance.init(i); the posted copy then returns at its own
if (!deinitializing) guard. Nothing nulls a freshly initialized myView.
The remedy is also worse than the defect it describes: Display.init()
runs ON the UI thread during an activity restart, so waiting for work
queued on that thread parks the thread that has to run it, and only the
bound would break the deadlock -- ten seconds per restart, with the
cleanup landing afterwards anyway.

Rejected, at the disposeAll() guard: that the ownership check be made
atomic with the disposal. It cannot be. Atomicity means holding the
display lock across disposeAll(), which fires application window
listeners and calls wm.hide/wm.dispose in the port. What makes the
sequence safe is that init() waits rather than races; the check is what
remains for the expiry case, and narrowing that window to two adjacent
statements is all that is available.

core-unittests 6144/6144; SpotBugs, PMD forbidden rules and Checkstyle 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 042878181b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/ui/Display.java Outdated
Review follow-up, taken but not in the shape suggested.

The wait treated an interrupt as a successful wait: it restored the flag
and returned, and init() went on to claim a generation while the previous
one was still tearing down. That is the interleaving the wait exists to
prevent, reached by a route nothing else checks.

Re-arming the flag inside the loop and continuing -- the first remedy the
review offered -- is worse than returning. Object.wait() clears the
interrupted status when it throws, so a restored flag makes the very next
wait() throw at once and the loop spins out the whole ten second deadline
instead of sleeping through it. The interrupt is remembered instead, the
serialization is held to the deadline as for any other caller, and the
flag is restored once on the way out, so nothing is swallowed either.

Aborting init() outright, the review's other suggestion, would leave the
caller with no display at all -- a worse outcome than the overlap, and
one no caller is written to expect.

EdtHandoverTest covers it: an init() interrupted while parked must still
be waiting afterwards, must come back once the teardown finishes, and
must carry its interrupt out. Verified by restoring the old handling,
which fails that test on its own assertion.

core-unittests 6145/6145; SpotBugs, PMD forbidden rules and Checkstyle 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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