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