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