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