davsclaus opened a new pull request, #28776:
URL: https://github.com/apache/camel/pull/28776

   [CAMEL-26441](https://issues.apache.org/jira/browse/CAMEL-26441)
   
   The zip and tar of the Camel CLI (what Homebrew, SDKMAN, WinGet, Scoop, 
Chocolatey, the website installer and `camel self-update` install) ran the 
Spring Boot fat JAR from `bin/`. Its class loader reads the 347 nested JARs, 
which costs about 100 ms on every command and more when all commands load, and 
the JVM cannot keep classes of that class loader in a CDS archive.
   
   ## Changes
   
   - **The distribution has the JARs in `lib/`**: a thin 
`lib/camel-launcher-<version>.jar` (the classes of the module, built by a new, 
not attached assembly `launcher-jar.xml`) whose manifest names the other JARs 
of `lib/` as its class path, and the dependency JARs under the same names 
(`useUniqueVersions=false` / `baseVersion`). `bin/` has the scripts and the 
Jolokia agent only. The fat JAR stays the Maven artifact (`java -jar`, the 
container image), unchanged.
   - **`camel.sh` and `camel.bat`** run `java -jar 
lib/camel-launcher-<version>.jar`, and add a dynamic CDS archive per command 
and Camel version (`-XX:+AutoCreateSharedArchive -XX:SharedArchiveFile=… 
-Xlog:cds*=off`, Java 19+) in 
`${XDG_CACHE_HOME:-~/.cache}/camel-cli/cds/<version>/<command>.jsa` 
(`%LOCALAPPDATA%\camel-cli\cds` on Windows). What I checked on JDK 21 and 25 
(Zulu) and OpenJ9 (Semeru 21):
     - the JVM does not start when the directory of the archive is missing, so 
the scripts create it and add no options when they cannot;
     - the JVM creates the archive again when the JDK or the class path 
changed, but not when the file is empty, so the scripts remove an empty one;
     - a mismatch prints warnings on stdout, hence `-Xlog:cds*=off`;
     - `-XX:AOTCache` in `JAVA_OPTS` together with `SharedArchiveFile` stops 
the JVM, so CDS/AOT options in `JAVA_OPTS` (and `CAMEL_CDS=false`) turn it off;
     - six runs writing the same archive at once all succeed;
     - OpenJ9 ignores the options.
   - **`CamelLauncher` takes the launcher JAR from its own class.** It took it 
from `LauncherHelper`, which is in `camel-jbang-core-<version>.jar` in `lib/`, 
so a CLI that starts the CLI again (`camel run --background`) would have run 
`java -jar camel-jbang-core.jar`. New `LauncherHelper.getJarPath(Class)`.
   - **`camel-jbang-plugin-generate` excludes `swagger-models` 2.2.52** from 
`openapi-generator`. It has the same 69 classes as `swagger-models-jakarta` 
2.2.36, which the plugin declares; 3 of them differ by 4 methods that nothing 
in the launcher calls. `camel-rest-openapi` excludes it the same way.
   - **The class path order** of `lib/` is the order of the dependencies of 
`camel-launcher`, which is the class path its tests run with. The fat JAR had 
them in alphabetical order (a side effect of the sorted map for reproducible 
builds). Of the 587 classes in two JARs, the jline ones are identical, 
`commons-logging` comes before `jcl-over-slf4j` in both orders, and the swagger 
ones are gone (above).
   - **The native exe workflow** runs `bin/camel.sh version` twice from the 
public zip (the layout, and the CDS archive), and its diff of the launcher JAR 
on a non-reproducible build looks in `lib/`.
   - Docs: the launcher page (layout, class data sharing, `CAMEL_CDS=false`), 
the 4.24 upgrade guide, the module README.
   
   ## Measured
   
   JDK 25, wall clock, median of 9, through `bin/camel.sh` (without #28774):
   
   | command | fat JAR (main) | `lib/` | `lib/` + CDS | first run (creates the 
archive) |
   |---|---|---|---|---|
   | `camel version` | 289 ms | 187 ms | 164 ms | 334 ms |
   | `camel generate` | 809 ms | 550 ms | 370 ms | 1363 ms |
   | `camel --help` | 771 ms | 506 ms | 343 ms | 1284 ms |
   
   With #28774 (lazy plugins) `camel generate` is about 185 ms with `lib/`, and 
about 150 ms with CDS.
   
   ## Tests
   
   - `CamelShDiscoveryTest` / `CamelBatDiscoveryTest`: the launcher home has 
the `bin/` + `lib/` layout (`FakeJava`); new tests for the archive per command, 
the archive of `camel` without a command, the cache in the home directory, 
removing an empty archive, `CAMEL_CDS=false` and CDS options in `JAVA_OPTS`, 
and no archive without a cache directory.
   - `PackagePlanTest`: the WinGet archive carries the JARs of `lib/` and the 
launcher JAR.
   - `LauncherArchiveLayoutIT` (with `-Dcamel.exe.build=true`, like 
`CamelLauncherNativeExeIT`): in both zips the class path of the launcher JAR is 
exactly the JARs of `lib/`, and `bin/` has no other JAR than the Jolokia agent.
   - `LauncherHelperTest`: `getJarPath`.
   - Ran locally: the full camel-launcher suite (310 tests; the `camel.bat` 
tests run on Windows CI only), `LauncherHelperTest` and `InstallDetectorTest`; 
the extracted zip with `sh`, `dash`, a symlink and a path with spaces; `camel 
run --background` + `camel ps` + `camel stop` from the zip (the child JVM runs 
`-jar lib/camel-launcher-<version>.jar`); `camel generate rest --dto` gives the 
same files as the fat JAR (only the `@Generated` date differs).
   
   Seen on the way, not changed here: `camel generate rest --dto 
--package=org.acme` prints `Failed to move generated DTOs to org/acme` when the 
directory `org` does not exist (`File.renameTo` does not create parent 
directories), on main too.
   
   The JDK 25 AOT cache (about 25 ms faster than CDS for `camel version`, but 
it needs a training run) can follow later.
   
   _Claude Code on behalf of davsclaus (Claus Ibsen)_
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   
   https://claude.ai/code/session_0128ThHB3iQdrvSRLVBGRrdP
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to