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.TunableConstants; 026 027import java.util.ArrayList; 028import java.util.Collection; 029import java.util.Collections; 030import java.util.List; 031import java.util.concurrent.Callable; 032import java.util.concurrent.ExecutionException; 033import java.util.concurrent.ExecutorService; 034import java.util.concurrent.Future; 035import java.util.concurrent.TimeUnit; 036import java.util.concurrent.TimeoutException; 037 038/** 039 * {@link java.util.concurrent.ExecutorService} that forwards the tasks 040 * to the {@link org.cache2k.impl.threading.GlobalPooledExecutor}. Keeps track of 041 * the number of tasks in flight and limits them. The idea is to have a 042 * lightweight ExecutorService implementation for a particular task which 043 * has no big creation and shutdown overhead. 044 * 045 * <p>Right now the thread limit is a fixed value. Maybe we implement an 046 * adaptive behaviour, that increases the thread count as long as throughput 047 * is increasing, too. Idea: Implementation similar to TCP congestion control? 048 * TODO-C: change to adaptive behaviour 049 * 050 * @author Jens Wilke; created: 2014-05-12 051 */ 052public class LimitedPooledExecutor implements ExecutorService { 053 054 private static final Tunable TUNABLE = new Tunable(); 055 056 private GlobalPooledExecutor globalPooledExecutor; 057 private MyNotifier notifier; 058 private boolean shutdown = false; 059 private Tunable tunable; 060 private ExceptionListener exceptionListener; 061 062 public LimitedPooledExecutor(GlobalPooledExecutor gpe) { 063 this(gpe, TUNABLE); 064 } 065 066 public LimitedPooledExecutor(GlobalPooledExecutor gpe, Tunable t) { 067 globalPooledExecutor = gpe; 068 notifier = new MyNotifier(t.maxThreadCount); 069 tunable = t; 070 } 071 072 public void setExceptionListener(ExceptionListener exceptionListener) { 073 this.exceptionListener = exceptionListener; 074 } 075 076 @Override 077 public void shutdown() { 078 shutdown = true; 079 } 080 081 /** 082 * Identical to {@link #shutdown}. Since we hand over everything to the 083 * global executor, implementing this would mean we need to keep track of 084 * out submitted task and finished tasks. Since we have no long running tasks 085 * there is no real benefit to implement this. 086 */ 087 @SuppressWarnings("unchecked") 088 @Override 089 public List<Runnable> shutdownNow() { 090 shutdown(); 091 return Collections.EMPTY_LIST; 092 } 093 094 @Override 095 public boolean isShutdown() { 096 return shutdown; 097 } 098 099 @Override 100 public boolean isTerminated() { 101 return shutdown && notifier.isTerminated(); 102 } 103 104 @Override 105 public boolean awaitTermination(long _timeout, TimeUnit _unit) throws InterruptedException { 106 if (!shutdown) { 107 throw new IllegalStateException("awaitTermination, expects shutdown first"); 108 } 109 notifier.waitUntilFinishedComplete(_unit.toMillis(_timeout)); 110 return isTerminated(); 111 } 112 113 @Override 114 public <T> Future<T> submit(Callable<T> c) { 115 if (notifier.isLimitReached() && ! (c instanceof NeverRunInCallingTask)) { 116 return stallAndRunInCallingThread(c); 117 } 118 notifier.stallAndCountSubmit(); 119 try { 120 Future<T> f = globalPooledExecutor.execute(c, notifier); 121 return f; 122 } catch (InterruptedException ex) { 123 return new Futures.ExceptionFuture<T>(ex); 124 } catch (TimeoutException ex) { 125 return new Futures.ExceptionFuture<T>(ex); 126 } 127 } 128 129 /** 130 * When the thread limit is reached, slow down by running the task within 131 * the callers thread. 132 */ 133 private <T> Future<T> stallAndRunInCallingThread(Callable<T> c) { 134 notifier.taskSubmittedNoStall(); 135 notifier.taskStarted(); 136 try { 137 T _result = c.call(); 138 return new Futures.FinishedFuture<T>(_result); 139 } catch (Exception ex) { 140 return new Futures.ExceptionFuture<T>(ex); 141 } finally { 142 notifier.taskFinished(); 143 } 144 } 145 146 @Override 147 public <T> Future<T> submit(final Runnable r, final T _result) { 148 Callable<T> c = new Callable<T>() { 149 @Override 150 public T call() throws Exception { 151 r.run(); 152 return _result; 153 } 154 }; 155 return submit(c); 156 } 157 158 @Override 159 public Future<?> submit(Runnable r) { 160 return submit(r, DUMMY_OBJECT); 161 } 162 163 @Override 164 public void execute(Runnable r) { 165 submit(r); 166 } 167 168 final static Object DUMMY_OBJECT = new Object(); 169 170 @Override 171 public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> _tasks) 172 throws InterruptedException { 173 if (!tunable.enableUntested) { 174 throw new UnsupportedOperationException("untested code"); 175 } 176 List<Future<T>> _list = new ArrayList<Future<T>>(); 177 try { 178 for (Callable<T> c : _tasks) { 179 notifier.stallAndCountSubmit(); 180 Future<T> f = globalPooledExecutor.execute(c, notifier); 181 _list.add(f); 182 } 183 } catch (TimeoutException ex) { 184 } 185 return _list; 186 } 187 188 @Override 189 public <T> List<Future<T>> invokeAll( 190 Collection<? extends Callable<T>> _tasks, long _timeout, TimeUnit _unit) 191 throws InterruptedException { 192 if (!tunable.enableUntested) { 193 throw new UnsupportedOperationException("untested code"); 194 } 195 long now = System.currentTimeMillis(); 196 long _timeoutMillis = _unit.toMillis(_timeout); 197 List<Future<T>> _list = new ArrayList<Future<T>>(); 198 try { 199 for (Callable<T> c : _tasks) { 200 long _restTimeout = _timeoutMillis - (System.currentTimeMillis() - now); 201 if (_restTimeout <= 0) { 202 break; 203 } 204 boolean _success = notifier.stallAndCountSubmit(_restTimeout); 205 if (!_success) { 206 break; 207 } 208 Future<T> f = globalPooledExecutor.execute(c, notifier, _restTimeout); 209 _restTimeout = _timeoutMillis - (System.currentTimeMillis() - now); 210 _list.add(f); 211 } 212 } catch (TimeoutException ex) { 213 } 214 return _list; 215 } 216 217 /** 218 * Not supported. If needed we can implement this by using a local notifier, to signal 219 * when the first task finished. 220 */ 221 @Override 222 public <T> T invokeAny(Collection<? extends Callable<T>> tasks) 223 throws InterruptedException, ExecutionException { 224 throw new UnsupportedOperationException(); 225 } 226 227 /** 228 * Not supported. If needed we can implement this by using a local notifier, to signal 229 * when the first task finished. 230 */ 231 @Override 232 public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) 233 throws InterruptedException, ExecutionException, TimeoutException { 234 throw new UnsupportedOperationException(); 235 } 236 237 class MyNotifier implements GlobalPooledExecutor.ProgressNotifier { 238 239 int counter; 240 int threadLimit; 241 242 MyNotifier(int _threadLimit) { 243 threadLimit = _threadLimit; 244 } 245 246 public synchronized boolean isTerminated() { 247 return counter == 0; 248 } 249 250 public synchronized void taskSubmittedNoStall() { 251 counter++; 252 } 253 254 @Override 255 public void taskStarted() { } 256 257 @Override 258 public synchronized void taskFinished() { 259 counter--; notify(); 260 } 261 262 @Override 263 public synchronized void taskFinishedWithException(Throwable ex) { 264 counter--; notify(); 265 if (exceptionListener != null) { 266 exceptionListener.exceptionWasThrown(ex); 267 } 268 } 269 270 public synchronized void waitUntilNextFinished() throws InterruptedException { 271 wait(); 272 } 273 274 public synchronized void waitUntilNextFinished(long _millis) throws InterruptedException { 275 wait(_millis); 276 } 277 278 public void waitUntilFinishedComplete() throws InterruptedException { 279 synchronized (this) { 280 while (counter > 0) { 281 waitUntilNextFinished(); 282 } 283 } 284 } 285 286 public void waitUntilFinishedComplete(long _millis) throws InterruptedException { 287 long t = System.currentTimeMillis(); 288 long _maxTime = t + _millis; 289 if (_maxTime < 0) { 290 waitUntilFinishedComplete(); 291 return; 292 } 293 synchronized (this) { 294 while (counter > 0 && t < _maxTime) { 295 long _waitTime = _maxTime - t; 296 if (_waitTime <= 0) { 297 return; 298 } 299 waitUntilNextFinished(_waitTime); 300 t = System.currentTimeMillis(); 301 } 302 } 303 } 304 305 public boolean stallAndCountSubmit(long _millis) { 306 long t = System.currentTimeMillis(); 307 long _maxTime = t + _millis; 308 if (_maxTime < 0) { 309 stallAndCountSubmit(); 310 return true; 311 } 312 while (true) { 313 if (isReady()) { 314 return true; 315 } 316 if (t >= _maxTime) { 317 return false; 318 } 319 try { 320 waitUntilNextFinished(Math.max(_maxTime - t, 0)); 321 t = System.currentTimeMillis(); 322 } catch (InterruptedException ex) { 323 } 324 } 325 } 326 327 private synchronized boolean isReady() { 328 if (counter < threadLimit) { 329 counter++; 330 return true; 331 } 332 return false; 333 } 334 335 private boolean isLimitReached() { 336 return counter >= threadLimit; 337 } 338 339 public void stallAndCountSubmit() { 340 while (!isReady()) { 341 try { 342 waitUntilNextFinished(); 343 } catch (InterruptedException ex) { 344 } 345 } 346 } 347 348 } 349 350 /** 351 * Marker interface for a Callable to indicate that we need the 352 * calling task to continue after the submit and a new thread for 353 * the Callable. 354 */ 355 public static interface NeverRunInCallingTask<V> extends Callable<V> { 356 357 } 358 359 public static interface ExceptionListener { 360 361 void exceptionWasThrown(Throwable ex); 362 363 } 364 365 public static class Tunable extends TunableConstants { 366 367 /** 368 * For now we use the available processor count as limit throughout. 369 * This may have adverse effects, e.g. if a storage on hard disk is 370 * starting to many requests in parallel. See outer class documentation. 371 */ 372 public int maxThreadCount = Runtime.getRuntime().availableProcessors() - 1; 373 374 /** 375 * Enables yet untested code. Default false. 376 */ 377 public boolean enableUntested = false; 378 379 } 380 381}