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