diff options
-rw-r--r-- | service/java/com/android/server/wifi/WifiThreadRunner.java | 116 | ||||
-rw-r--r-- | tests/wifitests/src/com/android/server/wifi/WifiThreadRunnerTest.java | 4 |
2 files changed, 116 insertions, 4 deletions
diff --git a/service/java/com/android/server/wifi/WifiThreadRunner.java b/service/java/com/android/server/wifi/WifiThreadRunner.java index aa79fcfa1..3a0fd048e 100644 --- a/service/java/com/android/server/wifi/WifiThreadRunner.java +++ b/service/java/com/android/server/wifi/WifiThreadRunner.java @@ -19,6 +19,8 @@ package com.android.server.wifi; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; import android.util.Log; import com.android.server.wifi.util.GeneralUtil.Mutable; @@ -66,7 +68,7 @@ public class WifiThreadRunner { @Nullable public <T> T call(@NonNull Supplier<T> supplier, T valueToReturnOnTimeout) { Mutable<T> result = new Mutable<>(); - boolean runWithScissorsSuccess = mHandler.runWithScissors( + boolean runWithScissorsSuccess = runWithScissors(mHandler, () -> result.value = supplier.get(), RUN_WITH_SCISSORS_TIMEOUT_MILLIS); if (runWithScissorsSuccess) { @@ -87,7 +89,7 @@ public class WifiThreadRunner { */ public boolean run(@NonNull Runnable runnable) { boolean runWithScissorsSuccess = - mHandler.runWithScissors(runnable, RUN_WITH_SCISSORS_TIMEOUT_MILLIS); + runWithScissors(mHandler, runnable, RUN_WITH_SCISSORS_TIMEOUT_MILLIS); if (runWithScissorsSuccess) { return true; } else { @@ -105,4 +107,114 @@ public class WifiThreadRunner { public boolean post(@NonNull Runnable runnable) { return mHandler.post(runnable); } + + // Note: @hide methods copied from android.os.Handler + /** + * Runs the specified task synchronously. + * <p> + * If the current thread is the same as the handler thread, then the runnable + * runs immediately without being enqueued. Otherwise, posts the runnable + * to the handler and waits for it to complete before returning. + * </p><p> + * This method is dangerous! Improper use can result in deadlocks. + * Never call this method while any locks are held or use it in a + * possibly re-entrant manner. + * </p><p> + * This method is occasionally useful in situations where a background thread + * must synchronously await completion of a task that must run on the + * handler's thread. However, this problem is often a symptom of bad design. + * Consider improving the design (if possible) before resorting to this method. + * </p><p> + * One example of where you might want to use this method is when you just + * set up a Handler thread and need to perform some initialization steps on + * it before continuing execution. + * </p><p> + * If timeout occurs then this method returns <code>false</code> but the runnable + * will remain posted on the handler and may already be in progress or + * complete at a later time. + * </p><p> + * When using this method, be sure to use {@link Looper#quitSafely} when + * quitting the looper. Otherwise {@link #runWithScissors} may hang indefinitely. + * (TODO: We should fix this by making MessageQueue aware of blocking runnables.) + * </p> + * + * @param r The Runnable that will be executed synchronously. + * @param timeout The timeout in milliseconds, or 0 to wait indefinitely. + * + * @return Returns true if the Runnable was successfully executed. + * Returns false on failure, usually because the + * looper processing the message queue is exiting. + * + * @hide This method is prone to abuse and should probably not be in the API. + * If we ever do make it part of the API, we might want to rename it to something + * less funny like runUnsafe(). + */ + private static boolean runWithScissors(@NonNull Handler handler, @NonNull Runnable r, + long timeout) { + if (r == null) { + throw new IllegalArgumentException("runnable must not be null"); + } + if (timeout < 0) { + throw new IllegalArgumentException("timeout must be non-negative"); + } + + if (Looper.myLooper() == handler.getLooper()) { + r.run(); + return true; + } + + BlockingRunnable br = new BlockingRunnable(r); + return br.postAndWait(handler, timeout); + } + + private static final class BlockingRunnable implements Runnable { + private final Runnable mTask; + private boolean mDone; + + BlockingRunnable(Runnable task) { + mTask = task; + } + + @Override + public void run() { + try { + mTask.run(); + } finally { + synchronized (this) { + mDone = true; + notifyAll(); + } + } + } + + public boolean postAndWait(Handler handler, long timeout) { + if (!handler.post(this)) { + return false; + } + + synchronized (this) { + if (timeout > 0) { + final long expirationTime = SystemClock.uptimeMillis() + timeout; + while (!mDone) { + long delay = expirationTime - SystemClock.uptimeMillis(); + if (delay <= 0) { + return false; // timeout + } + try { + wait(delay); + } catch (InterruptedException ex) { + } + } + } else { + while (!mDone) { + try { + wait(); + } catch (InterruptedException ex) { + } + } + } + } + return true; + } + } } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiThreadRunnerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiThreadRunnerTest.java index 7a051e2a8..59f2ed817 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiThreadRunnerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiThreadRunnerTest.java @@ -85,7 +85,7 @@ public class WifiThreadRunnerTest { @Test public void callFailure_returnValueOnTimeout() { - doReturn(false).when(mHandler).runWithScissors(any(), anyLong()); + doReturn(false).when(mHandler).post(any()); Integer result = mWifiThreadRunner.call(mSupplier, VALUE_ON_TIMEOUT); @@ -110,7 +110,7 @@ public class WifiThreadRunnerTest { @Test public void runFailure() { - doReturn(false).when(mHandler).runWithScissors(any(), anyLong()); + doReturn(false).when(mHandler).post(any()); boolean runSuccess = mWifiThreadRunner.run(mRunnable); |