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