/*
|
* Copyright (C) 2008 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.server;
|
|
import android.app.IActivityController;
|
import android.content.BroadcastReceiver;
|
import android.content.Context;
|
import android.content.Intent;
|
import android.content.IntentFilter;
|
import android.hidl.manager.V1_0.IServiceManager;
|
import android.os.Binder;
|
import android.os.Build;
|
import android.os.Debug;
|
import android.os.Handler;
|
import android.os.IPowerManager;
|
import android.os.Looper;
|
import android.os.Process;
|
import android.os.RemoteException;
|
import android.os.ServiceManager;
|
import android.os.SystemClock;
|
import android.system.ErrnoException;
|
import android.system.Os;
|
import android.system.OsConstants;
|
import android.system.StructRlimit;
|
import android.util.EventLog;
|
import android.util.Log;
|
import android.util.Slog;
|
import android.util.StatsLog;
|
|
import com.android.internal.os.ZygoteConnectionConstants;
|
import com.android.server.am.ActivityManagerService;
|
import com.android.server.wm.SurfaceAnimationThread;
|
|
import java.io.File;
|
import java.io.FileWriter;
|
import java.io.IOException;
|
import java.nio.charset.StandardCharsets;
|
import java.nio.file.Files;
|
import java.nio.file.Path;
|
import java.nio.file.Paths;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.HashSet;
|
import java.util.List;
|
|
/** This class calls its monitor every minute. Killing this process if they don't return **/
|
public class Watchdog extends Thread {
|
static final String TAG = "Watchdog";
|
|
/** Debug flag. */
|
public static final boolean DEBUG = false;
|
|
// Set this to true to use debug default values.
|
static final boolean DB = false;
|
|
// Note 1: Do not lower this value below thirty seconds without tightening the invoke-with
|
// timeout in com.android.internal.os.ZygoteConnection, or wrapped applications
|
// can trigger the watchdog.
|
// Note 2: The debug value is already below the wait time in ZygoteConnection. Wrapped
|
// applications may not work with a debug build. CTS will fail.
|
static final long DEFAULT_TIMEOUT = DB ? 10*1000 : 60*1000;
|
static final long CHECK_INTERVAL = DEFAULT_TIMEOUT / 2;
|
|
// These are temporally ordered: larger values as lateness increases
|
static final int COMPLETED = 0;
|
static final int WAITING = 1;
|
static final int WAITED_HALF = 2;
|
static final int OVERDUE = 3;
|
|
// Which native processes to dump into dropbox's stack traces
|
public static final String[] NATIVE_STACKS_OF_INTEREST = new String[] {
|
"/system/bin/audioserver",
|
"/system/bin/cameraserver",
|
"/system/bin/drmserver",
|
"/system/bin/mediadrmserver",
|
"/system/bin/mediaserver",
|
"/system/bin/sdcard",
|
"/system/bin/surfaceflinger",
|
"/system/bin/vold",
|
"media.extractor", // system/bin/mediaextractor
|
"media.metrics", // system/bin/mediametrics
|
"media.codec", // vendor/bin/hw/android.hardware.media.omx@1.0-service
|
"media.swcodec", // /apex/com.android.media.swcodec/bin/mediaswcodec
|
"com.android.bluetooth", // Bluetooth service
|
"/system/bin/statsd", // Stats daemon
|
};
|
|
public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
|
"android.hardware.audio@2.0::IDevicesFactory",
|
"android.hardware.audio@4.0::IDevicesFactory",
|
"android.hardware.bluetooth@1.0::IBluetoothHci",
|
"android.hardware.camera.provider@2.4::ICameraProvider",
|
"android.hardware.graphics.allocator@2.0::IAllocator",
|
"android.hardware.graphics.composer@2.1::IComposer",
|
"android.hardware.health@2.0::IHealth",
|
"android.hardware.media.c2@1.0::IComponentStore",
|
"android.hardware.media.omx@1.0::IOmx",
|
"android.hardware.media.omx@1.0::IOmxStore",
|
"android.hardware.sensors@1.0::ISensors",
|
"android.hardware.vr@1.0::IVr",
|
"android.hardware.biometrics.face@1.0::IBiometricsFace"
|
);
|
|
static Watchdog sWatchdog;
|
|
/* This handler will be used to post message back onto the main thread */
|
final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<>();
|
final HandlerChecker mMonitorChecker;
|
ActivityManagerService mActivity;
|
|
int mPhonePid;
|
IActivityController mController;
|
boolean mAllowRestart = true;
|
final OpenFdMonitor mOpenFdMonitor;
|
|
/**
|
* Used for checking status of handle threads and scheduling monitor callbacks.
|
*/
|
public final class HandlerChecker implements Runnable {
|
private final Handler mHandler;
|
private final String mName;
|
private final long mWaitMax;
|
private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
|
private final ArrayList<Monitor> mMonitorQueue = new ArrayList<Monitor>();
|
private boolean mCompleted;
|
private Monitor mCurrentMonitor;
|
private long mStartTime;
|
private int mPauseCount;
|
|
HandlerChecker(Handler handler, String name, long waitMaxMillis) {
|
mHandler = handler;
|
mName = name;
|
mWaitMax = waitMaxMillis;
|
mCompleted = true;
|
}
|
|
void addMonitorLocked(Monitor monitor) {
|
// We don't want to update mMonitors when the Handler is in the middle of checking
|
// all monitors. We will update mMonitors on the next schedule if it is safe
|
mMonitorQueue.add(monitor);
|
}
|
|
public void scheduleCheckLocked() {
|
if (mCompleted) {
|
// Safe to update monitors in queue, Handler is not in the middle of work
|
mMonitors.addAll(mMonitorQueue);
|
mMonitorQueue.clear();
|
}
|
if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
|
|| (mPauseCount > 0)) {
|
// Don't schedule until after resume OR
|
// If the target looper has recently been polling, then
|
// there is no reason to enqueue our checker on it since that
|
// is as good as it not being deadlocked. This avoid having
|
// to do a context switch to check the thread. Note that we
|
// only do this if we have no monitors since those would need to
|
// be executed at this point.
|
mCompleted = true;
|
return;
|
}
|
if (!mCompleted) {
|
// we already have a check in flight, so no need
|
return;
|
}
|
|
mCompleted = false;
|
mCurrentMonitor = null;
|
mStartTime = SystemClock.uptimeMillis();
|
mHandler.postAtFrontOfQueue(this);
|
}
|
|
boolean isOverdueLocked() {
|
return (!mCompleted) && (SystemClock.uptimeMillis() > mStartTime + mWaitMax);
|
}
|
|
public int getCompletionStateLocked() {
|
if (mCompleted) {
|
return COMPLETED;
|
} else {
|
long latency = SystemClock.uptimeMillis() - mStartTime;
|
if (latency < mWaitMax/2) {
|
return WAITING;
|
} else if (latency < mWaitMax) {
|
return WAITED_HALF;
|
}
|
}
|
return OVERDUE;
|
}
|
|
public Thread getThread() {
|
return mHandler.getLooper().getThread();
|
}
|
|
public String getName() {
|
return mName;
|
}
|
|
String describeBlockedStateLocked() {
|
if (mCurrentMonitor == null) {
|
return "Blocked in handler on " + mName + " (" + getThread().getName() + ")";
|
} else {
|
return "Blocked in monitor " + mCurrentMonitor.getClass().getName()
|
+ " on " + mName + " (" + getThread().getName() + ")";
|
}
|
}
|
|
@Override
|
public void run() {
|
// Once we get here, we ensure that mMonitors does not change even if we call
|
// #addMonitorLocked because we first add the new monitors to mMonitorQueue and
|
// move them to mMonitors on the next schedule when mCompleted is true, at which
|
// point we have completed execution of this method.
|
final int size = mMonitors.size();
|
for (int i = 0 ; i < size ; i++) {
|
synchronized (Watchdog.this) {
|
mCurrentMonitor = mMonitors.get(i);
|
}
|
mCurrentMonitor.monitor();
|
}
|
|
synchronized (Watchdog.this) {
|
mCompleted = true;
|
mCurrentMonitor = null;
|
}
|
}
|
|
/** Pause the HandlerChecker. */
|
public void pauseLocked(String reason) {
|
mPauseCount++;
|
// Mark as completed, because there's a chance we called this after the watchog
|
// thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
|
// the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
|
mCompleted = true;
|
Slog.i(TAG, "Pausing HandlerChecker: " + mName + " for reason: "
|
+ reason + ". Pause count: " + mPauseCount);
|
}
|
|
/** Resume the HandlerChecker from the last {@link #pauseLocked}. */
|
public void resumeLocked(String reason) {
|
if (mPauseCount > 0) {
|
mPauseCount--;
|
Slog.i(TAG, "Resuming HandlerChecker: " + mName + " for reason: "
|
+ reason + ". Pause count: " + mPauseCount);
|
} else {
|
Slog.wtf(TAG, "Already resumed HandlerChecker: " + mName);
|
}
|
}
|
}
|
|
final class RebootRequestReceiver extends BroadcastReceiver {
|
@Override
|
public void onReceive(Context c, Intent intent) {
|
if (intent.getIntExtra("nowait", 0) != 0) {
|
rebootSystem("Received ACTION_REBOOT broadcast");
|
return;
|
}
|
Slog.w(TAG, "Unsupported ACTION_REBOOT broadcast: " + intent);
|
}
|
}
|
|
/** Monitor for checking the availability of binder threads. The monitor will block until
|
* there is a binder thread available to process in coming IPCs to make sure other processes
|
* can still communicate with the service.
|
*/
|
private static final class BinderThreadMonitor implements Watchdog.Monitor {
|
@Override
|
public void monitor() {
|
Binder.blockUntilThreadAvailable();
|
}
|
}
|
|
public interface Monitor {
|
void monitor();
|
}
|
|
public static Watchdog getInstance() {
|
if (sWatchdog == null) {
|
sWatchdog = new Watchdog();
|
}
|
|
return sWatchdog;
|
}
|
|
private Watchdog() {
|
super("watchdog");
|
// Initialize handler checkers for each common thread we want to check. Note
|
// that we are not currently checking the background thread, since it can
|
// potentially hold longer running operations with no guarantees about the timeliness
|
// of operations there.
|
|
// The shared foreground thread is the main checker. It is where we
|
// will also dispatch monitor checks and do other work.
|
mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
|
"foreground thread", DEFAULT_TIMEOUT);
|
mHandlerCheckers.add(mMonitorChecker);
|
// Add checker for main thread. We only do a quick check since there
|
// can be UI running on the thread.
|
mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
|
"main thread", DEFAULT_TIMEOUT));
|
// Add checker for shared UI thread.
|
mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),
|
"ui thread", DEFAULT_TIMEOUT));
|
// And also check IO thread.
|
mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(),
|
"i/o thread", DEFAULT_TIMEOUT));
|
// And the display thread.
|
mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),
|
"display thread", DEFAULT_TIMEOUT));
|
// And the animation thread.
|
mHandlerCheckers.add(new HandlerChecker(AnimationThread.getHandler(),
|
"animation thread", DEFAULT_TIMEOUT));
|
// And the surface animation thread.
|
mHandlerCheckers.add(new HandlerChecker(SurfaceAnimationThread.getHandler(),
|
"surface animation thread", DEFAULT_TIMEOUT));
|
|
// Initialize monitor for Binder threads.
|
addMonitor(new BinderThreadMonitor());
|
|
mOpenFdMonitor = OpenFdMonitor.create();
|
|
// See the notes on DEFAULT_TIMEOUT.
|
assert DB ||
|
DEFAULT_TIMEOUT > ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
|
}
|
|
/**
|
* Registers a {@link BroadcastReceiver} to listen to reboot broadcasts and trigger reboot.
|
* Should be called during boot after the ActivityManagerService is up and registered
|
* as a system service so it can handle registration of a {@link BroadcastReceiver}.
|
*/
|
public void init(Context context, ActivityManagerService activity) {
|
mActivity = activity;
|
context.registerReceiver(new RebootRequestReceiver(),
|
new IntentFilter(Intent.ACTION_REBOOT),
|
android.Manifest.permission.REBOOT, null);
|
}
|
|
public void processStarted(String name, int pid) {
|
synchronized (this) {
|
if ("com.android.phone".equals(name)) {
|
mPhonePid = pid;
|
}
|
}
|
}
|
|
public void setActivityController(IActivityController controller) {
|
synchronized (this) {
|
mController = controller;
|
}
|
}
|
|
public void setAllowRestart(boolean allowRestart) {
|
synchronized (this) {
|
mAllowRestart = allowRestart;
|
}
|
}
|
|
public void addMonitor(Monitor monitor) {
|
synchronized (this) {
|
mMonitorChecker.addMonitorLocked(monitor);
|
}
|
}
|
|
public void addThread(Handler thread) {
|
addThread(thread, DEFAULT_TIMEOUT);
|
}
|
|
public void addThread(Handler thread, long timeoutMillis) {
|
synchronized (this) {
|
final String name = thread.getLooper().getThread().getName();
|
mHandlerCheckers.add(new HandlerChecker(thread, name, timeoutMillis));
|
}
|
}
|
|
/**
|
* Pauses Watchdog action for the currently running thread. Useful before executing long running
|
* operations that could falsely trigger the watchdog. Each call to this will require a matching
|
* call to {@link #resumeWatchingCurrentThread}.
|
*
|
* <p>If the current thread has not been added to the Watchdog, this call is a no-op.
|
*
|
* <p>If the Watchdog is already paused for the current thread, this call adds
|
* adds another pause and will require an additional {@link #resumeCurrentThread} to resume.
|
*
|
* <p>Note: Use with care, as any deadlocks on the current thread will be undetected until all
|
* pauses have been resumed.
|
*/
|
public void pauseWatchingCurrentThread(String reason) {
|
synchronized (this) {
|
for (HandlerChecker hc : mHandlerCheckers) {
|
if (Thread.currentThread().equals(hc.getThread())) {
|
hc.pauseLocked(reason);
|
}
|
}
|
}
|
}
|
|
/**
|
* Resumes the last pause from {@link #pauseWatchingCurrentThread} for the currently running
|
* thread.
|
*
|
* <p>If the current thread has not been added to the Watchdog, this call is a no-op.
|
*
|
* <p>If the Watchdog action for the current thread is already resumed, this call logs a wtf.
|
*
|
* <p>If all pauses have been resumed, the Watchdog action is finally resumed, otherwise,
|
* the Watchdog action for the current thread remains paused until resume is called at least
|
* as many times as the calls to pause.
|
*/
|
public void resumeWatchingCurrentThread(String reason) {
|
synchronized (this) {
|
for (HandlerChecker hc : mHandlerCheckers) {
|
if (Thread.currentThread().equals(hc.getThread())) {
|
hc.resumeLocked(reason);
|
}
|
}
|
}
|
}
|
|
/**
|
* Perform a full reboot of the system.
|
*/
|
void rebootSystem(String reason) {
|
Slog.i(TAG, "Rebooting system because: " + reason);
|
IPowerManager pms = (IPowerManager)ServiceManager.getService(Context.POWER_SERVICE);
|
try {
|
pms.reboot(false, reason, false);
|
} catch (RemoteException ex) {
|
}
|
}
|
|
private int evaluateCheckerCompletionLocked() {
|
int state = COMPLETED;
|
for (int i=0; i<mHandlerCheckers.size(); i++) {
|
HandlerChecker hc = mHandlerCheckers.get(i);
|
state = Math.max(state, hc.getCompletionStateLocked());
|
}
|
return state;
|
}
|
|
private ArrayList<HandlerChecker> getBlockedCheckersLocked() {
|
ArrayList<HandlerChecker> checkers = new ArrayList<HandlerChecker>();
|
for (int i=0; i<mHandlerCheckers.size(); i++) {
|
HandlerChecker hc = mHandlerCheckers.get(i);
|
if (hc.isOverdueLocked()) {
|
checkers.add(hc);
|
}
|
}
|
return checkers;
|
}
|
|
private String describeCheckersLocked(List<HandlerChecker> checkers) {
|
StringBuilder builder = new StringBuilder(128);
|
for (int i=0; i<checkers.size(); i++) {
|
if (builder.length() > 0) {
|
builder.append(", ");
|
}
|
builder.append(checkers.get(i).describeBlockedStateLocked());
|
}
|
return builder.toString();
|
}
|
|
private static ArrayList<Integer> getInterestingHalPids() {
|
try {
|
IServiceManager serviceManager = IServiceManager.getService();
|
ArrayList<IServiceManager.InstanceDebugInfo> dump =
|
serviceManager.debugDump();
|
HashSet<Integer> pids = new HashSet<>();
|
for (IServiceManager.InstanceDebugInfo info : dump) {
|
if (info.pid == IServiceManager.PidConstant.NO_PID) {
|
continue;
|
}
|
|
if (!HAL_INTERFACES_OF_INTEREST.contains(info.interfaceName)) {
|
continue;
|
}
|
|
pids.add(info.pid);
|
}
|
return new ArrayList<Integer>(pids);
|
} catch (RemoteException e) {
|
return new ArrayList<Integer>();
|
}
|
}
|
|
static ArrayList<Integer> getInterestingNativePids() {
|
ArrayList<Integer> pids = getInterestingHalPids();
|
|
int[] nativePids = Process.getPidsForCommands(NATIVE_STACKS_OF_INTEREST);
|
if (nativePids != null) {
|
pids.ensureCapacity(pids.size() + nativePids.length);
|
for (int i : nativePids) {
|
pids.add(i);
|
}
|
}
|
|
return pids;
|
}
|
|
@Override
|
public void run() {
|
boolean waitedHalf = false;
|
while (true) {
|
final List<HandlerChecker> blockedCheckers;
|
final String subject;
|
final boolean allowRestart;
|
int debuggerWasConnected = 0;
|
synchronized (this) {
|
long timeout = CHECK_INTERVAL;
|
// Make sure we (re)spin the checkers that have become idle within
|
// this wait-and-check interval
|
for (int i=0; i<mHandlerCheckers.size(); i++) {
|
HandlerChecker hc = mHandlerCheckers.get(i);
|
hc.scheduleCheckLocked();
|
}
|
|
if (debuggerWasConnected > 0) {
|
debuggerWasConnected--;
|
}
|
|
// NOTE: We use uptimeMillis() here because we do not want to increment the time we
|
// wait while asleep. If the device is asleep then the thing that we are waiting
|
// to timeout on is asleep as well and won't have a chance to run, causing a false
|
// positive on when to kill things.
|
long start = SystemClock.uptimeMillis();
|
while (timeout > 0) {
|
if (Debug.isDebuggerConnected()) {
|
debuggerWasConnected = 2;
|
}
|
try {
|
wait(timeout);
|
// Note: mHandlerCheckers and mMonitorChecker may have changed after waiting
|
} catch (InterruptedException e) {
|
Log.wtf(TAG, e);
|
}
|
if (Debug.isDebuggerConnected()) {
|
debuggerWasConnected = 2;
|
}
|
timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
|
}
|
|
boolean fdLimitTriggered = false;
|
if (mOpenFdMonitor != null) {
|
fdLimitTriggered = mOpenFdMonitor.monitor();
|
}
|
|
if (!fdLimitTriggered) {
|
final int waitState = evaluateCheckerCompletionLocked();
|
if (waitState == COMPLETED) {
|
// The monitors have returned; reset
|
waitedHalf = false;
|
continue;
|
} else if (waitState == WAITING) {
|
// still waiting but within their configured intervals; back off and recheck
|
continue;
|
} else if (waitState == WAITED_HALF) {
|
if (!waitedHalf) {
|
Slog.i(TAG, "WAITED_HALF");
|
// We've waited half the deadlock-detection interval. Pull a stack
|
// trace and wait another half.
|
ArrayList<Integer> pids = new ArrayList<Integer>();
|
pids.add(Process.myPid());
|
ActivityManagerService.dumpStackTraces(pids, null, null,
|
getInterestingNativePids());
|
waitedHalf = true;
|
}
|
continue;
|
}
|
|
// something is overdue!
|
blockedCheckers = getBlockedCheckersLocked();
|
subject = describeCheckersLocked(blockedCheckers);
|
} else {
|
blockedCheckers = Collections.emptyList();
|
subject = "Open FD high water mark reached";
|
}
|
allowRestart = mAllowRestart;
|
}
|
|
// If we got here, that means that the system is most likely hung.
|
// First collect stack traces from all threads of the system process.
|
// Then kill this process so that the system will restart.
|
EventLog.writeEvent(EventLogTags.WATCHDOG, subject);
|
|
ArrayList<Integer> pids = new ArrayList<>();
|
pids.add(Process.myPid());
|
if (mPhonePid > 0) pids.add(mPhonePid);
|
|
final File stack = ActivityManagerService.dumpStackTraces(
|
pids, null, null, getInterestingNativePids());
|
|
// Give some extra time to make sure the stack traces get written.
|
// The system's been hanging for a minute, another second or two won't hurt much.
|
SystemClock.sleep(5000);
|
|
// Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel log
|
doSysRq('w');
|
doSysRq('l');
|
|
// Try to add the error to the dropbox, but assuming that the ActivityManager
|
// itself may be deadlocked. (which has happened, causing this statement to
|
// deadlock and the watchdog as a whole to be ineffective)
|
Thread dropboxThread = new Thread("watchdogWriteToDropbox") {
|
public void run() {
|
// If a watched thread hangs before init() is called, we don't have a
|
// valid mActivity. So we can't log the error to dropbox.
|
if (mActivity != null) {
|
mActivity.addErrorToDropBox(
|
"watchdog", null, "system_server", null, null, null,
|
subject, null, stack, null);
|
}
|
StatsLog.write(StatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject);
|
}
|
};
|
dropboxThread.start();
|
try {
|
dropboxThread.join(2000); // wait up to 2 seconds for it to return.
|
} catch (InterruptedException ignored) {}
|
|
IActivityController controller;
|
synchronized (this) {
|
controller = mController;
|
}
|
if (controller != null) {
|
Slog.i(TAG, "Reporting stuck state to activity controller");
|
try {
|
Binder.setDumpDisabled("Service dumps disabled due to hung system process.");
|
// 1 = keep waiting, -1 = kill system
|
int res = controller.systemNotResponding(subject);
|
if (res >= 0) {
|
Slog.i(TAG, "Activity controller requested to coninue to wait");
|
waitedHalf = false;
|
continue;
|
}
|
} catch (RemoteException e) {
|
}
|
}
|
|
// Only kill the process if the debugger is not attached.
|
if (Debug.isDebuggerConnected()) {
|
debuggerWasConnected = 2;
|
}
|
if (debuggerWasConnected >= 2) {
|
Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
|
} else if (debuggerWasConnected > 0) {
|
Slog.w(TAG, "Debugger was connected: Watchdog is *not* killing the system process");
|
} else if (!allowRestart) {
|
Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process");
|
} else {
|
Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
|
WatchdogDiagnostics.diagnoseCheckers(blockedCheckers);
|
Slog.w(TAG, "*** GOODBYE!");
|
Process.killProcess(Process.myPid());
|
System.exit(10);
|
}
|
|
waitedHalf = false;
|
}
|
}
|
|
private void doSysRq(char c) {
|
try {
|
FileWriter sysrq_trigger = new FileWriter("/proc/sysrq-trigger");
|
sysrq_trigger.write(c);
|
sysrq_trigger.close();
|
} catch (IOException e) {
|
Slog.w(TAG, "Failed to write to /proc/sysrq-trigger", e);
|
}
|
}
|
|
public static final class OpenFdMonitor {
|
/**
|
* Number of FDs below the soft limit that we trigger a runtime restart at. This was
|
* chosen arbitrarily, but will need to be at least 6 in order to have a sufficient number
|
* of FDs in reserve to complete a dump.
|
*/
|
private static final int FD_HIGH_WATER_MARK = 12;
|
|
private final File mDumpDir;
|
private final File mFdHighWaterMark;
|
|
public static OpenFdMonitor create() {
|
// Only run the FD monitor on debuggable builds (such as userdebug and eng builds).
|
if (!Build.IS_DEBUGGABLE) {
|
return null;
|
}
|
|
final StructRlimit rlimit;
|
try {
|
rlimit = android.system.Os.getrlimit(OsConstants.RLIMIT_NOFILE);
|
} catch (ErrnoException errno) {
|
Slog.w(TAG, "Error thrown from getrlimit(RLIMIT_NOFILE)", errno);
|
return null;
|
}
|
|
// The assumption we're making here is that FD numbers are allocated (more or less)
|
// sequentially, which is currently (and historically) true since open is currently
|
// specified to always return the lowest-numbered non-open file descriptor for the
|
// current process.
|
//
|
// We do this to avoid having to enumerate the contents of /proc/self/fd in order to
|
// count the number of descriptors open in the process.
|
final File fdThreshold = new File("/proc/self/fd/" + (rlimit.rlim_cur - FD_HIGH_WATER_MARK));
|
return new OpenFdMonitor(new File("/data/anr"), fdThreshold);
|
}
|
|
OpenFdMonitor(File dumpDir, File fdThreshold) {
|
mDumpDir = dumpDir;
|
mFdHighWaterMark = fdThreshold;
|
}
|
|
/**
|
* Dumps open file descriptors and their full paths to a temporary file in {@code mDumpDir}.
|
*/
|
private void dumpOpenDescriptors() {
|
// We cannot exec lsof to get more info about open file descriptors because a newly
|
// forked process will not have the permissions to readlink. Instead list all open
|
// descriptors from /proc/pid/fd and resolve them.
|
List<String> dumpInfo = new ArrayList<>();
|
String fdDirPath = String.format("/proc/%d/fd/", Process.myPid());
|
File[] fds = new File(fdDirPath).listFiles();
|
if (fds == null) {
|
dumpInfo.add("Unable to list " + fdDirPath);
|
} else {
|
for (File f : fds) {
|
String fdSymLink = f.getAbsolutePath();
|
String resolvedPath = "";
|
try {
|
resolvedPath = Os.readlink(fdSymLink);
|
} catch (ErrnoException ex) {
|
resolvedPath = ex.getMessage();
|
}
|
dumpInfo.add(fdSymLink + "\t" + resolvedPath);
|
}
|
}
|
|
// Dump the fds & paths to a temp file.
|
try {
|
File dumpFile = File.createTempFile("anr_fd_", "", mDumpDir);
|
Path out = Paths.get(dumpFile.getAbsolutePath());
|
Files.write(out, dumpInfo, StandardCharsets.UTF_8);
|
} catch (IOException ex) {
|
Slog.w(TAG, "Unable to write open descriptors to file: " + ex);
|
}
|
}
|
|
/**
|
* @return {@code true} if the high water mark was breached and a dump was written,
|
* {@code false} otherwise.
|
*/
|
public boolean monitor() {
|
if (mFdHighWaterMark.exists()) {
|
dumpOpenDescriptors();
|
return true;
|
}
|
|
return false;
|
}
|
}
|
}
|