• 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.Process;
8 import android.util.Pair;
9 
10 import androidx.annotation.Nullable;
11 
12 import org.jni_zero.JNINamespace;
13 import org.jni_zero.NativeMethods;
14 
15 import org.chromium.base.TraceEvent;
16 
17 import java.lang.ref.ReferenceQueue;
18 import java.lang.ref.WeakReference;
19 import java.util.ArrayList;
20 import java.util.HashSet;
21 import java.util.LinkedList;
22 import java.util.List;
23 import java.util.Set;
24 
25 import javax.annotation.concurrent.GuardedBy;
26 
27 /**
28  * Implementation of the abstract class {@link TaskRunnerImpl}. Uses AsyncTasks until
29  * native APIs are available.
30  */
31 @JNINamespace("base")
32 public class TaskRunnerImpl implements TaskRunner {
33     // TaskRunnerCleaners are enqueued to this queue when their WeakReference to a TaskRunnerIml is
34     // cleared.
35     private static final ReferenceQueue<Object> sQueue = new ReferenceQueue<>();
36 
37     // Holds a strong reference to the pending TaskRunnerCleaners so they don't get GC'd before the
38     // TaskRunnerImpl they're weakly referencing does.
39     @GuardedBy("sCleaners")
40     private static final Set<TaskRunnerCleaner> sCleaners = new HashSet<>();
41 
42     private final @TaskTraits int mTaskTraits;
43     private final String mTraceEvent;
44     private final @TaskRunnerType int mTaskRunnerType;
45     // Volatile is sufficient for synchronization here since we never need to read-write and
46     // volatile makes writes to it immediately visible to other threads.
47     // When |mNativeTaskRunnerAndroid| is set, native has been initialized and pre-native tasks have
48     // been migrated to the native task runner.
49     private volatile long mNativeTaskRunnerAndroid;
50     protected final Runnable mRunPreNativeTaskClosure = this::runPreNativeTask;
51 
52     private final Object mPreNativeTaskLock = new Object();
53 
54     @GuardedBy("mPreNativeTaskLock")
55     private boolean mDidOneTimeInitialization;
56 
57     @Nullable
58     @GuardedBy("mPreNativeTaskLock")
59     private LinkedList<Runnable> mPreNativeTasks;
60 
61     @Nullable
62     @GuardedBy("mPreNativeTaskLock")
63     private List<Pair<Runnable, Long>> mPreNativeDelayedTasks;
64 
clearTaskQueueForTesting()65     int clearTaskQueueForTesting() {
66         int taskCount = 0;
67         synchronized (mPreNativeTaskLock) {
68             if (mPreNativeTasks != null) {
69                 taskCount = mPreNativeTasks.size() + mPreNativeDelayedTasks.size();
70                 mPreNativeTasks.clear();
71                 mPreNativeDelayedTasks.clear();
72             }
73         }
74         return taskCount;
75     }
76 
77     private static class TaskRunnerCleaner extends WeakReference<TaskRunnerImpl> {
78         final long mNativePtr;
79 
TaskRunnerCleaner(TaskRunnerImpl runner)80         TaskRunnerCleaner(TaskRunnerImpl runner) {
81             super(runner, sQueue);
82             mNativePtr = runner.mNativeTaskRunnerAndroid;
83         }
84 
destroy()85         void destroy() {
86             TaskRunnerImplJni.get().destroy(mNativePtr);
87         }
88     }
89 
90     /**
91      * The lifecycle for a TaskRunner is very complicated. Some task runners are static and never
92      * destroyed, some have a task posted to them and are immediately allowed to be GC'd by the
93      * creator, but if native isn't initialized the task would be lost if this were to be GC'd.
94      * This makes an explicit destroy impractical as it can't be enforced on static runners, and
95      * wouldn't actually destroy the runner before native initialization as that would cause tasks
96      * to be lost. A finalizer could give us the correct behaviour here, but finalizers are banned
97      * due to the performance cost they impose, and all of the many correctness gotchas around
98      * implementing a finalizer.
99      *
100      * The strategy we've gone with here is to use a ReferenceQueue to keep track of which
101      * TaskRunners are no longer reachable (and may have been GC'd), and to delete the native
102      * counterpart for those TaskRunners by polling the queue when doing non-performance-critical
103      * operations on TaskRunners, like creating a new one. In order to prevent this TaskRunner from
104      * being GC'd before its tasks can be posted to the native runner, PostTask holds a strong
105      * reference to each TaskRunner with a task posted to it before native initialization.
106      */
destroyGarbageCollectedTaskRunners()107     private static void destroyGarbageCollectedTaskRunners() {
108         while (true) {
109             // ReferenceQueue#poll immediately removes and returns an element from the queue,
110             // returning null if the queue is empty.
111             @SuppressWarnings("unchecked")
112             TaskRunnerCleaner cleaner = (TaskRunnerCleaner) sQueue.poll();
113             if (cleaner == null) return;
114             cleaner.destroy();
115             synchronized (sCleaners) {
116                 sCleaners.remove(cleaner);
117             }
118         }
119     }
120 
121     /**
122      * @param traits The TaskTraits associated with this TaskRunnerImpl.
123      */
TaskRunnerImpl(@askTraits int traits)124     TaskRunnerImpl(@TaskTraits int traits) {
125         this(traits, "TaskRunnerImpl", TaskRunnerType.BASE);
126         destroyGarbageCollectedTaskRunners();
127     }
128 
129     /**
130      * @param traits The TaskTraits associated with this TaskRunnerImpl.
131      * @param traceCategory Specifies the name of this instance's subclass for logging purposes.
132      * @param taskRunnerType Specifies which subclass is this instance for initialising the correct
133      *         native scheduler.
134      */
TaskRunnerImpl( @askTraits int traits, String traceCategory, @TaskRunnerType int taskRunnerType)135     protected TaskRunnerImpl(
136             @TaskTraits int traits, String traceCategory, @TaskRunnerType int taskRunnerType) {
137         mTaskTraits = traits;
138         mTraceEvent = traceCategory + ".PreNativeTask.run";
139         mTaskRunnerType = taskRunnerType;
140     }
141 
142     @Override
postTask(Runnable task)143     public void postTask(Runnable task) {
144         postDelayedTask(task, 0);
145     }
146 
147     @Override
postDelayedTask(Runnable task, long delay)148     public void postDelayedTask(Runnable task, long delay) {
149         // Lock-free path when native is initialized.
150         if (mNativeTaskRunnerAndroid != 0) {
151             TaskRunnerImplJni.get()
152                     .postDelayedTask(
153                             mNativeTaskRunnerAndroid, task, delay, task.getClass().getName());
154             return;
155         }
156         synchronized (mPreNativeTaskLock) {
157             oneTimeInitialization();
158             if (mNativeTaskRunnerAndroid != 0) {
159                 TaskRunnerImplJni.get()
160                         .postDelayedTask(
161                                 mNativeTaskRunnerAndroid, task, delay, task.getClass().getName());
162                 return;
163             }
164             // We don't expect a whole lot of these, if that changes consider pooling them.
165             // If a task is scheduled for immediate execution, we post it on the
166             // pre-native task runner. Tasks scheduled to run with a delay will
167             // wait until the native task runner is initialised.
168             if (delay == 0) {
169                 mPreNativeTasks.add(task);
170                 schedulePreNativeTask();
171             } else if (!schedulePreNativeDelayedTask(task, delay)) {
172                 Pair<Runnable, Long> preNativeDelayedTask = new Pair<>(task, delay);
173                 mPreNativeDelayedTasks.add(preNativeDelayedTask);
174             }
175         }
176     }
177 
belongsToCurrentThreadInternal()178     protected Boolean belongsToCurrentThreadInternal() {
179         // TODO(https://crbug.com/1026641): This function shouldn't be here, and should only be used
180         // by derived classes (eg. SingleThreadTaskRunner) until it is moved there, as TaskRunner
181         // has no notion of belonging to a thread.
182         assert !getClass().equals(TaskRunnerImpl.class);
183         synchronized (mPreNativeTaskLock) {
184             oneTimeInitialization();
185         }
186         if (mNativeTaskRunnerAndroid == 0) return null;
187         return TaskRunnerImplJni.get().belongsToCurrentThread(mNativeTaskRunnerAndroid);
188     }
189 
190     @GuardedBy("mPreNativeTaskLock")
oneTimeInitialization()191     private void oneTimeInitialization() {
192         if (mDidOneTimeInitialization) return;
193         mDidOneTimeInitialization = true;
194         if (!PostTask.registerPreNativeTaskRunner(this)) {
195             initNativeTaskRunner();
196         } else {
197             mPreNativeTasks = new LinkedList<>();
198             mPreNativeDelayedTasks = new ArrayList<>();
199         }
200     }
201 
202     /**
203      * Must be overridden in subclasses, schedules a call to runPreNativeTask() at an appropriate
204      * time.
205      */
schedulePreNativeTask()206     protected void schedulePreNativeTask() {
207         PostTask.getPrenativeThreadPoolExecutor().execute(mRunPreNativeTaskClosure);
208     }
209 
210     /**
211      * Overridden in subclasses that support Delayed tasks pre-native.
212      *
213      * @return true if the task has been scheduled and does not need to be forwarded to the native
214      *         task runner.
215      */
schedulePreNativeDelayedTask(Runnable task, long delay)216     protected boolean schedulePreNativeDelayedTask(Runnable task, long delay) {
217         return false;
218     }
219 
220     /** Runs a single task and returns when its finished. */
221     // The trace event name is derived from string literals.
222     @SuppressWarnings("NoDynamicStringsInTraceEventCheck")
runPreNativeTask()223     protected void runPreNativeTask() {
224         try (TraceEvent te = TraceEvent.scoped(mTraceEvent)) {
225             Runnable task;
226             synchronized (mPreNativeTaskLock) {
227                 if (mPreNativeTasks == null) return;
228                 task = mPreNativeTasks.poll();
229             }
230             switch (mTaskTraits) {
231                 case TaskTraits.BEST_EFFORT:
232                 case TaskTraits.BEST_EFFORT_MAY_BLOCK:
233                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
234                     break;
235                 case TaskTraits.USER_VISIBLE:
236                 case TaskTraits.USER_VISIBLE_MAY_BLOCK:
237                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
238                     break;
239                 case TaskTraits.USER_BLOCKING:
240                 case TaskTraits.USER_BLOCKING_MAY_BLOCK:
241                     Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);
242                     break;
243                     // We don't want to lower the Thread Priority of the UI Thread, especially
244                     // pre-native, as the Thread is oversubscribed, highly latency sensitive, and
245                     // there's only a single task queue so low priority tasks can run ahead of high
246                     // priority tasks.
247                 case TaskTraits.UI_BEST_EFFORT: // Fall-through.
248                 case TaskTraits.UI_USER_VISIBLE: // Fall-through.
249                 case TaskTraits.UI_USER_BLOCKING: // Fall-through.
250                     break;
251                     // lint ensures all cases are checked.
252             }
253             task.run();
254         }
255     }
256 
257     /**
258      * Instructs the TaskRunner to initialize the native TaskRunner and migrate any tasks over to
259      * it.
260      */
initNativeTaskRunner()261     /* package */ void initNativeTaskRunner() {
262         long nativeTaskRunnerAndroid = TaskRunnerImplJni.get().init(mTaskRunnerType, mTaskTraits);
263         synchronized (mPreNativeTaskLock) {
264             if (mPreNativeTasks != null) {
265                 for (Runnable task : mPreNativeTasks) {
266                     TaskRunnerImplJni.get()
267                             .postDelayedTask(
268                                     nativeTaskRunnerAndroid, task, 0, task.getClass().getName());
269                 }
270                 mPreNativeTasks = null;
271             }
272             if (mPreNativeDelayedTasks != null) {
273                 for (Pair<Runnable, Long> task : mPreNativeDelayedTasks) {
274                     TaskRunnerImplJni.get()
275                             .postDelayedTask(
276                                     nativeTaskRunnerAndroid,
277                                     task.first,
278                                     task.second,
279                                     task.getClass().getName());
280                 }
281                 mPreNativeDelayedTasks = null;
282             }
283 
284             // mNativeTaskRunnerAndroid is volatile and setting this indicates we've have migrated
285             // all pre-native tasks and are ready to use the native Task Runner.
286             assert mNativeTaskRunnerAndroid == 0;
287             mNativeTaskRunnerAndroid = nativeTaskRunnerAndroid;
288         }
289         synchronized (sCleaners) {
290             sCleaners.add(new TaskRunnerCleaner(this));
291         }
292 
293         // Destroying GC'd task runners here isn't strictly necessary, but the performance of
294         // initNativeTaskRunner() isn't critical, and calling the function more often will help
295         // prevent any potential build-up of orphaned native task runners.
296         destroyGarbageCollectedTaskRunners();
297     }
298 
299     @NativeMethods
300     interface Natives {
init(@askRunnerType int taskRunnerType, @TaskTraits int taskTraits)301         long init(@TaskRunnerType int taskRunnerType, @TaskTraits int taskTraits);
302 
destroy(long nativeTaskRunnerAndroid)303         void destroy(long nativeTaskRunnerAndroid);
304 
postDelayedTask( long nativeTaskRunnerAndroid, Runnable task, long delay, String runnableClassName)305         void postDelayedTask(
306                 long nativeTaskRunnerAndroid, Runnable task, long delay, String runnableClassName);
307 
belongsToCurrentThread(long nativeTaskRunnerAndroid)308         boolean belongsToCurrentThread(long nativeTaskRunnerAndroid);
309     }
310 }
311