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