shashank created CAMEL-26457:
--------------------------------

             Summary: camel-disruptor - disruptor-vm: CamelContexts that create 
or stop their endpoints of a queue at the same time can end up on different 
ring buffers, so the messages never reach the consumer
                 Key: CAMEL-26457
                 URL: https://issues.apache.org/jira/browse/CAMEL-26457
             Project: Camel
          Issue Type: Bug
          Components: camel-disruptor
            Reporter: shashank
            Assignee: shashank


Found with a TLA+ model of the disruptor-vm queue map shared by the 
CamelContexts of a JVM (two CamelContexts, one of them stopping and starting 
again).

The {{disruptor-vm}} component keeps the queues of all the CamelContexts of the 
JVM in one static {{java.util.HashMap}} ({{DisruptorVmComponent.DISRUPTORS}}). 
But it guards it with the lock of each component instance 
({{DisruptorComponent.getOrCreateDisruptor}} uses the {{BaseService}} lock), 
and each CamelContext has its own component instance, so the CamelContexts do 
not exclude each other:

# *Concurrent creation.* Two CamelContexts that create their endpoint of the 
same queue at the same time both find no queue in the map, both create one and 
both put it (the second put wins; the map is a plain {{HashMap}}). Short trace: 
C1 get -> null, C2 get -> null, C1 put R1, C2 put R2: the producer of C1 
publishes to R1 and the consumer of C2 consumes from R2.
# *Stop of the component.* {{DisruptorVmComponent.doStop}} clears the whole map 
when the last *started* vm component stops. A CamelContext that is not started 
yet (or is starting) has its endpoints already created on a queue of the map, 
but its component is not started, so the queue is dropped under it; the next 
endpoint of the queue (another CamelContext, or a redeployed one) gets a new 
ring buffer.
# *Shutdown of an endpoint.* {{DisruptorComponent.onShutdownEndpoint}} looks up 
the queue by key and removes it without a lock when its endpoint count is 0. 
The count only includes initialized endpoints, so an endpoint that is being 
created in another CamelContext (it already took the queue from the map) does 
not keep it, and if the queue is created again meanwhile the two endpoints use 
different ones.

A hook-free probe that creates the endpoint of a new queue in two CamelContexts 
at the same moment got two ring buffers in 1998 of 2000 runs (the window is the 
creation of the disruptor itself).

In every case the producer and the consumer of the queue use different ring 
buffers: the messages are never consumed (they are lost when the CamelContext 
stops; with {{blockWhenFull}} the producer blocks once the ring buffer is 
full). An InOnly producer gets no error; an InOut one times out.

Model: {{SameRef}} (every running endpoint of a key uses the same ring buffer) 
is violated in 9 steps (concurrent creation) and 18 steps (a CamelContext stops 
and starts again while another one creates its endpoint); with the change below 
it holds in the model, and the tests below pass.

Reproduced with the real classes (no broker, plain CamelContexts):
* an endpoint created in a CamelContext before it is started, then the only 
other CamelContext stops, then a third one starts: the producer and the 
consumer are on different disruptors, the message is not received;
* two CamelContexts create their endpoint at the same time (a test subclass 
pauses the first one after its lookup found nothing, until the second one 
stored its queue): different disruptors;
* a CamelContext creates its endpoint while the CamelContext of the only other 
endpoint stops: different disruptors.

*Proposal*
* Access the queue map only under a lock that all the components sharing it 
share (a static one for disruptor-vm): lookup and creation, endpoint shutdown, 
component stop.
* When an endpoint is initialized (it registers on its reference there 
already), use the reference of the key: put its own back when the key has none, 
take the one of the key when another CamelContext created a new one meanwhile.
* Remove the reference of the endpoint being shut down ({{remove(key, ref)}}) 
when no endpoint is registered or being registered on it, and on the stop of 
the last vm component remove only the references no endpoint uses. Read those 
counts without the lock of the reference, which a reconfiguration holds while 
it waits for the exchanges in flight.

_Filed with Claude Code on behalf of allthingssecurity._



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

Reply via email to