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}