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