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