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