/* * INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa * Copyright 2013 INESC-ID and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3.0 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.executors; import org.infinispan.Cache; import org.infinispan.configuration.cache.Configuration; import org.infinispan.factories.annotations.Inject; import org.infinispan.factories.annotations.Start; import org.infinispan.factories.annotations.Stop; import org.infinispan.jmx.annotations.MBean; import org.infinispan.jmx.annotations.ManagedAttribute; import org.infinispan.jmx.annotations.ManagedOperation; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.rhq.helpers.pluginAnnotations.agent.DisplayType; import org.rhq.helpers.pluginAnnotations.agent.Metric; import org.rhq.helpers.pluginAnnotations.agent.Operation; import org.rhq.helpers.pluginAnnotations.agent.Units; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * // TODO: Document this * * @author Pedro Ruivo * @since 5.2 */ @MBean(objectName = "ConditionalExecutorService", description = "Executor service that put a conditional runnable " + "when the runnable is ready to be processed") public class ConditionalExecutorService { private static final Log log = LogFactory.getLog(ConditionalExecutorService.class); private volatile SchedulerThread schedulerThread; private RunnableEntry activeRunnables; private RunnableEntry newRunnables; private volatile boolean enabled; private Configuration configuration; private String cacheName; @Inject public void inject(Configuration configuration, Cache<?, ?> cache) { this.configuration = configuration; this.cacheName = cache == null ? "default" : cache.getName(); } @Start public final void start() { enabled = true; } @Stop public final void stop() { enabled = false; if (schedulerThread != null) { schedulerThread.interrupt(); } } public void execute(ConditionalRunnable runnable) throws Exception { if (!enabled) { throw new Exception("Executor Service is not enabled"); } RunnableEntry runnableEntry = new RunnableEntry(runnable); synchronized (this) { initIfNeeded(); runnableEntry.next = newRunnables; newRunnables = runnableEntry; notify(); if (log.isTraceEnabled()) { log.tracef("Added a new task: %s waiting to be added", toAddSize()); } } } @ManagedAttribute(description = "The minimum number of threads in the thread pool") @Metric(displayName = "Minimum Number of Threads", displayType = DisplayType.DETAIL) public int getCorePoolSize() { SchedulerThread current = schedulerThread; return current == null ? 0 : current.getExecutorService().getCorePoolSize(); } @ManagedOperation(description = "Set the minimum number of threads in the thread pool") @Operation(displayName = "Set Minimum Number Of Threads") public void setCorePoolSize(int size) { SchedulerThread current = schedulerThread; if (!enabled || current == null) { return; } current.getExecutorService().setCorePoolSize(size); } @ManagedAttribute(description = "The maximum number of threads in the thread pool") @Metric(displayName = "Maximum Number of Threads", displayType = DisplayType.DETAIL) public int getMaximumPoolSize() { SchedulerThread current = schedulerThread; return current == null ? 0 : current.getExecutorService().getMaximumPoolSize(); } @ManagedOperation(description = "Set the maximum number of threads in the thread pool") @Operation(displayName = "Set Maximum Number Of Threads") public void setMaximumPoolSize(int size) { SchedulerThread current = schedulerThread; if (!enabled || current == null) { return; } current.getExecutorService().setMaximumPoolSize(size); } @ManagedAttribute(description = "The keep alive time of an idle thread in the thread pool (milliseconds)") @Metric(displayName = "Keep Alive Time of a Idle Thread", units = Units.MILLISECONDS, displayType = DisplayType.DETAIL) public long getKeepAliveTime() { SchedulerThread current = schedulerThread; return current == null ? 0 : current.getExecutorService().getKeepAliveTime(TimeUnit.MILLISECONDS); } @ManagedOperation(description = "Set the idle time of a thread in the thread pool (milliseconds)") @Operation(displayName = "Set Keep Alive Time of Idle Threads") public void setKeepAliveTime(long milliseconds) { SchedulerThread current = schedulerThread; if (!enabled || current == null) { return; } current.getExecutorService().setKeepAliveTime(milliseconds, TimeUnit.MILLISECONDS); } @ManagedAttribute(description = "The approximate percentage of active threads in the thread pool") @Metric(displayName = "Percentage of Active Threads", units = Units.PERCENTAGE, displayType = DisplayType.SUMMARY) public double getUsagePercentage() { SchedulerThread current = schedulerThread; if (current == null) { return 0D; } int max = current.getExecutorService().getMaximumPoolSize(); int actual = current.getExecutorService().getActiveCount(); double percentage = actual * 100.0 / max; return percentage > 100 ? 100.0 : percentage; } private ThreadFactory createThreadFactory() { return new ThreadFactory() { private final AtomicInteger threadCounter = new AtomicInteger(0); @SuppressWarnings("NullableProblems") @Override public Thread newThread(Runnable runnable) { return new Thread(runnable, "Cond-ThreadPool-" + cacheName + "-" + threadCounter.incrementAndGet()); } }; } private ThreadPoolExecutor createExecutorService() { return new ThreadPoolExecutor(configuration.conditionalExecutorService().corePoolSize(), configuration.conditionalExecutorService().maxPoolSize(), configuration.conditionalExecutorService().keepAliveTime(), TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(), createThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); } private int count(RunnableEntry first) { int size = 0; RunnableEntry iterator = first; while (iterator != null) { size++; iterator = iterator.next; } return size; } private synchronized void clearAll() { newRunnables = null; activeRunnables = null; } private void addNewRunnables() throws InterruptedException { RunnableEntry addList; synchronized (this) { addList = newRunnables; newRunnables = null; if (addList == null && activeRunnables == null) { wait(); } } if (addList == null) { return; } else if (activeRunnables == null) { activeRunnables = addList; if (log.isTraceEnabled()) { log.tracef("Adding pending tasks. Active=%s", count(activeRunnables)); } return; } RunnableEntry last = activeRunnables; while (last.next != null) { last = last.next; } last.next = addList; if (log.isTraceEnabled()) { log.tracef("Adding pending tasks. Active=%s", count(activeRunnables)); } } private synchronized int toAddSize() { return count(newRunnables); } private void initIfNeeded() { if (!enabled || schedulerThread != null) { return; } schedulerThread = new SchedulerThread(cacheName); schedulerThread.start(); } private class SchedulerThread extends Thread { private final ThreadPoolExecutor executorService; private volatile boolean running; public SchedulerThread(String cacheName) { super("Scheduler-" + cacheName); this.executorService = createExecutorService(); } @Override public void run() { running = true; while (running) { try { addNewRunnables(); if (activeRunnables == null) { continue; } int tasksExecuted = 0; while (activeRunnables != null && activeRunnables.runnable.isReady()) { executorService.execute(activeRunnables.runnable); activeRunnables = activeRunnables.next; tasksExecuted++; } if (activeRunnables == null) { if (log.isTraceEnabled() && tasksExecuted > 0) { log.tracef("Tasks executed=%s, still active=%s", tasksExecuted, count(activeRunnables)); } continue; } RunnableEntry iterator = activeRunnables; while (iterator.next != null) { RunnableEntry toAnalyze = iterator.next; if (toAnalyze.runnable.isReady()) { executorService.execute(toAnalyze.runnable); iterator.next = toAnalyze.next; tasksExecuted++; } else { iterator = iterator.next; } } if (log.isTraceEnabled() && tasksExecuted > 0) { log.tracef("Tasks executed=%s, still active=%s", tasksExecuted, count(activeRunnables)); } } catch (InterruptedException e) { break; } catch (Throwable throwable) { if (log.isTraceEnabled()) { log.tracef(throwable, "Exception caught while executing task"); } else { log.warnf("Exception caught while executing task: %s", throwable.getLocalizedMessage()); } } } executorService.shutdown(); clearAll(); } @Override public void interrupt() { running = false; super.interrupt(); } public ThreadPoolExecutor getExecutorService() { return executorService; } } private class RunnableEntry { private final ConditionalRunnable runnable; private RunnableEntry next; private RunnableEntry(ConditionalRunnable runnable) { this.runnable = runnable; this.next = null; } } }