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