• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 com.android.server.wifi;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.os.SystemClock;
24 import android.util.Log;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.server.wifi.util.GeneralUtil.Mutable;
28 
29 import java.util.function.Supplier;
30 
31 import javax.annotation.concurrent.ThreadSafe;
32 
33 /**
34  * Runs code on one of the Wifi service threads from another thread (For ex: incoming AIDL call from
35  * a binder thread), in order to prevent race conditions.
36  * Note: This is a utility class and each wifi service may have separate instances of this class on
37  * their corresponding main thread for servicing incoming AIDL calls.
38  */
39 @ThreadSafe
40 public class WifiThreadRunner {
41     private static final String TAG = "WifiThreadRunner";
42 
43     /** Max wait time for posting blocking runnables */
44     private static final int RUN_WITH_SCISSORS_TIMEOUT_MILLIS = 4000;
45 
46     /** For test only */
47     private boolean mTimeoutsAreErrors = false;
48     private volatile Thread mDispatchThread = null;
49 
50     private final Handler mHandler;
51 
WifiThreadRunner(Handler handler)52     public WifiThreadRunner(Handler handler) {
53         mHandler = handler;
54     }
55 
56     /**
57      * Synchronously runs code on the main Wifi thread and return a value.
58      * <b>Blocks</b> the calling thread until the callable completes execution on the main Wifi
59      * thread.
60      *
61      * BEWARE OF DEADLOCKS!!!
62      *
63      * @param <T> the return type
64      * @param supplier the lambda that should be run on the main Wifi thread
65      *                 e.g. wifiThreadRunner.call(() -> mWifiApConfigStore.getApConfiguration())
66      *                 or wifiThreadRunner.call(mWifiApConfigStore::getApConfiguration)
67      * @param valueToReturnOnTimeout If the lambda provided could not be run within the timeout (
68      *                 {@link #RUN_WITH_SCISSORS_TIMEOUT_MILLIS}), will return this provided value
69      *                 instead.
70      * @return value retrieved from Wifi thread, or |valueToReturnOnTimeout| if the call failed.
71      *         Beware of NullPointerExceptions when expecting a primitive (e.g. int, long) return
72      *         type, it may still return null and throw a NullPointerException when auto-unboxing!
73      *         Recommend capturing the return value in an Integer or Long instead and explicitly
74      *         handling nulls.
75      */
76     @Nullable
call(@onNull Supplier<T> supplier, T valueToReturnOnTimeout)77     public <T> T call(@NonNull Supplier<T> supplier, T valueToReturnOnTimeout) {
78         Mutable<T> result = new Mutable<>();
79         boolean runWithScissorsSuccess = runWithScissors(mHandler,
80                 () -> result.value = supplier.get(),
81                 RUN_WITH_SCISSORS_TIMEOUT_MILLIS, false);
82         if (runWithScissorsSuccess) {
83             return result.value;
84         } else {
85             Throwable callerThreadThrowable = new Throwable("Caller thread Stack trace:");
86             Throwable wifiThreadThrowable = new Throwable("Wifi thread Stack trace:");
87             wifiThreadThrowable.setStackTrace(mHandler.getLooper().getThread().getStackTrace());
88             Log.e(TAG, "WifiThreadRunner.call() timed out!", callerThreadThrowable);
89             Log.e(TAG, "WifiThreadRunner.call() timed out!", wifiThreadThrowable);
90             if (mTimeoutsAreErrors) {
91                 throw new RuntimeException("WifiThreadRunner.call() timed out!");
92             }
93             return valueToReturnOnTimeout;
94         }
95     }
96 
97     /**
98      * Runs a Runnable on the main Wifi thread and <b>blocks</b> the calling thread until the
99      * Runnable completes execution on the main Wifi thread.
100      *
101      * BEWARE OF DEADLOCKS!!!
102      *
103      * @return true if the runnable executed successfully, false otherwise
104      */
run(@onNull Runnable runnable)105     public boolean run(@NonNull Runnable runnable) {
106         boolean runWithScissorsSuccess =
107                 runWithScissors(mHandler, runnable, RUN_WITH_SCISSORS_TIMEOUT_MILLIS, false);
108         if (runWithScissorsSuccess) {
109             return true;
110         } else {
111             Throwable callerThreadThrowable = new Throwable("Caller thread Stack trace:");
112             Log.e(TAG, "WifiThreadRunner.run() timed out!", callerThreadThrowable);
113             if (mTimeoutsAreErrors) {
114                 throw new RuntimeException("WifiThreadRunner.run() timed out!");
115             }
116             return false;
117         }
118     }
119 
120     /**
121      * Runs a Runnable on the main Wifi thread on the next iteration and <b>blocks</b> the calling
122      * thread until the Runnable completes execution on the main Wifi thread.
123      *
124      * BEWARE OF DEADLOCKS!!!
125      *
126      * @return true if the runnable executed successfully, false otherwise
127      */
runAtFront(@onNull Runnable runnable)128     public boolean runAtFront(@NonNull Runnable runnable) {
129         boolean runWithScissorsSuccess =
130                 runWithScissors(mHandler, runnable, RUN_WITH_SCISSORS_TIMEOUT_MILLIS, true);
131         if (runWithScissorsSuccess) {
132             return true;
133         } else {
134             Throwable callerThreadThrowable = new Throwable("Caller thread Stack trace:");
135             Throwable wifiThreadThrowable = new Throwable("Wifi thread Stack trace:");
136             wifiThreadThrowable.setStackTrace(mHandler.getLooper().getThread().getStackTrace());
137             Log.e(TAG, "WifiThreadRunner.run() timed out!", callerThreadThrowable);
138             Log.e(TAG, "WifiThreadRunner.run() timed out!", wifiThreadThrowable);
139             if (mTimeoutsAreErrors) {
140                 throw new RuntimeException("WifiThreadRunner.run() timed out!");
141             }
142             return false;
143         }
144     }
145     /**
146      * Sets whether or not a RuntimeError should be thrown when a timeout occurs.
147      *
148      * For test purposes only!
149      */
150     @VisibleForTesting
setTimeoutsAreErrors(boolean timeoutsAreErrors)151     public void setTimeoutsAreErrors(boolean timeoutsAreErrors) {
152         Log.d(TAG, "setTimeoutsAreErrors " + timeoutsAreErrors);
153         mTimeoutsAreErrors = timeoutsAreErrors;
154     }
155 
156     /**
157      * Prepares to run on a different thread.
158      *
159      * Useful when TestLooper#startAutoDispatch is used to test code that uses #call or #run,
160      * because in this case the messages are dispatched by a thread that is not actually the
161      * thread associated with the looper. Should be called before each call
162      * to TestLooper#startAutoDispatch, without intervening calls to other TestLooper dispatch
163      * methods.
164      *
165      * For test purposes only!
166      */
167     @VisibleForTesting
prepareForAutoDispatch()168     public void prepareForAutoDispatch() {
169         mHandler.postAtFrontOfQueue(() -> {
170             mDispatchThread = Thread.currentThread();
171         });
172     }
173 
174     /**
175      * Asynchronously runs a Runnable on the main Wifi thread.
176      *
177      * @return true if the runnable was successfully posted <b>(not executed)</b> to the main Wifi
178      * thread, false otherwise
179      */
post(@onNull Runnable runnable)180     public boolean post(@NonNull Runnable runnable) {
181         return mHandler.post(runnable);
182     }
183 
184     /**
185      * Asynchronously runs a Runnable on the main Wifi thread with delay.
186      *
187      * @param runnable The Runnable that will be executed.
188      * @param delayMillis The delay (in milliseconds) until the Runnable
189      *        will be executed.
190      * @return true if the runnable was successfully posted <b>(not executed)</b> to the main Wifi
191      * thread, false otherwise
192      */
postDelayed(@onNull Runnable runnable, long delayMillis)193     public boolean postDelayed(@NonNull Runnable runnable, long delayMillis) {
194         return mHandler.postDelayed(runnable, delayMillis);
195     }
196 
197     /**
198      * Remove any pending posts of Runnable r that are in the message queue.
199      *
200      * @param r The Runnable that will be removed.
201      */
removeCallbacks(@onNull Runnable r)202     public final void removeCallbacks(@NonNull Runnable r) {
203         mHandler.removeCallbacks(r);
204     }
205 
206     /**
207      * Check if there are any pending posts of messages with callback r in the message queue.
208      *
209      * @param r The Runnable that will be used to query.
210      * @return true if exists, otherwise false.
211      */
hasCallbacks(@onNull Runnable r)212     public final boolean hasCallbacks(@NonNull Runnable r) {
213         return mHandler.hasCallbacks(r);
214     }
215 
216     /**
217      * Package private
218      * @return Scissors timeout threshold
219      */
getScissorsTimeoutThreshold()220     static long getScissorsTimeoutThreshold() {
221         return RUN_WITH_SCISSORS_TIMEOUT_MILLIS;
222     }
223 
224     // Note: @hide methods copied from android.os.Handler
225     /**
226      * Runs the specified task synchronously.
227      * <p>
228      * If the current thread is the same as the handler thread, then the runnable
229      * runs immediately without being enqueued.  Otherwise, posts the runnable
230      * to the handler and waits for it to complete before returning.
231      * </p><p>
232      * This method is dangerous!  Improper use can result in deadlocks.
233      * Never call this method while any locks are held or use it in a
234      * possibly re-entrant manner.
235      * </p><p>
236      * This method is occasionally useful in situations where a background thread
237      * must synchronously await completion of a task that must run on the
238      * handler's thread.  However, this problem is often a symptom of bad design.
239      * Consider improving the design (if possible) before resorting to this method.
240      * </p><p>
241      * One example of where you might want to use this method is when you just
242      * set up a Handler thread and need to perform some initialization steps on
243      * it before continuing execution.
244      * </p><p>
245      * If timeout occurs then this method returns <code>false</code> but the runnable
246      * will remain posted on the handler and may already be in progress or
247      * complete at a later time.
248      * </p><p>
249      * When using this method, be sure to use {@link Looper#quitSafely} when
250      * quitting the looper.  Otherwise {@link #runWithScissors} may hang indefinitely.
251      * (TODO: We should fix this by making MessageQueue aware of blocking runnables.)
252      * </p>
253      *
254      * @param r The Runnable that will be executed synchronously.
255      * @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
256      * @param atFront Message needs to be posted at the front of the queue or not.
257      *
258      * @return Returns true if the Runnable was successfully executed.
259      *         Returns false on failure, usually because the
260      *         looper processing the message queue is exiting.
261      *
262      * @hide This method is prone to abuse and should probably not be in the API.
263      * If we ever do make it part of the API, we might want to rename it to something
264      * less funny like runUnsafe().
265      */
runWithScissors(@onNull Handler handler, @NonNull Runnable r, long timeout, boolean atFront)266     private boolean runWithScissors(@NonNull Handler handler, @NonNull Runnable r,
267             long timeout, boolean atFront) {
268         if (r == null) {
269             throw new IllegalArgumentException("runnable must not be null");
270         }
271         if (timeout < 0) {
272             throw new IllegalArgumentException("timeout must be non-negative");
273         }
274 
275         if (Looper.myLooper() == handler.getLooper()) {
276             r.run();
277             return true;
278         }
279 
280         if (Thread.currentThread() == mDispatchThread) {
281             r.run();
282             return true;
283         }
284 
285         BlockingRunnable br = new BlockingRunnable(r);
286         return br.postAndWait(handler, timeout, atFront);
287     }
288 
289     private static final class BlockingRunnable implements Runnable {
290         private final Runnable mTask;
291         private boolean mDone;
292 
BlockingRunnable(Runnable task)293         BlockingRunnable(Runnable task) {
294             mTask = task;
295         }
296 
297         @Override
run()298         public void run() {
299             try {
300                 mTask.run();
301             } finally {
302                 synchronized (this) {
303                     mDone = true;
304                     notifyAll();
305                 }
306             }
307         }
308 
postAndWait(Handler handler, long timeout, boolean atFront)309         public boolean postAndWait(Handler handler, long timeout, boolean atFront) {
310             if (atFront) {
311                 if (!handler.postAtFrontOfQueue(this)) {
312                     return false;
313                 }
314             } else {
315                 if (!handler.post(this)) {
316                     return false;
317                 }
318             }
319 
320             synchronized (this) {
321                 if (timeout > 0) {
322                     final long expirationTime = SystemClock.uptimeMillis() + timeout;
323                     while (!mDone) {
324                         long delay = expirationTime - SystemClock.uptimeMillis();
325                         if (delay <= 0) {
326                             return false; // timeout
327                         }
328                         try {
329                             wait(delay);
330                         } catch (InterruptedException ex) {
331                         }
332                     }
333                 } else {
334                     while (!mDone) {
335                         try {
336                             wait();
337                         } catch (InterruptedException ex) {
338                         }
339                     }
340                 }
341             }
342             return true;
343         }
344     }
345 }
346