shashank created CAMEL-26538:
--------------------------------

             Summary: camel-support - API components: stopping one CamelContext 
breaks the API calls of the other CamelContexts (shared static thread pool)
                 Key: CAMEL-26538
                 URL: https://issues.apache.org/jira/browse/CAMEL-26538
             Project: Camel
          Issue Type: Bug
          Components: camel-support, camel-box, camel-google-drive, 
camel-zendesk
            Reporter: shashank
            Assignee: shashank


Found with a TLA+ model of the thread pool that {{AbstractApiEndpoint}} shares 
between the CamelContexts of a JVM, then reproduced with a test on the real 
classes.

The producers of the API components submit each call to a thread pool 
({{AbstractApiProducer.process}}: 
{{endpoint.getExecutorService().submit(invocation)}}). This affects 11 
components: as2, box, braintree, dhis2, fhir, google-calendar, google-drive, 
google-mail, google-sheets, twilio and zendesk. olingo2 and olingo4 have their 
own {{process}} and do not use the pool.

The pool is kept in a static map, {{EXECUTOR_SERVICE_MAP}}, with one pool per 
endpoint class for the whole JVM. The ExecutorServiceManager of the first 
CamelContext that calls the API creates it, so that CamelContext shuts it down 
when it stops. Each endpoint also keeps the pool in a field and never checks it 
again ({{getExecutorService()}}: {{if (executorService == null)}}).

When a JVM has two CamelContexts that use the same API component, stopping the 
first one breaks the second one:
* Calls of the second CamelContext that are still running when the shutdown 
await time ends ({{shutdownAwaitTermination}}, 10 seconds by default) are 
interrupted ({{shutdownNow}}).
* Every later call of the second CamelContext is dropped. The default thread 
pool profile uses {{CallerRuns}}, and the JDK {{CallerRunsPolicy}} discards a 
task submitted to a pool that is shut down. So the call never runs, the 
callback is never called, and the exchange never completes. A synchronous 
caller (the thread of a synchronous consumer, {{template.request}}) waits for 
good, and nothing is logged. With an {{Abort}} profile, the call fails with a 
{{RejectedExecutionException}} instead.

{noformat}
1 context A : sends to google-drive -> creates the pool (owned by A), puts it 
in the static map
2 context B : sends to google-drive -> takes A's pool from the static map, 
keeps it in its endpoint
3 context A : stop() -> A's ExecutorServiceManager shuts the pool down
4 context B : sends again -> submit() to the shut down pool -> CallerRuns drops 
the task -> the exchange hangs
{noformat}

This happens with any two CamelContexts in one JVM, for example several 
CamelContexts in one application, or one of several deployments on a shared 
runtime being undeployed. An endpoint created after step 3 is not affected: the 
static lookup sees that the pool is shut down and creates a new one. Only the 
endpoints that kept the pool before the stop are broken, and the routes of a 
running CamelContext keep their endpoints.

An endpoint object that is kept across a stop and start of its own CamelContext 
breaks the same way. For example, a route built with the endpoint instance 
({{to(endpoint)}}) keeps it, and that route's next call is dropped. Routes that 
use a URI get a new endpoint when the CamelContext starts again, so they are 
not affected.

Reproduced with:
* a stub API component in camel-core: step 4 never completes, a call running 
during step 3 is interrupted, the endpoint kept across a restart never 
completes its call, and endpoints of two CamelContexts that look up the pool at 
the same time share pools across the CamelContexts (and one CamelContext ends 
up with several pools);
* camel-google-drive with a mock HTTP transport, where the send of the second 
CamelContext after the first one stopped never completes.

*Model.* A TLA+ model with two CamelContexts, one endpoint each, checks that an 
exchange of a running CamelContext is never dropped and that stopping one 
CamelContext never interrupts a call of another. Both fail on main (11-step 
traces), and both also fail if the endpoint only re-checks the pool it kept 
(one pool per JVM). They hold with one pool per CamelContext.

*Proposal*
* Keep the pools per CamelContext, for example in a context plugin that holds 
one pool per endpoint class. Create each pool through that CamelContext's 
ExecutorServiceManager, as now.
* Let the endpoint look up the pool again when the one it kept was shut down. 
This covers an endpoint that is used again after its CamelContext was started 
again.
* Each CamelContext then has its own pool. The size of each comes from the 
thread pool profile of the component (such as {{CamelGoogleDrive}}) or the 
default profile of its own CamelContext. Add an upgrade guide note.

_Filed with Claude Code on behalf of allthingssecurity._



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

Reply via email to