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