001package org.cache2k.impl.threading;
002
003/*
004 * #%L
005 * cache2k core package
006 * %%
007 * Copyright (C) 2000 - 2015 headissue GmbH, Munich
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the 
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public 
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024
025import org.cache2k.impl.util.Log;
026import org.cache2k.impl.util.TunableConstants;
027import org.cache2k.impl.util.TunableFactory;
028
029import java.security.SecureRandom;
030import java.util.Properties;
031import java.util.Random;
032import java.util.concurrent.ArrayBlockingQueue;
033import java.util.concurrent.BlockingQueue;
034import java.util.concurrent.Callable;
035import java.util.concurrent.ExecutionException;
036import java.util.concurrent.Future;
037import java.util.concurrent.ThreadFactory;
038import java.util.concurrent.TimeUnit;
039import java.util.concurrent.TimeoutException;
040
041/**
042 * A thread pool to be shared amount several client caches for different purposes.
043 * The pool creates a new thread whenever a new task is submitted but one task is
044 * still in the queue and waiting for execution. After reaching a hard thread limit
045 * new submissions fill the queue and stall if the queue is full. The hard limit is
046 * only a precaution and not to be intended to be reached within normal operation.
047 *
048 * <p>The general idea is that a thread limit adapted to a specific use case
049 * is introduced on top, so the thread pool is not used directly but by using a
050 * {@link org.cache2k.impl.threading.LimitedPooledExecutor} which provides an
051 *  {@link java.util.concurrent.ExecutorService} interface.
052 *
053 * <p>After some time waiting a pool thread will die. If there is no work to be
054 * done no thread will be kept alive. Instead of defining a low pool size to have
055 * some threads always available for the typical workloads, each thread waits for
056 * a randomized idle time up to 30 minutes until it dies. This way the amount of
057 * threads staying in the pool adapts to the workload itself (hopefully...).
058 *
059 * @see org.cache2k.impl.threading.LimitedPooledExecutor
060 * @see java.util.concurrent.ExecutorService
061 * @author Jens Wilke; created: 2014-05-12
062 *
063 * FIXME: implementation is faulty, we need to come up with a better implementation or a different approach.
064 * Discovered a design error: If one task keeps busy or is stuck the pool is not spawning a new thread, since
065 * there is no real measure of active threads. I think we should adapt the thread pool executor.
066 */
067public class GlobalPooledExecutor {
068
069  private static final Task<?> CLOSE_TASK = new Task<Object>();
070  private static final Tunable TUNABLE = TunableFactory.get(Tunable.class);
071  private static final ProgressNotifier DUMMY_NOTIFIER = new DummyNotifier();
072
073  private int peakThreadCount = -1;
074  private Random delayRandom = new Random(new SecureRandom().nextLong());
075  private int threadCount;
076  private int diedThreadCount;
077  private BlockingQueue<Task<?>> taskQueue;
078  private boolean closed;
079  private Tunable tunable;
080  private ThreadFactory factory;
081  private Log log = Log.getLog(GlobalPooledExecutor.class);
082
083  /**
084   *
085   * @param _name used for the thread name prefix.
086   */
087  public GlobalPooledExecutor(String _name) {
088    this(TUNABLE, null, _name);
089  }
090
091  GlobalPooledExecutor() {
092    this((String) null);
093  }
094
095  GlobalPooledExecutor(Tunable t) {
096    this(t, null, null);
097  }
098
099  GlobalPooledExecutor(Tunable t, Properties _managerProperties, String _threadNamePrefix) {
100    tunable = t;
101    taskQueue = new ArrayBlockingQueue<Task<?>>(tunable.queueSize);
102    factory = tunable.threadFactoryProvider.newThreadFactory(_managerProperties, _threadNamePrefix);
103  }
104
105  public void execute(Runnable r) throws InterruptedException, TimeoutException  {
106    execute(r, DUMMY_NOTIFIER);
107  }
108
109  public <V> Future<V> execute(Callable<V> c) throws InterruptedException, TimeoutException  {
110    return execute(c, DUMMY_NOTIFIER);
111  }
112
113  public void execute(final Runnable r, ProgressNotifier n)
114    throws InterruptedException, TimeoutException {
115    Callable<Void> c = new Callable<Void>() {
116      @Override
117      public Void call() throws Exception {
118        r.run();
119        return null;
120      }
121    };
122    execute(c, n);
123  }
124
125  public <V> Future<V> execute(Callable<V> c, ProgressNotifier n)
126    throws InterruptedException, TimeoutException {
127    return execute(c, n, Long.MAX_VALUE);
128  }
129
130  /**
131   * @param _timeoutMillis 0 means immediately timeout, if no space is available
132   *
133   * @throws InterruptedException
134   * @throws TimeoutException
135   */
136  public <V> Future<V> execute(Callable<V> c, ProgressNotifier n, long _timeoutMillis)
137    throws InterruptedException, TimeoutException {
138    if (closed) {
139      throw new IllegalStateException("pool was shut down");
140    }
141    Task<V> t = new Task<V>(c, n);
142    int cnt;
143    synchronized (this) {
144      cnt = getThreadInUseCount();
145      if (cnt > 0) {
146        if (taskQueue.size() == 0) {
147          return queue(t, _timeoutMillis);
148        }
149        if (!tunable.disableHardLimit && cnt >= tunable.hardLimitThreadCount) {
150          return queue(t, _timeoutMillis);
151        }
152      }
153      threadCount++;
154      cnt = getThreadInUseCount();
155    }
156    Thread thr = factory.newThread(new ExecutorThread());
157    thr.start();
158    if (cnt > peakThreadCount) {
159      peakThreadCount = cnt;
160    }
161    return queue(t, _timeoutMillis);
162  }
163
164  private <V> Future<V> queue(Task<V> t, long _timeoutMillis)
165    throws InterruptedException, TimeoutException {
166    boolean _queued = taskQueue.offer(t, _timeoutMillis, TimeUnit.MILLISECONDS);
167    if (_queued) {
168      return t;
169    }
170    throw new TimeoutException();
171  }
172
173  public void waitUntilAllDied() {
174    int _delta;
175    for (;;) {
176      synchronized (this) {
177        _delta = threadCount - diedThreadCount;
178      }
179      if (_delta == 0) {
180         break;
181      }
182      try {
183        Thread.sleep(1);
184      } catch (InterruptedException e) {
185      }
186    }
187  }
188
189  /**
190   * Remove pending jobs from the task queue and stop threads in the pool.
191   * Threads which run jobs will finish them.
192   */
193  public synchronized void close() {
194    if (!closed) {
195      closed = true;
196      taskQueue.clear();
197      taskQueue.add(CLOSE_TASK);
198    }
199  }
200
201  public int getTotalStartedThreadCount() {
202    return threadCount;
203  }
204
205  public int getThreadInUseCount() {
206    return threadCount - diedThreadCount;
207  }
208
209  public int getDiedThreadCount() {
210    return diedThreadCount;
211  }
212
213  /** Used for alerting. */
214  public boolean wasWarningLimitReached() {
215    return peakThreadCount >= tunable.warningLimitThreadCount;
216  }
217
218  public int getPeakThreadCount() {
219    return peakThreadCount;
220  }
221
222  public interface ProgressNotifier {
223
224    void taskStarted();
225    void taskFinished();
226    void taskFinishedWithException(Throwable ex);
227
228  }
229
230  private static class Task<V> implements Future<V> {
231
232    ProgressNotifier progressNotifier;
233    int state = 0;
234    V result;
235    Throwable exception;
236
237    Callable<V> callable;
238
239    Task() { }
240
241    Task(Callable<V> _callable, ProgressNotifier _progressNotifier) {
242      callable = _callable;
243      progressNotifier = _progressNotifier;
244    }
245
246    synchronized Callable<V> start() {
247      if (state == 0) {
248        state = 1;
249        return callable;
250      }
251      return null;
252    }
253
254    synchronized void done(V _result, Throwable ex) {
255      result = _result;
256      exception = ex;
257      state = 2;
258      notifyAll();
259    }
260
261    @Override
262    public synchronized boolean cancel(boolean mayInterruptIfRunning) {
263      boolean f = callable != null && state == 0;
264      if (f) {
265        callable = null;
266        state = 2;
267        notifyAll();
268      }
269      return f;
270    }
271
272    @Override
273    public boolean isCancelled() {
274      return callable == null;
275    }
276
277    @Override
278    public boolean isDone() {
279      return state == 2;
280    }
281
282    @Override
283    public synchronized V get() throws InterruptedException, ExecutionException {
284      while (!isDone()) {
285        wait();
286        if (exception != null) {
287          throw new ExecutionException(exception);
288        }
289      }
290      if (exception != null) {
291        throw new ExecutionException(exception);
292      }
293      return result;
294    }
295
296    @Override
297    public synchronized V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
298      if (!isDone()) {
299        wait(unit.toMillis(timeout));
300        if (!isDone()) {
301          throw new TimeoutException();
302        }
303      }
304      if (exception != null) {
305        throw new ExecutionException(exception);
306      }
307      return result;
308    }
309  }
310
311  private class ExecutorThread implements Runnable {
312
313    int waitTime =
314      (tunable.randomizeIdleTime ? delayRandom.nextInt(tunable.randomIdleTimeMillis) : 0) +
315      tunable.idleTimeMillis;
316
317    @Override
318    public void run() {
319      try {
320        Task t;
321        for (;;) {
322          t = taskQueue.poll(waitTime, TimeUnit.MILLISECONDS);
323          if (t == CLOSE_TASK) {
324            taskQueue.put(t);
325            return;
326          }
327          if (t != null) {
328            t.progressNotifier.taskStarted();
329            try {
330              Callable c = t.start();
331              Object _result = c.call();
332              t.done(_result, null);
333              t.progressNotifier.taskFinished();
334            } catch (Throwable ex) {
335              log.warn("exception in thread", ex);
336              t.done(null, ex);
337              t.progressNotifier.taskFinishedWithException(ex);
338            }
339          } else {
340            break;
341          }
342        }
343      } catch (InterruptedException ex) {
344      } catch (Throwable ex) {
345        log.warn("unexpected exception", ex);
346      } finally {
347        synchronized (GlobalPooledExecutor.this) {
348          diedThreadCount++;
349        }
350      }
351    }
352
353  }
354
355  static class DummyNotifier implements ProgressNotifier {
356    @Override
357    public void taskStarted() { }
358
359    @Override
360    public void taskFinished() { }
361
362    @Override
363    public void taskFinishedWithException(Throwable ex) { }
364
365  }
366
367  public static class Tunable extends TunableConstants {
368
369    /**
370     * Waiting task queue size. Must be greater 0. The executor always
371     * queues in a task before starting a new thread. If the hardlimit
372     * is reached submitted tasks will be queued in first and than
373     * the submission stalls.
374     */
375    public int queueSize = 3;
376
377    /**
378     * Time a thread waits for a next task. Must be greater than zero.
379     */
380    public int idleTimeMillis = 9876;
381
382    /**
383     * A random value gets added to the idle time. A high value, so there
384     * is an average amount of threads always available for operations.
385     */
386    public int randomIdleTimeMillis = 30 * 60 * 1000;
387
388    /**
389     * Idle time is extended by a random interval between 0 and {@link #randomIdleTimeMillis}.
390     */
391    public boolean randomizeIdleTime = true;
392
393    /**
394     * No more threads than this limit are created. When this limit is reached the
395     * submission of new tasks stalls until a thread becomes available again.
396     * The default is 100 threads per processor. This value is rather high.
397     */
398    public int hardLimitThreadCount = 100 * Runtime.getRuntime().availableProcessors();
399
400    /**
401     * Can be needed for applications which need a high thread count.
402     */
403    public boolean disableHardLimit = false;
404
405    /**
406     * When this maximum thread count was reached once, an orange alert is issued.
407     */
408    public int warningLimitThreadCount = 33 * Runtime.getRuntime().availableProcessors();
409
410    public ThreadFactoryProvider threadFactoryProvider = new DefaultThreadFactoryProvider();
411
412  }
413
414}