001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util.concurrent;
018
019import java.util.Collection;
020import java.util.List;
021import java.util.concurrent.Callable;
022import java.util.concurrent.ExecutionException;
023import java.util.concurrent.Future;
024import java.util.concurrent.RejectedExecutionException;
025import java.util.concurrent.RejectedExecutionHandler;
026import java.util.concurrent.ScheduledExecutorService;
027import java.util.concurrent.ScheduledFuture;
028import java.util.concurrent.ScheduledThreadPoolExecutor;
029import java.util.concurrent.ThreadFactory;
030import java.util.concurrent.TimeUnit;
031import java.util.concurrent.TimeoutException;
032
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036/**
037 * A sized {@link ScheduledExecutorService} which will reject executing tasks if the task queue is full.
038 * <p/>
039 * The {@link ScheduledThreadPoolExecutor} which is the default implementation of the {@link ScheduledExecutorService}
040 * has unbounded task queue, which mean you can keep scheduling tasks which may cause the system to run out of memory.
041 * <p/>
042 * This class is a wrapped for {@link ScheduledThreadPoolExecutor} to reject executing tasks if an upper limit of the
043 * task queue has been reached.
044 */
045public class SizedScheduledExecutorService implements ScheduledExecutorService {
046
047    private static final Logger LOG = LoggerFactory.getLogger(SizedScheduledExecutorService.class);
048    public static final String QUEUE_SIZE_LIMIT_REACHED = "Task rejected due queue size limit reached";
049    private final ScheduledThreadPoolExecutor delegate;
050    private final long queueSize;
051
052    /**
053     * Creates a new sized {@link ScheduledExecutorService} with the given queue size as upper task limit.
054     *
055     * @param delegate  the delegate of the actual thread pool implementation
056     * @param queueSize the upper queue size, use 0 or negative value for unlimited
057     */
058    public SizedScheduledExecutorService(ScheduledThreadPoolExecutor delegate, long queueSize) {
059        this.delegate = delegate;
060        this.queueSize = queueSize;
061    }
062
063    /**
064     * Gets the wrapped {@link ScheduledThreadPoolExecutor}
065     */
066    public ScheduledThreadPoolExecutor getScheduledThreadPoolExecutor() {
067        return delegate;
068    }
069
070    @Override
071    public <V> ScheduledFuture<V> schedule(Callable<V> task, long delay, TimeUnit timeUnit) {
072        if (canScheduleOrExecute()) {
073            return delegate.schedule(task, delay, timeUnit);
074        } else {
075            throw new RejectedExecutionException(QUEUE_SIZE_LIMIT_REACHED);
076        }
077    }
078
079    @Override
080    public ScheduledFuture<?> schedule(Runnable task, long delay, TimeUnit timeUnit) {
081        if (canScheduleOrExecute()) {
082            return delegate.schedule(task, delay, timeUnit);
083        } else {
084            throw new RejectedExecutionException(QUEUE_SIZE_LIMIT_REACHED);
085        }
086    }
087
088    @Override
089    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit timeUnit) {
090        if (canScheduleOrExecute()) {
091            return delegate.scheduleAtFixedRate(task, initialDelay, period, timeUnit);
092        } else {
093            throw new RejectedExecutionException(QUEUE_SIZE_LIMIT_REACHED);
094        }
095    }
096
097    @Override
098    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long initialDelay, long period, TimeUnit timeUnit) {
099        if (canScheduleOrExecute()) {
100            return delegate.scheduleWithFixedDelay(task, initialDelay, period, timeUnit);
101        } else {
102            throw new RejectedExecutionException(QUEUE_SIZE_LIMIT_REACHED);
103        }
104    }
105
106    @Override
107    public boolean awaitTermination(long timeout, TimeUnit timeUnit) throws InterruptedException {
108        return delegate.awaitTermination(timeout, timeUnit);
109    }
110
111    public int getActiveCount() {
112        return delegate.getActiveCount();
113    }
114
115    public long getCompletedTaskCount() {
116        return delegate.getCompletedTaskCount();
117    }
118
119    public int getCorePoolSize() {
120        return delegate.getCorePoolSize();
121    }
122
123    public long getKeepAliveTime(TimeUnit timeUnit) {
124        return delegate.getKeepAliveTime(timeUnit);
125    }
126
127    public int getLargestPoolSize() {
128        return delegate.getLargestPoolSize();
129    }
130
131    public int getMaximumPoolSize() {
132        return delegate.getMaximumPoolSize();
133    }
134
135    public int getPoolSize() {
136        return delegate.getPoolSize();
137    }
138
139    public RejectedExecutionHandler getRejectedExecutionHandler() {
140        return delegate.getRejectedExecutionHandler();
141    }
142
143    public long getTaskCount() {
144        return delegate.getTaskCount();
145    }
146
147    public ThreadFactory getThreadFactory() {
148        return delegate.getThreadFactory();
149    }
150
151    @Override
152    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
153        if (canScheduleOrExecute()) {
154            return delegate.invokeAll(tasks);
155        } else {
156            throw new RejectedExecutionException(QUEUE_SIZE_LIMIT_REACHED);
157        }
158    }
159
160    @Override
161    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit timeUnit)
162            throws InterruptedException {
163        if (canScheduleOrExecute()) {
164            return delegate.invokeAll(tasks, timeout, timeUnit);
165        } else {
166            throw new RejectedExecutionException(QUEUE_SIZE_LIMIT_REACHED);
167        }
168    }
169
170    @Override
171    public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
172        if (canScheduleOrExecute()) {
173            return delegate.invokeAny(tasks);
174        } else {
175            throw new RejectedExecutionException(QUEUE_SIZE_LIMIT_REACHED);
176        }
177    }
178
179    @Override
180    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit timeUnit)
181            throws InterruptedException, ExecutionException, TimeoutException {
182        if (canScheduleOrExecute()) {
183            return delegate.invokeAny(tasks, timeout, timeUnit);
184        } else {
185            throw new RejectedExecutionException(QUEUE_SIZE_LIMIT_REACHED);
186        }
187    }
188
189    @Override
190    public boolean isShutdown() {
191        return delegate.isShutdown();
192    }
193
194    @Override
195    public boolean isTerminated() {
196        return delegate.isTerminated();
197    }
198
199    public boolean isTerminating() {
200        return delegate.isTerminating();
201    }
202
203    public int prestartAllCoreThreads() {
204        return delegate.prestartAllCoreThreads();
205    }
206
207    public boolean prestartCoreThread() {
208        return delegate.prestartCoreThread();
209    }
210
211    public void purge() {
212        delegate.purge();
213    }
214
215    public void setCorePoolSize(int corePoolSize) {
216        delegate.setCorePoolSize(corePoolSize);
217    }
218
219    public void setKeepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
220        delegate.setKeepAliveTime(keepAliveTime, timeUnit);
221    }
222
223    public void setMaximumPoolSize(int maximumPoolSize) {
224        delegate.setMaximumPoolSize(maximumPoolSize);
225    }
226
227    public void setRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler) {
228        delegate.setRejectedExecutionHandler(rejectedExecutionHandler);
229    }
230
231    public void setThreadFactory(ThreadFactory threadFactory) {
232        delegate.setThreadFactory(threadFactory);
233    }
234
235    @Override
236    public void shutdown() {
237        delegate.shutdown();
238    }
239
240    @Override
241    public List<Runnable> shutdownNow() {
242        return delegate.shutdownNow();
243    }
244
245    @Override
246    public <T> Future<T> submit(Callable<T> task) {
247        if (canScheduleOrExecute()) {
248            return delegate.submit(task);
249        } else {
250            throw new RejectedExecutionException(QUEUE_SIZE_LIMIT_REACHED);
251        }
252    }
253
254    @Override
255    public Future<?> submit(Runnable task) {
256        if (canScheduleOrExecute()) {
257            return delegate.submit(task);
258        } else {
259            throw new RejectedExecutionException(QUEUE_SIZE_LIMIT_REACHED);
260        }
261    }
262
263    @Override
264    public <T> Future<T> submit(Runnable task, T result) {
265        if (canScheduleOrExecute()) {
266            return delegate.submit(task, result);
267        } else {
268            throw new RejectedExecutionException(QUEUE_SIZE_LIMIT_REACHED);
269        }
270    }
271
272    @Override
273    public void execute(Runnable task) {
274        if (canScheduleOrExecute()) {
275            delegate.execute(task);
276        } else {
277            throw new RejectedExecutionException(QUEUE_SIZE_LIMIT_REACHED);
278        }
279    }
280
281    public void allowCoreThreadTimeOut(boolean value) {
282        delegate.allowCoreThreadTimeOut(value);
283    }
284
285    public boolean allowsCoreThreadTimeOut() {
286        return delegate.allowsCoreThreadTimeOut();
287    }
288
289    /**
290     * Can the task be scheduled or executed?
291     *
292     * @return <tt>true</tt> to accept, <tt>false</tt> to not accept
293     */
294    protected boolean canScheduleOrExecute() {
295        if (queueSize <= 0) {
296            return true;
297        }
298
299        int size = delegate.getQueue().size();
300        boolean answer = size < queueSize;
301        if (LOG.isTraceEnabled()) {
302            LOG.trace("canScheduleOrExecute {} < {} -> {}", size, queueSize, answer);
303        }
304        return answer;
305    }
306
307    @Override
308    public String toString() {
309        // the thread factory often have more precise details what the thread pool is used for
310        if (delegate.getThreadFactory() instanceof CamelThreadFactory camelThreadFactory) {
311            String name = camelThreadFactory.getName();
312            return super.toString() + "[" + name + "]";
313        } else {
314            return super.toString();
315        }
316    }
317}