/* * Copyright 2014 NAVER Corp. * * 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 com.navercorp.pinpoint.profiler.sender; import java.util.Collection; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.navercorp.pinpoint.common.util.PinpointThreadFactory; /** * @author emeroad */ public class AsyncQueueingExecutor<T> implements Runnable { private static final AsyncQueueingExecutorListener EMPTY_LISTENER = new EmptyAsyncQueueingExecutorListener(); private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final boolean isWarn = logger.isWarnEnabled(); private final LinkedBlockingQueue<T> queue; private final AtomicBoolean isRun = new AtomicBoolean(true); private final Thread executeThread; private final String executorName; private final int maxDrainSize; // Caution. single thread only. this Collection is simpler than ArrayList. private final Collection<T> drain; private AsyncQueueingExecutorListener<T> listener = EMPTY_LISTENER; public AsyncQueueingExecutor() { this(1024 * 5, "Pinpoint-AsyncQueueingExecutor"); } public AsyncQueueingExecutor(int queueSize, String executorName) { if (executorName == null) { throw new NullPointerException("executorName must not be null"); } // BEFORE executeThread start this.maxDrainSize = 10; this.drain = new UnsafeArrayCollection<T>(maxDrainSize); this.queue = new LinkedBlockingQueue<T>(queueSize); this.executeThread = this.createExecuteThread(executorName); this.executorName = executeThread.getName(); } private Thread createExecuteThread(String executorName) { final ThreadFactory threadFactory = new PinpointThreadFactory(executorName, true); Thread thread = threadFactory.newThread(this); thread.start(); return thread; } @Override public void run() { logger.info("{} started.", executorName); doExecute(); } private void doExecute() { drainStartEntry: while (isRun()) { try { Collection<T> dtoList = getDrainQueue(); int drainSize = takeN(dtoList, this.maxDrainSize); if (drainSize > 0) { doExecute(dtoList); continue; } while (isRun()) { T dto = takeOne(); if (dto != null) { doExecute(dto); continue drainStartEntry; } } } catch (Throwable th) { logger.warn("{} doExecute(). Unexpected Error. Cause:{}", executorName, th.getMessage(), th); } } flushQueue(); } private void flushQueue() { boolean debugEnabled = logger.isDebugEnabled(); if (debugEnabled) { logger.debug("Loop is stop."); } while(true) { Collection<T> dtoList = getDrainQueue(); int drainSize = takeN(dtoList, this.maxDrainSize); if (drainSize == 0) { break; } if (debugEnabled) { logger.debug("flushData size {}", drainSize); } doExecute(dtoList); } } protected T takeOne() { try { return queue.poll(1000 * 2, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; } } protected int takeN(Collection<T> drain, int maxDrainSize) { return queue.drainTo(drain, maxDrainSize); } public boolean execute(T data) { if (data == null) { if (isWarn) { logger.warn("execute(). data is null"); } return false; } if (!isRun.get()) { if (isWarn) { logger.warn("{} is shutdown. discard data:{}", executorName, data); } return false; } boolean offer = queue.offer(data); if (!offer) { if (isWarn) { logger.warn("{} Drop data. queue is full. size:{}", executorName, queue.size()); } } return offer; } public void setListener(AsyncQueueingExecutorListener<T> listener) { if (listener == null) { throw new NullPointerException("listener must not be null"); } this.listener = listener; } private void doExecute(Collection<T> dtoList) { this.listener.execute(dtoList); } private void doExecute(T dto) { this.listener.execute(dto); } public boolean isEmpty() { return queue.isEmpty(); } public boolean isRun() { return isRun.get(); } public void stop() { isRun.set(false); if (!isEmpty()) { logger.info("Wait 5 seconds. Flushing queued data."); } executeThread.interrupt(); try { executeThread.join(5000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.warn("{} stopped incompletely.", executorName); } logger.info("{} stopped.", executorName); } Collection<T> getDrainQueue() { this.drain.clear(); return drain; } }