blob: e0a224629174c973a3424f6b629adb622f4124aa [file] [log] [blame]
/*
* Copyright (C) 2020 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.am;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.content.pm.ApplicationInfo;
import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.TimeoutRecord;
import com.android.server.wm.WindowProcessController;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* The helper class to handle no response process. An independent thread will be created on demand
* so the caller can report the ANR without worrying about taking long time.
*/
class AnrHelper {
private static final String TAG = TAG_WITH_CLASS_NAME ? "AnrHelper" : TAG_AM;
/**
* If the system is extremely slow somehow that the ANR has been pending too long for more than
* this time, the information might be outdated. So we only the dump the unresponsive process
* instead of including other processes to avoid making the system more busy.
*/
private static final long EXPIRED_REPORT_TIME_MS = TimeUnit.SECONDS.toMillis(10);
/**
* If the last ANR occurred within this given time, consider it's anomaly.
*/
private static final long CONSECUTIVE_ANR_TIME_MS = TimeUnit.MINUTES.toMillis(2);
/**
* The keep alive time for the threads in the helper threadpool executor
*/
private static final int DEFAULT_THREAD_KEEP_ALIVE_SECOND = 10;
private static final ThreadFactory sDefaultThreadFactory = r ->
new Thread(r, "AnrAuxiliaryTaskExecutor");
private static final ThreadFactory sMainProcessDumpThreadFactory = r ->
new Thread(r, "AnrMainProcessDumpThread");
@GuardedBy("mAnrRecords")
private final ArrayList<AnrRecord> mAnrRecords = new ArrayList<>();
private final Set<Integer> mTempDumpedPids =
Collections.synchronizedSet(new ArraySet<Integer>());
private final AtomicBoolean mRunning = new AtomicBoolean(false);
private final ActivityManagerService mService;
/**
* The timestamp when the last ANR occurred.
*/
private long mLastAnrTimeMs = 0L;
/** The pid which is running appNotResponding(). */
@GuardedBy("mAnrRecords")
private int mProcessingPid = -1;
private final ExecutorService mAuxiliaryTaskExecutor;
private final ExecutorService mEarlyDumpExecutor;
AnrHelper(final ActivityManagerService service) {
// All the ANR threads need to expire after a period of inactivity, given the
// ephemeral nature of ANRs and how infrequent they are.
this(service, makeExpiringThreadPoolWithSize(1, sDefaultThreadFactory),
makeExpiringThreadPoolWithSize(2, sMainProcessDumpThreadFactory));
}
@VisibleForTesting
AnrHelper(ActivityManagerService service, ExecutorService auxExecutor,
ExecutorService earlyDumpExecutor) {
mService = service;
mAuxiliaryTaskExecutor = auxExecutor;
mEarlyDumpExecutor = earlyDumpExecutor;
}
void appNotResponding(ProcessRecord anrProcess, TimeoutRecord timeoutRecord) {
appNotResponding(anrProcess, null /* activityShortComponentName */, null /* aInfo */,
null /* parentShortComponentName */, null /* parentProcess */,
false /* aboveSystem */, timeoutRecord, /*isContinuousAnr*/ false);
}
void appNotResponding(ProcessRecord anrProcess, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName,
WindowProcessController parentProcess, boolean aboveSystem,
TimeoutRecord timeoutRecord, boolean isContinuousAnr) {
try {
timeoutRecord.mLatencyTracker.appNotRespondingStarted();
final int incomingPid = anrProcess.mPid;
timeoutRecord.mLatencyTracker.waitingOnAnrRecordLockStarted();
synchronized (mAnrRecords) {
timeoutRecord.mLatencyTracker.waitingOnAnrRecordLockEnded();
if (incomingPid == 0) {
// Extreme corner case such as zygote is no response
// to return pid for the process.
Slog.i(TAG, "Skip zero pid ANR, process=" + anrProcess.processName);
return;
}
if (mProcessingPid == incomingPid) {
Slog.i(TAG,
"Skip duplicated ANR, pid=" + incomingPid + " "
+ timeoutRecord.mReason);
return;
}
if (!mTempDumpedPids.add(incomingPid)) {
Slog.i(TAG,
"Skip ANR being predumped, pid=" + incomingPid + " "
+ timeoutRecord.mReason);
return;
}
for (int i = mAnrRecords.size() - 1; i >= 0; i--) {
if (mAnrRecords.get(i).mPid == incomingPid) {
Slog.i(TAG,
"Skip queued ANR, pid=" + incomingPid + " "
+ timeoutRecord.mReason);
return;
}
}
// We dump the main process as soon as we can on a different thread,
// this is done as the main process's dump can go stale in a few hundred
// milliseconds and the average full ANR dump takes a few seconds.
timeoutRecord.mLatencyTracker.earlyDumpRequestSubmittedWithSize(
mTempDumpedPids.size());
Future<File> firstPidDumpPromise = mEarlyDumpExecutor.submit(() -> {
// the class AnrLatencyTracker is not generally thread safe but the values
// recorded/touched by the Temporary dump thread(s) are all volatile/atomic.
File tracesFile = StackTracesDumpHelper.dumpStackTracesTempFile(incomingPid,
timeoutRecord.mLatencyTracker);
mTempDumpedPids.remove(incomingPid);
return tracesFile;
});
timeoutRecord.mLatencyTracker.anrRecordPlacingOnQueueWithSize(mAnrRecords.size());
mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,
parentShortComponentName, parentProcess, aboveSystem, timeoutRecord,
isContinuousAnr, firstPidDumpPromise));
}
startAnrConsumerIfNeeded();
} finally {
timeoutRecord.mLatencyTracker.appNotRespondingEnded();
}
}
private void startAnrConsumerIfNeeded() {
if (mRunning.compareAndSet(false, true)) {
new AnrConsumerThread().start();
}
}
private static ThreadPoolExecutor makeExpiringThreadPoolWithSize(int size,
ThreadFactory factory) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(/* corePoolSize= */ size,
/* maximumPoolSize= */ size, /* keepAliveTime= */ DEFAULT_THREAD_KEEP_ALIVE_SECOND,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(), factory);
// We allow the core threads to expire after the keepAliveTime.
pool.allowCoreThreadTimeOut(true);
return pool;
}
/**
* The thread to execute {@link ProcessErrorStateRecord#appNotResponding}. It will terminate if
* all records are handled.
*/
private class AnrConsumerThread extends Thread {
AnrConsumerThread() {
super("AnrConsumer");
}
private AnrRecord next() {
synchronized (mAnrRecords) {
if (mAnrRecords.isEmpty()) {
return null;
}
final AnrRecord record = mAnrRecords.remove(0);
mProcessingPid = record.mPid;
record.mTimeoutRecord.mLatencyTracker.anrRecordsQueueSizeWhenPopped(
mAnrRecords.size());
return record;
}
}
@Override
public void run() {
AnrRecord r;
while ((r = next()) != null) {
scheduleBinderHeavyHitterAutoSamplerIfNecessary();
final int currentPid = r.mApp.mPid;
if (currentPid != r.mPid) {
// The process may have restarted or died.
Slog.i(TAG, "Skip ANR with mismatched pid=" + r.mPid + ", current pid="
+ currentPid);
continue;
}
final long startTime = SystemClock.uptimeMillis();
// If there are many ANR at the same time, the latency may be larger.
// If the latency is too large, the stack trace might not be meaningful.
final long reportLatency = startTime - r.mTimestamp;
final boolean onlyDumpSelf = reportLatency > EXPIRED_REPORT_TIME_MS;
r.appNotResponding(onlyDumpSelf);
final long endTime = SystemClock.uptimeMillis();
Slog.d(TAG, "Completed ANR of " + r.mApp.processName + " in "
+ (endTime - startTime) + "ms, latency " + reportLatency
+ (onlyDumpSelf ? "ms (expired, only dump ANR app)" : "ms"));
}
mRunning.set(false);
synchronized (mAnrRecords) {
mProcessingPid = -1;
// The race should be unlikely to happen. Just to make sure we don't miss.
if (!mAnrRecords.isEmpty()) {
startAnrConsumerIfNeeded();
}
}
}
}
private void scheduleBinderHeavyHitterAutoSamplerIfNecessary() {
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"scheduleBinderHeavyHitterAutoSamplerIfNecessary()");
final long now = SystemClock.uptimeMillis();
if (mLastAnrTimeMs + CONSECUTIVE_ANR_TIME_MS > now) {
mService.scheduleBinderHeavyHitterAutoSampler();
}
mLastAnrTimeMs = now;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
private class AnrRecord {
final ProcessRecord mApp;
final int mPid;
final int mUid;
final String mActivityShortComponentName;
final String mParentShortComponentName;
final TimeoutRecord mTimeoutRecord;
final ApplicationInfo mAppInfo;
final WindowProcessController mParentProcess;
final boolean mAboveSystem;
final long mTimestamp = SystemClock.uptimeMillis();
final boolean mIsContinuousAnr;
final Future<File> mFirstPidFilePromise;
AnrRecord(ProcessRecord anrProcess, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName,
WindowProcessController parentProcess, boolean aboveSystem,
TimeoutRecord timeoutRecord, boolean isContinuousAnr,
Future<File> firstPidFilePromise) {
mApp = anrProcess;
mPid = anrProcess.mPid;
mUid = anrProcess.uid;
mActivityShortComponentName = activityShortComponentName;
mParentShortComponentName = parentShortComponentName;
mTimeoutRecord = timeoutRecord;
mAppInfo = aInfo;
mParentProcess = parentProcess;
mAboveSystem = aboveSystem;
mIsContinuousAnr = isContinuousAnr;
mFirstPidFilePromise = firstPidFilePromise;
}
void appNotResponding(boolean onlyDumpSelf) {
try {
mTimeoutRecord.mLatencyTracker.anrProcessingStarted();
mApp.mErrorState.appNotResponding(mActivityShortComponentName, mAppInfo,
mParentShortComponentName, mParentProcess, mAboveSystem,
mTimeoutRecord, mAuxiliaryTaskExecutor, onlyDumpSelf,
mIsContinuousAnr, mFirstPidFilePromise);
} finally {
mTimeoutRecord.mLatencyTracker.anrProcessingEnded();
}
}
}
}