/*
* Copyright 2015 Confluent Inc.
*
* 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 io.confluent.kafkarest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import kafka.javaapi.consumer.SimpleConsumer;
/**
* The SizeLimitedSimpleConsumerPool keeps a pool of SimpleConsumers
* and can increase the pool within a specified limit
*/
public class SimpleConsumerPool {
private static final Logger log = LoggerFactory.getLogger(SimpleConsumerPool.class);
// maxPoolSize = 0 means unlimited
private final int maxPoolSize;
// poolInstanceAvailabilityTimeoutMs = 0 means there is no timeout
private final int poolInstanceAvailabilityTimeoutMs;
private final Time time;
private final SimpleConsumerFactory simpleConsumerFactory;
private final Map<String, SimpleConsumer> simpleConsumers;
private final Queue<String> availableConsumers;
public SimpleConsumerPool(
int maxPoolSize,
int poolInstanceAvailabilityTimeoutMs,
Time time,
SimpleConsumerFactory simpleConsumerFactory
) {
this.maxPoolSize = maxPoolSize;
this.poolInstanceAvailabilityTimeoutMs = poolInstanceAvailabilityTimeoutMs;
this.time = time;
this.simpleConsumerFactory = simpleConsumerFactory;
simpleConsumers = new HashMap<String, SimpleConsumer>();
availableConsumers = new LinkedList<String>();
}
public synchronized SimpleFetcher get(final String host, final int port) {
final long expiration = time.milliseconds() + poolInstanceAvailabilityTimeoutMs;
while (true) {
// If there is a SimpleConsumer available
if (availableConsumers.size() > 0) {
final String consumerId = availableConsumers.remove();
return new SimpleFetcher(simpleConsumers.get(consumerId), this);
}
// If not consumer is available, but we can instantiate a new one
if (simpleConsumers.size() < maxPoolSize || maxPoolSize == 0) {
final SimpleConsumer simpleConsumer = simpleConsumerFactory.createConsumer(host, port);
simpleConsumers.put(simpleConsumer.clientId(), simpleConsumer);
return new SimpleFetcher(simpleConsumer, this);
}
// If no consumer is available and we reached the limit
try {
// The behavior of wait when poolInstanceAvailabilityTimeoutMs=0 is consistent as it
// won't timeout
wait(poolInstanceAvailabilityTimeoutMs);
} catch (InterruptedException e) {
log.warn("A thread requesting a SimpleConsumer has been interrupted while waiting", e);
}
// In some cases ("spurious wakeup", see wait() doc), the thread will resume before the
// timeout
// We have to guard against that and throw only if the timeout has expired for real
if (time.milliseconds() > expiration && poolInstanceAvailabilityTimeoutMs != 0) {
throw Errors.simpleConsumerPoolTimeoutException();
}
}
}
public synchronized void release(SimpleFetcher simpleFetcher) {
log.debug("Releasing into the pool SimpleConsumer with id " + simpleFetcher.clientId());
availableConsumers.add(simpleFetcher.clientId());
notify();
}
public void shutdown() {
for (SimpleConsumer simpleConsumer : simpleConsumers.values()) {
simpleConsumer.close();
}
}
public int size() {
return simpleConsumers.size();
}
}