package com.leansoft.luxun.consumer;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.thrift.TException;
import com.leansoft.luxun.api.generated.ConsumeRequest;
import com.leansoft.luxun.api.generated.ConsumeResponse;
import com.leansoft.luxun.api.generated.ErrorCode;
import com.leansoft.luxun.api.generated.Result;
import com.leansoft.luxun.api.generated.ResultCode;
import com.leansoft.luxun.common.annotations.ClientSide;
import com.leansoft.luxun.common.exception.ErrorMapper;
import com.leansoft.luxun.message.MessageList;
/**
*
* @author bulldog
*
*/
@ClientSide
public class FetcherRunnable extends Thread {
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private final SimpleConsumer simpleConsumer;
private volatile boolean stopped = false;
private final ConsumerConfig config;
private final TopicInfo topicInfo;
private final Logger logger = LoggerFactory.getLogger(FetcherRunnable.class);
private final static AtomicInteger threadIndex = new AtomicInteger(0);
private int errorRetryCount = 0;
public FetcherRunnable(String name,//
ConsumerConfig config,
TopicInfo topicInfo) {
super(name + "-" + threadIndex.getAndIncrement());
this.config = config;
this.topicInfo = topicInfo;
this.simpleConsumer = new SimpleConsumer(topicInfo.broker.host, topicInfo.broker.port, config.getSocketTimeoutMs(), config.getConnectTimeoutMs());
}
@Override
public void run() {
logger.info(String.format("%s consume at %s:%d with %s", getName(), topicInfo.broker.host, topicInfo.broker.port, topicInfo.topic));
try {
final long maxFetchBackoffMs = config.getMaxFetchBackoffMs();
long fetchBackoffMs = config.getFetchBackoffMs();
while (!stopped) {
if (fetchOnce() <= 0) { // read zero message
if (logger.isDebugEnabled()) {
logger.debug("backing off " + fetchBackoffMs + " ms");
}
Thread.sleep(fetchBackoffMs);
if (fetchBackoffMs < maxFetchBackoffMs) {
fetchBackoffMs += fetchBackoffMs / 10;
}
} else {
fetchBackoffMs = config.getFetchBackoffMs();
}
}
} catch (Exception e) {
if (stopped) {
logger.info("FetchRunnable " + this + " interrupted");
} else {
logger.info("error in FetcherRunnable", e);
}
}
logger.info("stopping fetcher " + getName() + " to broker " + topicInfo.broker);
simpleConsumer.close();
shutdownComplete();
}
private int fetchOnce() throws TException, InterruptedException {
ConsumeRequest consumeRequest = new ConsumeRequest();
consumeRequest.setTopic(topicInfo.topic);
consumeRequest.setFanoutId(config.getGroupId());
consumeRequest.setMaxFetchSize(config.getFetchSize());
ConsumeResponse consumeResponse = simpleConsumer.consume(consumeRequest);
int read = processResponse(consumeResponse);
if (read >= 0) {
this.errorRetryCount = 0; // reset
}
return read;
}
private int processResponse(ConsumeResponse consumeResponse) throws InterruptedException, TException {
Result result = consumeResponse.getResult();
if (result.getResultCode() == ResultCode.SUCCESS) {
int size = 0;
for(ByteBuffer buffer : consumeResponse.getItemList()) {
MessageList messageList = MessageList.fromThriftBuffer(buffer);
size += topicInfo.enqueue(messageList);
}
return size;
} else if (result.getResultCode() == ResultCode.TRY_LATER) {
return 0;
} else {
if (result.getErrorCode() == ErrorCode.TOPIC_IS_EMPTY ||
result.getErrorCode() == ErrorCode.ALL_MESSAGE_CONSUMED ||
result.getErrorCode() == ErrorCode.TOPIC_NOT_EXIST) {
return 0; // just wait a moment to let producers produce some messages
}
errorRetryCount++;
if (errorRetryCount > config.getConsumerNumRetires()) {
RuntimeException re = ErrorMapper.toException(result.getErrorCode(), result.getErrorMessage());
logger.error("error to consume message", re);
throw re;
} else {
return -1; // error retry;
}
}
}
public void shutdown() throws InterruptedException {
logger.debug("shutdown the fetcher " + getName());
stopped = true;
shutdownLatch.await(5,TimeUnit.SECONDS);
}
private void shutdownComplete() {
this.shutdownLatch.countDown();
}
}