Source code: edu/emory/mathcs/util/concurrent/AsyncTask.java
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is the Emory Utilities.
15 *
16 * The Initial Developer of the Original Code is
17 * The Distributed Computing Laboratory, Emory University.
18 * Portions created by the Initial Developer are Copyright (C) 2002
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Alternatively, the contents of this file may be used under the terms of
22 * either the GNU General Public License Version 2 or later (the "GPL"), or
23 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
24 * in which case the provisions of the GPL or the LGPL are applicable instead
25 * of those above. If you wish to allow use of your version of this file only
26 * under the terms of either the GPL or the LGPL, and not to allow others to
27 * use your version of this file under the terms of the MPL, indicate your
28 * decision by deleting the provisions above and replace them with the notice
29 * and other provisions required by the GPL or the LGPL. If you do not delete
30 * the provisions above, a recipient may use your version of this file under
31 * the terms of any one of the MPL, the GPL or the LGPL.
32 *
33 * ***** END LICENSE BLOCK ***** */
34
35 package edu.emory.mathcs.util.concurrent;
36
37 import edu.emory.mathcs.util.concurrent.*;
38
39 /**
40 * A class maintaining a single reference variable serving as the result
41 * of an operation. The result cannot be accessed until it has been set.
42 * <p>
43 * This class is intended primarily for subclassing. Typical usage scenario
44 * is to create a new instance and invoke
45 * {@link #createPerformer createPerformer}, thus
46 * obtaining runnable that will execute specified task and set results in
47 * that instance. Note that such obtained runnable should be executed only
48 * once -- subsequent execution attempts will fail due to "task already
49 * completed" condition.
50 *
51 * @see Executor
52 **/
53 public class AsyncTask implements Future, Cancellable {
54
55 /** completion status */
56 volatile boolean completed = false;
57
58 CancellationException cancellationException;
59
60 /**
61 * Wrapper for the thread in which async task is executed, or the task
62 * itself if it implements {@link Cancellable}. Set by the thread that
63 * is just about to start executing the task.
64 */
65 protected Cancellable cancellationHandler;
66
67 /** Optional callback to invoke when task completes */
68 final Callback callback;
69
70 /** result of a task */
71 Object result;
72
73 /** exception object in case task completed abruptly */
74 Throwable resultException;
75
76 protected AsyncTask() { this(null); }
77
78 protected AsyncTask(Callback cb) {
79 this.callback = cb;
80 }
81
82 /**
83 * Checks if the task has completed. Not synchronized to improve
84 * concurrency, using "volatile" instead
85 * @return <tt>true</tt> if task completed, <tt>false</tt> otherwise.
86 */
87 public boolean isDone() {
88 return completed;
89 }
90
91 public boolean cancel(boolean mayInterruptIfRunning) {
92 Throwable resultException = null;
93 synchronized (this) {
94 if (cancellationHandler != null) {
95 // task is currently running
96 if (mayInterruptIfRunning) {
97 return cancellationHandler.cancel(true);
98 }
99 else {
100 return false;
101 }
102 }
103 else if (!completed) {
104 // task not yet started. We could set this as completed
105 // abruptly at this point, but then we would have to call
106 // the callback, which would execute within the current thread,
107 // possibly blocking. It seems safer to guarantee that
108 // "interrupt()" always return promptly, and the interruption
109 // status is propagated by the same thread which would execute
110 // the task.
111 this.cancellationException = new CancellationException("task cancelled");
112 return true;
113 }
114 else {
115 // task already completed
116 return false;
117 }
118 }
119 }
120
121 public synchronized boolean isCancelled() {
122 if (cancellationException != null) return true;
123 if (cancellationHandler != null) return cancellationHandler.isCancelled();
124 return false;
125 }
126
127 /**
128 * Marks the task as completed.
129 * @param result the result of a task.
130 */
131 protected void setCompleted(Object result) {
132 synchronized (this) {
133 if (completed) {
134 throw new IllegalStateException("task already completed");
135 }
136 this.completed = true;
137 this.result = result;
138 this.cancellationHandler = null;
139 notifyAll();
140 }
141
142 // invoking callbacks *after* setting future as completed and
143 // outside the synchronization block makes it safe to call
144 // interrupt() from within callback code (in which case it will be
145 // ignored rather than cause deadlock / illegal state exception)
146 invokeCallback(result, null);
147 }
148
149 /**
150 * Marks the task as failed.
151 * @param exception the cause of abrupt completion.
152 * @throws IllegalStateException if task had been completed already.
153 */
154 protected void setFailed(Throwable exception) {
155 synchronized (this) {
156 if (completed) {
157 throw new IllegalStateException("task already completed");
158 }
159 this.completed = true;
160 this.resultException = exception;
161 this.cancellationHandler = null;
162 notifyAll();
163 }
164
165 // invoking callbacks *after* setting future as completed and
166 // outside the synchronization block makes it safe to call
167 // interrupt() from within callback code (in which case it will be
168 // ignored rather than cause deadlock / illegal state exception)
169 invokeCallback(null, exception);
170 }
171
172 public synchronized Object get()
173 throws InterruptedException, ExecutionException
174 {
175 waitFor();
176 return getResult();
177 }
178
179 public synchronized Object get(long timeout, TimeUnit tunit)
180 throws InterruptedException, ExecutionException, TimeoutException
181 {
182 waitFor(tunit.convert(timeout, TimeUnit.MILLISECONDS));
183 if (!completed) {
184 throw new TimeoutException("Timeout when waiting for a result");
185 }
186 return getResult();
187 }
188
189 /**
190 * Waits for the task to complete.
191 */
192 private void waitFor() throws InterruptedException {
193 while (!completed) {
194 wait();
195 }
196 }
197
198 /**
199 * Waits for the task to complete for timeout milliseconds.
200 */
201 private void waitFor(long timeout) throws InterruptedException {
202 while (!completed && timeout > 0) {
203 long timestart = System.currentTimeMillis();
204 wait(timeout);
205 if (completed) return;
206 timeout -= (System.currentTimeMillis() - timestart);
207 }
208 }
209
210 /**
211 * Gets the result of the task.
212 *
213 * PRE: task completed
214 * PRE: called from synchronized block
215 */
216 private Object getResult() throws ExecutionException {
217 if (resultException != null) {
218 throw new ExecutionException("task completed abruptly", resultException);
219 }
220 return result;
221 }
222
223 /**
224 * Schedules specified task with given executor, and returns
225 * completion handle that can be used to access the result or to cancel
226 * the task. Later, if task completes successfully, the handle is marked
227 * completed with the result that has been returned from the task.
228 * If the task ends with an exception, the handle is marked
229 * failed with cause being that exception. In such case, as a debugging
230 * aid, current stack trace (i.e. that of this method's invoker) is
231 * appended to the original stack trace.
232 *
233 * @param executor the executor to use
234 * @param call the task to schedule
235 * @return completion handle that can be used to access the result or
236 * cancel the task.
237 */
238 public static AsyncTask start(final Executor executor, final Callable call)
239 {
240 return start(executor, call, null);
241 }
242
243 /**
244 * Schedules specified task with given executor, and returns
245 * completion handle that can be used to access the result or to cancel
246 * the task. Later, if task completes successfully, the handle is marked
247 * completed with the result that has been returned from the task.
248 * If the task ends with an exception, the handle is marked
249 * failed with cause being that exception. In such case, as a debugging
250 * aid, current stack trace (i.e. that of this method's invoker) is
251 * appended to the original stack trace.
252 *
253 * @param executor the executor to use
254 * @param call the task to schedule
255 * @param cb callback to invoke upon completion
256 * @return completion handle that can be used to access the result or
257 * cancel the task.
258 */
259 public static AsyncTask start(final Executor executor, final Callable call,
260 final Callback cb)
261 {
262 return start(executor, call, cb, false);
263 }
264
265 /**
266 * Schedules specified task with given executor, and returns
267 * completion handle that can be used to access the result or to cancel
268 * the task. Later, if task completes successfully, the handle is marked
269 * completed with the result that has been returned from the task.
270 * If the task ends with an exception, the handle is marked
271 * failed with cause being that exception. In such case, as a debugging
272 * aid, current stack trace (i.e. that of this method's invoker) is
273 * appended to the original stack trace unless
274 * the disableStackTraces parameter is set to false
275 *
276 * @param executor the executor to use
277 * @param call the task to schedule
278 * @param cb callback to invoke upon completion
279 * @param disableStackTraces if true, does not append invoker stack trace
280 * to traces of exceptions thrown during task execution
281 * @return completion handle that can be used to access the result or
282 * cancel the task.
283 */
284 public static AsyncTask start(final Executor executor, final Callable call,
285 final Callback cb, boolean disableStackTraces)
286 {
287 final AsyncTask task = new AsyncTask(cb);
288 executor.execute(task.createPerformer(call, disableStackTraces));
289 return task;
290 }
291
292 /**
293 * Creates a runnable that will execute specified call and then mark this
294 * AsyncTask with the result of that call. If the call completes
295 * successfully, this AsyncTask is marked
296 * completed with the result returned by the call. If the call
297 * throws an exception, the AsyncTask is marked as failed with
298 * cause being that exception. The stack trace of the thread in which
299 * the performer is created is appended to the failure cause stack
300 * trace unless the disableStackTraces parameter is set to false.
301 * <p>
302 * This method is intended to be used by subclasses. Runnable
303 * returned from this method should be executed only once -- subsequent
304 * execution attempts will fail due to "task already completed" condition.
305 *
306 * @param call the call to execute
307 * @param disableStackTraces if true, does not append invoker stack trace
308 * to traces of exceptions thrown during execution of the runnable
309 * @return runnable that will execute specified call and
310 * set the result of this AsyncTask upon completion
311 */
312 protected Runnable createPerformer(final Callable call,
313 boolean disableStackTraces)
314 {
315 final Throwable stackCxt;
316 if (disableStackTraces) {
317 stackCxt = null;
318 }
319 else {
320 stackCxt = new Throwable();
321 }
322
323 return new Runnable() {
324 public void run() {
325 try {
326 synchronized (this) {
327 if (cancellationException != null) {
328 // async interruption before task started; propagate
329 throw cancellationException;
330 }
331 else {
332 cancellationHandler = createCancellationHandler(call);
333 }
334 }
335
336 Object result = call.call();
337 synchronized (this) {
338 cancellationHandler = null;
339 if (Thread.interrupted()) {
340 throw new InterruptedException("Task interrupted");
341 }
342 }
343 setCompleted(result);
344 }
345 catch (Throwable ex) {
346 if (stackCxt != null) {
347 appendContextStackTrace(ex, stackCxt);
348 }
349 setFailed(ex);
350 }
351 }
352 };
353 }
354
355 /**
356 * Overridable cancellation policy that governs what should be done upon
357 * cancellation of tasks that have already started running.
358 * This method is invoked in the worker thread by the runnable created
359 * by {@link #createPerformer createPerformer}, before invoking the actual
360 * call. The
361 * cancellationHandler returned from this method is then stored in this
362 * AsyncTask. Later, if the user attempts cancellation while the call is
363 * already executing, the request is delegated to the handler which must
364 * then supply the appropriate action and indication of success or failure.
365 * <p>
366 * The default implementation behaves as follows. If the callable for
367 * which the cancellation handler is requested implements
368 * {@link Cancellable} itself, that callable itself is returned as its own
369 * cancellation handler; in other words, the cancellation policy will be
370 * supplied directly by the callable implementation. Otherwise, the
371 * default behavior is to interrupt the worker thread if the
372 * mayInterruptIfRunning parameter is set to true, and fail in the other
373 * case.
374 *
375 * @param call the call for which the cancellation handler is requested
376 * @return cancellation handler that handles cancellation attempts
377 * while the task is already executing
378 */
379 protected Cancellable createCancellationHandler(Callable call) {
380 if (call instanceof Cancellable) {
381 return (Cancellable) call;
382 }
383 else {
384 final Thread workThread = Thread.currentThread();
385 return new Cancellable() {
386 public boolean cancel(boolean mayInterruptIfRunning) {
387 if (mayInterruptIfRunning) {
388 workThread.interrupt();
389 return true;
390 }
391 return false;
392 }
393 public boolean isDone() {
394 return false;
395 }
396 public boolean isCancelled() {
397 return workThread.isInterrupted();
398 }
399 };
400 }
401 }
402
403 private static void appendContextStackTrace(Throwable ex, Throwable cxt) {
404 StackTraceElement[] exTrace = ex.getStackTrace();
405 StackTraceElement[] cxtTrace = cxt.getStackTrace();
406 StackTraceElement[] combinedTrace =
407 new StackTraceElement[exTrace.length + cxtTrace.length];
408 System.arraycopy(exTrace, 0, combinedTrace, 0,
409 exTrace.length);
410 System.arraycopy(cxtTrace, 0, combinedTrace, exTrace.length, cxtTrace.length);
411 ex.setStackTrace(combinedTrace);
412 }
413
414 private void invokeCallback(Object result, Throwable resultException) {
415 if (callback != null) {
416 try {
417 if (resultException != null) {
418 callback.failed(resultException);
419 } else {
420 callback.completed(result);
421 }
422 }
423 catch (Throwable error) {
424 // cannot propagate; caller of e.g. setCompleted() is not
425 // interested in the exception from callback
426 java.io.PrintStream s = System.err;
427 synchronized (s) {
428 s.print("Exception occured within callback: ");
429 error.printStackTrace(s);
430 }
431 }
432 }
433 }
434 }