fix(launcher): honour JAVA_OPTS in the native distribution - #72
Conversation
yano.sh forwarded JAVA_OPTS only on the jar path. The native branches dropped it silently, so a working jar deployment moved to native would lose its settings without any message -- a JAVA_OPTS-supplied quarkus.http.port was ignored and the node bound the default port instead. A GraalVM native image accepts -D system properties and the -X memory flags, so those are now forwarded verbatim. It has no HotSpot -XX: namespace, agents or module flags -- passing those through aborts startup with "error: Could not find option ..." -- so they are dropped with an explicit warning naming the entries rather than silently ignored or allowed to break the launch. Verified against the built binary: -Xmx/-Xms/-D/-verbose accepted, -XX:+UseG1GC and -XX:MaxRAMPercentage rejected by the image.
The first version rejected the whole -XX: namespace, but a GraalVM image implements its own: this binary advertises 48 options including MaxHeapSize, MinHeapSize, MaximumHeapSizePercent and VerboseGC. Dropping them silently discarded valid native tuning -- the same class of bug this change set out to remove -- and the warning wrongly claimed they had no native equivalent. -XX: is now forwarded. A HotSpot-only flag is rejected by the image itself with an immediate, precise "error: Could not find option 'X'. Use -XX:PrintFlags= to list all available options", which is a better outcome than silently changing the GC the operator asked for. The launcher no longer second-guesses an option set that varies by GraalVM version. Only JVM agents and module-system flags, which a native image can never implement, are still dropped, and the warning now says exactly that. Verified end to end: the native node starts with -XX:MaximumHeapSizePercent=70 -XX:+VerboseGC and the GC tracing takes effect.
|
Good catch — verified and fixed in 6b57d88. The claim is correct. I probed the built binary and it advertises 48 So the blanket drop discarded valid native tuning, and the warning text was wrong to say these have no native equivalent. It was also self-defeating: silently dropping an option the operator asked for is exactly the bug this PR set out to remove. Fix: That beats silently changing the GC the operator configured. The launcher no longer second-guesses an option set that varies by GraalVM version. Still dropped, and only these: JVM agents ( Verified end to end, not just in argv: the native node starts with |
…image Two follow-ups to the -XX: correction. -X is the asymmetric case. A native image rejects an unknown -XX: loudly but accepts an unknown -X without a word: -Xdefinitely-not-an-option starts the node normally. Forwarding all of -X therefore recreated the silent misconfiguration this change set out to remove -- a typo or a HotSpot-only flag would take no effect and say nothing. Only -Xmx/-Xms/-Xss are verified, so anything else under -X is still forwarded, so a newer GraalVM keeps working, but is now called out. docker/native/entrypoint.sh dropped JAVA_OPTS entirely while its JVM counterpart forwarded it -- the same defect as yano.sh, in a launcher the first commit missed. It now applies the identical rules. Verified against the built binary: unknown -XX: rejected, unknown -X silently accepted, and a mixed JAVA_OPTS starts the node with both warnings emitted and the compatible entries applied.
|
Correct again — fixed in bb52afa. And chasing it turned up a launcher the PR had missed entirely. The
|
| unknown flag | native binary |
|---|---|
-XX:bogus |
loud — error: Could not find option 'bogus'. Use -XX:PrintFlags= ... |
-Xdefinitely-not-an-option |
silently ignored, node starts normally |
-X alone doesn't print help either, so the image cannot tell an operator which -X it honours. Blanket-forwarding -X therefore recreated exactly the silent misconfiguration this PR exists to remove: a typo or a HotSpot-only -X takes no effect and says nothing.
That asymmetry is also why the two namespaces now get different treatment, which is the underlying principle: forward where the binary reports errors loudly; warn where it fails silently.
Rather than a hard allowlist I kept forwarding unverified -X but call it out, so a newer GraalVM adding an -X keeps working instead of being silently dropped by us — swapping one silent failure for another would miss the point. Three-way classification now:
- forwarded silently —
-D,-Xmx,-Xms,-Xss,-XX:,-verbose - forwarded with a warning — any other
-X, "this image may ignore silently" - dropped with a warning — JVM agents and module-system flags
The launcher the PR missed
While checking whether other copies existed, docker/native/entrypoint.sh:31 had the identical original bug — it dropped JAVA_OPTS entirely while docker/jvm/entrypoint.sh:31 forwarded it. Anyone running the native container with JAVA_OPTS set lost it silently, exactly as the host launcher did. It now applies the same rules (POSIX sh, so a separate implementation, cross-referenced in comments).
Thanks for pushing on this one — the first fix was self-consistent but wrong in both directions.
Verified
Real binary, mixed JAVA_OPTS="-Xmx6g -Xss1m -XX:+VerboseGC -Xbogus-flag -javaagent:/x.jar -Dquarkus.http.port=7071":
Warning: dropping JAVA_OPTS entries a native image cannot implement: -javaagent:/x.jar
Warning: forwarding JAVA_OPTS entries this image may ignore silently: -Xbogus-flag
health=200
process args: -Xmx6g -Xss1m -XX:+VerboseGC -Xbogus-flag
bash -n and sh -n clean on both launchers.
Problem
yano.shforwardedJAVA_OPTSonly on the jar path. Both native branches dropped itsilently, so a working jar deployment moved to the native distribution loses its settings
with no message.
This is not theoretical — it cost a full test run. The native node was started with
JAVA_OPTS="-Xmx6g -Dquarkus.http.port=7071 ...", the port property was discarded, the nodebound the default 7070, and the health check timed out against a node that was in fact
perfectly healthy (it had produced 2,863 blocks and shut down cleanly).
The usage text also claimed
JAVA_OPTSwas "JVM options for jar distribution only".Change
JAVA_OPTSis now honoured by both distributions. The split is based on what the nativebinary actually accepts, established by probing the built image rather than assumed:
-Xmx512m,-Xms256m-Dfoo=bar-verbose:gc-XX:+UseG1GCerror: Could not find option 'UseG1GC'-XX:MaxRAMPercentage=50-javaagent:,-agentlib:,--add-*HotSpot
-XX:flags are dropped rather than forwarded deliberately. A GraalVM image hasits own
-XX:namespace, so passing HotSpot tuning through would turn a silentmisconfiguration into a hard startup failure — strictly worse than the current behaviour. The
warning names the exact entries dropped, so nothing disappears silently in either direction.
The namespaces really do differ, which is the justification for the split — from the image's
own option table:
MaximumHeapSizePercentexists where HotSpot'sMaxRAMPercentagedoes not.Scope:
app/bin/yano.shonly. No production Java touched.Verification
Launcher logic (stub binary, so the assembled argv is visible):
Values are honoured, not merely passed. Both flag classes were A/B tested by changing the
value and looking for a changed outcome.
-Dproperties — starting withJAVA_OPTS="-Dquarkus.http.port=7071"alone moves thelistener off its configured 7070 default on both distributions. On native this is exactly the
case that failed before.
-Xmx— same workload (300 faucet calls), only the flag changed:-Xmx6g-Xmx24mOutOfMemoryErrorBoth react, so the flag is genuinely applied. The asymmetry is expected: native tolerates a
24 MB Java heap where the JVM cannot, because much of a native image's footprint is off-heap
(RocksDB, image data) with far less JVM overhead.
No regression — full load and SDK suite re-run against both distributions after the change
(3 min CCL + 90 s MeshJS + 90 s Evolution + contract tests each):
JVM CCL throughput went 276.9 → 279.9 tx/s versus the pre-change baseline. The change only
touches argument assembly in a shell script and cannot affect node behaviour.
bash -nclean; the launcher targets bash 3.2 (macOS default) and uses no newer syntax.Notes
Unrelated items observed during the same testing and deliberately not addressed here:
/txs/{hash}and/txs/{hash}/utxosare canonical-only;include_mempool=truedoes not cover them). CCL andEvolution chain fine.
(Quarkus auto-activates the
nativeprofile at build) and assessed as non-blocking.