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