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); 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); 108 if (runWithScissorsSuccess) { 109 return true; 110 } else { 111 Throwable callerThreadThrowable = new Throwable("Caller thread Stack trace:"); 112 Throwable wifiThreadThrowable = new Throwable("Wifi thread Stack trace:"); 113 wifiThreadThrowable.setStackTrace(mHandler.getLooper().getThread().getStackTrace()); 114 Log.e(TAG, "WifiThreadRunner.run() timed out!", callerThreadThrowable); 115 Log.e(TAG, "WifiThreadRunner.run() timed out!", wifiThreadThrowable); 116 if (mTimeoutsAreErrors) { 117 throw new RuntimeException("WifiThreadRunner.run() timed out!"); 118 } 119 return false; 120 } 121 } 122 123 /** 124 * Sets whether or not a RuntimeError should be thrown when a timeout occurs. 125 * 126 * For test purposes only! 127 */ 128 @VisibleForTesting setTimeoutsAreErrors(boolean timeoutsAreErrors)129 public void setTimeoutsAreErrors(boolean timeoutsAreErrors) { 130 Log.d(TAG, "setTimeoutsAreErrors " + timeoutsAreErrors); 131 mTimeoutsAreErrors = timeoutsAreErrors; 132 } 133 134 /** 135 * Prepares to run on a different thread. 136 * 137 * Useful when TestLooper#startAutoDispatch is used to test code that uses #call or #run, 138 * because in this case the messages are dispatched by a thread that is not actually the 139 * thread associated with the looper. Should be called before each call 140 * to TestLooper#startAutoDispatch, without intervening calls to other TestLooper dispatch 141 * methods. 142 * 143 * For test purposes only! 144 */ 145 @VisibleForTesting prepareForAutoDispatch()146 public void prepareForAutoDispatch() { 147 mHandler.postAtFrontOfQueue(() -> { 148 mDispatchThread = Thread.currentThread(); 149 }); 150 } 151 152 /** 153 * Asynchronously runs a Runnable on the main Wifi thread. 154 * 155 * @return true if the runnable was successfully posted <b>(not executed)</b> to the main Wifi 156 * thread, false otherwise 157 */ post(@onNull Runnable runnable)158 public boolean post(@NonNull Runnable runnable) { 159 return mHandler.post(runnable); 160 } 161 162 /** 163 * Asynchronously runs a Runnable on the main Wifi thread with delay. 164 * 165 * @param runnable The Runnable that will be executed. 166 * @param delayMillis The delay (in milliseconds) until the Runnable 167 * will be executed. 168 * @return true if the runnable was successfully posted <b>(not executed)</b> to the main Wifi 169 * thread, false otherwise 170 */ postDelayed(@onNull Runnable runnable, long delayMillis)171 public boolean postDelayed(@NonNull Runnable runnable, long delayMillis) { 172 return mHandler.postDelayed(runnable, delayMillis); 173 } 174 175 /** 176 * Remove any pending posts of Runnable r that are in the message queue. 177 * 178 * @param r The Runnable that will be removed. 179 */ removeCallbacks(@onNull Runnable r)180 public final void removeCallbacks(@NonNull Runnable r) { 181 mHandler.removeCallbacks(r); 182 } 183 184 // Note: @hide methods copied from android.os.Handler 185 /** 186 * Runs the specified task synchronously. 187 * <p> 188 * If the current thread is the same as the handler thread, then the runnable 189 * runs immediately without being enqueued. Otherwise, posts the runnable 190 * to the handler and waits for it to complete before returning. 191 * </p><p> 192 * This method is dangerous! Improper use can result in deadlocks. 193 * Never call this method while any locks are held or use it in a 194 * possibly re-entrant manner. 195 * </p><p> 196 * This method is occasionally useful in situations where a background thread 197 * must synchronously await completion of a task that must run on the 198 * handler's thread. However, this problem is often a symptom of bad design. 199 * Consider improving the design (if possible) before resorting to this method. 200 * </p><p> 201 * One example of where you might want to use this method is when you just 202 * set up a Handler thread and need to perform some initialization steps on 203 * it before continuing execution. 204 * </p><p> 205 * If timeout occurs then this method returns <code>false</code> but the runnable 206 * will remain posted on the handler and may already be in progress or 207 * complete at a later time. 208 * </p><p> 209 * When using this method, be sure to use {@link Looper#quitSafely} when 210 * quitting the looper. Otherwise {@link #runWithScissors} may hang indefinitely. 211 * (TODO: We should fix this by making MessageQueue aware of blocking runnables.) 212 * </p> 213 * 214 * @param r The Runnable that will be executed synchronously. 215 * @param timeout The timeout in milliseconds, or 0 to wait indefinitely. 216 * 217 * @return Returns true if the Runnable was successfully executed. 218 * Returns false on failure, usually because the 219 * looper processing the message queue is exiting. 220 * 221 * @hide This method is prone to abuse and should probably not be in the API. 222 * If we ever do make it part of the API, we might want to rename it to something 223 * less funny like runUnsafe(). 224 */ runWithScissors(@onNull Handler handler, @NonNull Runnable r, long timeout)225 private boolean runWithScissors(@NonNull Handler handler, @NonNull Runnable r, 226 long timeout) { 227 if (r == null) { 228 throw new IllegalArgumentException("runnable must not be null"); 229 } 230 if (timeout < 0) { 231 throw new IllegalArgumentException("timeout must be non-negative"); 232 } 233 234 if (Looper.myLooper() == handler.getLooper()) { 235 r.run(); 236 return true; 237 } 238 239 if (Thread.currentThread() == mDispatchThread) { 240 r.run(); 241 return true; 242 } 243 244 BlockingRunnable br = new BlockingRunnable(r); 245 return br.postAndWait(handler, timeout); 246 } 247 248 private static final class BlockingRunnable implements Runnable { 249 private final Runnable mTask; 250 private boolean mDone; 251 BlockingRunnable(Runnable task)252 BlockingRunnable(Runnable task) { 253 mTask = task; 254 } 255 256 @Override run()257 public void run() { 258 try { 259 mTask.run(); 260 } finally { 261 synchronized (this) { 262 mDone = true; 263 notifyAll(); 264 } 265 } 266 } 267 postAndWait(Handler handler, long timeout)268 public boolean postAndWait(Handler handler, long timeout) { 269 if (!handler.post(this)) { 270 return false; 271 } 272 273 synchronized (this) { 274 if (timeout > 0) { 275 final long expirationTime = SystemClock.uptimeMillis() + timeout; 276 while (!mDone) { 277 long delay = expirationTime - SystemClock.uptimeMillis(); 278 if (delay <= 0) { 279 return false; // timeout 280 } 281 try { 282 wait(delay); 283 } catch (InterruptedException ex) { 284 } 285 } 286 } else { 287 while (!mDone) { 288 try { 289 wait(); 290 } catch (InterruptedException ex) { 291 } 292 } 293 } 294 } 295 return true; 296 } 297 } 298 } 299