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

   Found by a source audit of `components/camel-vertx`.
   
   ### The problem
   
   `VertxWebsocketHostKey` is `{host, port}` only, so every consumer on the 
same port shares one
   `VertxWebsocketHost` (`VertxWebsocketComponent:167`, `computeIfAbsent`). But 
the host was removed from the
   registry by whichever consumer stopped first:
   
   ```java
   VertxWebsocketHost vertxWebsocketHost = vertxHostRegistry.remove(hostKey);
   if (vertxWebsocketHost != null) {
       vertxWebsocketHost.disconnect(configuration.getWebsocketURI().getPath());
   }
   ```
   
   With consumers on `/test/a` and `/test/b` sharing a port:
   
   * stopping `a` removes the shared host from the registry and unregisters 
route `a`. The host correctly keeps
     its server up, because `b` is still in its own `routeRegistry`;
   * stopping `b` then finds nothing — `remove` returns null, the guard skips, 
and **route `b` is never removed
     and its server never stops**, outliving every Camel route;
   * starting a consumer on that host and port again builds a *second* host 
with a new Router and calls
     `start()` on a port the first server still holds.
   
   `doStop` closes Vert.x only when Camel created it, so with an 
application-supplied `Vertx` or `Router` —
   Spring Boot, Quarkus — nothing tears the server down at all.
   
   Multiple consumers per port is supported and tested behaviour 
(`VertxWebsocketMultiConsumerTest`), so this
   is reachable in an ordinary configuration.
   
   ### The change
   
   The host is looked up rather than removed, and leaves the registry only once 
it serves no consumer — which
   is the same moment it stops its own server. `isServingConsumers()` is new 
and additive; `disconnect` keeps
   its signature, since `VertxWebsocketHost` is public.
   
   Two smaller problems in the same lifecycle go with it:
   
   * `routeRegistry` becomes a `ConcurrentHashMap`. It is mutated by 
`connect`/`disconnect`, i.e. as the route
     controller starts and stops consumers, which it can do concurrently; the 
field immediately below it was
     already a `CopyOnWriteArrayList` carrying a `// thread-safe` comment, so 
the difference read as an
     oversight rather than a decision.
   * `disconnect` no longer calls `route.remove()` on a null route when handed 
a path it does not serve.
   
   ### Tests
   
   `VertxWebsocketMultiConsumerLifecycleTest` (new, 3 cases) covers two 
consumers sharing a port: the host
   survives the first stop and the surviving consumer still receives; the host 
is gone after the last stop; and
   stop/start/stop of one consumer leaves the registry intact.
   
   **Checked that the tests actually catch this**, rather than trusting green: 
with the component change
   reverted, 2 of the 3 fail (`theHostOutlivesTheFirstConsumerToStop`, 
`stoppingAConsumerTwiceIsHarmless`). The
   third passes either way — it is the degenerate case, kept because it pins 
the intended end state.
   
   `mvn clean install -DskipITs` on `components/camel-vertx` is green — 88 
tests. Full reactor
   `mvn clean install -DskipTests -DskipITs -Dquickly` green.
   
   `org.assertj:assertj-core` was not on this module's test classpath and is 
added.
   
   ---
   _Claude Code on behalf of oscerd_
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
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