This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new a52096eea1c1 CAMEL-20199: Replace synchronized with ReentrantLock in
misc components (#25247)
a52096eea1c1 is described below
commit a52096eea1c1ece9a0ab43973232f4bdf464dce4
Author: Guillaume Nodet <[email protected]>
AuthorDate: Thu Jul 30 17:49:59 2026 +0200
CAMEL-20199: Replace synchronized with ReentrantLock in misc components
(#25247)
CAMEL-20199: Replace synchronized with ReentrantLock in misc components
Convert synchronized blocks to ReentrantLock in camel-aws2-eventbridge,
camel-azure-eventhubs, camel-event, camel-flink, camel-milo, camel-mock,
camel-netty-http, and camel-tarfile to eliminate virtual thread pinning.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
.../aws2/eventbridge/EventbridgeEndpoint.java | 15 +++-
.../azure/eventhubs/EventHubsConsumer.java | 94 ++++++++++++----------
.../component/camelevent/CamelEventConsumer.java | 12 ++-
.../component/flink/DataStreamFlinkProducer.java | 7 +-
.../client/MiloClientCachingConnectionManager.java | 54 ++++++++-----
.../camel/component/mock/MockExpressionClause.java | 7 +-
.../netty/http/NettyChannelBufferStreamCache.java | 11 ++-
.../tarfile/TarElementInputStreamWrapper.java | 28 ++++---
8 files changed, 144 insertions(+), 84 deletions(-)
diff --git
a/components/camel-aws/camel-aws2-eventbridge/src/main/java/org/apache/camel/component/aws2/eventbridge/EventbridgeEndpoint.java
b/components/camel-aws/camel-aws2-eventbridge/src/main/java/org/apache/camel/component/aws2/eventbridge/EventbridgeEndpoint.java
index a90ba69a69da..cafdc9b673c0 100644
---
a/components/camel-aws/camel-aws2-eventbridge/src/main/java/org/apache/camel/component/aws2/eventbridge/EventbridgeEndpoint.java
+++
b/components/camel-aws/camel-aws2-eventbridge/src/main/java/org/apache/camel/component/aws2/eventbridge/EventbridgeEndpoint.java
@@ -17,6 +17,7 @@
package org.apache.camel.component.aws2.eventbridge;
import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
import org.apache.camel.Category;
import org.apache.camel.Component;
@@ -41,6 +42,7 @@ import software.amazon.awssdk.services.sqs.SqsClient;
headersClass = EventbridgeConstants.class)
public class EventbridgeEndpoint extends ScheduledPollEndpoint implements
EndpointServiceLocation {
+ private final ReentrantLock lock = new ReentrantLock();
private EventBridgeClient eventbridgeClient;
private SqsClient sqsClient;
@@ -110,11 +112,16 @@ public class EventbridgeEndpoint extends
ScheduledPollEndpoint implements Endpoi
* Returns the SQS client used by the consumer, creating one if necessary.
Uses the same credentials and region as
* the EventBridge client.
*/
- public synchronized SqsClient getSqsClient() {
- if (sqsClient == null) {
- sqsClient = AwsClientBuilderUtil.buildClient(configuration,
SqsClient::builder);
+ public SqsClient getSqsClient() {
+ lock.lock();
+ try {
+ if (sqsClient == null) {
+ sqsClient = AwsClientBuilderUtil.buildClient(configuration,
SqsClient::builder);
+ }
+ return sqsClient;
+ } finally {
+ lock.unlock();
}
- return sqsClient;
}
@Override
diff --git
a/components/camel-azure/camel-azure-eventhubs/src/main/java/org/apache/camel/component/azure/eventhubs/EventHubsConsumer.java
b/components/camel-azure/camel-azure-eventhubs/src/main/java/org/apache/camel/component/azure/eventhubs/EventHubsConsumer.java
index 21a04f735b7e..09345bdb8930 100644
---
a/components/camel-azure/camel-azure-eventhubs/src/main/java/org/apache/camel/component/azure/eventhubs/EventHubsConsumer.java
+++
b/components/camel-azure/camel-azure-eventhubs/src/main/java/org/apache/camel/component/azure/eventhubs/EventHubsConsumer.java
@@ -22,6 +22,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
import com.azure.messaging.eventhubs.EventProcessorClient;
import com.azure.messaging.eventhubs.models.ErrorContext;
@@ -48,6 +49,7 @@ public class EventHubsConsumer extends DefaultConsumer
implements ShutdownAware
// we use the EventProcessorClient as recommended by Azure docs to consume
from all partitions
private EventProcessorClient processorClient;
+ private final ReentrantLock lock = new ReentrantLock();
private final AtomicInteger pendingExchanges = new AtomicInteger();
private final Map<String, AtomicInteger> processedEventsByPartition = new
ConcurrentHashMap<>();
private final Map<String, ScheduledFuture<?>> scheduledTasksByPartition =
new ConcurrentHashMap<>();
@@ -213,52 +215,58 @@ public class EventHubsConsumer extends DefaultConsumer
implements ShutdownAware
*
* @param exchange the exchange
*/
- private synchronized void processCommit(final Exchange exchange, final
EventContext eventContext) {
- final String partitionId =
eventContext.getPartitionContext().getPartitionId();
- final AtomicInteger processedEvents =
processedEventsByPartition.computeIfAbsent(partitionId,
- ignored -> new AtomicInteger());
- EventHubsCheckpointUpdaterTask checkpointTask =
checkpointTasksByPartition.get(partitionId);
- ScheduledFuture<?> scheduledTask =
scheduledTasksByPartition.get(partitionId);
-
- if (checkpointTask == null || checkpointTask.isExpired()) {
- checkpointTask = new EventHubsCheckpointUpdaterTask(eventContext,
processedEvents);
- // delegate the checkpoint update to a dedicated Thread
- long timeout = getConfiguration().getCheckpointBatchTimeout();
- checkpointTask.setScheduledTime(System.currentTimeMillis() +
timeout);
- scheduledTask = scheduledExecutorService.schedule(checkpointTask,
timeout, TimeUnit.MILLISECONDS);
- checkpointTasksByPartition.put(partitionId, checkpointTask);
- scheduledTasksByPartition.put(partitionId, scheduledTask);
- } else {
- // updates the eventContext to use for the offset to be the most
accurate
- checkpointTask.setEventContext(eventContext);
- }
-
+ private void processCommit(final Exchange exchange, final EventContext
eventContext) {
+ lock.lock();
try {
- var cnt = processedEvents.incrementAndGet();
- if (cnt == getConfiguration().getCheckpointBatchSize()) {
- processedEvents.set(0);
-
exchange.getIn().setHeader(EventHubsConstants.CHECKPOINT_UPDATED_BY,
COMPLETED_BY_SIZE);
- LOG.debug("eventhub consumer batch size of reached for
partition {}", partitionId);
- if (scheduledTask != null) {
- scheduledTask.cancel(false);
- }
- eventContext.updateCheckpointAsync()
- .subscribe(unused -> LOG.debug("Processed one
event..."),
- error -> LOG.warn("Error when updating
Checkpoint: {}", error.getMessage(), error),
- () -> {
- LOG.debug("Checkpoint updated.");
- });
- } else if (checkpointTask.isExpired()) {
-
exchange.getIn().setHeader(EventHubsConstants.CHECKPOINT_UPDATED_BY,
COMPLETED_BY_TIMEOUT);
- LOG.debug("eventhub consumer batch timeout reached for
partition {}", partitionId);
+ final String partitionId =
eventContext.getPartitionContext().getPartitionId();
+ final AtomicInteger processedEvents =
processedEventsByPartition.computeIfAbsent(partitionId,
+ ignored -> new AtomicInteger());
+ EventHubsCheckpointUpdaterTask checkpointTask =
checkpointTasksByPartition.get(partitionId);
+ ScheduledFuture<?> scheduledTask =
scheduledTasksByPartition.get(partitionId);
+
+ if (checkpointTask == null || checkpointTask.isExpired()) {
+ checkpointTask = new
EventHubsCheckpointUpdaterTask(eventContext, processedEvents);
+ // delegate the checkpoint update to a dedicated Thread
+ long timeout = getConfiguration().getCheckpointBatchTimeout();
+ checkpointTask.setScheduledTime(System.currentTimeMillis() +
timeout);
+ scheduledTask =
scheduledExecutorService.schedule(checkpointTask, timeout,
TimeUnit.MILLISECONDS);
+ checkpointTasksByPartition.put(partitionId, checkpointTask);
+ scheduledTasksByPartition.put(partitionId, scheduledTask);
} else {
- LOG.debug("neither eventhub consumer batch size of {}/{} nor
batch timeout reached yet for partition {}",
- cnt, getConfiguration().getCheckpointBatchSize(),
partitionId);
+ // updates the eventContext to use for the offset to be the
most accurate
+ checkpointTask.setEventContext(eventContext);
+ }
+
+ try {
+ var cnt = processedEvents.incrementAndGet();
+ if (cnt == getConfiguration().getCheckpointBatchSize()) {
+ processedEvents.set(0);
+
exchange.getIn().setHeader(EventHubsConstants.CHECKPOINT_UPDATED_BY,
COMPLETED_BY_SIZE);
+ LOG.debug("eventhub consumer batch size of reached for
partition {}", partitionId);
+ if (scheduledTask != null) {
+ scheduledTask.cancel(false);
+ }
+ eventContext.updateCheckpointAsync()
+ .subscribe(unused -> LOG.debug("Processed one
event..."),
+ error -> LOG.warn("Error when updating
Checkpoint: {}", error.getMessage(), error),
+ () -> {
+ LOG.debug("Checkpoint updated.");
+ });
+ } else if (checkpointTask.isExpired()) {
+
exchange.getIn().setHeader(EventHubsConstants.CHECKPOINT_UPDATED_BY,
COMPLETED_BY_TIMEOUT);
+ LOG.debug("eventhub consumer batch timeout reached for
partition {}", partitionId);
+ } else {
+ LOG.debug("neither eventhub consumer batch size of {}/{}
nor batch timeout reached yet for partition {}",
+ cnt, getConfiguration().getCheckpointBatchSize(),
partitionId);
+ }
+ // we assume that the scheduled task has done the update by
its side
+ } catch (Exception ex) {
+ getExceptionHandler().handleException(
+ "Error occurred during updating the checkpoint. This
exception is ignored.",
+ exchange, ex);
}
- // we assume that the scheduled task has done the update by its
side
- } catch (Exception ex) {
- getExceptionHandler().handleException("Error occurred during
updating the checkpoint. This exception is ignored.",
- exchange, ex);
+ } finally {
+ lock.unlock();
}
}
diff --git
a/components/camel-event/src/main/java/org/apache/camel/component/camelevent/CamelEventConsumer.java
b/components/camel-event/src/main/java/org/apache/camel/component/camelevent/CamelEventConsumer.java
index 808cdd29460a..0623eb16a773 100644
---
a/components/camel-event/src/main/java/org/apache/camel/component/camelevent/CamelEventConsumer.java
+++
b/components/camel-event/src/main/java/org/apache/camel/component/camelevent/CamelEventConsumer.java
@@ -24,6 +24,7 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
@@ -48,6 +49,7 @@ public class CamelEventConsumer extends DefaultConsumer {
private BlockingQueue<CamelEvent> eventQueue;
private ScheduledExecutorService batchScheduler;
private final List<CamelEvent> batchBuffer = new ArrayList<>();
+ private final ReentrantLock batchBufferLock = new ReentrantLock();
public CamelEventConsumer(CamelEventEndpoint endpoint, Processor
processor) {
super(endpoint, processor);
@@ -145,12 +147,15 @@ public class CamelEventConsumer extends DefaultConsumer {
*/
private void addToBatch(CamelEvent event) {
List<CamelEvent> toFlush = null;
- synchronized (batchBuffer) {
+ batchBufferLock.lock();
+ try {
batchBuffer.add(event);
if (batchBuffer.size() >= getEndpoint().getBatchSize()) {
toFlush = new ArrayList<>(batchBuffer);
batchBuffer.clear();
}
+ } finally {
+ batchBufferLock.unlock();
}
if (toFlush != null) {
processBatch(toFlush);
@@ -162,11 +167,14 @@ public class CamelEventConsumer extends DefaultConsumer {
*/
private void flushBatch() {
List<CamelEvent> toFlush = null;
- synchronized (batchBuffer) {
+ batchBufferLock.lock();
+ try {
if (!batchBuffer.isEmpty()) {
toFlush = new ArrayList<>(batchBuffer);
batchBuffer.clear();
}
+ } finally {
+ batchBufferLock.unlock();
}
if (toFlush != null) {
processBatch(toFlush);
diff --git
a/components/camel-flink/src/main/java/org/apache/camel/component/flink/DataStreamFlinkProducer.java
b/components/camel-flink/src/main/java/org/apache/camel/component/flink/DataStreamFlinkProducer.java
index 25a359694713..5ab877b20e8f 100644
---
a/components/camel-flink/src/main/java/org/apache/camel/component/flink/DataStreamFlinkProducer.java
+++
b/components/camel-flink/src/main/java/org/apache/camel/component/flink/DataStreamFlinkProducer.java
@@ -17,6 +17,7 @@
package org.apache.camel.component.flink;
import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
import org.apache.camel.Exchange;
import org.apache.camel.support.DefaultProducer;
@@ -35,6 +36,7 @@ public class DataStreamFlinkProducer extends DefaultProducer {
private static final Logger LOG =
LoggerFactory.getLogger(DataStreamFlinkProducer.class);
+ private final ReentrantLock lock = new ReentrantLock();
private volatile boolean environmentConfigured = false;
public DataStreamFlinkProducer(FlinkEndpoint endpoint) {
@@ -47,11 +49,14 @@ public class DataStreamFlinkProducer extends
DefaultProducer {
// Configure environment on first use when DataStream is available
if (!environmentConfigured && ds != null) {
- synchronized (this) {
+ lock.lock();
+ try {
if (!environmentConfigured) {
configureStreamExecutionEnvironment(ds);
environmentConfigured = true;
}
+ } finally {
+ lock.unlock();
}
}
diff --git
a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientCachingConnectionManager.java
b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientCachingConnectionManager.java
index e9b4f8cba722..2d8ad23434e8 100644
---
a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientCachingConnectionManager.java
+++
b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientCachingConnectionManager.java
@@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
+import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,6 +33,7 @@ public class MiloClientCachingConnectionManager implements
MiloClientConnectionM
private static final Logger LOG =
LoggerFactory.getLogger(MiloClientCachingConnectionManager.class);
+ private final ReentrantLock lock = new ReentrantLock();
private final Map<String, ManagedConnection> cache = new HashMap<>();
private static class ManagedConnection {
@@ -52,33 +54,43 @@ public class MiloClientCachingConnectionManager implements
MiloClientConnectionM
}
@Override
- public synchronized MiloClientConnection createConnection(
+ public MiloClientConnection createConnection(
MiloClientConfiguration configuration,
MonitorFilterConfiguration monitorFilterConfiguration) {
- final String identifier = configuration.toCacheId();
- final ManagedConnection managedConnection
- = cache.computeIfAbsent(identifier, k ->
managedConnection(configuration, monitorFilterConfiguration));
- managedConnection.increment();
- return managedConnection.connection;
+ lock.lock();
+ try {
+ final String identifier = configuration.toCacheId();
+ final ManagedConnection managedConnection
+ = cache.computeIfAbsent(identifier, k ->
managedConnection(configuration, monitorFilterConfiguration));
+ managedConnection.increment();
+ return managedConnection.connection;
+ } finally {
+ lock.unlock();
+ }
}
@Override
- public synchronized void releaseConnection(MiloClientConnection
connection) {
- final Optional<Entry<String, ManagedConnection>> existingConnection =
this.cache.entrySet().stream()
- .filter(entry ->
entry.getValue().connection.equals(connection)).findFirst();
- existingConnection.ifPresent(entry -> {
- entry.getValue().decrement();
- if (entry.getValue().consumers <= 0) {
- try {
- LOG.debug("Closing connection {}", entry.getKey());
- entry.getValue().connection.close();
- } catch (Exception e) {
- LOG.debug("Error while closing connection with id {}. This
exception is ignored.", entry.getKey());
- } finally {
- cache.remove(entry.getKey());
+ public void releaseConnection(MiloClientConnection connection) {
+ lock.lock();
+ try {
+ final Optional<Entry<String, ManagedConnection>>
existingConnection = this.cache.entrySet().stream()
+ .filter(entry ->
entry.getValue().connection.equals(connection)).findFirst();
+ existingConnection.ifPresent(entry -> {
+ entry.getValue().decrement();
+ if (entry.getValue().consumers <= 0) {
+ try {
+ LOG.debug("Closing connection {}", entry.getKey());
+ entry.getValue().connection.close();
+ } catch (Exception e) {
+ LOG.debug("Error while closing connection with id {}.
This exception is ignored.", entry.getKey());
+ } finally {
+ cache.remove(entry.getKey());
+ }
}
- }
- });
+ });
+ } finally {
+ lock.unlock();
+ }
}
private ManagedConnection managedConnection(
diff --git
a/components/camel-mock/src/main/java/org/apache/camel/component/mock/MockExpressionClause.java
b/components/camel-mock/src/main/java/org/apache/camel/component/mock/MockExpressionClause.java
index 0138366f431e..f2a53c63d9cd 100644
---
a/components/camel-mock/src/main/java/org/apache/camel/component/mock/MockExpressionClause.java
+++
b/components/camel-mock/src/main/java/org/apache/camel/component/mock/MockExpressionClause.java
@@ -17,6 +17,7 @@
package org.apache.camel.component.mock;
import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -38,6 +39,7 @@ import org.apache.camel.support.ExpressionToPredicateAdapter;
* are specialized for being used with the mock component and separated from
camel-core.
*/
public class MockExpressionClause<T> implements Expression, Predicate {
+ private final ReentrantLock lock = new ReentrantLock();
private final MockExpressionClauseSupport<T> delegate;
private volatile Expression expr;
@@ -429,7 +431,8 @@ public class MockExpressionClause<T> implements Expression,
Predicate {
@Override
public void init(CamelContext context) {
if (expr == null) {
- synchronized (this) {
+ lock.lock();
+ try {
if (expr == null) {
Expression newExpression = getExpressionValue();
if (newExpression == null) {
@@ -438,6 +441,8 @@ public class MockExpressionClause<T> implements Expression,
Predicate {
newExpression.init(context);
expr = newExpression;
}
+ } finally {
+ lock.unlock();
}
}
}
diff --git
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyChannelBufferStreamCache.java
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyChannelBufferStreamCache.java
index 9bc782ff5a9c..1f22f51e0418 100644
---
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyChannelBufferStreamCache.java
+++
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyChannelBufferStreamCache.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.netty.http;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.concurrent.locks.ReentrantLock;
import io.netty.buffer.ByteBuf;
import org.apache.camel.Exchange;
@@ -36,6 +37,7 @@ import org.apache.camel.util.IOHelper;
*/
public final class NettyChannelBufferStreamCache extends InputStream
implements StreamCache {
+ private final ReentrantLock lock = new ReentrantLock();
private final ByteBuf buffer;
public NettyChannelBufferStreamCache(ByteBuf buffer) {
@@ -76,8 +78,13 @@ public final class NettyChannelBufferStreamCache extends
InputStream implements
}
@Override
- public synchronized void reset() {
- buffer.resetReaderIndex();
+ public void reset() {
+ lock.lock();
+ try {
+ buffer.resetReaderIndex();
+ } finally {
+ lock.unlock();
+ }
}
@Override
diff --git
a/components/camel-tarfile/src/main/java/org/apache/camel/dataformat/tarfile/TarElementInputStreamWrapper.java
b/components/camel-tarfile/src/main/java/org/apache/camel/dataformat/tarfile/TarElementInputStreamWrapper.java
index 84f78e39b585..604332903df7 100644
---
a/components/camel-tarfile/src/main/java/org/apache/camel/dataformat/tarfile/TarElementInputStreamWrapper.java
+++
b/components/camel-tarfile/src/main/java/org/apache/camel/dataformat/tarfile/TarElementInputStreamWrapper.java
@@ -19,6 +19,7 @@ package org.apache.camel.dataformat.tarfile;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
@@ -27,6 +28,8 @@ import
org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
*/
public class TarElementInputStreamWrapper extends BufferedInputStream {
+ private final ReentrantLock lock = new ReentrantLock();
+
public TarElementInputStreamWrapper(InputStream in, int size) {
super(in, size);
}
@@ -47,17 +50,22 @@ public class TarElementInputStreamWrapper extends
BufferedInputStream {
}
@Override
- public synchronized int available() throws IOException {
- if (in instanceof TarArchiveInputStream) {
- TarArchiveInputStream tai = (TarArchiveInputStream) in;
- if (tai.getCurrentEntry() != null) {
- // avoid NPE in TarArchiveInputStream.available which
- // only works if there is a current entry
- return tai.available();
- } else {
- return 0;
+ public int available() throws IOException {
+ lock.lock();
+ try {
+ if (in instanceof TarArchiveInputStream) {
+ TarArchiveInputStream tai = (TarArchiveInputStream) in;
+ if (tai.getCurrentEntry() != null) {
+ // avoid NPE in TarArchiveInputStream.available which
+ // only works if there is a current entry
+ return tai.available();
+ } else {
+ return 0;
+ }
}
+ return super.available();
+ } finally {
+ lock.unlock();
}
- return super.available();
}
}