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
17package android.app;
18
19import android.os.Handler;
20import android.os.HandlerThread;
21import android.os.Looper;
22import android.os.Message;
23import android.os.Process;
24import android.os.StrictMode;
25import android.util.Log;
26
27import com.android.internal.annotations.GuardedBy;
28import com.android.internal.util.ExponentiallyBucketedHistogram;
29
30import 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 */
49public 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     */
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     */
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     */
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     */
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     */
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     */
232    public static boolean hasPendingWork() {
233        synchronized (sLock) {
234            return !sWork.isEmpty();
235        }
236    }
237
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
272        QueuedWorkHandler(Looper looper) {
273            super(looper);
274        }
275
276        public void handleMessage(Message msg) {
277            if (msg.what == MSG_RUN) {
278                processPendingWork();
279            }
280        }
281    }
282}
283