Merge "Freeze package cgroup before killing"

This commit is contained in:
T.J. Mercier
2022-10-05 16:11:22 +00:00
committed by Android (Google) Code Review
7 changed files with 123 additions and 17 deletions

View File

@@ -1481,6 +1481,18 @@ public class Process {
*/
public static final native int killProcessGroup(int uid, int pid);
/**
* Freeze the cgroup for the given UID.
* This cgroup may contain child cgroups which will also be frozen. If this cgroup or its
* children contain processes with Binder interfaces, those interfaces should be frozen before
* the cgroup to avoid blocking synchronous callers indefinitely.
*
* @param uid The UID to be frozen
* @param freeze true = freeze; false = unfreeze
* @hide
*/
public static final native void freezeCgroupUid(int uid, boolean freeze);
/**
* Remove all process groups. Expected to be called when ActivityManager
* is restarted.

View File

@@ -1252,6 +1252,20 @@ static jint android_os_Process_nativePidFdOpen(JNIEnv* env, jobject, jint pid, j
return fd;
}
void android_os_Process_freezeCgroupUID(JNIEnv* env, jobject clazz, jint uid, jboolean freeze) {
bool success = true;
if (freeze) {
success = SetUserProfiles(uid, {"Frozen"});
} else {
success = SetUserProfiles(uid, {"Unfrozen"});
}
if (!success) {
jniThrowRuntimeException(env, "Could not apply user profile");
}
}
static const JNINativeMethod methods[] = {
{"getUidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName},
{"getGidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName},
@@ -1293,6 +1307,7 @@ static const JNINativeMethod methods[] = {
{"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
{"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups},
{"nativePidFdOpen", "(II)I", (void*)android_os_Process_nativePidFdOpen},
{"freezeCgroupUid", "(IZ)V", (void*)android_os_Process_freezeCgroupUID},
};
int register_android_os_Process(JNIEnv* env)

View File

@@ -112,6 +112,8 @@ public final class CachedAppOptimizer {
private static final String ATRACE_COMPACTION_TRACK = "Compaction";
private static final String ATRACE_FREEZER_TRACK = "Freezer";
private static final int FREEZE_BINDER_TIMEOUT_MS = 100;
// Defaults for phenotype flags.
@VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
@VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = true;
@@ -929,11 +931,13 @@ public final class CachedAppOptimizer {
* @param pid the target pid for which binder transactions are to be frozen
* @param freeze specifies whether to flush transactions and then freeze (true) or unfreeze
* binder for the specificed pid.
* @param timeoutMs the timeout in milliseconds to wait for the binder interface to freeze
* before giving up.
*
* @throws RuntimeException in case a flush/freeze operation could not complete successfully.
* @return 0 if success, or -EAGAIN indicating there's pending transaction.
*/
private static native int freezeBinder(int pid, boolean freeze);
public static native int freezeBinder(int pid, boolean freeze, int timeoutMs);
/**
* Retrieves binder freeze info about a process.
@@ -1300,7 +1304,7 @@ public final class CachedAppOptimizer {
long freezeTime = opt.getFreezeUnfreezeTime();
try {
freezeBinder(pid, false);
freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName
+ ". Killing it");
@@ -1355,7 +1359,7 @@ public final class CachedAppOptimizer {
}
Slog.d(TAG_AM, "quick sync unfreeze " + pid);
try {
freezeBinder(pid, false);
freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to quick unfreeze binder for " + pid);
return;
@@ -1950,7 +1954,7 @@ public final class CachedAppOptimizer {
// Freeze binder interface before the process, to flush any
// transactions that might be pending.
try {
if (freezeBinder(pid, true) != 0) {
if (freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) {
rescheduleFreeze(proc, "outstanding txns");
return;
}

View File

@@ -32,6 +32,7 @@ import static android.os.Process.getFreeMemory;
import static android.os.Process.getTotalMemory;
import static android.os.Process.killProcessQuiet;
import static android.os.Process.startWebView;
import static android.system.OsConstants.*;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
@@ -2711,6 +2712,50 @@ public final class ProcessList {
}
}
private static boolean freezePackageCgroup(int packageUID, boolean freeze) {
try {
Process.freezeCgroupUid(packageUID, freeze);
} catch (RuntimeException e) {
final String logtxt = freeze ? "freeze" : "unfreeze";
Slog.e(TAG, "Unable to " + logtxt + " cgroup uid: " + packageUID + ": " + e);
return false;
}
return true;
}
private static void freezeBinderAndPackageCgroup(ArrayList<Pair<ProcessRecord, Boolean>> procs,
int packageUID) {
// Freeze all binder processes under the target UID (whose cgroup is about to be frozen).
// Since we're going to kill these, we don't need to unfreze them later.
// The procs list may not include all processes under the UID cgroup, but unincluded
// processes (forks) should not be Binder users.
int N = procs.size();
for (int i = 0; i < N; i++) {
final int uid = procs.get(i).first.uid;
final int pid = procs.get(i).first.getPid();
int nRetries = 0;
// We only freeze the cgroup of the target package, so we do not need to freeze the
// Binder interfaces of dependant processes in other UIDs.
if (pid > 0 && uid == packageUID) {
try {
int rc;
do {
rc = CachedAppOptimizer.freezeBinder(pid, true, 10 /* timeout_ms */);
} while (rc == -EAGAIN && nRetries++ < 1);
if (rc != 0) Slog.e(TAG, "Unable to freeze binder for " + pid + ": " + rc);
} catch (RuntimeException e) {
Slog.e(TAG, "Unable to freeze binder for " + pid + ": " + e);
}
}
}
// We freeze the entire UID (parent) cgroup so that newly-specialized processes also freeze
// despite being added to a new child cgroup. The cgroups of package dependant processes are
// not frozen, since it's possible this would freeze processes with no dependency on the
// package being killed here.
freezePackageCgroup(packageUID, true);
}
@GuardedBy({"mService", "mProcLock"})
boolean killPackageProcessesLSP(String packageName, int appId,
int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -2763,7 +2808,7 @@ public final class ProcessList {
boolean shouldAllowRestart = false;
// If no package is specified, we call all processes under the
// give user id.
// given user id.
if (packageName == null) {
if (userId != UserHandle.USER_ALL && app.userId != userId) {
continue;
@@ -2806,14 +2851,24 @@ public final class ProcessList {
}
}
final int packageUID = UserHandle.getUid(userId, appId);
final boolean doFreeze = appId >= Process.FIRST_APPLICATION_UID
&& appId <= Process.LAST_APPLICATION_UID;
if (doFreeze) {
freezeBinderAndPackageCgroup(procs, packageUID);
}
int N = procs.size();
for (int i=0; i<N; i++) {
final Pair<ProcessRecord, Boolean> proc = procs.get(i);
removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
reasonCode, subReason, reason);
reasonCode, subReason, reason, !doFreeze /* async */);
}
killAppZygotesLocked(packageName, appId, userId, false /* force */);
mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
if (doFreeze) {
freezePackageCgroup(packageUID, false);
}
return N > 0;
}
@@ -2821,12 +2876,19 @@ public final class ProcessList {
boolean removeProcessLocked(ProcessRecord app,
boolean callerWillRestart, boolean allowRestart, int reasonCode, String reason) {
return removeProcessLocked(app, callerWillRestart, allowRestart, reasonCode,
ApplicationExitInfo.SUBREASON_UNKNOWN, reason);
ApplicationExitInfo.SUBREASON_UNKNOWN, reason, true);
}
@GuardedBy("mService")
boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart,
boolean allowRestart, int reasonCode, int subReason, String reason) {
return removeProcessLocked(app, callerWillRestart, allowRestart, reasonCode, subReason,
reason, true);
}
@GuardedBy("mService")
boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart,
boolean allowRestart, int reasonCode, int subReason, String reason, boolean async) {
final String name = app.processName;
final int uid = app.uid;
if (DEBUG_PROCESSES) Slog.d(TAG_PROCESSES,
@@ -2863,7 +2925,7 @@ public final class ProcessList {
needRestart = true;
}
}
app.killLocked(reason, reasonCode, subReason, true);
app.killLocked(reason, reasonCode, subReason, true, async);
mService.handleAppDiedLocked(app, pid, willRestart, allowRestart,
false /* fromBinderDied */);
if (willRestart) {

View File

@@ -1056,18 +1056,30 @@ class ProcessRecord implements WindowProcessListener {
@GuardedBy("mService")
void killLocked(String reason, @Reason int reasonCode, boolean noisy) {
killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy);
killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy, true);
}
@GuardedBy("mService")
void killLocked(String reason, @Reason int reasonCode, @SubReason int subReason,
boolean noisy) {
killLocked(reason, reason, reasonCode, subReason, noisy);
killLocked(reason, reason, reasonCode, subReason, noisy, true);
}
@GuardedBy("mService")
void killLocked(String reason, String description, @Reason int reasonCode,
@SubReason int subReason, boolean noisy) {
killLocked(reason, description, reasonCode, subReason, noisy, true);
}
@GuardedBy("mService")
void killLocked(String reason, @Reason int reasonCode, @SubReason int subReason,
boolean noisy, boolean asyncKPG) {
killLocked(reason, reason, reasonCode, subReason, noisy, asyncKPG);
}
@GuardedBy("mService")
void killLocked(String reason, String description, @Reason int reasonCode,
@SubReason int subReason, boolean noisy, boolean asyncKPG) {
if (!mKilledByAm) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "kill");
if (reasonCode == ApplicationExitInfo.REASON_ANR
@@ -1084,7 +1096,8 @@ class ProcessRecord implements WindowProcessListener {
EventLog.writeEvent(EventLogTags.AM_KILL,
userId, mPid, processName, mState.getSetAdj(), reason);
Process.killProcessQuiet(mPid);
ProcessList.killProcessGroup(uid, mPid);
if (asyncKPG) ProcessList.killProcessGroup(uid, mPid);
else Process.killProcessGroup(uid, mPid);
} else {
mPendingStart = false;
}

View File

@@ -491,10 +491,10 @@ static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, job
compactProcessOrFallback(pid, compactionFlags);
}
static jint com_android_server_am_CachedAppOptimizer_freezeBinder(
JNIEnv *env, jobject clazz, jint pid, jboolean freeze) {
jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */);
static jint com_android_server_am_CachedAppOptimizer_freezeBinder(JNIEnv* env, jobject clazz,
jint pid, jboolean freeze,
jint timeout_ms) {
jint retVal = IPCThreadState::freeze(pid, freeze, timeout_ms);
if (retVal != 0 && retVal != -EAGAIN) {
jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
}
@@ -548,7 +548,7 @@ static const JNINativeMethod sMethods[] = {
(void*)com_android_server_am_CachedAppOptimizer_getMemoryFreedCompaction},
{"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
{"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
{"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
{"freezeBinder", "(IZI)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
{"getBinderFreezeInfo", "(I)I",
(void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
{"getFreezerCheckPath", "()Ljava/lang/String;",

View File

@@ -364,7 +364,7 @@ public class BroadcastQueueTest {
mActiveProcesses.remove(r);
mRegisteredReceivers.remove(r.getPid());
return invocation.callRealMethod();
}).when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean());
}).when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean());
// If we're entirely dead, rely on default behaviors above
if (dead) return r;