1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.app; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.os.Build; 21 import android.os.Handler; 22 import android.os.HandlerThread; 23 import android.os.Looper; 24 import android.os.Message; 25 import android.os.Process; 26 import android.os.StrictMode; 27 import android.util.Log; 28 29 import com.android.internal.annotations.GuardedBy; 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.internal.util.ExponentiallyBucketedHistogram; 32 33 import java.util.LinkedList; 34 35 /** 36 * Internal utility class to keep track of process-global work that's outstanding and hasn't been 37 * finished yet. 38 * 39 * New work will be {@link #queue queued}. 40 * 41 * It is possible to add 'finisher'-runnables that are {@link #waitToFinish guaranteed to be run}. 42 * This is used to make sure the work has been finished. 43 * 44 * This was created for writing SharedPreference edits out asynchronously so we'd have a mechanism 45 * to wait for the writes in Activity.onPause and similar places, but we may use this mechanism for 46 * other things in the future. 47 * 48 * The queued asynchronous work is performed on a separate, dedicated thread. 49 * 50 * @hide 51 */ 52 public class QueuedWork { 53 private static final String LOG_TAG = QueuedWork.class.getSimpleName(); 54 private static final boolean DEBUG = false; 55 56 /** Delay for delayed runnables, as big as possible but low enough to be barely perceivable */ 57 private static final long DELAY = 100; 58 59 /** If a {@link #waitToFinish()} takes more than {@value #MAX_WAIT_TIME_MILLIS} ms, warn */ 60 private static final long MAX_WAIT_TIME_MILLIS = 512; 61 62 /** Lock for this class */ 63 private static final Object sLock = new Object(); 64 65 /** 66 * Used to make sure that only one thread is processing work items at a time. This means that 67 * they are processed in the order added. 68 * 69 * This is separate from {@link #sLock} as this is held the whole time while work is processed 70 * and we do not want to stall the whole class. 71 */ 72 private static Object sProcessingWork = new Object(); 73 74 /** Finishers {@link #addFinisher added} and not yet {@link #removeFinisher removed} */ 75 @GuardedBy("sLock") 76 @UnsupportedAppUsage 77 private static final LinkedList<Runnable> sFinishers = new LinkedList<>(); 78 79 /** {@link #getHandler() Lazily} created handler */ 80 @GuardedBy("sLock") 81 private static Handler sHandler = null; 82 83 /** Work queued via {@link #queue} */ 84 @GuardedBy("sLock") 85 private static LinkedList<Runnable> sWork = new LinkedList<>(); 86 87 /** If new work can be delayed or not */ 88 @GuardedBy("sLock") 89 private static boolean sCanDelay = true; 90 91 /** Time (and number of instances) waited for work to get processed */ 92 @GuardedBy("sLock") 93 private final static ExponentiallyBucketedHistogram 94 mWaitTimes = new ExponentiallyBucketedHistogram( 95 16); 96 private static int mNumWaits = 0; 97 98 /** 99 * Lazily create a handler on a separate thread. 100 * 101 * @return the handler 102 */ 103 @UnsupportedAppUsage getHandler()104 private static Handler getHandler() { 105 synchronized (sLock) { 106 if (sHandler == null) { 107 HandlerThread handlerThread = new HandlerThread("queued-work-looper", 108 Process.THREAD_PRIORITY_FOREGROUND); 109 handlerThread.start(); 110 111 sHandler = new QueuedWorkHandler(handlerThread.getLooper()); 112 } 113 return sHandler; 114 } 115 } 116 117 /** 118 * Tear down the handler. 119 */ 120 @VisibleForTesting resetHandler()121 public static void resetHandler() { 122 synchronized (sLock) { 123 if (sHandler == null) { 124 return; 125 } 126 sHandler.getLooper().quitSafely(); 127 sHandler = null; 128 } 129 } 130 131 /** 132 * Remove all Messages from the Handler with the given code. 133 * 134 * This method intentionally avoids creating the Handler if it doesn't 135 * already exist. 136 */ handlerRemoveMessages(int what)137 private static void handlerRemoveMessages(int what) { 138 synchronized (sLock) { 139 if (sHandler == null) { 140 // Nothing to remove 141 return; 142 } 143 getHandler().removeMessages(what); 144 } 145 } 146 147 /** 148 * Add a finisher-runnable to wait for {@link #queue asynchronously processed work}. 149 * 150 * Used by SharedPreferences$Editor#startCommit(). 151 * 152 * Note that this doesn't actually start it running. This is just a scratch set for callers 153 * doing async work to keep updated with what's in-flight. In the common case, caller code 154 * (e.g. SharedPreferences) will pretty quickly call remove() after an add(). The only time 155 * these Runnables are run is from {@link #waitToFinish}. 156 * 157 * @param finisher The runnable to add as finisher 158 */ 159 @UnsupportedAppUsage addFinisher(Runnable finisher)160 public static void addFinisher(Runnable finisher) { 161 synchronized (sLock) { 162 sFinishers.add(finisher); 163 } 164 } 165 166 /** 167 * Remove a previously {@link #addFinisher added} finisher-runnable. 168 * 169 * @param finisher The runnable to remove. 170 */ 171 @UnsupportedAppUsage removeFinisher(Runnable finisher)172 public static void removeFinisher(Runnable finisher) { 173 synchronized (sLock) { 174 sFinishers.remove(finisher); 175 } 176 } 177 178 /** 179 * Trigger queued work to be processed immediately. The queued work is processed on a separate 180 * thread asynchronous. While doing that run and process all finishers on this thread. The 181 * finishers can be implemented in a way to check weather the queued work is finished. 182 * 183 * Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive, 184 * after Service command handling, etc. (so async work is never lost) 185 */ waitToFinish()186 public static void waitToFinish() { 187 long startTime = System.currentTimeMillis(); 188 boolean hadMessages = false; 189 190 synchronized (sLock) { 191 if (DEBUG) { 192 hadMessages = getHandler().hasMessages(QueuedWorkHandler.MSG_RUN); 193 } 194 handlerRemoveMessages(QueuedWorkHandler.MSG_RUN); 195 if (DEBUG && hadMessages) { 196 Log.d(LOG_TAG, "waiting"); 197 } 198 199 // We should not delay any work as this might delay the finishers 200 sCanDelay = false; 201 } 202 203 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); 204 try { 205 processPendingWork(); 206 } finally { 207 StrictMode.setThreadPolicy(oldPolicy); 208 } 209 210 try { 211 while (true) { 212 Runnable finisher; 213 214 synchronized (sLock) { 215 finisher = sFinishers.poll(); 216 } 217 218 if (finisher == null) { 219 break; 220 } 221 222 finisher.run(); 223 } 224 } finally { 225 sCanDelay = true; 226 } 227 228 synchronized (sLock) { 229 long waitTime = System.currentTimeMillis() - startTime; 230 231 if (waitTime > 0 || hadMessages) { 232 mWaitTimes.add(Long.valueOf(waitTime).intValue()); 233 mNumWaits++; 234 235 if (DEBUG || mNumWaits % 1024 == 0 || waitTime > MAX_WAIT_TIME_MILLIS) { 236 mWaitTimes.log(LOG_TAG, "waited: "); 237 } 238 } 239 } 240 } 241 242 /** 243 * Queue a work-runnable for processing asynchronously. 244 * 245 * @param work The new runnable to process 246 * @param shouldDelay If the message should be delayed 247 */ 248 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) queue(Runnable work, boolean shouldDelay)249 public static void queue(Runnable work, boolean shouldDelay) { 250 Handler handler = getHandler(); 251 252 synchronized (sLock) { 253 sWork.add(work); 254 255 if (shouldDelay && sCanDelay) { 256 handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY); 257 } else { 258 handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); 259 } 260 } 261 } 262 263 /** 264 * @return True iff there is any {@link #queue async work queued}. 265 */ hasPendingWork()266 public static boolean hasPendingWork() { 267 synchronized (sLock) { 268 return !sWork.isEmpty(); 269 } 270 } 271 processPendingWork()272 private static void processPendingWork() { 273 long startTime = 0; 274 275 if (DEBUG) { 276 startTime = System.currentTimeMillis(); 277 } 278 279 synchronized (sProcessingWork) { 280 LinkedList<Runnable> work; 281 282 synchronized (sLock) { 283 work = sWork; 284 sWork = new LinkedList<>(); 285 286 // Remove all msg-s as all work will be processed now 287 handlerRemoveMessages(QueuedWorkHandler.MSG_RUN); 288 } 289 290 if (work.size() > 0) { 291 for (Runnable w : work) { 292 w.run(); 293 } 294 295 if (DEBUG) { 296 Log.d(LOG_TAG, "processing " + work.size() + " items took " + 297 +(System.currentTimeMillis() - startTime) + " ms"); 298 } 299 } 300 } 301 } 302 303 private static class QueuedWorkHandler extends Handler { 304 static final int MSG_RUN = 1; 305 QueuedWorkHandler(Looper looper)306 QueuedWorkHandler(Looper looper) { 307 super(looper); 308 } 309 handleMessage(Message msg)310 public void handleMessage(Message msg) { 311 if (msg.what == MSG_RUN) { 312 processPendingWork(); 313 } 314 } 315 } 316 } 317