• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.net.module.util;
18 
19 import android.annotation.NonNull;
20 import android.os.Handler;
21 import android.os.Looper;
22 
23 import java.util.concurrent.CountDownLatch;
24 import java.util.concurrent.TimeUnit;
25 import java.util.concurrent.atomic.AtomicReference;
26 
27 /**
28  * Helper class for Handler related utilities.
29  *
30  * @hide
31  */
32 public class HandlerUtils {
33     /**
34      * Runs the specified task synchronously for dump method.
35      * <p>
36      * If the current thread is the same as the handler thread, then the runnable
37      * runs immediately without being enqueued.  Otherwise, posts the runnable
38      * to the handler and waits for it to complete before returning.
39      * </p><p>
40      * This method is dangerous!  Improper use can result in deadlocks.
41      * Never call this method while any locks are held or use it in a
42      * possibly re-entrant manner.
43      * </p><p>
44      * This method is made to let dump method access members on the handler thread to
45      * avoid concurrent access problems or races.
46      * </p><p>
47      * If timeout occurs then this method returns <code>false</code> but the runnable
48      * will remain posted on the handler and may already be in progress or
49      * complete at a later time.
50      * </p><p>
51      * When using this method, be sure to use {@link Looper#quitSafely} when
52      * quitting the looper.  Otherwise {@link #runWithScissorsForDump} may hang indefinitely.
53      * (TODO: We should fix this by making MessageQueue aware of blocking runnables.)
54      * </p>
55      *
56      * @param h The target handler.
57      * @param r The Runnable that will be executed synchronously.
58      * @param timeout The timeout in milliseconds, or 0 to not wait at all.
59      *
60      * @return Returns true if the Runnable was successfully executed.
61      *         Returns false on failure, usually because the
62      *         looper processing the message queue is exiting.
63      *
64      * @hide
65      */
runWithScissorsForDump(@onNull Handler h, @NonNull Runnable r, long timeout)66     public static boolean runWithScissorsForDump(@NonNull Handler h, @NonNull Runnable r,
67                                                  long timeout) {
68         if (r == null) {
69             throw new IllegalArgumentException("runnable must not be null");
70         }
71         if (timeout < 0) {
72             throw new IllegalArgumentException("timeout must be non-negative");
73         }
74         if (Looper.myLooper() == h.getLooper()) {
75             r.run();
76             return true;
77         }
78 
79         final CountDownLatch latch = new CountDownLatch(1);
80 
81         // Don't crash in the handler if something in the runnable throws an exception,
82         // but try to propagate the exception to the caller.
83         AtomicReference<RuntimeException> exceptionRef = new AtomicReference<>();
84         h.post(() -> {
85             try {
86                 r.run();
87             } catch (RuntimeException e) {
88                 exceptionRef.set(e);
89             }
90             latch.countDown();
91         });
92 
93         try {
94             if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
95                 return false;
96             }
97         } catch (InterruptedException e) {
98             exceptionRef.compareAndSet(null, new IllegalStateException("Thread interrupted", e));
99         }
100 
101         final RuntimeException e = exceptionRef.get();
102         if (e != null) throw e;
103         return true;
104     }
105 
106     /**
107      * Ensures that the current running thread is the same as the thread associated with the given
108      * handler.
109      *
110      * @param handler The handler whose thread to compare.
111      * @throws IllegalStateException if the thread associated with the given handler is not the same
112      *                               as the current running thread.
113      * @hide
114      */
ensureRunningOnHandlerThread(@onNull Handler handler)115     public static void ensureRunningOnHandlerThread(@NonNull Handler handler) {
116         if (!isRunningOnHandlerThread(handler)) {
117             throw new IllegalStateException(
118                     "Not running on Handler thread: " + Thread.currentThread().getName());
119         }
120     }
121 
122     /**
123      * Checks if the current running thread is the same as the thread associated with the given
124      * handler.
125      *
126      * @param handler The handler whose thread to compare.
127      * @return {@code true} if the thread associated with the given handler is the same as the
128      *         current running thread, {@code false} otherwise.
129      *
130      * @hide
131      */
isRunningOnHandlerThread(@onNull Handler handler)132     public static boolean isRunningOnHandlerThread(@NonNull Handler handler) {
133         if (handler.getLooper().getThread() == Thread.currentThread()) {
134             return true;
135         }
136         return false;
137     }
138 }
139