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