shashank created CAMEL-26570:
--------------------------------

             Summary: camel-milo - the loss report of a consumer started 
without a connection runs on the request pool and can deadlock the connection
                 Key: CAMEL-26570
                 URL: https://issues.apache.org/jira/browse/CAMEL-26570
             Project: Camel
          Issue Type: Bug
          Components: camel-milo
            Reporter: shashank
            Assignee: shashank


Found with a TLA+ model of the camel-milo client connection, then reproduced 
with the embedded Milo server of the tests.

Since CAMEL-25835 a consumer that starts while its connection is not up (the 
server cannot be reached, or Camel is rebuilding the connection) reports its 
nodes with {{Bad_NoCommunication}}. {{SubscriptionManager.registerItems}} hands 
that report to the {{MiloClient}} pool of the connection (CAMEL-25713), and 
{{MiloClientConsumer}} runs the route synchronously on that thread. The same 
pool completes the producer requests ({{outcomeOf}}: {{whenCompleteAsync(..., 
executor)}}) and has as many threads as there are processors: one in a 
container limited to one CPU.

When the route waits for a request of the same connection (an awaited write, 
read or method call: {{defaultAwaitWrites=true}} or the {{CamelMiloAwait}} 
header) that is sent once the connection is up, its outcome is queued behind 
the thread that waits for it. With as many such consumers as the pool has 
threads:
* their exchanges wait for ever, and so does every other awaited request of the 
connection;
* the values stop for every consumer of the connection: the waiting report 
holds the lock of its node ({{Subscription.report}} is synchronized), and the 
delivery queue of the shared subscription waits behind the first value of that 
node;
* stopping the context waits for its timeouts and for the interrupt of the 
waiting threads.

The request reaches the connection after it is up when the error handler 
delivers it again (while there is no connection it fails at once with "No 
connected"), or when the route is slower than the connection attempt:
{code:java}
errorHandler(defaultErrorHandler().maximumRedeliveries(600).redeliveryDelay(100));
from("milo-client:opc.tcp://localhost:4840?node=RAW(ns=2;s=valve1)")
    .setBody(constant(new Variant("ack")))
    
.to("milo-client:opc.tcp://localhost:4840?node=RAW(ns=2;s=valve1Ack)&defaultAwaitWrites=true");
{code}
Not affected: requests that are not awaited (the default), routes that do not 
wait for a request after the connection came up, and virtual threads with more 
than one processor.

Short trace (model, one thread):
{noformat}
reconnect thread: performConnect (outside the lock)
consumer:         registerItems: connected == null -> executor.execute(report)
reconnect thread: putSubscriptions, connected = C1
executor thread:  report -> route -> write on C1, waits for its outcome
Milo thread:      answer -> whenCompleteAsync(executor): queued behind the 
waiting thread
{noformat}

On main (1b12eea752e6), with one consumer route like the above per processor 
and the server reachable a moment after the routes started, none of the writes 
completed in 30 s; with one consumer fewer they all completed. A manager with a 
one-thread request pool whose consumer writes from the report once the 
connection is up times out the same way.

*Proposal*: deliver that report on Milo's shared executor 
({{Stack.sharedExecutor()}}, the cached pool the clients deliver the values 
on), not on the request pool. It keeps generation 0, so it is still dropped 
once a connection has delivered a value. The model holds with this change.

Affects main only (4.24.0-SNAPSHOT: CAMEL-25835 and CAMEL-25713 are not 
released).

_Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to