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