Repository navigation
feat(devloop): run on various application servers - #25851
Conversation
979f9b4 to
385c2a7
Compare
a198f84 to
f67ebb1
Compare
0a9c4dd to
2e45907
Compare
3724211 to
2a85db1
Compare
A forked server's JVM flags now travel through a build-extension hop before Maven runs the goalsequenceDiagram
participant D as MavenGoalRuntime.invocation()
participant M as Maven CLI
participant E as DevLoopBuildExtension
participant P as wildfly-maven-plugin
participant S as forked server JVM
D->>M: runs mvn with wildfly.skip=true, wildfly.javaOpts carrying the agents
Note over D,M: Jetty still sets MAVEN_OPTS directly, no extension involved
M->>E: afterProjectsRead(session) (new)
E->>E: reconfigure() forces skip=false and javaOpts in the app module only (new)
E->>M: effective model rewritten for that module (new)
M->>P: runs wildfly:run in the app module alone
P->>S: starts WildFly, javaOpts handing the agents to this JVM (new hop)
Note over S: MAVEN_OPTS never reaches this JVM, only the plugin's own parameter does
Per Diagram Bot draws the mechanism this pull request touches; it does not review the change. Verify it against the diff.
|
Both run goals fork a server JVM, so the agents cannot ride in on MAVEN_OPTS the way they do for embedded Jetty. ServerPlugin records where a server takes its JVM flags, and MavenGoalRuntime routes the agents and the application's own -D settings into that parameter. What the forked shape forced: - WildFly groups module options before it builds the server command line, so a two-token `--add-opens X` arrives as every flag ahead of every value and the JVM refuses to start; they are folded into `--add-opens=X`. - TomEE reads its `args` the way a shell would and drops a backslash as an escape, so a Windows path survives only when doubled. WildFly splits on whitespace and must not be escaped, so the escaping is per plugin. - WildFly's goal already forks `package` and TomEE's forks nothing, so only TomEE is given a phase. - Provisioning a server on a first start outruns the five minutes a boot is given, so the startup window is now a runtime's to declare. The agent no longer publishes the Instrumentation handle into the system properties table: a non-String value in a table the rest of the JVM reads as strings is a trap. WildFly's transactions subsystem merges those properties when it loads jbossts-properties.xml, took the handle for a string, and failed its boot with a NullPointerException after the HTTP listener had already bound. The handle travels by DevLoopAgent.get() through the system class loader instead, and DevLoopRedefiner falls back to the old property so that a newer dev server still works with an older agent jar.
Tomcat has no Maven plugin a Jakarta EE project can use: Apache's tomcat7-maven-plugin was last released in 2013 and runs a javax.servlet container. Codehaus Cargo is what the ecosystem uses instead - this repository's own servlet-container tests included - so the entry is named after the plugin rather than after Tomcat, and it covers every other container Cargo drives. What the plugin forced: - No parameter of cargo:run that could carry the loop's JVM flags has a user property, so no -D on a command line reaches one. What Cargo does read is any Maven project property named cargo.*, so DevLoopBuildExtension now also sets project properties for the run. One command-line setting per property rather than a separated list: the value holds the application's whole JVM command line, and on Windows -Dvaadin.devloop.classes= alone contains semicolons. - cargo.start.jvmargs rather than cargo.jvmargs, because Cargo appends both to the container's command line and only the second is one a project is likely to have written for itself. - A goal named on a Maven command line runs on every project in the reactor, and the loop names one with -pl :app -am. Jetty's mojo supports war packaging alone and skips the rest; Cargo's does not, and failed the build on the reactor root before anything had started. The goal is switched off reactor-wide by cargo.maven.skip and switched back on for the module that declares the plugin by a forced <skip>false</skip> - the rule Competing already exists for, used the other way round. - Cargo parses the value with Ant's translateCommandline, where a backslash is an ordinary character, so a Windows path needs no escaping here - unlike TomEE's. Readiness reads Cargo's own "<name> started on port [<port>]" rather than the container's, which is what lets one entry answer for every container Cargo drives and survives a project that sends the container's output to a file. This is the one runtime that cannot work without the build extension, so a daemon running from an exploded build directory says so rather than starting a server the agents never reached. Verified against Tomcat 11.0.26: detected, started, registered, and a method body hot-swapped in 0.97s with no restart.
staleResources asks whether the classpath copy is current and whether the daemon has acted on this content, and both were asked of timestamps. A timestamp says a file was written, not that it changed: tsc rewrites nine .d.ts files under vaadin-dev-server/src/main/resources on every build with identical content, and because they are startup-only resources every apply restarted the application over an edit that was one method body. The bytes on the classpath are what the running application read, so a yes from either question is now confirmed against them before it counts.
A third fixture, for the shape the other two do not cover: a container installed and started as a process of its own and handed a packaged copy of the WAR, rather than one serving the module's output in place. Laid out exactly like test-devloop-jetty, so the ITs in test-devloop-support run against it unchanged through <dependenciesToScan> - 45 tests, with the DevLoopCargo*IT classes holding what is true only of a fork reading a deployed WAR. DevLoopCssIT is excluded and DevLoopCargoResourceIT replaces it: the sibling module's stylesheet reaches the container inside a jar in the deployed WAR, so refreshing that module's target/classes - the whole of what an apply can do for a resource - never reaches what Tomcat serves. The class half of the same leg is unaffected, and holds for WildFly and TomEE too. vaadin-dev-server is declared without <optional>, unlike the Jetty fixture's: maven-war-plugin leaves optional dependencies out of WEB-INF/lib, and the application would otherwise start, serve, and never register with the daemon.
Both entries run the start goal, never dev, whose watcher rebuilds and redeploys in competition with every apply. Payara Server cost one new field. Its flags parameter is a List<String>, which Maven splits on commas, and the mojo then drops any piece with no "=" in it - so -DdisabledPlugins=Vaadin,Spring,... would have arrived as -DdisabledPlugins=Vaadin in silence. commaSplitFlags marks that channel and the comma-bearing flags travel in a JVM argument file instead. Micro's exec.args is a plain string and needed no field. Also fixes the Cargo entry, which set cargo.start.jvmargs. Cargo's GlassFish family strips cargo.jvmargs from the asadmin client and writes it into the domain config, while start.jvmargs reaches only asadmin - so a Payara container would have started with no agents and every apply would have restarted. cargo.jvmargs is correct for Tomcat too. Since a project may set it itself, the build extension now appends to a project property rather than replacing it.
wildfly:run never checks packaging, so it ended the build on the reactor root asking for a deployment named after a pom. It is now switched off reactor-wide with wildfly.skip and forced back on for the application's own module, as Cargo and both Payaras already are. Its packaging fork covers that module alone, so a sibling stopped at compile and reached WEB-INF/lib as a directory rather than a jar. A forked container now names package across the reactor; this also covers Liberty.
The classpath copy cannot answer whether the running application has seen a resource: an IDE that copies resources on save makes the copy current while the app still holds the old content, so the restart never happened. Remember a digest per resource instead.
Sonar (java:S864) flagged the ternary mixing a condition with string concatenation. An early return says the same without relying on operator precedence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…25957) Adds a fixture per forked server the dev loop drives, each running the shared suite from flow-test-devloop-support in dev-bundle mode, and runs them in CI slots 9 and 10. Liberty is served under /devloop, so the ITs read a context path from the fixture pom.
The settle window ended a start on the deployment WildFly booted from its persisted configuration when replacing it outlasted the window, and the replacement line could be read before the replaced deployment's close reached the daemon. Only the launch's own deployment now ends the start, after a short window for that close to arrive.
|
@tltv I pushed a fix for these found minor problems, please let me know if you thing these are redundant: Problems
Fix
|
The CLI output was read to the end before waiting on the process, so a command that hung, or a process it started that kept the pipe open, blocked the test until the CI step timed out, with no output. Read the output on its own thread, fail after 5 minutes with what the CLI said, and kill what the command started.
When a dev loop IT hangs on a start or an apply, the reason is in target/devloop/app.log and daemon.log, which the report left out.
|
Add WildFly, TomEE, Payara Server, Payara Micro, Open Liberty, and Cargo (Apache Tomcat) to the Dev Loop CLI's supported runtimes table, the `-Dvaadin.dev.runtime` override values, and the differences a forked server process brings versus an embedded one. Documents vaadin/flow#25851 (`ef202175510f770337e555bc007e8ce439cc91ad`).
|
Pull request created: #6213
|
|
Documentation Bot: Draft documentation pull request for this change: vaadin/docs#6213 Files updated:
It was written from the state of this pull request as you see it now. Please review it and mark it ready for review.
|
…#26066) This PR cherry-picks changes from the original PR #25851 to branch 25.3. --- #### Original PR description > Fixes #25458 > > **Background — how the loop reaches the application.** The dev loop hot > swaps by attaching two `-javaagent` jars to the JVM the application runs > in and redefining classes through `Instrumentation`. For a server that > runs inside the build's own JVM, that JVM is Maven's, so `MAVEN_OPTS` > carries them and there is nothing else to arrange. > > Until now the loop could only run an application whose server shared the > build's JVM. It now also runs WildFly, TomEE, Payara Server, Payara > Micro, Open Liberty and Apache Tomcat, which each start a server of > their own, with the agents travelling in a plugin parameter that reaches > the forked JVM. Two fixes and an integration-test fixture come with it. > > **Context.** Nothing on Maven's command line and nothing in its > environment reaches a JVM that Maven forked, so each forked container > needs a channel of its own. Four of them expose one as a user property > and Liberty as a property prefix; Cargo exposes none that any command > line can set, which is why it is the one runtime that cannot work > without the loop's build extension. > > - Added WildFly and TomEE as runtimes, with the JVM flags travelling in > `wildfly.javaOpts` and `tomee-plugin.args`, module options folded into > one token for WildFly, and backslashes doubled for TomEE alone > - Added Payara Server and Payara Micro, both on the `start` goal, never > `dev`, whose watcher redeploys in competition with every apply > - Payara Server's `payara.javaCommandLineOptions` is a `List<String>`, > which Maven splits on commas and the mojo then drops any piece > without an `=` in it, so comma-bearing flags travel in a JVM > argument file instead > - Added Open Liberty, whose channel is one Maven property per flag > (`liberty.jvm.<key>`), with `looseApplication` forced off so that > Liberty's own application monitor stops competing with every apply > - Added Apache Tomcat, driven through Codehaus Cargo, in one entry that > answers for every container Cargo drives > - Its flags go on the model as the `cargo.jvmargs` project property, > because no parameter of its run goal carries a user property, and > the build extension appends to a value a project set itself rather > than replacing it > - Switched the run goal off for the whole reactor and back on for the > application's module for Cargo, both Payaras and WildFly: unlike > Jetty's, their mojos do not skip a project that builds no deployment, > and failed or blocked the build on the reactor root > - A forked container now names `package` across the reactor as well, > so a sibling module reaches `WEB-INF/lib` as a jar rather than as a > directory > - Gave a forked container a startup window of its own, since > provisioning a server outruns the time an ordinary boot is given > - Confirmed a resource against the bytes on the classpath before > counting it as changed, so a rewritten-but-identical file is no longer > a change > - Tried both loopback literals when the application dials the daemon, > after a Payara Micro boot left the application JVM preferring `::1` > while the daemon listened on `127.0.0.1` and the application ran > unregistered > - Moved the agent's `Instrumentation` handle out of the system > properties, which WildFly's transactions subsystem read as a string > and died on > - Added `test-devloop-cargo`, a third IT fixture running the shared > dev-loop ITs against a WAR deployed on Tomcat 11, and wired it into > the CI matrix > - Adds a fixture per forked server the dev loop drives, each running the > shared suite from flow-test-devloop-support in dev-bundle mode, and runs > them in CI slots 9 and 10. Liberty is served under /devloop, so the ITs > read a context path from the fixture pom. --------- Co-authored-by: Tomi Virtanen <tltv@vaadin.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: mikhail <mikhail@vaadin.com> Co-authored-by: Mikhail Shabarov <61410877+mshabarov@users.noreply.github.com> Co-authored-by: Marco Collovati <marco@vaadin.com>
Documentation for vaadin/flow#25851. **Change categories:** NEW_FEATURE, BEHAVIOR_CHANGE | File | Change | |------|--------| | `articles/flow/configuration/live-reload/dev-loop-cli.adoc` | Added `wildfly`, `tomee`, `payara`, `payara-micro`, `liberty`, and `cargo` to the "Launch With Various Containers" runtime table and the `-Dvaadin.dev.runtime` override list; replaced the stale "more runtimes are planned" note; added bullets explaining that these runtimes fork a server process of their own (longer startup window) and that a not-yet-loaded class keeps its pre-edit bytes until the next restart. | This pull request covers the reader-facing surface only. The rest of the source pull request is Maven-plugin wiring, integration-test fixtures, and CI changes internal to the `flow-devloop-daemon` module, none of which a documentation reader needs. Co-authored-by: Tomi Virtanen <tltv@vaadin.com>



Fixes #25458
Background — how the loop reaches the application. The dev loop hot
swaps by attaching two
-javaagentjars to the JVM the application runsin and redefining classes through
Instrumentation. For a server thatruns inside the build's own JVM, that JVM is Maven's, so
MAVEN_OPTScarries them and there is nothing else to arrange.
Until now the loop could only run an application whose server shared the
build's JVM. It now also runs WildFly, TomEE, Payara Server, Payara
Micro, Open Liberty and Apache Tomcat, which each start a server of
their own, with the agents travelling in a plugin parameter that reaches
the forked JVM. Two fixes and an integration-test fixture come with it.
Context. Nothing on Maven's command line and nothing in its
environment reaches a JVM that Maven forked, so each forked container
needs a channel of its own. Four of them expose one as a user property
and Liberty as a property prefix; Cargo exposes none that any command
line can set, which is why it is the one runtime that cannot work
without the loop's build extension.
wildfly.javaOptsandtomee-plugin.args, module options folded intoone token for WildFly, and backslashes doubled for TomEE alone
startgoal, neverdev, whose watcher redeploys in competition with every applypayara.javaCommandLineOptionsis aList<String>,which Maven splits on commas and the mojo then drops any piece
without an
=in it, so comma-bearing flags travel in a JVMargument file instead
(
liberty.jvm.<key>), withlooseApplicationforced off so thatLiberty's own application monitor stops competing with every apply
answers for every container Cargo drives
cargo.jvmargsproject property,because no parameter of its run goal carries a user property, and
the build extension appends to a value a project set itself rather
than replacing it
application's module for Cargo, both Payaras and WildFly: unlike
Jetty's, their mojos do not skip a project that builds no deployment,
and failed or blocked the build on the reactor root
packageacross the reactor as well,so a sibling module reaches
WEB-INF/libas a jar rather than as adirectory
provisioning a server outruns the time an ordinary boot is given
counting it as changed, so a rewritten-but-identical file is no longer
a change
after a Payara Micro boot left the application JVM preferring
::1while the daemon listened on
127.0.0.1and the application ranunregistered
Instrumentationhandle out of the systemproperties, which WildFly's transactions subsystem read as a string
and died on
test-devloop-cargo, a third IT fixture running the shareddev-loop ITs against a WAR deployed on Tomcat 11, and wired it into
the CI matrix
shared suite from flow-test-devloop-support in dev-bundle mode, and runs
them in CI slots 9 and 10. Liberty is served under /devloop, so the ITs
read a context path from the fixture pom.