/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.jafka.producer.async; import io.jafka.common.IllegalQueueStateException; import io.jafka.producer.SyncProducer; import io.jafka.producer.serializer.Encoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static java.lang.String.format; /** * @author adyliu (imxylz@gmail.com) * @since 1.0 */ 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 volatile boolean shutdown = false; public ProducerSendThread(String threadName, // BlockingQueue<QueueItem<T>> queue, // Encoder<T> serializer, // SyncProducer underlyingProducer,// EventHandler<T> eventHandler, // CallbackHandler<T> callbackHandler, // long queueTime, // int batchSize) { 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; } @Override public void run() { try { List<QueueItem<T>> remainingEvents = processEvents(); //handle remaining events if (remainingEvents.size() > 0) { logger.debug(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() { shutdown = true; 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 (!shutdown) { 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 (callbackHandler != null) { List<QueueItem<T>> items = callbackHandler.afterDequeuingExistingData(item); if (items != null) { events.addAll(items); } } 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(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>> remainEvents = this.callbackHandler.lastBatchBeforeClose(); if (remainEvents != null) { events.addAll(remainEvents); } } 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 (RuntimeException e) { logger.error("Error in handling batch of " + events.size() + " events", e); } } } }