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