Skip to content

fix(JS port) #5145: correct worker↔host barrier ownership model#5148

Draft
shai-almog wants to merge 3 commits into
masterfrom
fix-jsport-barrier-model
Draft

fix(JS port) #5145: correct worker↔host barrier ownership model#5148
shai-almog wants to merge 3 commits into
masterfrom
fix-jsport-barrier-model

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Reworks the native barrier to the C/iOS ownership model (per maintainer guidance): the JS host is a dumb hard-reference table that never GCs on its own; cleanup is the Java side's job via a finalizer that frees the front-end resource it owns.

1. Stop crossing the barrier for known dims/context. Cached displayWidth/displayHeight + the outputCanvas 2D context Java-side; getDisplayWidth/Height and the paint flush no longer round-trip every frame. This removes the dimension/context round-trip storm whose responses crossed into getDocument/getContext object reads (the degraded-receiver source).

2. Owning-object finalizer release. NativeImage owns its backing canvas / HTMLImageElement; registerImageResource() arms a FinalizationRegistry keyed on that single stable resource, releasing its host id when the image is GC'd. Replaces #5143's wrapper-refcount release (which raced via re-created wrappers + raw markers → "Missing host receiver").

Validated locally: NUMBER_FOR_OBJECT=0, Missing host receiver=0, grid NPE=0 across the whole run — the response-cross and receiver-deletion are eliminated.

Known remaining (WIP): a separate, pre-existing mechanism-3 capture/emit stall (worker silent right after __cn1_capture_canvas_png__ resolves) still intermittently wedges; being fixed before this lands. Refs #5145.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

Cloudflare Preview

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 2, 2026

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

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 2, 2026

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

Benchmark Results

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

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 896.000 ms
Base64 CN1 encode 1675.000 ms
Base64 encode ratio (CN1/native) 1.869x (86.9% slower)
Base64 native decode 499.000 ms
Base64 CN1 decode 1250.000 ms
Base64 decode ratio (CN1/native) 2.505x (150.5% slower)
Base64 SIMD encode 508.000 ms
Base64 encode ratio (SIMD/native) 0.567x (43.3% faster)
Base64 encode ratio (SIMD/CN1) 0.303x (69.7% faster)
Base64 SIMD decode 483.000 ms
Base64 decode ratio (SIMD/native) 0.968x (3.2% faster)
Base64 decode ratio (SIMD/CN1) 0.386x (61.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 72.000 ms
Image createMask (SIMD on) 16.000 ms
Image createMask ratio (SIMD on/off) 0.222x (77.8% faster)
Image applyMask (SIMD off) 233.000 ms
Image applyMask (SIMD on) 130.000 ms
Image applyMask ratio (SIMD on/off) 0.558x (44.2% faster)
Image modifyAlpha (SIMD off) 249.000 ms
Image modifyAlpha (SIMD on) 139.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.558x (44.2% faster)
Image modifyAlpha removeColor (SIMD off) 225.000 ms
Image modifyAlpha removeColor (SIMD on) 104.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.462x (53.8% faster)
Image PNG encode (SIMD off) 1414.000 ms
Image PNG encode (SIMD on) 1753.000 ms
Image PNG encode ratio (SIMD on/off) 1.240x (24.0% slower)
Image JPEG encode 645.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 2, 2026

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 98000 ms
Simulator Boot (Run) 1000 ms
App Install 19000 ms
App Launch 20000 ms
Test Execution 359000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1180.000 ms
Base64 CN1 encode 1976.000 ms
Base64 encode ratio (CN1/native) 1.675x (67.5% slower)
Base64 native decode 446.000 ms
Base64 CN1 decode 1389.000 ms
Base64 decode ratio (CN1/native) 3.114x (211.4% slower)
Base64 SIMD encode 607.000 ms
Base64 encode ratio (SIMD/native) 0.514x (48.6% faster)
Base64 encode ratio (SIMD/CN1) 0.307x (69.3% faster)
Base64 SIMD decode 1292.000 ms
Base64 decode ratio (SIMD/native) 2.897x (189.7% slower)
Base64 decode ratio (SIMD/CN1) 0.930x (7.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 154.000 ms
Image createMask (SIMD on) 21.000 ms
Image createMask ratio (SIMD on/off) 0.136x (86.4% faster)
Image applyMask (SIMD off) 274.000 ms
Image applyMask (SIMD on) 124.000 ms
Image applyMask ratio (SIMD on/off) 0.453x (54.7% faster)
Image modifyAlpha (SIMD off) 267.000 ms
Image modifyAlpha (SIMD on) 183.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.685x (31.5% faster)
Image modifyAlpha removeColor (SIMD off) 349.000 ms
Image modifyAlpha removeColor (SIMD on) 204.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.585x (41.5% faster)
Image PNG encode (SIMD off) 2160.000 ms
Image PNG encode (SIMD on) 1584.000 ms
Image PNG encode ratio (SIMD on/off) 0.733x (26.7% faster)
Image JPEG encode 932.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 2, 2026

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 58000 ms
Simulator Boot (Run) 0 ms
App Install 9000 ms
App Launch 5000 ms
Test Execution 234000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 635.000 ms
Base64 CN1 encode 1414.000 ms
Base64 encode ratio (CN1/native) 2.227x (122.7% slower)
Base64 native decode 314.000 ms
Base64 CN1 decode 927.000 ms
Base64 decode ratio (CN1/native) 2.952x (195.2% slower)
Base64 SIMD encode 508.000 ms
Base64 encode ratio (SIMD/native) 0.800x (20.0% faster)
Base64 encode ratio (SIMD/CN1) 0.359x (64.1% faster)
Base64 SIMD decode 531.000 ms
Base64 decode ratio (SIMD/native) 1.691x (69.1% slower)
Base64 decode ratio (SIMD/CN1) 0.573x (42.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 107.000 ms
Image createMask (SIMD on) 25.000 ms
Image createMask ratio (SIMD on/off) 0.234x (76.6% faster)
Image applyMask (SIMD off) 132.000 ms
Image applyMask (SIMD on) 74.000 ms
Image applyMask ratio (SIMD on/off) 0.561x (43.9% faster)
Image modifyAlpha (SIMD off) 214.000 ms
Image modifyAlpha (SIMD on) 170.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.794x (20.6% faster)
Image modifyAlpha removeColor (SIMD off) 323.000 ms
Image modifyAlpha removeColor (SIMD on) 182.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.563x (43.7% faster)
Image PNG encode (SIMD off) 1309.000 ms
Image PNG encode (SIMD on) 939.000 ms
Image PNG encode ratio (SIMD on/off) 0.717x (28.3% faster)
Image JPEG encode 672.000 ms

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 716 total, 0 failed, 3 skipped

Benchmark Results

  • Execution Time: 9035 ms

  • Hotspots (Top 20 sampled methods):

    • 24.69% com.codename1.tools.translator.Parser.isMethodUsed (375 samples)
    • 20.87% java.lang.String.indexOf (317 samples)
    • 6.32% java.lang.Object.hashCode (96 samples)
    • 5.79% com.codename1.tools.translator.Parser.addToConstantPool (88 samples)
    • 5.13% com.codename1.tools.translator.BytecodeMethod.addToConstantPool (78 samples)
    • 5.00% java.util.ArrayList.indexOf (76 samples)
    • 2.37% com.codename1.tools.translator.ByteCodeClass.calcUsedByNative (36 samples)
    • 2.24% java.lang.System.identityHashCode (34 samples)
    • 2.11% com.codename1.tools.translator.ByteCodeClass.markDependent (32 samples)
    • 1.97% com.codename1.tools.translator.ByteCodeClass.updateAllDependencies (30 samples)
    • 1.38% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (21 samples)
    • 1.12% com.codename1.tools.translator.Parser.cullMethods (17 samples)
    • 1.05% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (16 samples)
    • 1.05% java.lang.StringBuilder.append (16 samples)
    • 0.92% com.codename1.tools.translator.BytecodeMethod.equals (14 samples)
    • 0.92% com.codename1.tools.translator.BytecodeMethod.isMethodUsedByNative (14 samples)
    • 0.72% com.codename1.tools.translator.BytecodeMethod.optimize (11 samples)
    • 0.72% com.codename1.tools.translator.Parser.getClassByName (11 samples)
    • 0.72% java.lang.StringCoding.encode (11 samples)
    • 0.72% org.objectweb.asm.ClassReader.readCode (11 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 2, 2026

Compared 122 screenshots: 122 matched.

Native Android coverage

  • 📊 Line coverage: 13.02% (7685/59026 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 10.55% (38191/362037), branch 4.53% (1545/34090), complexity 5.52% (1808/32763), method 9.64% (1481/15358), class 15.77% (339/2150)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 13.02% (7685/59026 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 10.55% (38191/362037), branch 4.53% (1545/34090), complexity 5.52% (1808/32763), method 9.64% (1481/15358), class 15.77% (339/2150)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1776.000 ms
Base64 CN1 encode 391.000 ms
Base64 encode ratio (CN1/native) 0.220x (78.0% faster)
Base64 native decode 1513.000 ms
Base64 CN1 decode 158.000 ms
Base64 decode ratio (CN1/native) 0.104x (89.6% faster)
Image encode benchmark status skipped (SIMD unsupported)

Rework the native barrier to the C/iOS backend's model: the JS host side is a
dumb, hard-reference table that never GCs on its own; GC applies only to the
Java side, and the Java side's finalizer frees the front-end resource it owns.

1. Stop crossing the barrier for values the Java side already knows. The paint
   flush and layout were calling outputCanvas.getContext('2d') /
   getWidth()/getHeight() and getDisplayWidth()/Height() (-> canvas.getWidth())
   on every frame -- a continuous storm of round-trips whose responses
   intermittently crossed into concurrent object reads (getDocument/getContext
   resuming with a width/height number -> degraded receiver -> the ButtonTheme
   hard-stall). Cache displayWidth/displayHeight (set in updateCanvasSize) and
   the outputCanvas 2D context (stable for a canvas) in Java fields and reuse
   them; getDisplayWidth/Height return the cached values. No dimension/context
   round-trips during steady-state paint.

2. Drive host-ref release from the OWNING Java object's finalizer, not from JSO
   wrapper GC. NativeImage owns its backing canvas / HTMLImageElement;
   registerImageResource() arms a FinalizationRegistry keyed on that resource
   (a single, stable wrapper held only by the image), and on collection the
   worker posts releaseHostRef for its id. The host keeps a hard ref until then.
   This replaces #5143's wrapper-refcount release, which raced: the host dedups
   one id across many re-created worker wrappers and a raw __jsValue marker
   could outlive them, so refcount-zero released canvases still in use ->
   "Missing host receiver". Single-owner keying cannot do that.

browser_bridge.releaseHostRefs now evicts whatever id the dead owner held
(canvas or image) behind the never-release singleton guard.

Refs #5145.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog force-pushed the fix-jsport-barrier-model branch 2 times, most recently from 30c5f71 to 2df519c Compare June 2, 2026 13:48
…rks (#5145)

The late-suite screenshot wedge (after the #5143 host-ref fix) has two roots in
the worker<->host postMessage channel under a dense paint burst:

1. Response-cross: an idempotent object read (document.createElement,
   canvas.getContext, getImageData, measureText, canvas.toDataURL) resumes with
   a corrupted value -- a number, an empty {} that lost its host-ref marker,
   null from a never-null method, or a thrown "Missing JS member"/"Missing host
   receiver". The old substitute-null turned each into a hard NPE / EDT deadlock
   (createElement -> null canvas; or an emit-time toDataURL throw escaping a
   lock).
2. Lost-response: a host callback never arrives, so the green thread parks on
   pendingHostCalls[id] forever (hard wedge, heartbeat alive but runnable=0).

Mirroring the C/iOS backend model (the host is a thin, dumb pixel sink; the
worker must never blindly trust it to always respond):

- Barrier-model reductions to cut cross frequency at the source: cache the
  document Java-side (doc(); no per-createCanvas window.getDocument()); pass the
  known width/height into the HTML5Graphics ctor and BufferedGraphics instead of
  reading canvas.getWidth()/getHeight() back across the barrier; track the
  scratch-buffer dims Java-side.
- invokeJsoBridge retry: re-issue an idempotent read up to 12x (with a growing
  backoff sleep so the concurrent numeric-getter burst that caused the cross
  drains before retrying) on a degraded result (number / empty-{} /
  null-for-never-null) -- or on a transient throw for ANY round-trip method (a
  "Missing JS member"/"Missing host receiver" throw means the call never
  executed, so re-issuing is side-effect-free; this is what recovers an
  emit-time canvas.toDataURL() cross).
- Host-call watchdog: for bounded host natives (jso bridge, DOM-element create,
  ui-settle, canvas->PNG capture, etc.) a lost response resumes the parked
  thread with a transient error so the retry re-issues / the caller advances.
  Unbounded natives (image load, fetch, the cn1ss WebSocket) are never aborted.
  Zero-cost on a healthy channel.
- Re-park LightweightPickerButtons (a lightweight-popup EDT deadlock, distinct
  from the cross, so the retry/watchdog can't rescue it); ValidatorLightweightPicker
  now runs clean and stays un-parked.

Worker-liveness heartbeat + host-ref counters are gated to diag-only (zero
production cost). Validated in CI (javascript-screenshots).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog force-pushed the fix-jsport-barrier-model branch 2 times, most recently from d13fefe to 4820dc8 Compare June 2, 2026 19:30
…om CI

The screenshot harness wrote received PNGs only to a runner temp dir; nothing
uploaded them, so a faithful golden could not be (re)seeded from a CI render.
Copy $CN1SS_WS_DIR/*.png into $ARTIFACTS_DIR/delivered/ on both the normal and
timeout exit paths so they ride along in the javascript-ui-tests artifact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog force-pushed the fix-jsport-barrier-model branch from 3d4c032 to 5ae7507 Compare June 2, 2026 20:58
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