1 /*
2  * Copyright (C) 2008 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;
18 
19 import android.app.IActivityController;
20 import android.os.Binder;
21 import android.os.Build;
22 import android.os.RemoteException;
23 import android.system.ErrnoException;
24 import android.system.OsConstants;
25 import android.system.StructRlimit;
26 import com.android.internal.os.ZygoteConnectionConstants;
27 import com.android.server.am.ActivityManagerService;
28 
29 import android.content.BroadcastReceiver;
30 import android.content.ContentResolver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.hidl.manager.V1_0.IServiceManager;
35 import android.os.Debug;
36 import android.os.Handler;
37 import android.os.IPowerManager;
38 import android.os.Looper;
39 import android.os.Process;
40 import android.os.ServiceManager;
41 import android.os.SystemClock;
42 import android.os.SystemProperties;
43 import android.util.EventLog;
44 import android.util.Log;
45 import android.util.Slog;
46 
47 import java.io.File;
48 import java.io.FileWriter;
49 import java.io.IOException;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collections;
53 import java.util.HashSet;
54 import java.util.List;
55 
56 /** This class calls its monitor every minute. Killing this process if they don't return **/
57 public class Watchdog extends Thread {
58     static final String TAG = "Watchdog";
59 
60     // Set this to true to use debug default values.
61     static final boolean DB = false;
62 
63     // Note 1: Do not lower this value below thirty seconds without tightening the invoke-with
64     //         timeout in com.android.internal.os.ZygoteConnection, or wrapped applications
65     //         can trigger the watchdog.
66     // Note 2: The debug value is already below the wait time in ZygoteConnection. Wrapped
67     //         applications may not work with a debug build. CTS will fail.
68     static final long DEFAULT_TIMEOUT = DB ? 10*1000 : 60*1000;
69     static final long CHECK_INTERVAL = DEFAULT_TIMEOUT / 2;
70 
71     // These are temporally ordered: larger values as lateness increases
72     static final int COMPLETED = 0;
73     static final int WAITING = 1;
74     static final int WAITED_HALF = 2;
75     static final int OVERDUE = 3;
76 
77     // Which native processes to dump into dropbox's stack traces
78     public static final String[] NATIVE_STACKS_OF_INTEREST = new String[] {
79         "/system/bin/audioserver",
80         "/system/bin/cameraserver",
81         "/system/bin/drmserver",
82         "/system/bin/mediadrmserver",
83         "/system/bin/mediaserver",
84         "/system/bin/sdcard",
85         "/system/bin/surfaceflinger",
86         "media.extractor", // system/bin/mediaextractor
87         "media.metrics", // system/bin/mediametrics
88         "media.codec", // vendor/bin/hw/android.hardware.media.omx@1.0-service
89         "com.android.bluetooth",  // Bluetooth service
90         "statsd",  // Stats daemon
91     };
92 
93     public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
94         "android.hardware.audio@2.0::IDevicesFactory",
95         "android.hardware.audio@4.0::IDevicesFactory",
96         "android.hardware.bluetooth@1.0::IBluetoothHci",
97         "android.hardware.camera.provider@2.4::ICameraProvider",
98         "android.hardware.graphics.composer@2.1::IComposer",
99         "android.hardware.media.omx@1.0::IOmx",
100         "android.hardware.media.omx@1.0::IOmxStore",
101         "android.hardware.sensors@1.0::ISensors",
102         "android.hardware.vr@1.0::IVr"
103     );
104 
105     static Watchdog sWatchdog;
106 
107     /* This handler will be used to post message back onto the main thread */
108     final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<>();
109     final HandlerChecker mMonitorChecker;
110     ContentResolver mResolver;
111     ActivityManagerService mActivity;
112 
113     int mPhonePid;
114     IActivityController mController;
115     boolean mAllowRestart = true;
116     final OpenFdMonitor mOpenFdMonitor;
117 
118     /**
119      * Used for checking status of handle threads and scheduling monitor callbacks.
120      */
121     public final class HandlerChecker implements Runnable {
122         private final Handler mHandler;
123         private final String mName;
124         private final long mWaitMax;
125         private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
126         private boolean mCompleted;
127         private Monitor mCurrentMonitor;
128         private long mStartTime;
129 
HandlerChecker(Handler handler, String name, long waitMaxMillis)130         HandlerChecker(Handler handler, String name, long waitMaxMillis) {
131             mHandler = handler;
132             mName = name;
133             mWaitMax = waitMaxMillis;
134             mCompleted = true;
135         }
136 
addMonitor(Monitor monitor)137         public void addMonitor(Monitor monitor) {
138             mMonitors.add(monitor);
139         }
140 
scheduleCheckLocked()141         public void scheduleCheckLocked() {
142             if (mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) {
143                 // If the target looper has recently been polling, then
144                 // there is no reason to enqueue our checker on it since that
145                 // is as good as it not being deadlocked.  This avoid having
146                 // to do a context switch to check the thread.  Note that we
147                 // only do this if mCheckReboot is false and we have no
148                 // monitors, since those would need to be executed at this point.
149                 mCompleted = true;
150                 return;
151             }
152 
153             if (!mCompleted) {
154                 // we already have a check in flight, so no need
155                 return;
156             }
157 
158             mCompleted = false;
159             mCurrentMonitor = null;
160             mStartTime = SystemClock.uptimeMillis();
161             mHandler.postAtFrontOfQueue(this);
162         }
163 
isOverdueLocked()164         public boolean isOverdueLocked() {
165             return (!mCompleted) && (SystemClock.uptimeMillis() > mStartTime + mWaitMax);
166         }
167 
getCompletionStateLocked()168         public int getCompletionStateLocked() {
169             if (mCompleted) {
170                 return COMPLETED;
171             } else {
172                 long latency = SystemClock.uptimeMillis() - mStartTime;
173                 if (latency < mWaitMax/2) {
174                     return WAITING;
175                 } else if (latency < mWaitMax) {
176                     return WAITED_HALF;
177                 }
178             }
179             return OVERDUE;
180         }
181 
getThread()182         public Thread getThread() {
183             return mHandler.getLooper().getThread();
184         }
185 
getName()186         public String getName() {
187             return mName;
188         }
189 
describeBlockedStateLocked()190         public String describeBlockedStateLocked() {
191             if (mCurrentMonitor == null) {
192                 return "Blocked in handler on " + mName + " (" + getThread().getName() + ")";
193             } else {
194                 return "Blocked in monitor " + mCurrentMonitor.getClass().getName()
195                         + " on " + mName + " (" + getThread().getName() + ")";
196             }
197         }
198 
199         @Override
run()200         public void run() {
201             final int size = mMonitors.size();
202             for (int i = 0 ; i < size ; i++) {
203                 synchronized (Watchdog.this) {
204                     mCurrentMonitor = mMonitors.get(i);
205                 }
206                 mCurrentMonitor.monitor();
207             }
208 
209             synchronized (Watchdog.this) {
210                 mCompleted = true;
211                 mCurrentMonitor = null;
212             }
213         }
214     }
215 
216     final class RebootRequestReceiver extends BroadcastReceiver {
217         @Override
onReceive(Context c, Intent intent)218         public void onReceive(Context c, Intent intent) {
219             if (intent.getIntExtra("nowait", 0) != 0) {
220                 rebootSystem("Received ACTION_REBOOT broadcast");
221                 return;
222             }
223             Slog.w(TAG, "Unsupported ACTION_REBOOT broadcast: " + intent);
224         }
225     }
226 
227     /** Monitor for checking the availability of binder threads. The monitor will block until
228      * there is a binder thread available to process in coming IPCs to make sure other processes
229      * can still communicate with the service.
230      */
231     private static final class BinderThreadMonitor implements Watchdog.Monitor {
232         @Override
monitor()233         public void monitor() {
234             Binder.blockUntilThreadAvailable();
235         }
236     }
237 
238     public interface Monitor {
monitor()239         void monitor();
240     }
241 
getInstance()242     public static Watchdog getInstance() {
243         if (sWatchdog == null) {
244             sWatchdog = new Watchdog();
245         }
246 
247         return sWatchdog;
248     }
249 
Watchdog()250     private Watchdog() {
251         super("watchdog");
252         // Initialize handler checkers for each common thread we want to check.  Note
253         // that we are not currently checking the background thread, since it can
254         // potentially hold longer running operations with no guarantees about the timeliness
255         // of operations there.
256 
257         // The shared foreground thread is the main checker.  It is where we
258         // will also dispatch monitor checks and do other work.
259         mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
260                 "foreground thread", DEFAULT_TIMEOUT);
261         mHandlerCheckers.add(mMonitorChecker);
262         // Add checker for main thread.  We only do a quick check since there
263         // can be UI running on the thread.
264         mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
265                 "main thread", DEFAULT_TIMEOUT));
266         // Add checker for shared UI thread.
267         mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),
268                 "ui thread", DEFAULT_TIMEOUT));
269         // And also check IO thread.
270         mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(),
271                 "i/o thread", DEFAULT_TIMEOUT));
272         // And the display thread.
273         mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),
274                 "display thread", DEFAULT_TIMEOUT));
275 
276         // Initialize monitor for Binder threads.
277         addMonitor(new BinderThreadMonitor());
278 
279         mOpenFdMonitor = OpenFdMonitor.create();
280 
281         // See the notes on DEFAULT_TIMEOUT.
282         assert DB ||
283                 DEFAULT_TIMEOUT > ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
284     }
285 
init(Context context, ActivityManagerService activity)286     public void init(Context context, ActivityManagerService activity) {
287         mResolver = context.getContentResolver();
288         mActivity = activity;
289 
290         context.registerReceiver(new RebootRequestReceiver(),
291                 new IntentFilter(Intent.ACTION_REBOOT),
292                 android.Manifest.permission.REBOOT, null);
293     }
294 
processStarted(String name, int pid)295     public void processStarted(String name, int pid) {
296         synchronized (this) {
297             if ("com.android.phone".equals(name)) {
298                 mPhonePid = pid;
299             }
300         }
301     }
302 
setActivityController(IActivityController controller)303     public void setActivityController(IActivityController controller) {
304         synchronized (this) {
305             mController = controller;
306         }
307     }
308 
setAllowRestart(boolean allowRestart)309     public void setAllowRestart(boolean allowRestart) {
310         synchronized (this) {
311             mAllowRestart = allowRestart;
312         }
313     }
314 
addMonitor(Monitor monitor)315     public void addMonitor(Monitor monitor) {
316         synchronized (this) {
317             if (isAlive()) {
318                 throw new RuntimeException("Monitors can't be added once the Watchdog is running");
319             }
320             mMonitorChecker.addMonitor(monitor);
321         }
322     }
323 
addThread(Handler thread)324     public void addThread(Handler thread) {
325         addThread(thread, DEFAULT_TIMEOUT);
326     }
327 
addThread(Handler thread, long timeoutMillis)328     public void addThread(Handler thread, long timeoutMillis) {
329         synchronized (this) {
330             if (isAlive()) {
331                 throw new RuntimeException("Threads can't be added once the Watchdog is running");
332             }
333             final String name = thread.getLooper().getThread().getName();
334             mHandlerCheckers.add(new HandlerChecker(thread, name, timeoutMillis));
335         }
336     }
337 
338     /**
339      * Perform a full reboot of the system.
340      */
rebootSystem(String reason)341     void rebootSystem(String reason) {
342         Slog.i(TAG, "Rebooting system because: " + reason);
343         IPowerManager pms = (IPowerManager)ServiceManager.getService(Context.POWER_SERVICE);
344         try {
345             pms.reboot(false, reason, false);
346         } catch (RemoteException ex) {
347         }
348     }
349 
evaluateCheckerCompletionLocked()350     private int evaluateCheckerCompletionLocked() {
351         int state = COMPLETED;
352         for (int i=0; i<mHandlerCheckers.size(); i++) {
353             HandlerChecker hc = mHandlerCheckers.get(i);
354             state = Math.max(state, hc.getCompletionStateLocked());
355         }
356         return state;
357     }
358 
getBlockedCheckersLocked()359     private ArrayList<HandlerChecker> getBlockedCheckersLocked() {
360         ArrayList<HandlerChecker> checkers = new ArrayList<HandlerChecker>();
361         for (int i=0; i<mHandlerCheckers.size(); i++) {
362             HandlerChecker hc = mHandlerCheckers.get(i);
363             if (hc.isOverdueLocked()) {
364                 checkers.add(hc);
365             }
366         }
367         return checkers;
368     }
369 
describeCheckersLocked(List<HandlerChecker> checkers)370     private String describeCheckersLocked(List<HandlerChecker> checkers) {
371         StringBuilder builder = new StringBuilder(128);
372         for (int i=0; i<checkers.size(); i++) {
373             if (builder.length() > 0) {
374                 builder.append(", ");
375             }
376             builder.append(checkers.get(i).describeBlockedStateLocked());
377         }
378         return builder.toString();
379     }
380 
getInterestingHalPids()381     private ArrayList<Integer> getInterestingHalPids() {
382         try {
383             IServiceManager serviceManager = IServiceManager.getService();
384             ArrayList<IServiceManager.InstanceDebugInfo> dump =
385                     serviceManager.debugDump();
386             HashSet<Integer> pids = new HashSet<>();
387             for (IServiceManager.InstanceDebugInfo info : dump) {
388                 if (info.pid == IServiceManager.PidConstant.NO_PID) {
389                     continue;
390                 }
391 
392                 if (!HAL_INTERFACES_OF_INTEREST.contains(info.interfaceName)) {
393                     continue;
394                 }
395 
396                 pids.add(info.pid);
397             }
398             return new ArrayList<Integer>(pids);
399         } catch (RemoteException e) {
400             return new ArrayList<Integer>();
401         }
402     }
403 
getInterestingNativePids()404     private ArrayList<Integer> getInterestingNativePids() {
405         ArrayList<Integer> pids = getInterestingHalPids();
406 
407         int[] nativePids = Process.getPidsForCommands(NATIVE_STACKS_OF_INTEREST);
408         if (nativePids != null) {
409             pids.ensureCapacity(pids.size() + nativePids.length);
410             for (int i : nativePids) {
411                 pids.add(i);
412             }
413         }
414 
415         return pids;
416     }
417 
418     @Override
run()419     public void run() {
420         boolean waitedHalf = false;
421         while (true) {
422             final List<HandlerChecker> blockedCheckers;
423             final String subject;
424             final boolean allowRestart;
425             int debuggerWasConnected = 0;
426             synchronized (this) {
427                 long timeout = CHECK_INTERVAL;
428                 // Make sure we (re)spin the checkers that have become idle within
429                 // this wait-and-check interval
430                 for (int i=0; i<mHandlerCheckers.size(); i++) {
431                     HandlerChecker hc = mHandlerCheckers.get(i);
432                     hc.scheduleCheckLocked();
433                 }
434 
435                 if (debuggerWasConnected > 0) {
436                     debuggerWasConnected--;
437                 }
438 
439                 // NOTE: We use uptimeMillis() here because we do not want to increment the time we
440                 // wait while asleep. If the device is asleep then the thing that we are waiting
441                 // to timeout on is asleep as well and won't have a chance to run, causing a false
442                 // positive on when to kill things.
443                 long start = SystemClock.uptimeMillis();
444                 while (timeout > 0) {
445                     if (Debug.isDebuggerConnected()) {
446                         debuggerWasConnected = 2;
447                     }
448                     try {
449                         wait(timeout);
450                     } catch (InterruptedException e) {
451                         Log.wtf(TAG, e);
452                     }
453                     if (Debug.isDebuggerConnected()) {
454                         debuggerWasConnected = 2;
455                     }
456                     timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
457                 }
458 
459                 boolean fdLimitTriggered = false;
460                 if (mOpenFdMonitor != null) {
461                     fdLimitTriggered = mOpenFdMonitor.monitor();
462                 }
463 
464                 if (!fdLimitTriggered) {
465                     final int waitState = evaluateCheckerCompletionLocked();
466                     if (waitState == COMPLETED) {
467                         // The monitors have returned; reset
468                         waitedHalf = false;
469                         continue;
470                     } else if (waitState == WAITING) {
471                         // still waiting but within their configured intervals; back off and recheck
472                         continue;
473                     } else if (waitState == WAITED_HALF) {
474                         if (!waitedHalf) {
475                             // We've waited half the deadlock-detection interval.  Pull a stack
476                             // trace and wait another half.
477                             ArrayList<Integer> pids = new ArrayList<Integer>();
478                             pids.add(Process.myPid());
479                             ActivityManagerService.dumpStackTraces(true, pids, null, null,
480                                 getInterestingNativePids());
481                             waitedHalf = true;
482                         }
483                         continue;
484                     }
485 
486                     // something is overdue!
487                     blockedCheckers = getBlockedCheckersLocked();
488                     subject = describeCheckersLocked(blockedCheckers);
489                 } else {
490                     blockedCheckers = Collections.emptyList();
491                     subject = "Open FD high water mark reached";
492                 }
493                 allowRestart = mAllowRestart;
494             }
495 
496             // If we got here, that means that the system is most likely hung.
497             // First collect stack traces from all threads of the system process.
498             // Then kill this process so that the system will restart.
499             EventLog.writeEvent(EventLogTags.WATCHDOG, subject);
500 
501             ArrayList<Integer> pids = new ArrayList<>();
502             pids.add(Process.myPid());
503             if (mPhonePid > 0) pids.add(mPhonePid);
504             // Pass !waitedHalf so that just in case we somehow wind up here without having
505             // dumped the halfway stacks, we properly re-initialize the trace file.
506             final File stack = ActivityManagerService.dumpStackTraces(
507                     !waitedHalf, pids, null, null, getInterestingNativePids());
508 
509             // Give some extra time to make sure the stack traces get written.
510             // The system's been hanging for a minute, another second or two won't hurt much.
511             SystemClock.sleep(2000);
512 
513             // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel log
514             doSysRq('w');
515             doSysRq('l');
516 
517             // Try to add the error to the dropbox, but assuming that the ActivityManager
518             // itself may be deadlocked.  (which has happened, causing this statement to
519             // deadlock and the watchdog as a whole to be ineffective)
520             Thread dropboxThread = new Thread("watchdogWriteToDropbox") {
521                     public void run() {
522                         mActivity.addErrorToDropBox(
523                                 "watchdog", null, "system_server", null, null,
524                                 subject, null, stack, null);
525                     }
526                 };
527             dropboxThread.start();
528             try {
529                 dropboxThread.join(2000);  // wait up to 2 seconds for it to return.
530             } catch (InterruptedException ignored) {}
531 
532             IActivityController controller;
533             synchronized (this) {
534                 controller = mController;
535             }
536             if (controller != null) {
537                 Slog.i(TAG, "Reporting stuck state to activity controller");
538                 try {
539                     Binder.setDumpDisabled("Service dumps disabled due to hung system process.");
540                     // 1 = keep waiting, -1 = kill system
541                     int res = controller.systemNotResponding(subject);
542                     if (res >= 0) {
543                         Slog.i(TAG, "Activity controller requested to coninue to wait");
544                         waitedHalf = false;
545                         continue;
546                     }
547                 } catch (RemoteException e) {
548                 }
549             }
550 
551             // Only kill the process if the debugger is not attached.
552             if (Debug.isDebuggerConnected()) {
553                 debuggerWasConnected = 2;
554             }
555             if (debuggerWasConnected >= 2) {
556                 Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
557             } else if (debuggerWasConnected > 0) {
558                 Slog.w(TAG, "Debugger was connected: Watchdog is *not* killing the system process");
559             } else if (!allowRestart) {
560                 Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process");
561             } else {
562                 Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
563                 WatchdogDiagnostics.diagnoseCheckers(blockedCheckers);
564                 Slog.w(TAG, "*** GOODBYE!");
565                 Process.killProcess(Process.myPid());
566                 System.exit(10);
567             }
568 
569             waitedHalf = false;
570         }
571     }
572 
doSysRq(char c)573     private void doSysRq(char c) {
574         try {
575             FileWriter sysrq_trigger = new FileWriter("/proc/sysrq-trigger");
576             sysrq_trigger.write(c);
577             sysrq_trigger.close();
578         } catch (IOException e) {
579             Slog.w(TAG, "Failed to write to /proc/sysrq-trigger", e);
580         }
581     }
582 
583     public static final class OpenFdMonitor {
584         /**
585          * Number of FDs below the soft limit that we trigger a runtime restart at. This was
586          * chosen arbitrarily, but will need to be at least 6 in order to have a sufficient number
587          * of FDs in reserve to complete a dump.
588          */
589         private static final int FD_HIGH_WATER_MARK = 12;
590 
591         private final File mDumpDir;
592         private final File mFdHighWaterMark;
593 
create()594         public static OpenFdMonitor create() {
595             // Only run the FD monitor on debuggable builds (such as userdebug and eng builds).
596             if (!Build.IS_DEBUGGABLE) {
597                 return null;
598             }
599 
600             // Don't run the FD monitor on builds that have a global ANR trace file. We're using
601             // the ANR trace directory as a quick hack in order to get these traces in bugreports
602             // and we wouldn't want to overwrite something important.
603             final String dumpDirStr = SystemProperties.get("dalvik.vm.stack-trace-dir", "");
604             if (dumpDirStr.isEmpty()) {
605                 return null;
606             }
607 
608             final StructRlimit rlimit;
609             try {
610                 rlimit = android.system.Os.getrlimit(OsConstants.RLIMIT_NOFILE);
611             } catch (ErrnoException errno) {
612                 Slog.w(TAG, "Error thrown from getrlimit(RLIMIT_NOFILE)", errno);
613                 return null;
614             }
615 
616             // The assumption we're making here is that FD numbers are allocated (more or less)
617             // sequentially, which is currently (and historically) true since open is currently
618             // specified to always return the lowest-numbered non-open file descriptor for the
619             // current process.
620             //
621             // We do this to avoid having to enumerate the contents of /proc/self/fd in order to
622             // count the number of descriptors open in the process.
623             final File fdThreshold = new File("/proc/self/fd/" + (rlimit.rlim_cur - FD_HIGH_WATER_MARK));
624             return new OpenFdMonitor(new File(dumpDirStr), fdThreshold);
625         }
626 
OpenFdMonitor(File dumpDir, File fdThreshold)627         OpenFdMonitor(File dumpDir, File fdThreshold) {
628             mDumpDir = dumpDir;
629             mFdHighWaterMark = fdThreshold;
630         }
631 
dumpOpenDescriptors()632         private void dumpOpenDescriptors() {
633             try {
634                 File dumpFile = File.createTempFile("anr_fd_", "", mDumpDir);
635                 java.lang.Process proc = new ProcessBuilder()
636                     .command("/system/bin/lsof", "-p", String.valueOf(Process.myPid()))
637                     .redirectErrorStream(true)
638                     .redirectOutput(dumpFile)
639                     .start();
640 
641                 int returnCode = proc.waitFor();
642                 if (returnCode != 0) {
643                     Slog.w(TAG, "Unable to dump open descriptors, lsof return code: "
644                         + returnCode);
645                     dumpFile.delete();
646                 }
647             } catch (IOException | InterruptedException ex) {
648                 Slog.w(TAG, "Unable to dump open descriptors: " + ex);
649             }
650         }
651 
652         /**
653          * @return {@code true} if the high water mark was breached and a dump was written,
654          *     {@code false} otherwise.
655          */
monitor()656         public boolean monitor() {
657             if (mFdHighWaterMark.exists()) {
658                 dumpOpenDescriptors();
659                 return true;
660             }
661 
662             return false;
663         }
664     }
665 }
666