Skip to content

Package: RunnableManager$BackgroundStage

RunnableManager$BackgroundStage

Coverage

1: /*******************************************************************************
2: * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
3: *
4: * All rights reserved. This program and the accompanying materials
5: * are made available under the terms of the Eclipse Public License 2.0
6: * which accompanies this distribution, and is available at
7: * https://www.eclipse.org/legal/epl-2.0/
8: *
9: * SPDX-License-Identifier: EPL-2.0
10: *
11: * Contributors:
12: * Edgar Mueller - initial API and implementation
13: * Christian W. Damus - bugs 544116, 545686
14: ******************************************************************************/
15: package org.eclipse.emf.ecp.view.internal.table.swt;
16:
17: import java.util.concurrent.CompletableFuture;
18: import java.util.concurrent.TimeUnit;
19: import java.util.concurrent.atomic.AtomicBoolean;
20: import java.util.concurrent.atomic.AtomicReference;
21: import java.util.concurrent.locks.Condition;
22: import java.util.concurrent.locks.Lock;
23: import java.util.concurrent.locks.ReentrantLock;
24:
25: import org.eclipse.swt.widgets.Display;
26:
27: /**
28: * <p>
29: * A utility class that is capable of maintaining the running
30: * state of a {@link Runnable}, i.e. frequent calls of the {@link #executeAsync(Runnable)}
31: * with long running {@link Runnable}s will not result in each {@link Runnable} being called.
32: * This is useful, for instance, if the same {@link Runnable} is
33: * submitted multiple times unnecessarily.
34: * </p>
35: * <p>
36: * If a runnable additionally implements the {@link BackgroundStage} interface, then
37: * it will be invoked on a background thread and when complete will be asked to
38: * {@linkplain BackgroundStage#getNextStage() provide a subsequent stage} to execute.
39: * This may similarly be a background stage, iterating the process, or may be a terminal
40: * runnable that then is posted on the UI thread to update the UI.
41: * </p>
42: */
43: public class RunnableManager {
44:
45:         private final Lock lock = new ReentrantLock();
46:         private final AtomicBoolean isRunning = new AtomicBoolean(false);
47:         private final Condition runningCond = lock.newCondition();
48:         private final AtomicReference<Runnable> pending = new AtomicReference<>();
49:         private final Display display;
50:
51:         /**
52:          * Constructor.
53:          *
54:          * @param display the {@link Display} that is used to submit a runnable
55:          */
56:         public RunnableManager(Display display) {
57:                 this.display = display;
58:         }
59:
60:         private void finish(Runnable runnable) {
61:                 lock.lock();
62:
63:                 try {
64:                         // task has finished
65:                         isRunning.compareAndSet(true, false);
66:
67:                         // Running state has changed
68:                         runningCond.signalAll();
69:
70:                         // trigger next task if we have a pending request
71:                         final Runnable next = pending.getAndSet(null);
72:                         if (next != null) {
73:                                 executeAsync(next);
74:                         }
75:                 } finally {
76:                         lock.unlock();
77:                 }
78:         }
79:
80:         private Runnable createWrapperRunnable(final Runnable runnable) {
81:                 return new Runnable() {
82:                         @Override
83:                         public void run() {
84:                                 try {
85:                                         runnable.run();
86:                                 } finally {
87:                                         finish(runnable);
88:                                 }
89:                         }
90:                 };
91:         }
92:
93:         /**
94:          * Execute the given {@link Runnable} via {@link Display#asyncExec(Runnable)}.
95:          *
96:          * @param runnable the {@link Runnable} to be executed asynchronously
97:          */
98:         public void executeAsync(final Runnable runnable) {
99:                 lock.lock();
100:
101:                 try {
102:                         if (isRunning.compareAndSet(false, true)) {
103:                                 doExecuteAsync(runnable);
104:
105:                                 // Running state has changed
106:                                 runningCond.signalAll();
107:                         } else {
108:                                 pending.compareAndSet(null, runnable);
109:                         }
110:                 } finally {
111:                         lock.unlock();
112:                 }
113:         }
114:
115:         private void doExecuteAsync(final Runnable runnable) {
116:                 if (runnable instanceof BackgroundStage) {
117:                         runStage(runnable, (BackgroundStage) runnable);
118:                 } else {
119:                         getDisplay().asyncExec(createWrapperRunnable(runnable));
120:                 }
121:         }
122:
123:         /**
124:          * @return the display
125:          */
126:         public synchronized Display getDisplay() {
127:                 return display;
128:         }
129:
130:         /**
131:          * Query whether a runnable is in progress (asynchronously) on the display thread.
132:          * The implication is that posting a new runnable at this instant would be redundant.
133:          *
134:          * @return {@code true} if a runnable is currently running on the display thread
135:          * or waiting to run on the display thread; {@code false}, otherwise
136:          * @since 1.20
137:          */
138:         public boolean isRunning() {
139:                 return isRunning.get();
140:         }
141:
142:         /**
143:          * Wait until no task is running. Returns immediately if there is currently
144:          * no task running.
145:          *
146:          * @throws InterruptedException if interrupted while waiting
147:          * @since 1.21
148:          */
149:         public final void waitForIdle() throws InterruptedException {
150:                 if (!isRunning()) {
151:                         // Short-circuit
152:                         return;
153:                 }
154:
155:                 final boolean busyWait = Display.getCurrent() == display;
156:
157:                 lock.lock();
158:
159:                 try {
160:                         out: while (isRunning()) {
161:                                 if (busyWait) {
162:                                         try {
163:                                                 lock.unlock();
164:
165:                                                 // We can only finish on the display thread, which is
166:                                                 // performed by a runnable in this queue
167:                                                 do {
168:                                                         if (!isRunning()) {
169:                                                                 break out;
170:                                                         }
171:                                                 } while (display.readAndDispatch());
172:
173:                                                 display.sleep();
174:                                         } finally {
175:                                                 lock.lock();
176:                                         }
177:                                 } else {
178:                                         runningCond.await();
179:                                 }
180:                         }
181:                 } finally {
182:                         lock.unlock();
183:                 }
184:         }
185:
186:         /**
187:          * Wait until no task is running or the given {@code time} elapses.
188:          * Returns immediately if there is currently no task running. Note that if called on the
189:          * UI thread, then the actual wait time in case of time-out can be longer than requested
190:          * because of UI event queue processing.
191:          *
192:          * @param time the amount of time to wait, or a non-positive amount to wait indefinitely
193:          * as in {@link #waitForIdle()}
194:          * @param unit the unit of measure of the {@code time} to wait
195:          * @return {@code true} if on return there is no task running; {@code false} on time-out
196:          * (which does not mean that since the time-out occurred the manager did not become idle)
197:          *
198:          * @throws InterruptedException if interrupted while waiting
199:          * @since 1.21
200:          */
201:         public final boolean waitForIdle(long time, TimeUnit unit) throws InterruptedException {
202:                 if (time <= 0L) {
203:                         waitForIdle();
204:                         return true;
205:                 }
206:
207:                 final long deadline = System.nanoTime() + unit.toNanos(time);
208:
209:                 if (!isRunning()) {
210:                         // Short-circuit
211:                         return true;
212:                 }
213:
214:                 final boolean busyWait = Display.getCurrent() == display;
215:
216:                 lock.lock();
217:
218:                 try {
219:                         out: while (isRunning()) {
220:                                 final long remaining = deadline - System.nanoTime();
221:                                 if (remaining <= 0L) {
222:                                         return false;
223:                                 }
224:
225:                                 if (busyWait) {
226:                                         try {
227:                                                 lock.unlock();
228:
229:                                                 // We can only finish on the display thread, which is
230:                                                 // performed by a runnable in this queue
231:                                                 do {
232:                                                         if (!isRunning()) {
233:                                                                 break out;
234:                                                         }
235:                                                 } while (display.readAndDispatch());
236:                                         } finally {
237:                                                 lock.lock();
238:                                         }
239:
240:                                         runningCond.await(50L, TimeUnit.MILLISECONDS);
241:                                 } else {
242:                                         runningCond.awaitNanos(remaining);
243:                                 }
244:                         }
245:                 } finally {
246:                         lock.unlock();
247:                 }
248:
249:                 return true;
250:         }
251:
252:         private void runStage(Runnable computation, BackgroundStage stage) {
253:                 CompletableFuture.runAsync(computation)
254:                         .whenComplete((result, exception) -> {
255:                                 if (exception != null) {
256:                                         // Computation failed. Just finish
257:                                         finish(computation);
258:                                         Activator.getInstance().log(exception);
259:                                 } else {
260:                                         // Send the next stage
261:                                         final Runnable next = stage.getNextStage();
262:                                         if (next == null) {
263:                                                 // Okay, then. Just finish
264:                                                 finish(computation);
265:                                         } else {
266:                                                 doExecuteAsync(next);
267:                                         }
268:                                 }
269:                         });
270:         }
271:
272:         //
273:         // Nested types
274:         //
275:
276:         /**
277:          * An optional mix-in interface for a {@link Runnable} scheduled on the
278:          * {@link RunnableManager} that should be run in a background thread and which
279:          * produces a subsequent stage for further execution.
280:          *
281:          * @since 1.21
282:          */
283:         public interface BackgroundStage {
284:                 /**
285:                  * Provides the next stage of computation. If the result is another
286:                  * {@code BackgroundStage}, then it, too, will run in the background.
287:                  * The final stage is some {@link Runnable} that is not a {@code BackgroundStage}
288:                  * which then is posted on the display thread to update the UI.
289:                  *
290:                  * @return the next stage of computation/update
291:                  */
292:                 Runnable getNextStage();
293:         }
294:
295: }