PageRenderTime 26ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/platform/util/src/com/intellij/util/concurrency/SchedulingWrapper.java

http://github.com/JetBrains/intellij-community
Java | 395 lines | 286 code | 51 blank | 58 comment | 31 complexity | f9a2999e103dfae406cfb65883acaf1d MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, MPL-2.0-no-copyleft-exception, MIT, EPL-1.0, AGPL-1.0
  1. // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
  2. package com.intellij.util.concurrency;
  3. import com.intellij.util.IncorrectOperationException;
  4. import org.jetbrains.annotations.NotNull;
  5. import java.util.ArrayList;
  6. import java.util.Collection;
  7. import java.util.Collections;
  8. import java.util.List;
  9. import java.util.concurrent.*;
  10. import java.util.concurrent.atomic.AtomicBoolean;
  11. import java.util.concurrent.atomic.AtomicLong;
  12. /**
  13. * Makes a {@link ScheduledExecutorService} from the supplied plain, non-scheduling {@link ExecutorService} by awaiting scheduled tasks in a separate thread
  14. * and then passing them for execution to the {@code backendExecutorService}.
  15. * Unlike the existing {@link ScheduledThreadPoolExecutor}, this pool can be unbounded if the {@code backendExecutorService} is.
  16. */
  17. class SchedulingWrapper implements ScheduledExecutorService {
  18. private final AtomicBoolean shutdown = new AtomicBoolean();
  19. @NotNull final ExecutorService backendExecutorService;
  20. final AppDelayQueue delayQueue;
  21. SchedulingWrapper(@NotNull ExecutorService backendExecutorService, @NotNull AppDelayQueue delayQueue) {
  22. this.delayQueue = delayQueue;
  23. if (backendExecutorService instanceof ScheduledExecutorService) {
  24. throw new IllegalArgumentException("backendExecutorService: "+backendExecutorService+" is already ScheduledExecutorService");
  25. }
  26. this.backendExecutorService = backendExecutorService;
  27. }
  28. @NotNull
  29. @Override
  30. public List<Runnable> shutdownNow() {
  31. return doShutdownNow();
  32. }
  33. @Override
  34. public void shutdown() {
  35. doShutdown();
  36. }
  37. void doShutdown() {
  38. if (!shutdown.compareAndSet(false, true)) {
  39. throw new IllegalStateException("Already shutdown");
  40. }
  41. }
  42. @NotNull
  43. List<Runnable> doShutdownNow() {
  44. doShutdown(); // shutdown me first to avoid further delayQueue offers
  45. return cancelAndRemoveTasksFromQueue();
  46. }
  47. @NotNull
  48. List<Runnable> cancelAndRemoveTasksFromQueue() {
  49. List<MyScheduledFutureTask<?>> result = new ArrayList<>();
  50. for (MyScheduledFutureTask<?> task : delayQueue) {
  51. if (task.getBackendExecutorService() == backendExecutorService) {
  52. task.cancel(false);
  53. result.add(task);
  54. }
  55. }
  56. if (result.isEmpty()) {
  57. return Collections.emptyList();
  58. }
  59. delayQueue.removeAll(result);
  60. //noinspection unchecked,rawtypes
  61. return (List)result;
  62. }
  63. @Override
  64. public boolean isShutdown() {
  65. return shutdown.get();
  66. }
  67. @Override
  68. public boolean isTerminated() {
  69. return isShutdown();
  70. }
  71. @Override
  72. public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException {
  73. if (!isShutdown()) throw new IllegalStateException("must await termination after shutdown() or shutdownNow() only");
  74. List<MyScheduledFutureTask<?>> tasks = new ArrayList<>(delayQueue);
  75. for (MyScheduledFutureTask<?> task : tasks) {
  76. if (task.getBackendExecutorService() != backendExecutorService) {
  77. continue;
  78. }
  79. try {
  80. task.get(timeout, unit);
  81. }
  82. catch (ExecutionException ignored) {
  83. }
  84. catch (TimeoutException e) {
  85. return false;
  86. }
  87. }
  88. return backendExecutorService.awaitTermination(timeout, unit);
  89. }
  90. class MyScheduledFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
  91. /**
  92. * Sequence number to break ties FIFO
  93. */
  94. private final long sequenceNumber;
  95. /**
  96. * The time the task is enabled to execute in nanoTime units
  97. */
  98. private long time;
  99. /**
  100. * Period in nanoseconds for repeating tasks. A positive
  101. * value indicates fixed-rate execution. A negative value
  102. * indicates fixed-delay execution. A value of 0 indicates a
  103. * non-repeating task.
  104. */
  105. private final long period;
  106. /**
  107. * Creates a one-shot action with given nanoTime-based trigger time.
  108. */
  109. MyScheduledFutureTask(@NotNull Runnable r, V result, long ns) {
  110. super(r, result);
  111. time = ns;
  112. period = 0;
  113. sequenceNumber = sequencer.getAndIncrement();
  114. }
  115. /**
  116. * Creates a periodic action with given nano time and period.
  117. */
  118. private MyScheduledFutureTask(@NotNull Runnable r, V result, long ns, long period) {
  119. super(r, result);
  120. time = ns;
  121. this.period = period;
  122. sequenceNumber = sequencer.getAndIncrement();
  123. }
  124. /**
  125. * Creates a one-shot action with given nanoTime-based trigger time.
  126. */
  127. private MyScheduledFutureTask(@NotNull Callable<V> callable, long ns) {
  128. super(callable);
  129. time = ns;
  130. period = 0;
  131. sequenceNumber = sequencer.getAndIncrement();
  132. }
  133. @Override
  134. public boolean cancel(boolean mayInterruptIfRunning) {
  135. boolean canceled = super.cancel(mayInterruptIfRunning);
  136. delayQueue.remove(this);
  137. return canceled;
  138. }
  139. @Override
  140. public long getDelay(@NotNull TimeUnit unit) {
  141. return unit.convert(time - now(), TimeUnit.NANOSECONDS);
  142. }
  143. @Override
  144. public int compareTo(@NotNull Delayed other) {
  145. if (other == this) {
  146. return 0;
  147. }
  148. if (other instanceof MyScheduledFutureTask) {
  149. MyScheduledFutureTask<?> x = (MyScheduledFutureTask<?>)other;
  150. long diff = time - x.time;
  151. if (diff < 0) {
  152. return -1;
  153. }
  154. if (diff > 0) {
  155. return 1;
  156. }
  157. if (sequenceNumber < x.sequenceNumber) {
  158. return -1;
  159. }
  160. return 1;
  161. }
  162. long diff = getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS);
  163. return diff < 0 ? -1 : diff > 0 ? 1 : 0;
  164. }
  165. /**
  166. * Returns {@code true} if this is a periodic (not a one-shot) action.
  167. *
  168. * @return {@code true} if periodic
  169. */
  170. @Override
  171. public boolean isPeriodic() {
  172. return period != 0;
  173. }
  174. /**
  175. * Sets the next time to run for a periodic task.
  176. */
  177. private void setNextRunTime() {
  178. long p = period;
  179. if (p > 0) {
  180. time += p;
  181. }
  182. else {
  183. time = triggerTime(delayQueue, -p);
  184. }
  185. }
  186. /**
  187. * Overrides FutureTask version so as to reset/requeue if periodic.
  188. */
  189. @Override
  190. public void run() {
  191. boolean periodic = isPeriodic();
  192. if (!periodic) {
  193. super.run();
  194. futureDone(this);
  195. }
  196. else if (runAndReset()) {
  197. setNextRunTime();
  198. delayQueue.offer(this);
  199. }
  200. }
  201. @Override
  202. public String toString() {
  203. Object info = BoundedTaskExecutor.info(this);
  204. return "Delay: " + getDelay(TimeUnit.MILLISECONDS) + "ms; " + (info == this ? super.toString() : info) + " backendExecutorService: "+backendExecutorService;
  205. }
  206. @NotNull
  207. private ExecutorService getBackendExecutorService() {
  208. return backendExecutorService;
  209. }
  210. void executeMeInBackendExecutor() {
  211. backendExecutorService.execute(this);
  212. }
  213. }
  214. void futureDone(@NotNull Future<?> task) {
  215. }
  216. /**
  217. * Sequence number to break scheduling ties, and in turn to
  218. * guarantee FIFO order among tied entries.
  219. */
  220. private static final AtomicLong sequencer = new AtomicLong();
  221. /**
  222. * Returns the trigger time of a delayed action.
  223. */
  224. static long triggerTime(@NotNull AppDelayQueue queue, long delay, TimeUnit unit) {
  225. return triggerTime(queue, unit.toNanos(delay < 0 ? 0 : delay));
  226. }
  227. private static long now() {
  228. return System.nanoTime();
  229. }
  230. /**
  231. * Returns the trigger time of a delayed action.
  232. */
  233. private static long triggerTime(@NotNull AppDelayQueue queue, long delay) {
  234. return now() + (delay < Long.MAX_VALUE >> 1 ? delay : overflowFree(queue, delay));
  235. }
  236. /**
  237. * Constrains the values of all delays in the queue to be within
  238. * Long.MAX_VALUE of each other, to avoid overflow in compareTo.
  239. * This may occur if a task is eligible to be dequeued, but has
  240. * not yet been, while some other task is added with a delay of
  241. * Long.MAX_VALUE.
  242. */
  243. private static long overflowFree(@NotNull AppDelayQueue queue, long delay) {
  244. Delayed head = queue.peek();
  245. if (head != null) {
  246. long headDelay = head.getDelay(TimeUnit.NANOSECONDS);
  247. if (headDelay < 0 && delay - headDelay < 0) {
  248. delay = Long.MAX_VALUE + headDelay;
  249. }
  250. }
  251. return delay;
  252. }
  253. @NotNull
  254. @Override
  255. public ScheduledFuture<?> schedule(@NotNull Runnable command,
  256. long delay,
  257. @NotNull TimeUnit unit) {
  258. MyScheduledFutureTask<?> t = new MyScheduledFutureTask<Void>(command, null, triggerTime(delayQueue, delay, unit));
  259. return delayedExecute(t);
  260. }
  261. @NotNull
  262. <T> MyScheduledFutureTask<T> delayedExecute(@NotNull MyScheduledFutureTask<T> t) {
  263. if (isShutdown()) {
  264. throw new RejectedExecutionException("Already shutdown");
  265. }
  266. delayQueue.add(t);
  267. if (t.getDelay(TimeUnit.DAYS) > 31 && !t.isPeriodic()) {
  268. // guard against inadvertent queue overflow
  269. throw new IllegalArgumentException("Unsupported crazy delay " + t.getDelay(TimeUnit.DAYS) + " days: " + BoundedTaskExecutor.info(t));
  270. }
  271. return t;
  272. }
  273. @NotNull
  274. @Override
  275. public <V> ScheduledFuture<V> schedule(@NotNull Callable<V> callable,
  276. long delay,
  277. @NotNull TimeUnit unit) {
  278. MyScheduledFutureTask<V> t = new MyScheduledFutureTask<>(callable, triggerTime(delayQueue, delay, unit));
  279. return delayedExecute(t);
  280. }
  281. @NotNull
  282. @Override
  283. public ScheduledFuture<?> scheduleAtFixedRate(@NotNull Runnable command,
  284. long initialDelay,
  285. long period,
  286. @NotNull TimeUnit unit) {
  287. throw new IncorrectOperationException("Not supported because it's bad for hibernation; use scheduleWithFixedDelay() with the same parameters instead.");
  288. }
  289. @NotNull
  290. @Override
  291. public ScheduledFuture<?> scheduleWithFixedDelay(@NotNull Runnable command,
  292. long initialDelay,
  293. long delay,
  294. @NotNull TimeUnit unit) {
  295. if (delay <= 0) {
  296. throw new IllegalArgumentException("delay must be positive but got: "+delay);
  297. }
  298. MyScheduledFutureTask<Void> sft = new MyScheduledFutureTask<>(command,
  299. null,
  300. triggerTime(delayQueue, initialDelay, unit),
  301. unit.toNanos(-delay));
  302. return delayedExecute(sft);
  303. }
  304. /////////////////////// delegates for ExecutorService ///////////////////////////
  305. @NotNull
  306. @Override
  307. public <T> Future<T> submit(@NotNull Callable<T> task) {
  308. return backendExecutorService.submit(task);
  309. }
  310. @NotNull
  311. @Override
  312. public <T> Future<T> submit(@NotNull Runnable task, T result) {
  313. return backendExecutorService.submit(task, result);
  314. }
  315. @NotNull
  316. @Override
  317. public Future<?> submit(@NotNull Runnable task) {
  318. return backendExecutorService.submit(task);
  319. }
  320. @NotNull
  321. @Override
  322. public <T> List<Future<T>> invokeAll(@NotNull Collection<? extends Callable<T>> tasks) throws InterruptedException {
  323. return backendExecutorService.invokeAll(tasks);
  324. }
  325. @NotNull
  326. @Override
  327. public <T> List<Future<T>> invokeAll(@NotNull Collection<? extends Callable<T>> tasks, long timeout, @NotNull TimeUnit unit) throws InterruptedException {
  328. return backendExecutorService.invokeAll(tasks, timeout, unit);
  329. }
  330. @NotNull
  331. @Override
  332. public <T> T invokeAny(@NotNull Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
  333. return backendExecutorService.invokeAny(tasks);
  334. }
  335. @Override
  336. public <T> T invokeAny(@NotNull Collection<? extends Callable<T>> tasks, long timeout, @NotNull TimeUnit unit)
  337. throws InterruptedException, ExecutionException, TimeoutException {
  338. return backendExecutorService.invokeAny(tasks, timeout, unit);
  339. }
  340. @Override
  341. public void execute(@NotNull Runnable command) {
  342. backendExecutorService.execute(command);
  343. }
  344. }