• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.base.task;
6 
7 import android.os.Binder;
8 
9 import androidx.annotation.IntDef;
10 import androidx.annotation.MainThread;
11 import androidx.annotation.WorkerThread;
12 
13 import org.chromium.base.Log;
14 import org.chromium.base.ThreadUtils;
15 import org.chromium.base.TraceEvent;
16 import org.chromium.base.metrics.RecordHistogram;
17 import org.chromium.build.annotations.DoNotInline;
18 
19 import java.lang.annotation.Retention;
20 import java.lang.annotation.RetentionPolicy;
21 import java.util.concurrent.Callable;
22 import java.util.concurrent.CancellationException;
23 import java.util.concurrent.ExecutionException;
24 import java.util.concurrent.Executor;
25 import java.util.concurrent.FutureTask;
26 import java.util.concurrent.RejectedExecutionHandler;
27 import java.util.concurrent.ThreadPoolExecutor;
28 import java.util.concurrent.TimeUnit;
29 import java.util.concurrent.TimeoutException;
30 import java.util.concurrent.atomic.AtomicBoolean;
31 
32 /**
33  * A Chromium version of android.os.AsyncTask.
34  *
35  * The API is quite close to Android's Oreo version, but with a number of things removed.
36  * @param <Result> Return type of the background task.
37  */
38 public abstract class AsyncTask<Result> {
39     private static final String TAG = "AsyncTask";
40 
41     private static final String GET_STATUS_UMA_HISTOGRAM =
42             "Android.Jank.AsyncTaskGetOnUiThreadStatus";
43 
44     /**
45      * An {@link Executor} that can be used to execute tasks in parallel.
46      * We use the lowest task priority, and mayBlock = true since any user of this could
47      * block.
48      */
49     public static final Executor THREAD_POOL_EXECUTOR =
50             (Runnable r) -> PostTask.postTask(TaskTraits.BEST_EFFORT_MAY_BLOCK, r);
51 
52     /**
53      * An {@link Executor} that executes tasks one at a time in serial
54      * order.  This serialization is global to a particular process.
55      */
56     public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
57 
58     private static final StealRunnableHandler STEAL_RUNNABLE_HANDLER = new StealRunnableHandler();
59 
60     private final Callable<Result> mWorker;
61     private final NamedFutureTask mFuture;
62 
63     private volatile @Status int mStatus = Status.PENDING;
64 
65     private final AtomicBoolean mCancelled = new AtomicBoolean();
66     private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
67     private int mIterationIdForTesting = PostTask.sTestIterationForTesting;
68 
69     private static class StealRunnableHandler implements RejectedExecutionHandler {
70         @Override
rejectedExecution(Runnable r, ThreadPoolExecutor executor)71         public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
72             THREAD_POOL_EXECUTOR.execute(r);
73         }
74     }
75 
76     /**
77      * Indicates the current status of the task. Each status will be set only once during the
78      * lifetime of a task. AsyncTaskStatus corresponding to this is defined in
79      * tools/metrics/histograms/enums.xml. Entries should not be renumbered and numeric values
80      * should never be reused.
81      */
82     @IntDef({Status.PENDING, Status.RUNNING, Status.FINISHED})
83     @Retention(RetentionPolicy.SOURCE)
84     public @interface Status {
85         /**
86          * Indicates that the task has not been executed yet.
87          */
88         int PENDING = 0;
89         /**
90          * Indicates that the task is running.
91          */
92         int RUNNING = 1;
93         /**
94          * Indicates that {@link AsyncTask#onPostExecute} has finished.
95          */
96         int FINISHED = 2;
97         /**
98          * Just used for reporting this status to UMA.
99          */
100         int NUM_ENTRIES = 3;
101     }
102 
103     @SuppressWarnings("NoAndroidAsyncTaskCheck")
takeOverAndroidThreadPool()104     public static void takeOverAndroidThreadPool() {
105         ThreadPoolExecutor exec = (ThreadPoolExecutor) android.os.AsyncTask.THREAD_POOL_EXECUTOR;
106         exec.setRejectedExecutionHandler(STEAL_RUNNABLE_HANDLER);
107         exec.shutdown();
108     }
109 
110     /**
111      * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
112      */
AsyncTask()113     public AsyncTask() {
114         mWorker = new Callable<Result>() {
115             @Override
116             public Result call() throws Exception {
117                 mTaskInvoked.set(true);
118                 Result result = null;
119                 try {
120                     result = doInBackground();
121                     Binder.flushPendingCommands();
122                 } catch (Throwable tr) {
123                     mCancelled.set(true);
124                     throw tr;
125                 } finally {
126                     postResult(result);
127                 }
128                 return result;
129             }
130         };
131 
132         mFuture = new NamedFutureTask(mWorker);
133     }
134 
postResultIfNotInvoked(Result result)135     private void postResultIfNotInvoked(Result result) {
136         final boolean wasTaskInvoked = mTaskInvoked.get();
137         if (!wasTaskInvoked) {
138             postResult(result);
139         }
140     }
141 
postResult(Result result)142     private void postResult(Result result) {
143         // We check if this task is of a type which does not require post-execution.
144         if (this instanceof BackgroundOnlyAsyncTask) {
145             mStatus = Status.FINISHED;
146         } else if (mIterationIdForTesting == PostTask.sTestIterationForTesting) {
147             ThreadUtils.postOnUiThread(() -> { finish(result); });
148         }
149     }
150 
151     /**
152      * Returns the current status of this task.
153      *
154      * @return The current status.
155      */
getStatus()156     public final @Status int getStatus() {
157         return mStatus;
158     }
159 
160     /**
161      * Returns the current status of this task, with adjustments made to make UMA more useful.
162      * Namely, we are going to return "PENDING" until the asynctask actually starts running. Right
163      * now, as soon as you try to schedule the AsyncTask, it gets set to "RUNNING" which doesn't
164      * make sense. However, we aren't fixing this globally as this is the well-defined API
165      * AsyncTasks have, so we are just fixing this for our UMA reporting.
166      *
167      * @return The current status.
168      */
getUmaStatus()169     public final @Status int getUmaStatus() {
170         if (mStatus == Status.RUNNING && !mTaskInvoked.get()) return Status.PENDING;
171         return mStatus;
172     }
173 
174     /**
175      * Override this method to perform a computation on a background thread.
176      *
177      * @return A result, defined by the subclass of this task.
178      *
179      * @see #onPreExecute()
180      * @see #onPostExecute
181      */
182     @WorkerThread
doInBackground()183     protected abstract Result doInBackground();
184 
185     /**
186      * Runs on the UI thread before {@link #doInBackground}.
187      *
188      * @see #onPostExecute
189      * @see #doInBackground
190      */
191     @MainThread
onPreExecute()192     protected void onPreExecute() {}
193 
194     /**
195      * <p>Runs on the UI thread after {@link #doInBackground}. The
196      * specified result is the value returned by {@link #doInBackground}.</p>
197      *
198      * <p>This method won't be invoked if the task was cancelled.</p>
199      *
200      * <p> Must be overridden by subclasses. If a subclass doesn't need
201      * post-execution, is should extend BackgroundOnlyAsyncTask instead.
202      *
203      * @param result The result of the operation computed by {@link #doInBackground}.
204      *
205      * @see #onPreExecute
206      * @see #doInBackground
207      * @see #onCancelled(Object)
208      */
209     @SuppressWarnings({"UnusedDeclaration"})
210     @MainThread
onPostExecute(Result result)211     protected abstract void onPostExecute(Result result);
212 
213     /**
214      * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
215      * {@link #doInBackground()} has finished.</p>
216      *
217      * <p>The default implementation simply invokes {@link #onCancelled()} and
218      * ignores the result. If you write your own implementation, do not call
219      * <code>super.onCancelled(result)</code>.</p>
220      *
221      * @param result The result, if any, computed in
222      *               {@link #doInBackground()}, can be null
223      *
224      * @see #cancel(boolean)
225      * @see #isCancelled()
226      */
227     @SuppressWarnings({"UnusedParameters"})
228     @MainThread
onCancelled(Result result)229     protected void onCancelled(Result result) {
230         onCancelled();
231     }
232 
233     /**
234      * <p>Applications should preferably override {@link #onCancelled(Object)}.
235      * This method is invoked by the default implementation of
236      * {@link #onCancelled(Object)}.</p>
237      *
238      * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
239      * {@link #doInBackground()} has finished.</p>
240      *
241      * @see #onCancelled(Object)
242      * @see #cancel(boolean)
243      * @see #isCancelled()
244      */
245     @MainThread
onCancelled()246     protected void onCancelled() {}
247 
248     /**
249      * Returns <tt>true</tt> if this task was cancelled before it completed
250      * normally. If you are calling {@link #cancel(boolean)} on the task,
251      * the value returned by this method should be checked periodically from
252      * {@link #doInBackground()} to end the task as soon as possible.
253      *
254      * @return <tt>true</tt> if task was cancelled before it completed
255      *
256      * @see #cancel(boolean)
257      */
isCancelled()258     public final boolean isCancelled() {
259         return mCancelled.get();
260     }
261 
262     /**
263      * <p>Attempts to cancel execution of this task.  This attempt will
264      * fail if the task has already completed, already been cancelled,
265      * or could not be cancelled for some other reason. If successful,
266      * and this task has not started when <tt>cancel</tt> is called,
267      * this task should never run. If the task has already started,
268      * then the <tt>mayInterruptIfRunning</tt> parameter determines
269      * whether the thread executing this task should be interrupted in
270      * an attempt to stop the task.</p>
271      *
272      * <p>Calling this method will result in {@link #onCancelled(Object)} being
273      * invoked on the UI thread after {@link #doInBackground()}
274      * returns. Calling this method guarantees that {@link #onPostExecute(Object)}
275      * is never invoked. After invoking this method, you should check the
276      * value returned by {@link #isCancelled()} periodically from
277      * {@link #doInBackground()} to finish the task as early as
278      * possible.</p>
279      *
280      * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
281      *        task should be interrupted; otherwise, in-progress tasks are allowed
282      *        to complete.
283      *
284      * @return <tt>false</tt> if the task could not be cancelled,
285      *         typically because it has already completed normally;
286      *         <tt>true</tt> otherwise
287      *
288      * @see #isCancelled()
289      * @see #onCancelled(Object)
290      */
cancel(boolean mayInterruptIfRunning)291     public final boolean cancel(boolean mayInterruptIfRunning) {
292         mCancelled.set(true);
293         return mFuture.cancel(mayInterruptIfRunning);
294     }
295 
296     /**
297      * Waits if necessary for the computation to complete, and then
298      * retrieves its result.
299      *
300      * @return The computed result.
301      *
302      * @throws CancellationException If the computation was cancelled.
303      * @throws ExecutionException If the computation threw an exception.
304      * @throws InterruptedException If the current thread was interrupted
305      *         while waiting.
306      */
307     @DoNotInline
308     // The string passed is safe since it is class and method name.
309     @SuppressWarnings("NoDynamicStringsInTraceEventCheck")
get()310     public final Result get() throws InterruptedException, ExecutionException {
311         Result r;
312         int status = getUmaStatus();
313         if (status != Status.FINISHED && ThreadUtils.runningOnUiThread()) {
314             RecordHistogram.recordEnumeratedHistogram(
315                     GET_STATUS_UMA_HISTOGRAM, status, Status.NUM_ENTRIES);
316             StackTraceElement[] stackTrace = new Exception().getStackTrace();
317             String caller = "";
318             if (stackTrace.length > 1) {
319                 caller = stackTrace[1].getClassName() + '.' + stackTrace[1].getMethodName() + '.';
320             }
321             try (TraceEvent e = TraceEvent.scoped(caller + "AsyncTask.get")) {
322                 r = mFuture.get();
323             }
324         } else {
325             r = mFuture.get();
326         }
327         return r;
328     }
329 
330     /**
331      * Waits if necessary for at most the given time for the computation to complete, and then
332      * retrieves its result.
333      *
334      * @param timeout Time to wait before cancelling the operation.
335      * @param unit The time unit for the timeout.
336      *
337      * @return The computed result.
338      *
339      * @throws CancellationException If the computation was cancelled.
340      * @throws ExecutionException If the computation threw an exception.
341      * @throws InterruptedException If the current thread was interrupted while waiting.
342      * @throws TimeoutException If the wait timed out.
343      */
344     @DoNotInline
345     // The string passed is safe since it is class and method name.
346     @SuppressWarnings("NoDynamicStringsInTraceEventCheck")
get(long timeout, TimeUnit unit)347     public final Result get(long timeout, TimeUnit unit)
348             throws InterruptedException, ExecutionException, TimeoutException {
349         Result r;
350         int status = getUmaStatus();
351         if (status != Status.FINISHED && ThreadUtils.runningOnUiThread()) {
352             RecordHistogram.recordEnumeratedHistogram(
353                     GET_STATUS_UMA_HISTOGRAM, status, Status.NUM_ENTRIES);
354             StackTraceElement[] stackTrace = new Exception().getStackTrace();
355             String caller = "";
356             if (stackTrace.length > 1) {
357                 caller = stackTrace[1].getClassName() + '.' + stackTrace[1].getMethodName() + '.';
358             }
359             try (TraceEvent e = TraceEvent.scoped(caller + "AsyncTask.get")) {
360                 r = mFuture.get(timeout, unit);
361             }
362         } else {
363             r = mFuture.get(timeout, unit);
364         }
365         return r;
366     }
367 
368     @SuppressWarnings({"MissingCasesInEnumSwitch"})
executionPreamble()369     private void executionPreamble() {
370         if (mStatus != Status.PENDING) {
371             switch (mStatus) {
372                 case Status.RUNNING:
373                     throw new IllegalStateException("Cannot execute task:"
374                             + " the task is already running.");
375                 case Status.FINISHED:
376                     throw new IllegalStateException("Cannot execute task:"
377                             + " the task has already been executed "
378                             + "(a task can be executed only once)");
379             }
380         }
381 
382         mStatus = Status.RUNNING;
383 
384         onPreExecute();
385     }
386 
387     /**
388      * Executes the task with the specified parameters. The task returns
389      * itself (this) so that the caller can keep a reference to it.
390      *
391      * <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to
392      * allow multiple tasks to run in parallel on a pool of threads managed by
393      * AsyncTask, however you can also use your own {@link Executor} for custom
394      * behavior.
395      *
396      * <p><em>Warning:</em> Allowing multiple tasks to run in parallel from
397      * a thread pool is generally <em>not</em> what one wants, because the order
398      * of their operation is not defined.  For example, if these tasks are used
399      * to modify any state in common (such as writing a file due to a button click),
400      * there are no guarantees on the order of the modifications.
401      * Without careful work it is possible in rare cases for the newer version
402      * of the data to be over-written by an older one, leading to obscure data
403      * loss and stability issues.  Such changes are best
404      * executed in serial; to guarantee such work is serialized regardless of
405      * platform version you can use this function with {@link #SERIAL_EXECUTOR}.
406      *
407      * <p>This method must be invoked on the UI thread.
408      *
409      * @param exec The executor to use.  {@link #THREAD_POOL_EXECUTOR} is available as a
410      *              convenient process-wide thread pool for tasks that are loosely coupled.
411      *
412      * @return This instance of AsyncTask.
413      *
414      * @throws IllegalStateException If {@link #getStatus()} returns either
415      *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
416      */
417     @MainThread
executeOnExecutor(Executor exec)418     public final AsyncTask<Result> executeOnExecutor(Executor exec) {
419         executionPreamble();
420         exec.execute(mFuture);
421         return this;
422     }
423 
424     /**
425      * Executes an AsyncTask on the given TaskRunner.
426      *
427      * @param taskRunner taskRunner to run this AsyncTask on.
428      * @return This instance of AsyncTask.
429      */
430     @MainThread
executeOnTaskRunner(TaskRunner taskRunner)431     public final AsyncTask<Result> executeOnTaskRunner(TaskRunner taskRunner) {
432         executionPreamble();
433         taskRunner.postTask(mFuture);
434         return this;
435     }
436 
437     /**
438      * Executes an AsyncTask with the given task traits. Provides no guarantees about sequencing or
439      * which thread it runs on.
440      *
441      * @param taskTraits traits which describe this AsyncTask.
442      * @return This instance of AsyncTask.
443      */
444     @MainThread
executeWithTaskTraits(@askTraits int taskTraits)445     public final AsyncTask<Result> executeWithTaskTraits(@TaskTraits int taskTraits) {
446         executionPreamble();
447         PostTask.postTask(taskTraits, mFuture);
448         return this;
449     }
450 
finish(Result result)451     private void finish(Result result) {
452         if (isCancelled()) {
453             onCancelled(result);
454         } else {
455             onPostExecute(result);
456         }
457         mStatus = Status.FINISHED;
458     }
459 
460     class NamedFutureTask extends FutureTask<Result> {
NamedFutureTask(Callable<Result> c)461         NamedFutureTask(Callable<Result> c) {
462             super(c);
463         }
464 
getBlamedClass()465         Class getBlamedClass() {
466             return AsyncTask.this.getClass();
467         }
468 
469         @Override
470         @SuppressWarnings("NoDynamicStringsInTraceEventCheck")
run()471         public void run() {
472             try (TraceEvent e = TraceEvent.scoped(
473                          "AsyncTask.run: " + mFuture.getBlamedClass().getName())) {
474                 super.run();
475             }
476         }
477 
478         @Override
done()479         protected void done() {
480             try {
481                 postResultIfNotInvoked(get());
482             } catch (InterruptedException e) {
483                 Log.w(TAG, e.toString());
484             } catch (ExecutionException e) {
485                 throw new RuntimeException(
486                         "An error occurred while executing doInBackground()", e.getCause());
487             } catch (CancellationException e) {
488                 postResultIfNotInvoked(null);
489             }
490         }
491     }
492 }
493