KAFKA-10: Fix for ConsumerConfiguration.executor

Fixes GH10 https://github.com/spring-projects/spring-integration-kafka/issues/10

Previously the `ConsumerConfiguration` used an internal `ExecutorService`.
In the management environment (App Server) it causes an issue with unmanaged Threads.

* Add `setExecutor` option and expose it to the Namespace to inject a managed `Executor`
* Stop an internal `ExecutorService` manually from the `ConsumerConfiguration.shutdown()`

Polishing
This commit is contained in:
Artem Bilan
2015-01-08 12:05:26 +02:00
committed by Artem Bilan
parent c3d696187c
commit 46d06fd404
4 changed files with 92 additions and 17 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -81,6 +81,8 @@ public class KafkaConsumerContextParser extends AbstractSingleBeanDefinitionPars
"key-decoder");
IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerConfigurationBuilder, consumerConfiguration,
"max-messages");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(consumerConfigurationBuilder, consumerConfiguration,
"executor");
IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, parentElem,
"consumer-timeout");

View File

@@ -1,42 +1,66 @@
/*
* Copyright 2002-2013 the original author or authors. Licensed under the Apache License, Version 2.0 (the "License");
* Copyright 2002-2015 the original author or authors. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and limitations under the
* License.
*/
package org.springframework.integration.kafka.support;
import java.util.*;
import java.util.concurrent.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.MessagingException;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.Assert;
import kafka.consumer.ConsumerTimeoutException;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;
import kafka.message.MessageAndMetadata;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.MessagingException;
/**
* @author Soby Chacko
* @author Rajasekar Elango
* @author Artem Bilan
* @since 0.5
*/
public class ConsumerConfiguration<K, V> {
private static final Log LOGGER = LogFactory.getLog(ConsumerConfiguration.class);
private final ConsumerMetadata<K, V> consumerMetadata;
private final ConsumerConnectionProvider consumerConnectionProvider;
private final MessageLeftOverTracker<K, V> messageLeftOverTracker;
private ConsumerConnector consumerConnector;
private volatile int count = 0;
private int maxMessages = 1;
private Collection<List<KafkaStream<K, V>>> consumerMessageStreams;
private final ExecutorService executorService = Executors.newCachedThreadPool();
private ExecutorService executorService = Executors.newCachedThreadPool();
private boolean executorExplicitlySet;
private volatile boolean stopped;
public ConsumerConfiguration(final ConsumerMetadata<K, V> consumerMetadata,
final ConsumerConnectionProvider consumerConnectionProvider,
@@ -46,6 +70,19 @@ public class ConsumerConfiguration<K, V> {
this.messageLeftOverTracker = messageLeftOverTracker;
}
public void setExecutor(Executor executor) {
boolean isExecutorService = executor instanceof ExecutorService;
boolean isThreadPoolTaskExecutor = executor instanceof ThreadPoolTaskExecutor;
Assert.isTrue(isExecutorService || isThreadPoolTaskExecutor);
if (isExecutorService) {
this.executorService = (ExecutorService) executor;
}
else {
this.executorService = ((ThreadPoolTaskExecutor) executor).getThreadPoolExecutor();
}
this.executorExplicitlySet = true;
}
public ConsumerMetadata<K, V> getConsumerMetadata() {
return consumerMetadata;
}
@@ -75,7 +112,8 @@ public class ConsumerConfiguration<K, V> {
}
}
}
} catch (ConsumerTimeoutException cte) {
}
catch (ConsumerTimeoutException cte) {
LOGGER.debug("Consumer timed out");
}
return rawMessages;
@@ -93,7 +131,7 @@ public class ConsumerConfiguration<K, V> {
messages.putAll(getLeftOverMessageMap());
try {
for (final Future<List<MessageAndMetadata<K, V>>> result : executorService.invokeAll(tasks)) {
for (final Future<List<MessageAndMetadata<K, V>>> result : this.executorService.invokeAll(tasks)) {
if (!result.get().isEmpty()) {
final String topic = result.get().get(0).topic();
if (!messages.containsKey(topic)) {
@@ -106,8 +144,14 @@ public class ConsumerConfiguration<K, V> {
}
}
}
} catch (Exception e) {
throw new MessagingException("Consuming from Kafka failed", e);
}
catch (Exception e) {
if (!this.stopped) {
throw new MessagingException("Consuming from Kafka failed", e);
}
else {
LOGGER.warn("Consuming from Kafka failed", e);
}
}
if (messages.isEmpty()) {
@@ -221,4 +265,13 @@ public class ConsumerConfiguration<K, V> {
}
return consumerConnector;
}
public void shutdown() {
this.stopped = true;
if (!this.executorExplicitlySet) {
this.executorService.shutdownNow();
}
getConsumerConnector().shutdown();
}
}

View File

@@ -82,7 +82,7 @@ public class KafkaConsumerContext<K, V> implements DisposableBean {
@Override
public void destroy() throws Exception {
for (ConsumerConfiguration<K, V> config : this.consumerConfigurations.values()) {
config.getConsumerConnector().shutdown();
config.shutdown();
}
}

View File

@@ -311,6 +311,21 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="executor" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
A java.util.concurrent.Executor bean reference.
Typically
org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.
Used to execute iterator task on KafkaStreams.
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="java.util.concurrent.Executor"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -328,9 +343,14 @@
</xsd:attribute>
<xsd:attribute name="zookeeper-connect" use="required" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Kafka Server Bean Name
</xsd:documentation>
<xsd:appinfo>
<xsd:documentation>
Kafka Server Bean Name
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.kafka.support.ZookeeperConnect"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="consumer-properties" use="optional" type="xsd:string">