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