/* * Data Hub Service (DHuS) - For Space data distribution. * Copyright (C) 2013,2014,2015 GAEL Systems * * This file is part of DHuS software sources. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package fr.gael.dhus.datastore.processing.fair; import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.core.task.TaskRejectedException; import org.springframework.scheduling.SchedulingTaskExecutor; import org.springframework.scheduling.concurrent.ExecutorConfigurationSupport; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.Assert; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.util.concurrent.ListenableFutureTask; /** * Overriding Spring {@link ThreadPoolTaskExecutor} to use our {@link FairQueue} * instead of BlockingQueue. * * Its specificity is that the {@link ThreadPoolExecutor} is initialized with * corePoolSize equals to maximumPoolSize. This is an impact of using the * {@link FairQueue}, which is not limited in size. * So according to the algorithm described in {@link ThreadPoolExecutor}, the * first coming tasks will be executed until corePoolSize threads are running. * Then the coming tasks will be stored in the {@link FairQueue} and consumed * according to its algorithm. * * @see ThreadPoolTaskExecutor * @See FairQueue */ public class FairThreadPoolTaskExecutor extends ExecutorConfigurationSupport implements AsyncListenableTaskExecutor, SchedulingTaskExecutor { private static final long serialVersionUID = -3679186020281566377L; private final Object poolSizeMonitor = new Object (); private ThreadPoolExecutor threadPoolExecutor; private int corePoolSize = 1; private int keepAliveSeconds = 60; /** * Set the ThreadPoolExecutor's core pool size. Default is 1. * <p> * <b>This setting can be modified at runtime, for example through JMX.</b> */ public void setCorePoolSize (int corePoolSize) { synchronized (this.poolSizeMonitor) { this.corePoolSize = corePoolSize; if (this.threadPoolExecutor != null) { this.threadPoolExecutor.setCorePoolSize (corePoolSize); } } } /** * Return the ThreadPoolExecutor's core pool size. */ public int getCorePoolSize () { synchronized (this.poolSizeMonitor) { return this.corePoolSize; } } private ThreadPoolExecutor getThreadPoolExecutor () throws IllegalStateException { Assert.state (this.threadPoolExecutor != null, "ThreadPoolTaskExecutor not initialized"); return this.threadPoolExecutor; } @Override protected ExecutorService initializeExecutor (ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) { FairQueue<Runnable> queue = new FairQueue<Runnable> (); ThreadPoolExecutor executor = new ThreadPoolExecutor (this.corePoolSize, this.corePoolSize, keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler); this.threadPoolExecutor = executor; return executor; } @Override public void execute (Runnable task) { Executor executor = getThreadPoolExecutor (); try { executor.execute (task); } catch (RejectedExecutionException ex) { throw new TaskRejectedException ("Executor [" + executor + "] did not accept task: " + task, ex); } } @Override public void execute (Runnable task, long startTimeout) { execute (task); } @Override public Future<?> submit (Runnable task) { ExecutorService executor = getThreadPoolExecutor (); try { return executor.submit (task); } catch (RejectedExecutionException ex) { throw new TaskRejectedException ("Executor [" + executor + "] did not accept task: " + task, ex); } } @Override public <T> Future<T> submit (Callable<T> task) { ExecutorService executor = getThreadPoolExecutor (); try { return executor.submit (task); } catch (RejectedExecutionException ex) { throw new TaskRejectedException ("Executor [" + executor + "] did not accept task: " + task, ex); } } @Override public ListenableFuture<?> submitListenable (Runnable task) { ExecutorService executor = getThreadPoolExecutor (); try { ListenableFutureTask<Object> future = new ListenableFutureTask<Object> (task, null); executor.execute (future); return future; } catch (RejectedExecutionException ex) { throw new TaskRejectedException ("Executor [" + executor + "] did not accept task: " + task, ex); } } @Override public <T> ListenableFuture<T> submitListenable (Callable<T> task) { ExecutorService executor = getThreadPoolExecutor (); try { ListenableFutureTask<T> future = new ListenableFutureTask<T> (task); executor.execute (future); return future; } catch (RejectedExecutionException ex) { throw new TaskRejectedException ("Executor [" + executor + "] did not accept task: " + task, ex); } } /** * This task executor prefers short-lived work units. */ @Override public boolean prefersShortLivedTasks () { return true; } }