Skip to content

fix(devloop): restart for a new Spring bean or entity the running app never had - #25595

Open
totally-not-ai[bot] wants to merge 16 commits into
mainfrom
issues/25559-new-spring-bean-restart
Open

fix(devloop): restart for a new Spring bean or entity the running app never had#25595
totally-not-ai[bot] wants to merge 16 commits into
mainfrom
issues/25559-new-spring-bean-restart

Conversation

@totally-not-ai

@totally-not-ai totally-not-ai Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

When you add a new Spring bean and run apply, the dev loop said "hot-reload" and the app then failed with Spring's NoSuchBeanDefinitionException. Component scanning only runs at startup, so a brand-new bean needs a restart. The dev loop now spots those classes and restarts instead.

What changed

Behavior change: apply now escalates to a restart, with the reason new Spring bean (X), when the change-set contains a class carrying @Component, @Service, @Repository, @Controller, @RestController, @ControllerAdvice, @RestControllerAdvice or @Configuration and the running application never had that class. This only affects dev-loop users; before, the same change was reported as a successful hot reload. Nothing else about apply changes: a bean the app started with still hot swaps as before.

The answer is built from two halves, because neither side can give it alone:

  • The connector (DevLoopRedefiner) reads the stereotype off the compiled bytes, not off the loaded class — HotswapAgent may have defined the class already, and a defined class is still not a bean definition. It reports this in a new stereotypes= field on the REDEFINE reply, appended last so every field an existing daemon reads stays where it was. Unknown fields are ignored on both sides, so this is compatible in either direction; an old connector simply sends no field and nothing escalates.
  • The daemon (Compile) answers "did the running application have this class?" from a snapshot of the classpath taken at each application start, keyed by binary name. No timestamps are involved, so nothing depends on file-time granularity or on the clock.

Three correctness details fall out of that:

  • The question is asked per class, not per source. A second top-level class, or a nested one, added to a file the app has always had is still a class the app never scanned.
  • Matching is on binary names. Before, an unrelated bean sharing a simple name in another package caused a restart nobody needed. The printed reason still uses the short name.
  • A class an earlier apply compiled stays "unknown" however many applies rewrite it (add-then-annotate still escalates), and a class the app did start with stays "known" however often it is recompiled (a bean edited twice still hot swaps both times).

Supporting fixes:

  • The baseline is now taken when the application registers, not on the first apply, and only from an already-resolved classpath (Launch.projectIfResolved) so the registration thread never waits on Maven. A project mid-resolve falls back to the old behaviour.
  • Both sides of the baseline are read against the application launch timestamp, which does not move, instead of against when the baseline happened to be taken.
  • DevLoopRedefiner.redefine was split: inspect (decide) and reply (format) are now separate and testable without a running application.
  • The class-file walk tolerates an unreadable output tree, so a concurrent IDE build no longer fails the apply.
  • Two SonarQube findings addressed; the reply's free-text tail is a constant.
  • flow-devloop-daemon/README.md documents the new escalation and its one known gap.

No public or protected API changed — every touched class is package-private.

Test summary

# Status What the test verifies Why it matters
1 A new stereotyped class the app never had escalates with new Spring bean (X), even though nothing was redefined The reported bug: otherwise the app fails later with NoSuchBeanDefinitionException
2 A bean the app started with is edited twice in a row and hot swaps both times, with no new Spring bean Guards the regression a timestamp-based answer caused: a needless restart on every second edit
3 Matching is by binary name — same simple name in another package does not escalate; a nested type escalates under its own name Prevents restarts nobody needed, and stops a nested bean hiding behind its outer class
4 A second top-level class added to a file the app has always had still escalates The per-source answer got this wrong
5 A class added by one apply and annotated by a later apply still escalates An apply writing the class file must not count as "the app has it"
6 On the first apply of a daemon's life the new bean escalates, whether the daemon or an IDE/mvn compiled it — and never reports "no changes" The ordering miss that hid the bug from every other test
7 All eight stereotype descriptors are detected from raw bytes; entity and bean checks do not answer for each other A typo in one descriptor would silently stop escalating for that annotation
8 inspect reports duplicates, not-loaded names and UI classes; a not-loaded class off the search path is no error; a loaded class with missing bytes is the one error Redefining a partial set would report success over a stale page
9 The full OK ... reply line, including stereotypes= last The daemon reads fields by name; a renamed or dropped field is a wrong answer, not a parse error
10 projectIfResolved returns empty until a classpath has been resolved The registration thread must not block on Maven
11 gap The class snapshot walk survives an unreadable output directory A concurrent IDE build could otherwise fail the apply

Tests added or changed on this branch:

  • DevLoopApplyIT.aBeanEditedTwiceOverStaysAHotSwapBothTimes — 2
  • DevLoopRestartIT.aNewSpringBean_escalatesEvenThoughNothingWasRedefined (@Component, @Service) — 1
  • DevLoopRestartIT.aSecondClassInAFileTheAppAlreadyHas_stillEscalates — 4
  • DevLoopRestartIT.aClassAnnotatedAfterAnEarlierApply_stillEscalates — 5
  • DevLoopRestartIT.aNewSpringBean_escalatesOnTheFirstApplyOfADaemonsLife (daemon-compiled and externally compiled) — 6
  • TransactionEngineTest.blockedReason_escalatesForABeanTheRunningApplicationHasNeverHad — 1, 2, 3
  • CompileTest.classesUnknownToTheApp_answersFromTheLaunchSnapshot — 2, 4, 5
  • DevLoopRedefinerTest.declaresFromBytes_answersForAClassNothingHasLoaded — 7
  • DevLoopRedefinerTest.inspect_readsALoadedClassAndTheBytesItIsAboutToBeGiven, inspect_namesTheBeansAndEntitiesThisJvmHasNoClassFor, inspect_aLoadedClassWithNoNewBytesIsTheOneError — 8
  • DevLoopRedefinerTest.reply_carriesEveryFieldTheDaemonReadsAVerdictFrom — 9
  • LaunchTest.projectIfResolved_isEmptyUntilOneHasBeenResolved — 10

Left untested on purpose: a stereotype composed through a project's own meta-annotation, which only names the custom annotation in its constant pool — a known limitation written down in the daemon README, not a behaviour this branch claims.

Component scanning is a startup act, so a class annotated @component that
did not exist when the application started gets no bean definition, and the
first view to inject it fails with NoSuchBeanDefinitionException. The apply
before it reported Stable, because every signal the runtime leg decides on
is read from a loaded class and a new class has none: the connector had
nothing to classify and blockedReason nothing to escalate on.

The connector now reads the freshly compiled bytes of each requested class
the JVM has not loaded - the same reading of the constant pool that
declaresEntity does, and for the same reason - and reports a Spring
stereotype found there as newBeans. The daemon escalates on that field and
names it, so the restart is the loop's own answer rather than Spring's
exception several steps later.

Fixes #25559
…loaded

Three things the first cut got wrong.

A brand-new @entity fell through the same hole: classify() only ever sees a
loaded class, so a type that appeared after startup was matched by no
metamodel and reported live. The new bytes are read for it now, the way an
@entity added to an existing class already was.

"Not loaded in the app" turned out to be a race rather than a signal.
HotswapAgent watches the output directory on its own schedule and defines a
new class when it sees one, so whether the class is loaded by the time the
reply is composed depends on which watcher got there first - and a defined
class is still not a bean definition. Measured: the same apply reported
Stable on one run and escalated on the next. So the connector now answers
only what the bytes say (stereotypes=, for every requested class) and the
daemon gates it on the inventory, which is re-seeded from disk at every
registration and therefore does not flip. That also settles the case of a
second apply --no-restart over the same new bean.

The list of stereotypes grew by the two advice annotations, and the javadoc
no longer claims the check only over-reports: a stereotype composed through
a project's own meta-annotation is invisible to a constant-pool read, and
that direction of error is worth stating where the trade is explained.

The byte scan itself is now one method with the encoding comment, delegated
to by declaresEntity and declaresSpringBean.
@github-actions github-actions Bot added the +0.0.1 label Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Test Results

 1 440 files  ± 0   1 524 suites  ±0   1h 33m 11s ⏱️ + 1m 24s
12 051 tests +15  11 983 ✅ +15  68 💤 ±0  0 ❌ ±0 
12 369 runs  +15  12 301 ✅ +15  68 💤 ±0  0 ❌ ±0 

Results for commit a8ee6d3. ± Comparison against base commit 8ca3328.

♻️ This comment has been updated with latest results.

The coverage gate failed on new code, and it was pointing at something real:
the change put its reasoning inside redefine(), which needs an
Instrumentation handle and a live service, so none of it could be tested
without the end-to-end module.

Both halves of that method that are decision rather than effect are now
their own package-private steps, and tested. inspect() reads what a request
amounts to - which loaded copies to redefine, and what the classes and their
new bytes say about whether a redefine can be the whole answer - and defines
nothing. reply() renders the line the daemon parses, which nothing pinned
before: it is read by field name, so a renamed or dropped field is a
silently different answer rather than a parse failure, and the test asserts
the whole line.

The new field also moved to the end of that line, where adding it leaves
every field a daemon already reads exactly where it was.
The classify half was only asserted in its all-empty form, which does not
tell "it ran and found nothing" from "it was never called". A loaded
Component is the one case of it that needs neither Spring nor JPA on the
classpath, and ui= is load-bearing: a change-set with no Vaadin component in
it is reported as live but not yet visible rather than simply stable.

@tltv tltv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase and fix conflicts. Fix SonarQ findings.

…ng-bean-restart

# Conflicts:
#	vaadin-dev-server/src/test/java/com/vaadin/base/devserver/devloop/DevLoopRedefinerTest.java
…ists

Both are SonarQube findings on this branch. typesUnknownToTheApp made ".java"
a third literal in Compile, so it is a constant now and the two walks read
it too. BEAN_DESCRIPTORS was declared between methods, next to the check
that reads it; it moves to the constants at the top of the class, and
ENTITY_DESCRIPTORS - which reads the same way and is used by the same
method - goes with it rather than being left behind in the middle.

@tltv tltv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the original issue reproducible with the main branch still with latest devloop fixes? Changes and new Spring beans could escalate restart already without this pull request changes.

@tltv

tltv commented Sep 10, 2026

Copy link
Copy Markdown
Member

Is the original issue reproducible with the main branch still with latest devloop fixes? Changes and new Spring beans could escalate restart already without this pull request changes.

@claude can you test that?

@claude

claude Bot commented Sep 10, 2026

Copy link
Copy Markdown

Claude finished @tltv's task in 3m 47s —— View job


Does #25559 still reproduce on main? — Yes.

@tltv I traced the exact apply code path on current origin/main (with the latest devloop fixes merged) for the issue's scenario: add a brand-new @Service class while the app is running, then apply --no-restart. On main this is reported Stable, and Spring throws NoSuchBeanDefinitionException on the first view that injects it — i.e. the bug reproduces.

Why it still reproduces (code trace on main)

A new class has never been loaded by the running JVM. In DevLoopRedefiner.redefine() on main, a requested name with no loaded copy is put on a notLoaded list and skipped before anything else happens:

List<Class<?>> targets = loaded.getOrDefault(name, List.of());
if (targets.isEmpty()) {
    notLoaded.add(name);
    continue;              // classify() and declaresEntity(bytes) are below this line
}

vaadin-dev-server/.../DevLoopRedefiner.java:217-222

Because of that continue:

  • classify(...) (:231) never runs → beans=-, ui=-
  • declaresEntity(bytes) (:244) never runs → entities=-
  • the class is never added to definitions, so structural/proxied stay -

The reply is therefore OK redefined=0 notLoaded=1 … entities=- beans=- proxied=- structural=- frontendImports=-.

The daemon then decides in TransactionEngine.blockedReason(...) (:1525-1576). Every branch there requires one of entities, frontendImports, or structural+(beans/proxied) to be non--. All are -, so it returns empty, no log error has been emitted yet (the injection failure only happens on a later request), and the runtime leg returns:

return finish(tx, Outcome.STABLE, "", "hot-reload", …);   // :719

So main reports Stable for a bean the context can never resolve. The same holds for a brand-new @Entity: declaresEntity is only reached for already-loaded classes, so a type first introduced after startup is reported live too.

On your specific concern — "changes and new beans could escalate restart already"

That is true for changes to a bean that already existed: a structural edit to a loaded bean sets structural + beans/proxied and does escalate on main (blockedReason bean/proxy branches). The gap is narrower and is exactly #25559: a class the running app never had. Nothing is loaded to diff, so none of the existing escalation signals can fire.

The empirical check

The PR's own IT is precisely this test — DevLoopRestartIT.aNewSpringBean_escalatesEvenThoughNothingWasRedefined — and it asserts:

outcome.assertOutputContains("new Spring bean (ExtraService)");

That reason string does not exist anywhere on main (confirmed by grep; it appears only in this PR's TransactionEngine/test). So that assertion would fail on main with Stable instead — which is the reproduction. It passes on the PR branch (CI green), confirming the fix closes it.

Note on methodology: this is a code-path analysis against current origin/main, plus the grep showing the escalation reason is absent there. I did not run the full flow-tests/test-devloop IT against main — that harness builds much of Flow and boots a real agent-instrumented JVM (far beyond a single comment turn). The definitive empirical run is the PR's test #1 itself: green here, and would fail on main at the assertion above.

Verdict: #25559 is not already handled by main. The new-bean/new-entity case slips through as Stable; this PR is what escalates it.
· branch issues/25559-new-spring-bean-restart

@tltv

tltv commented Sep 10, 2026

Copy link
Copy Markdown
Member

Restart will escalate if there's active browser opened on /bean view when apply is called. This is due to automatic page refresh and error is written in log -> apply has short time window to catch it and escalate restart. This PR fixes the case when browser is not open already/not viewing target page, and also it makes it more reliable even when browser is open.

Taking the new bean as a constructor parameter of a view that is already
running - which is how the issue describes it - restarts on a stock JVM
whatever the bean rule reports, because adding a parameter is a structural
change and redefineClasses rejects it. Measured on main: that shape
escalates with "class redefinition failed: attempted to add a method", and
only a JVM with enhanced class redefinition shows the difference. The
fixture is one new file for that reason, and now says so.
Parameterized rather than copied: the connector matches these by descriptor,
one literal per annotation, so each is an entry that can be wrong on its own
- and @component, which the rest are composed from, is the one a reader
expects to see covered.
A stereotype is matched by exactly one literal, so a typo in one entry is
one annotation that silently stops escalating while the rest keep working.
All eight are asserted now, hand-spelled so the test cannot pass by reading
the list it is checking - which is also where the per-entry guarantee
belongs, since it costs no application. The integration test says so rather
than claiming that job for itself.

@tltv tltv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduced a bug: added Extra.java with a bare @Component, ran apply, got classification: "hot-reload" / Stable — no restart.

What's actually going wrong: using the redefine diagnostic command against the new class, the app-side connector correctly reports stereotypes=Extra (it does see the @Component annotation on the never-before-loaded class). But the daemon still isn't escalating. Two live wrinkles I noticed along the way that point at where the remaining bug is:

  1. notLoaded=0 for a class that was never loaded before this transaction — HotswapAgent's own directory watcher is defining the new class into the JVM before the daemon's REDEFINE round-trip runs. The stereotypes field is (correctly) computed from bytes so it survives that race, but the beans field (isSpringBean on the now-loaded Class object) also fires — it's just an annotation check, not a real Spring-context lookup, so it's a false "this is already a running bean" signal riding along. Doesn't explain the miss by itself, but it's noise worth being aware of.

Given the connector-side stereotypes value is right, the miss is almost certainly in Compile.typesUnknownToTheApp on the daemon side — whether it's really excluding "Extra" from its "unknown" set for this transaction (inventory/applied-map tracking), not in the annotation detection itself.

Reported: start, add a class with a bare @component, apply - and the first
apply of a daemon's life reported hot-reload over a bean the context had no
definition for. The connector's half was right (stereotypes=Extra, read from
the bytes, so HotswapAgent defining the class first does not hide it); the
daemon's half was not.

The compile leg is built lazily, on the first apply, and seeds its inventory
from disk as it is built. A source created between the application starting
and that first apply is therefore in the inventory as though the application
had always had it, so "missing from the inventory" answered no and nothing
escalated. Every applied-once path hides it, which is why the existing
tests - which all apply in setUp - passed.

So the artifact answers too, and it is the fact that settles this case: a
type whose .class was not on the classpath before this apply compiled it is
one the application had nothing to load, nothing to scan and nothing to
register. That has to be read before javac writes anything, so the question
moves back to the detection phase where the change-set is built.

An edit to a class the application does have keeps its artifact and its
inventory entry, so it still hot swaps.
@totally-not-ai

Copy link
Copy Markdown
Contributor Author

@tltv Reproduced it, and your diagnosis was exactly right — the miss was in Compile.typesUnknownToTheApp, not in the annotation detection. Fixed in 74ac99d.

What was wrong. The compile leg is built lazily, on the first apply, and seeds its inventory from disk as it is built. So a source created between the application starting and that first apply lands in the inventory as though the application had always had it, !applied.containsKey(source) answers no, and nothing escalates. Your ordering — start, add Extra.java, apply — is exactly that window. Measured before the fix, first apply of a fresh daemon:

tx#1  outcome=stable  classification=hot-reload  escalation=null

and after:

tx#1  outcome=stable  classification=restart
      escalation=new Spring bean (Extra): component scanning ran at startup, so the running context has no definition for it

The fix. The inventory is no longer asked on its own. A second, independent fact settles this case: a type whose .class was not on the classpath before this apply compiled it is one the application had nothing to load, nothing to scan and nothing to register. That has to be read before javac runs — afterwards every artifact exists — so the question moved back to the detection phase where the change-set is built. An edit to a class the application does have keeps both its artifact and its inventory entry, so it still hot swaps.

Why every existing test missed it: they all apply once in setUp, which builds the compile leg before the fixture file exists. There is now an IT for the ordering itself (shutdown, start, then create, then the first apply), and I checked it is load-bearing: with the artifact half of the rule removed, that test fails and the other five still pass. CompileTest pins both halves directly.

On your two wrinkles — both accurate, and worth recording:

  • notLoaded=0 for a never-before-loaded class is HotswapAgent's watcher defining it ahead of the round-trip, as you say. That race is why stereotypes is computed from the bytes rather than from the loaded Class, so it survives.
  • beans= firing from isSpringBean on the freshly-defined class is indeed a false "already a running bean" signal. It is harmless today because beans is only consulted behind the structural gate, and a class redefined with the bytes it was just defined from has no structural change — so it cannot reach that branch. Noise rather than a second bug, but it is real noise, and I would rather it were not there; happy to narrow that field in a follow-up if you agree it is worth a change of its own.

…apply

The baseline is meant to be "what the running application started with", and
it was read when the compile leg was first needed instead - which is the
first apply, by which time the disk has moved on. Anything added in between
was recorded as something the application had always had.

Two misses came out of that, both on the first apply of a daemon's life.
A new @component reported hot-reload, which is the reported one. Worse, if
something other than the daemon had compiled the class first - an IDE
building on save, or a plain mvn run - the apply reported no changes at all:
the artifact was newer than the source and the inventory had the stamp, so
nothing in the change-set saw the file.

Registration is when that question has an answer, so the baseline is taken
there. Only from a classpath that is already resolved: it runs on the thread
answering the registration connection, which must not wait on Maven, and a
project mid-resolve simply leaves the baseline to the first apply as before.

Measured, first apply after start, with the class compiled by something
else beforehand: was "no changes", now "new Spring bean (Extra)".
…lock

Taking the baseline when the application registers was the right moment and
not a reliable one: the command that waited for the registration returns
before the daemon has handled it, so a file created immediately afterwards
still landed inside the baseline. The integration test for the ordering
caught it - it passed alone and failed in the suite.

So the baseline no longer trusts when it happens to be taken. A source is
recorded as the application's own only if it was on disk before the
application was launched, and what separates a new type from an edited one
is then the class file: a type whose .class was not on the classpath at
launch is one the application had nothing to load, while an edited type's
class was there all along. Both sides are read against the launch timestamp,
which does not move.

Also from the review of the previous commit: the registration seed no longer
walks the tree twice, it keeps the frontend cutoff that leaves a file edited
during startup visible to the first apply, its one diagnostic line goes to
daemon.log instead of a no-op sink, and a classpath that fell back to the
application module alone no longer counts as resolved - building a baseline
on that would report "module set changed" with no pom edit behind it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found three issues:

  • [P1] Compile.java:844 treats a known source file as proof that every class it declares existed at startup. Adding a nested static @Component, or another package-private top-level component, to an existing source therefore produces no unknownTypes entry. The connector reports the new class’s stereotype, but blockedReason does not restart, leaving the bean unregistered—the core failure this PR intends to fix.

  • [P1] Compile.java:870 updates the same applied map used as the startup inventory after successful hot swaps. Sequence: add a plain class → apply successfully → annotate it with @Component → apply again. The first apply inserts its source into applied; the second considers the class known even though component scanning has never seen it, so no restart occurs.

  • [P2] Compile.java:814 and TransactionEngine.java:1662 correlate classes by simple name. If an existing stereotype and a newly added plain class share a name in different packages, editing both in one transaction falsely reports the plain class as a new Spring bean and forces an unnecessary restart. Binary names are needed to preserve identity.

Also take care of the remaining 2 Sonar findings.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tltv All three fixed, with a test each, and the two Sonar findings with them.

P1, per-source answer. Right — and the fix is to stop asking about sources. The question is now asked of each class the apply compiled, against the class files its output packages held before it compiled them: a class file that was not there, or that is newer than the launch, is one the application started without. A second top-level @Component in a file the application has always had now escalates under its own name.

One thing worth recording from testing it: a nested @Component escalates on this JVM for an unrelated reason — nesting rewrites the enclosing class's NestMembers attribute and redefineClasses refuses outright (class redefinition failed: attempted to change the class NestHost, NestMembers...). So the test uses a second top-level class, which leaves the enclosing class byte-identical and actually exercises this rule rather than the JVM's refusal. The nested case is covered too, by the same per-class read.

P1, applied doubling as the startup inventory. Same root, same fix: the class file an earlier apply wrote is newer than the launch, so add-plain-class → apply → annotate → apply now escalates on the second apply. There is an integration test for exactly that sequence.

P2, simple-name correlation. Agreed, it was an identity bug. stereotypes= now carries binary names — alone among the reply's fields, because it is the one read by machine rather than by a person, and the daemon matches the change-set against it exactly. Only the reason shortens the name again, so the message still reads new Spring bean (Extra). The unit test pins that a stereotype sharing a simple name with a new class in another package escalates nothing.

Sonar. The class-file read no longer swallows an IOException: an unreadable output directory is not a state to decide a verdict from, so the apply fails with that as its reason. And the protocol's free-text tail is a constant.

Green: 149 daemon and 346 vaadin-dev-server unit tests, and 38 dev-loop ITs — the exception being DevLoopBrowserIT.siblingModuleEdit_isVisibleOnceTheViewRendersAgain, which times out on my machine identically at the commit before any of this work, so it is not from these changes.

Re-measured end to end on the reported flow (start, add a class with a bare @Component, apply) and on the variant where something else compiles the class first: both tx#1 classification=restart, escalating with the named reason.

… simple name

Three ways the answer could be wrong, all of them reported.

A source was taken as proof about every class it declares. Add a second
top-level @component to a file the application has always had, or a nested
one, and the source is in the baseline, so the class beside it - which the
context has never scanned - was reported as the application's own. The
question is now asked of each class the apply compiled, against the class
files its output packages held beforehand: one that was not there, or that
is newer than the launch, is one the application started without. Which
also settles the second case, add-then-annotate: the class file an earlier
apply wrote is newer than the launch, so annotating it on a later apply
still escalates, where the source inventory had already claimed it.

And the correlation was on simple names, so a new plain class could be
reported as a bean because an unrelated stereotype in another package
shares its name - a restart nobody needed. The connector reports that one
field under binary names now, alone among its fields, because it is the one
read by machine rather than by a person; the reason still prints the short
name.

Also the two SonarQube findings: the class-file read no longer swallows an
IOException - an unreadable output directory is not a state to decide a
verdict from, so it fails the apply with that as the reason - and the
protocol's free-text tail is a constant.
…files

Dating them broke the thing this branch exists to protect. An apply rewrites
the class files it swaps, so the second method-body edit to a @service read
its own class as newer than the launch, called it a bean the context had
never scanned, and restarted for it. Measured on the fixture app, and there
is an integration test for it now: a bean edited twice over has to hot swap
both times.

So the classpath is snapshotted by binary name when the baseline is taken -
once per application start, beside the source walk that was already there -
and the question is a set lookup against it. A class the launch did not have
stays unknown however many applies rewrite it, which is the add-then-
annotate case, and a class it did have stays its own however often it is
recompiled, which is the hot swap. Nothing depends on a timestamp, so
nothing depends on file granularity or on the clock.

It also drops the ordering constraint that the snapshot had to be read
before javac ran, and with it the failure branch that turned a concurrent
IDE build into a failed apply: the walk tolerates an unreadable output tree
the way every other walk here does.
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant