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