Skip to content

fix: XD cold-start scenario races JVM startup on CI runners#13

Merged
Tagar merged 1 commit into
masterfrom
fix-xd-codspeed-readiness
May 22, 2026
Merged

fix: XD cold-start scenario races JVM startup on CI runners#13
Tagar merged 1 commit into
masterfrom
fix-xd-codspeed-readiness

Conversation

@Tagar

@Tagar Tagar commented May 22, 2026

Copy link
Copy Markdown
Member

Summary

PR-0 (#12) introduced XD as a CodSpeed cold-start scenario, but its body used time.sleep(0.25) + direct JavaGateway() connect — on CI runners the JVM hadn't bound its listen socket yet, surfacing as ConnectionRefusedError: [Errno 111] and failing the whole codspeed workflow on the merged commit.

Switch XD to the existing fresh_jvm(readiness_retries=5) context manager so connection readiness is polled rather than blind-slept.

Diff

  • macro.py: -24 / +10 — replaced inline spawn/sleep/try-finally with fresh_jvm delegation.
  • No new scenarios; no behavior change in other macros.

Local verification

test_cold_start_scenario[XD]: median ~355 ms (M-series + JDK 21)

Variance is higher in cold-start measurement by nature — what matters is the elimination of hard CI failures.

Test plan

  • pytest src/py4j/tests/perf/scenarios/codspeed_macros.py::test_cold_start_scenario -v passes locally
  • codspeed workflow on byteoak master goes green post-merge

The XD ``test_cold_start_scenario`` introduced in #12 used a blind
``time.sleep(0.25)`` followed by a direct ``JavaGateway()`` connect
attempt. On CodSpeed CI runners (and any host slower than a modern
laptop) the JVM hadn't yet bound its listen socket when the connect
fired, surfacing as ``ConnectionRefusedError: [Errno 111] Connection
refused`` and failing the whole codspeed workflow.

Switch XD's body to the existing ``fresh_jvm`` context manager with
``readiness_retries=5``. ``fresh_jvm`` polls connection readiness
with ``check_connection`` and only proceeds once the JVM is actually
accepting calls — same convention the rest of the test suite uses.
This also drops a bunch of try/finally boilerplate since
``fresh_jvm`` handles spawn/shutdown.

Locally on M-series + JDK 21: XD median ~355 ms (unchanged from
post-#12 baseline). The variance is higher in cold-start measurement
by nature; what matters is the elimination of hard failures on
slower hosts.

Co-authored-by: Isaac
@Tagar
Tagar merged commit ef5bfd8 into master May 22, 2026
57 checks passed
Tagar added a commit that referenced this pull request May 28, 2026
py4j#557) (py4j#595)

Adds four CodSpeed scenarios mapping 1:1 to py4j cold-start improvement
work areas, plus tightens the perf-framework JVM readiness check so
slow scenarios get enough samples per benchmark budget.

Pure measurement infrastructure — zero behavior change in py4j proper.
Touches only the perf testing subtree.

New scenarios:
* XA-3 — attribute_walk_jvm_java_lang_System
    1 000 walks of ``gateway.jvm.java.lang.System``. Each walk
    re-issues 3 reflection round-trips today because JVMView /
    JavaPackage / JavaClass __getattr__ don't memoize. Cache canary
    for the upcoming Python attribute-cache PR.
* XB — gateway_reconnect_against_running_jvm
    50 fresh ``JavaGateway()`` against the fixture JVM per round.
    Measures per-connection setup cost — Java accept() loop allocates
    14 command class instances per connection
    (GatewayConnection.java:196-208), Python runs _create_connection
    + socket setup.
* XC — callback_infra_overhead_unused
    X1-1 workload with ``CallbackServer`` started, but no callbacks
    actually invoked. Delta vs X1-1 = the steady-state cost of
    running callback infrastructure alongside the main gateway.
* XD — full_cold_start_subprocess_first_call
    subprocess.Popen -> first call -> shutdown, one cycle per round.
    Slow per-iteration; CodSpeed adapts iteration count. Target of
    the JVM-flag work (AppCDS, -XX:TieredStopAtLevel=1, CRaC).

XA/XB/XC share the long-running fixture JVM (``test_macro_scenario``).
XD owns its full JVM lifecycle inside ``measure()`` and is dispatched
via a separate ``test_cold_start_scenario`` test function — it can't
share the fixture because both default to port 25333.

Readiness model overhaul in ``perf.jvm.fresh_jvm``:

  before: 0.25 s sleep + 1 retry x 2.0 s = 2.25 s ceiling
  after:  0 s sleep + 300 retries x 0.05 s = 15 s ceiling

Tight polling matters for XD on CodSpeed: under the old model each
XD iteration cost ~2.7 s and CodSpeed could fit only one sample per
benchmark budget, making variance and regression detection
impossible. Under the new model XD per-iteration finishes in
~500-1100 ms — enough for 2-5 samples per budget. The unconditional
0.25 s startup_sleep's original rationale ("let the OS reuse the
listen port") no longer applies: GatewayServer.startSocket() already
uses SO_REUSEADDR, so bind() succeeds immediately even over
TIME_WAIT. Backwards compatibility preserved — callers can pass
``startup_sleep=0.25`` to restore the old wait pattern.

Validated on #12, #13, #16 (where these changes
originated as 3 iterative PRs; bundled here for a clean single
upstream review surface). Full 56-cell CI matrix green on each
byteoak iteration. CodSpeed scenarios surface real cold-start work
on subsequent PRs — issue py4j#557.

Co-authored-by: Isaac
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