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}