package com.leansoft.luxun.producer.async;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.leansoft.luxun.common.exception.ConnectionRefusedException;
import com.leansoft.luxun.common.exception.IllegalQueueStateException;
import com.leansoft.luxun.producer.SyncProducer;
import com.leansoft.luxun.serializer.Encoder;
public class ProducerSendThread<T> extends Thread {
final String threadName;
final BlockingQueue<QueueItem<T>> queue;
final Encoder<T> serializer;
final SyncProducer underlyingProducer;
final EventHandler<T> eventHandler;
final CallbackHandler<T> callbackHandler;
final long queueTime;
final int batchSize;
private final Logger logger = LoggerFactory.getLogger(ProducerSendThread.class);
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private final Object shutdownCommand;
public ProducerSendThread(String threadName, //
BlockingQueue<QueueItem<T>> queue, //
Encoder<T> serializer, //
SyncProducer underlyingProducer,//
EventHandler<T> eventHandler, //
CallbackHandler<T> callbackHandler, //
long queueTime, //
int batchSize,
Object shutdownCommand) {
super();
this.threadName = threadName;
this.queue = queue;
this.serializer = serializer;
this.underlyingProducer = underlyingProducer;
this.eventHandler = eventHandler;
this.callbackHandler = callbackHandler;
this.queueTime = queueTime;
this.batchSize = batchSize;
this.shutdownCommand = shutdownCommand;
}
public void run() {
try {
List<QueueItem<T>> remainingEvents = processEvents();
//handle remaining events
if (remainingEvents.size() > 0) {
logger.debug(String.format("Dispatching last batch of %d events to the event handler", remainingEvents.size()));
tryToHandle(remainingEvents);
}
} catch (Exception e) {
logger.error("Error in sending events: ", e);
} finally {
shutdownLatch.countDown();
}
}
public void awaitShutdown() {
try {
shutdownLatch.await();
} catch (InterruptedException e) {
logger.warn(e.getMessage());
}
}
public void shutdown() {
eventHandler.close();
logger.info("Shutdown thread complete");
}
private List<QueueItem<T>> processEvents() {
long lastSend = System.currentTimeMillis();
final List<QueueItem<T>> events = new ArrayList<QueueItem<T>>();
boolean full = false;
while(true) {
try {
QueueItem<T> item = queue.poll(Math.max(0, (lastSend + queueTime) - System.currentTimeMillis()), TimeUnit.MILLISECONDS);
long elapsed = System.currentTimeMillis() - lastSend;
boolean expired = item == null;
if (item != null) {
if (item.data == shutdownCommand) {
logger.info("Producer sending thread got a shutdown command, exit...");
break;
}
if (callbackHandler != null) {
List<QueueItem<T>> items = callbackHandler.afterDequeuingExistingData(item);
if (items != null) {
events.addAll(items);
} else {
events.add(item);
}
} else {
events.add(item);
}
full = events.size() >= batchSize;
}
if (full || expired) {
if (logger.isDebugEnabled()) {
if (expired) {
logger.debug(elapsed + " ms elapsed. Queue time reached. Sending..");
} else {
logger.debug(String.format("Batch(%d) full. Sending..", batchSize));
}
}
tryToHandle(events);
lastSend = System.currentTimeMillis();
events.clear();
}
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
}
if (queue.size() > 0) {
throw new IllegalQueueStateException("Invalid queue state! After queue shutdown, " + queue.size() + " remaining items in the queue");
}
if (this.callbackHandler != null) {
List<QueueItem<T>> items = this.callbackHandler.lastBatchBeforeClose();
if (items != null) {
events.addAll(items);
}
}
return events;
}
private void tryToHandle(List<QueueItem<T>> events) {
if (logger.isDebugEnabled()) {
logger.debug("handling " + events.size() + " events");
}
if (events.size() > 0) {
try {
this.eventHandler.handle(events, underlyingProducer, serializer);
} catch (ConnectionRefusedException e) {
List<QueueItem<T>> remainedItems = new ArrayList<QueueItem<T>>(events);
while (queue.size() > 0) {
remainedItems.add(queue.poll());
}
if (this.callbackHandler != null) {
this.callbackHandler.connectionRefused(e.getMessage(), remainedItems);
}
} catch (RuntimeException e) {
logger.error("Error in handling batch of " + events.size() + " events", e);
try {
queue.addAll(events);
} catch (Exception ex) {
logger.error("Event queue is full, retry send events", ex);
tryToHandle(events);
}
}
}
}
}