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