• 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);
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);
108         if (runWithScissorsSuccess) {
109             return true;
110         } else {
111             Throwable callerThreadThrowable = new Throwable("Caller thread Stack trace:");
112             Throwable wifiThreadThrowable = new Throwable("Wifi thread Stack trace:");
113             wifiThreadThrowable.setStackTrace(mHandler.getLooper().getThread().getStackTrace());
114             Log.e(TAG, "WifiThreadRunner.run() timed out!", callerThreadThrowable);
115             Log.e(TAG, "WifiThreadRunner.run() timed out!", wifiThreadThrowable);
116             if (mTimeoutsAreErrors) {
117                 throw new RuntimeException("WifiThreadRunner.run() timed out!");
118             }
119             return false;
120         }
121     }
122 
123     /**
124      * Sets whether or not a RuntimeError should be thrown when a timeout occurs.
125      *
126      * For test purposes only!
127      */
128     @VisibleForTesting
setTimeoutsAreErrors(boolean timeoutsAreErrors)129     public void setTimeoutsAreErrors(boolean timeoutsAreErrors) {
130         Log.d(TAG, "setTimeoutsAreErrors " + timeoutsAreErrors);
131         mTimeoutsAreErrors = timeoutsAreErrors;
132     }
133 
134     /**
135      * Prepares to run on a different thread.
136      *
137      * Useful when TestLooper#startAutoDispatch is used to test code that uses #call or #run,
138      * because in this case the messages are dispatched by a thread that is not actually the
139      * thread associated with the looper. Should be called before each call
140      * to TestLooper#startAutoDispatch, without intervening calls to other TestLooper dispatch
141      * methods.
142      *
143      * For test purposes only!
144      */
145     @VisibleForTesting
prepareForAutoDispatch()146     public void prepareForAutoDispatch() {
147         mHandler.postAtFrontOfQueue(() -> {
148             mDispatchThread = Thread.currentThread();
149         });
150     }
151 
152     /**
153      * Asynchronously runs a Runnable on the main Wifi thread.
154      *
155      * @return true if the runnable was successfully posted <b>(not executed)</b> to the main Wifi
156      * thread, false otherwise
157      */
post(@onNull Runnable runnable)158     public boolean post(@NonNull Runnable runnable) {
159         return mHandler.post(runnable);
160     }
161 
162     /**
163      * Asynchronously runs a Runnable on the main Wifi thread with delay.
164      *
165      * @param runnable The Runnable that will be executed.
166      * @param delayMillis The delay (in milliseconds) until the Runnable
167      *        will be executed.
168      * @return true if the runnable was successfully posted <b>(not executed)</b> to the main Wifi
169      * thread, false otherwise
170      */
postDelayed(@onNull Runnable runnable, long delayMillis)171     public boolean postDelayed(@NonNull Runnable runnable, long delayMillis) {
172         return mHandler.postDelayed(runnable, delayMillis);
173     }
174 
175     /**
176      * Remove any pending posts of Runnable r that are in the message queue.
177      *
178      * @param r The Runnable that will be removed.
179      */
removeCallbacks(@onNull Runnable r)180     public final void removeCallbacks(@NonNull Runnable r) {
181         mHandler.removeCallbacks(r);
182     }
183 
184     /**
185      * Check if there are any pending posts of messages with callback r in the message queue.
186      *
187      * @param r The Runnable that will be used to query.
188      * @return true if exists, otherwise false.
189      */
hasCallbacks(@onNull Runnable r)190     public final boolean hasCallbacks(@NonNull Runnable r) {
191         return mHandler.hasCallbacks(r);
192     }
193 
194     // Note: @hide methods copied from android.os.Handler
195     /**
196      * Runs the specified task synchronously.
197      * <p>
198      * If the current thread is the same as the handler thread, then the runnable
199      * runs immediately without being enqueued.  Otherwise, posts the runnable
200      * to the handler and waits for it to complete before returning.
201      * </p><p>
202      * This method is dangerous!  Improper use can result in deadlocks.
203      * Never call this method while any locks are held or use it in a
204      * possibly re-entrant manner.
205      * </p><p>
206      * This method is occasionally useful in situations where a background thread
207      * must synchronously await completion of a task that must run on the
208      * handler's thread.  However, this problem is often a symptom of bad design.
209      * Consider improving the design (if possible) before resorting to this method.
210      * </p><p>
211      * One example of where you might want to use this method is when you just
212      * set up a Handler thread and need to perform some initialization steps on
213      * it before continuing execution.
214      * </p><p>
215      * If timeout occurs then this method returns <code>false</code> but the runnable
216      * will remain posted on the handler and may already be in progress or
217      * complete at a later time.
218      * </p><p>
219      * When using this method, be sure to use {@link Looper#quitSafely} when
220      * quitting the looper.  Otherwise {@link #runWithScissors} may hang indefinitely.
221      * (TODO: We should fix this by making MessageQueue aware of blocking runnables.)
222      * </p>
223      *
224      * @param r The Runnable that will be executed synchronously.
225      * @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
226      *
227      * @return Returns true if the Runnable was successfully executed.
228      *         Returns false on failure, usually because the
229      *         looper processing the message queue is exiting.
230      *
231      * @hide This method is prone to abuse and should probably not be in the API.
232      * If we ever do make it part of the API, we might want to rename it to something
233      * less funny like runUnsafe().
234      */
runWithScissors(@onNull Handler handler, @NonNull Runnable r, long timeout)235     private boolean runWithScissors(@NonNull Handler handler, @NonNull Runnable r,
236             long timeout) {
237         if (r == null) {
238             throw new IllegalArgumentException("runnable must not be null");
239         }
240         if (timeout < 0) {
241             throw new IllegalArgumentException("timeout must be non-negative");
242         }
243 
244         if (Looper.myLooper() == handler.getLooper()) {
245             r.run();
246             return true;
247         }
248 
249         if (Thread.currentThread() == mDispatchThread) {
250             r.run();
251             return true;
252         }
253 
254         BlockingRunnable br = new BlockingRunnable(r);
255         return br.postAndWait(handler, timeout);
256     }
257 
258     private static final class BlockingRunnable implements Runnable {
259         private final Runnable mTask;
260         private boolean mDone;
261 
BlockingRunnable(Runnable task)262         BlockingRunnable(Runnable task) {
263             mTask = task;
264         }
265 
266         @Override
run()267         public void run() {
268             try {
269                 mTask.run();
270             } finally {
271                 synchronized (this) {
272                     mDone = true;
273                     notifyAll();
274                 }
275             }
276         }
277 
postAndWait(Handler handler, long timeout)278         public boolean postAndWait(Handler handler, long timeout) {
279             if (!handler.post(this)) {
280                 return false;
281             }
282 
283             synchronized (this) {
284                 if (timeout > 0) {
285                     final long expirationTime = SystemClock.uptimeMillis() + timeout;
286                     while (!mDone) {
287                         long delay = expirationTime - SystemClock.uptimeMillis();
288                         if (delay <= 0) {
289                             return false; // timeout
290                         }
291                         try {
292                             wait(delay);
293                         } catch (InterruptedException ex) {
294                         }
295                     }
296                 } else {
297                     while (!mDone) {
298                         try {
299                             wait();
300                         } catch (InterruptedException ex) {
301                         }
302                     }
303                 }
304             }
305             return true;
306         }
307     }
308 }
309