• 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     // Note: @hide methods copied from android.os.Handler
185     /**
186      * Runs the specified task synchronously.
187      * <p>
188      * If the current thread is the same as the handler thread, then the runnable
189      * runs immediately without being enqueued.  Otherwise, posts the runnable
190      * to the handler and waits for it to complete before returning.
191      * </p><p>
192      * This method is dangerous!  Improper use can result in deadlocks.
193      * Never call this method while any locks are held or use it in a
194      * possibly re-entrant manner.
195      * </p><p>
196      * This method is occasionally useful in situations where a background thread
197      * must synchronously await completion of a task that must run on the
198      * handler's thread.  However, this problem is often a symptom of bad design.
199      * Consider improving the design (if possible) before resorting to this method.
200      * </p><p>
201      * One example of where you might want to use this method is when you just
202      * set up a Handler thread and need to perform some initialization steps on
203      * it before continuing execution.
204      * </p><p>
205      * If timeout occurs then this method returns <code>false</code> but the runnable
206      * will remain posted on the handler and may already be in progress or
207      * complete at a later time.
208      * </p><p>
209      * When using this method, be sure to use {@link Looper#quitSafely} when
210      * quitting the looper.  Otherwise {@link #runWithScissors} may hang indefinitely.
211      * (TODO: We should fix this by making MessageQueue aware of blocking runnables.)
212      * </p>
213      *
214      * @param r The Runnable that will be executed synchronously.
215      * @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
216      *
217      * @return Returns true if the Runnable was successfully executed.
218      *         Returns false on failure, usually because the
219      *         looper processing the message queue is exiting.
220      *
221      * @hide This method is prone to abuse and should probably not be in the API.
222      * If we ever do make it part of the API, we might want to rename it to something
223      * less funny like runUnsafe().
224      */
runWithScissors(@onNull Handler handler, @NonNull Runnable r, long timeout)225     private boolean runWithScissors(@NonNull Handler handler, @NonNull Runnable r,
226             long timeout) {
227         if (r == null) {
228             throw new IllegalArgumentException("runnable must not be null");
229         }
230         if (timeout < 0) {
231             throw new IllegalArgumentException("timeout must be non-negative");
232         }
233 
234         if (Looper.myLooper() == handler.getLooper()) {
235             r.run();
236             return true;
237         }
238 
239         if (Thread.currentThread() == mDispatchThread) {
240             r.run();
241             return true;
242         }
243 
244         BlockingRunnable br = new BlockingRunnable(r);
245         return br.postAndWait(handler, timeout);
246     }
247 
248     private static final class BlockingRunnable implements Runnable {
249         private final Runnable mTask;
250         private boolean mDone;
251 
BlockingRunnable(Runnable task)252         BlockingRunnable(Runnable task) {
253             mTask = task;
254         }
255 
256         @Override
run()257         public void run() {
258             try {
259                 mTask.run();
260             } finally {
261                 synchronized (this) {
262                     mDone = true;
263                     notifyAll();
264                 }
265             }
266         }
267 
postAndWait(Handler handler, long timeout)268         public boolean postAndWait(Handler handler, long timeout) {
269             if (!handler.post(this)) {
270                 return false;
271             }
272 
273             synchronized (this) {
274                 if (timeout > 0) {
275                     final long expirationTime = SystemClock.uptimeMillis() + timeout;
276                     while (!mDone) {
277                         long delay = expirationTime - SystemClock.uptimeMillis();
278                         if (delay <= 0) {
279                             return false; // timeout
280                         }
281                         try {
282                             wait(delay);
283                         } catch (InterruptedException ex) {
284                         }
285                     }
286                 } else {
287                     while (!mDone) {
288                         try {
289                             wait();
290                         } catch (InterruptedException ex) {
291                         }
292                     }
293                 }
294             }
295             return true;
296         }
297     }
298 }
299