summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Li <dualli@google.com>2023-10-07 21:11:54 -0700
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-11-16 18:44:30 +0000
commit4a5f3fd57508a91d5ca15c491f69af6f7acaa5ae (patch)
tree24c7e260d987ed456d2675239a818afd80e32907
parentf6e816f08ee45c71435c26f3656e4c488656e90f (diff)
downloadbase-4a5f3fd57508a91d5ca15c491f69af6f7acaa5ae.tar.gz
Add callback for binder transaction errors
If a process has set its binder callback for transaction errors, those binder transaction erros will be delivered to AMS. Freezer then checks the frozen apps to kill whoever recevie sync binder calls or run out of async binder buffer. Bug: 199336863 Bug: 308190346 Test: atest BinderfsStatsReaderTest Test: send binder calls to frozen apps and check logcat (cherry picked from commit 99defbeddf32679a59bc0df48c1d2b82e8b2dd22) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:7b3c52a3eeea9a1d895a3bd3a4ff4ef86657f526) Merged-In: Iedf4d1c988d5afdeb4fad0f354322828c0c3698e Change-Id: Iedf4d1c988d5afdeb4fad0f354322828c0c3698e
-rw-r--r--core/java/android/app/ActivityThread.java13
-rw-r--r--core/java/android/app/ApplicationExitInfo.java36
-rw-r--r--core/java/android/app/IActivityManager.aidl10
-rw-r--r--core/java/android/os/Binder.java26
-rw-r--r--core/java/android/os/IBinderCallback.java34
-rw-r--r--core/java/com/android/internal/os/BinderfsStatsReader.java108
-rw-r--r--core/jni/android_util_Binder.cpp10
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java192
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java19
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java176
-rw-r--r--services/java/com/android/server/SystemServer.java9
11 files changed, 628 insertions, 5 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 016d7214bda9..3bc0b0bf0f41 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -127,6 +127,7 @@ import android.os.GraphicsEnvironment;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
+import android.os.IBinderCallback;
import android.os.ICancellationSignal;
import android.os.LocaleList;
import android.os.Looper;
@@ -7096,6 +7097,18 @@ public final class ActivityThread extends ClientTransactionHandler
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
+
+ // Set binder transaction callback after finishing bindApplication
+ Binder.setTransactionCallback(new IBinderCallback() {
+ @Override
+ public void onTransactionError(int pid, int code, int flags, int err) {
+ try {
+ mgr.frozenBinderTransactionDetected(pid, code, flags, err);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+ });
}
@UnsupportedAppUsage
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index d859f3f9e175..24cb9ea87a12 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -460,6 +460,33 @@ public final class ApplicationExitInfo implements Parcelable {
*/
public static final int SUBREASON_SDK_SANDBOX_NOT_NEEDED = 28;
+ /**
+ * The process was killed because the binder proxy limit for system server was exceeded.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_EXCESSIVE_BINDER_OBJECTS = 29;
+
+ /**
+ * The process was killed by the [kernel] Out-of-memory (OOM) killer; this
+ * would be set only when the reason is {@link #REASON_LOW_MEMORY}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_OOM_KILL = 30;
+
+ /**
+ * The process was killed because its async kernel binder buffer is running out
+ * while being frozen.
+ * this would be set only when the reason is {@link #REASON_FREEZER}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_FREEZER_BINDER_ASYNC_FULL = 31;
+
// If there is any OEM code which involves additional app kill reasons, it should
// be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
@@ -635,6 +662,9 @@ public final class ApplicationExitInfo implements Parcelable {
SUBREASON_KILL_BACKGROUND,
SUBREASON_PACKAGE_UPDATE,
SUBREASON_UNDELIVERED_BROADCAST,
+ SUBREASON_EXCESSIVE_BINDER_OBJECTS,
+ SUBREASON_OOM_KILL,
+ SUBREASON_FREEZER_BINDER_ASYNC_FULL,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SubReason {}
@@ -1360,6 +1390,12 @@ public final class ApplicationExitInfo implements Parcelable {
return "PACKAGE UPDATE";
case SUBREASON_UNDELIVERED_BROADCAST:
return "UNDELIVERED BROADCAST";
+ case SUBREASON_EXCESSIVE_BINDER_OBJECTS:
+ return "EXCESSIVE BINDER OBJECTS";
+ case SUBREASON_OOM_KILL:
+ return "OOM KILL";
+ case SUBREASON_FREEZER_BINDER_ASYNC_FULL:
+ return "FREEZER BINDER ASYNC FULL";
default:
return "UNKNOWN";
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 46260ea5e658..37616e7d76af 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -924,4 +924,14 @@ interface IActivityManager {
void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
int[] getUidFrozenState(in int[] uids);
+
+ /**
+ * Notify AMS about binder transactions to frozen apps.
+ *
+ * @param debugPid The binder transaction sender
+ * @param code The binder transaction code
+ * @param flags The binder transaction flags
+ * @param err The binder transaction error
+ */
+ oneway void frozenBinderTransactionDetected(int debugPid, int code, int flags, int err);
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 01e8fea1019d..b5a7c9bb2f3e 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -642,6 +642,32 @@ public class Binder implements IBinder {
*/
public static final native void blockUntilThreadAvailable();
+
+ /**
+ * TODO (b/308179628): Move this to libbinder for non-Java usages.
+ */
+ private static IBinderCallback sBinderCallback = null;
+
+ /**
+ * Set callback function for unexpected binder transaction errors.
+ *
+ * @hide
+ */
+ public static final void setTransactionCallback(IBinderCallback callback) {
+ sBinderCallback = callback;
+ }
+
+ /**
+ * Execute the callback function if it's already set.
+ *
+ * @hide
+ */
+ public static final void transactionCallback(int pid, int code, int flags, int err) {
+ if (sBinderCallback != null) {
+ sBinderCallback.onTransactionError(pid, code, flags, err);
+ }
+ }
+
/**
* Default constructor just initializes the object.
*
diff --git a/core/java/android/os/IBinderCallback.java b/core/java/android/os/IBinderCallback.java
new file mode 100644
index 000000000000..e4be5b02e7e0
--- /dev/null
+++ b/core/java/android/os/IBinderCallback.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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 android.os;
+
+/**
+ * Callback interface for binder transaction errors
+ *
+ * @hide
+ */
+public interface IBinderCallback {
+ /**
+ * Callback function for unexpected binder transaction errors.
+ *
+ * @param debugPid The binder transaction sender
+ * @param code The binder transaction code
+ * @param flags The binder transaction flags
+ * @param err The binder transaction error
+ */
+ void onTransactionError(int debugPid, int code, int flags, int err);
+}
diff --git a/core/java/com/android/internal/os/BinderfsStatsReader.java b/core/java/com/android/internal/os/BinderfsStatsReader.java
new file mode 100644
index 000000000000..9cc4a35b5c65
--- /dev/null
+++ b/core/java/com/android/internal/os/BinderfsStatsReader.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 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.internal.os;
+
+import com.android.internal.util.ProcFileReader;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * Reads and parses {@code binder_logs/stats} file in the {@code binderfs} filesystem.
+ * Reuse procFileReader as the contents are generated by Linux kernel in the same way.
+ *
+ * A typical example of binderfs stats log
+ *
+ * binder stats:
+ * BC_TRANSACTION: 378004
+ * BC_REPLY: 268352
+ * BC_FREE_BUFFER: 665854
+ * ...
+ * proc 12645
+ * context binder
+ * threads: 12
+ * requested threads: 0+5/15
+ * ready threads 0
+ * free async space 520192
+ * ...
+ */
+public class BinderfsStatsReader {
+ private final String mPath;
+
+ public BinderfsStatsReader() {
+ mPath = "/dev/binderfs/binder_logs/stats";
+ }
+
+ public BinderfsStatsReader(String path) {
+ mPath = path;
+ }
+
+ /**
+ * Read binderfs stats and call the consumer(pid, free) function for each valid process
+ *
+ * @param predicate Test if the pid is valid.
+ * @param biConsumer Callback function for each valid pid and its free async space
+ * @param consumer The error function to deal with exceptions
+ */
+ public void handleFreeAsyncSpace(Predicate<Integer> predicate,
+ BiConsumer<Integer, Integer> biConsumer, Consumer<Exception> consumer) {
+ try (ProcFileReader mReader = new ProcFileReader(new FileInputStream(mPath))) {
+ while (mReader.hasMoreData()) {
+ // find the next process
+ if (!mReader.nextString().equals("proc")) {
+ mReader.finishLine();
+ continue;
+ }
+
+ // read pid
+ int pid = mReader.nextInt();
+ mReader.finishLine();
+
+ // check if we have interest in this process
+ if (!predicate.test(pid)) {
+ continue;
+ }
+
+ // read free async space
+ mReader.finishLine(); // context binder
+ mReader.finishLine(); // threads:
+ mReader.finishLine(); // requested threads:
+ mReader.finishLine(); // ready threads
+ if (!mReader.nextString().equals("free")) {
+ mReader.finishLine();
+ continue;
+ }
+ if (!mReader.nextString().equals("async")) {
+ mReader.finishLine();
+ continue;
+ }
+ if (!mReader.nextString().equals("space")) {
+ mReader.finishLine();
+ continue;
+ }
+ int free = mReader.nextInt();
+ mReader.finishLine();
+ biConsumer.accept(pid, free);
+ }
+ } catch (IOException | NumberFormatException e) {
+ consumer.accept(e);
+ }
+ }
+}
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index b24dc8a9e63b..3b7ba68ed4a2 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -75,6 +75,7 @@ static struct bindernative_offsets_t
jclass mClass;
jmethodID mExecTransact;
jmethodID mGetInterfaceDescriptor;
+ jmethodID mTransactionCallback;
// Object state.
jfieldID mObject;
@@ -1121,6 +1122,8 @@ static int int_register_android_os_Binder(JNIEnv* env)
gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z");
gBinderOffsets.mGetInterfaceDescriptor = GetMethodIDOrDie(env, clazz, "getInterfaceDescriptor",
"()Ljava/lang/String;");
+ gBinderOffsets.mTransactionCallback =
+ GetStaticMethodIDOrDie(env, clazz, "transactionCallback", "(IIII)V");
gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
return RegisterMethodsOrDie(
@@ -1452,7 +1455,12 @@ static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
if (err == NO_ERROR) {
return JNI_TRUE;
- } else if (err == UNKNOWN_TRANSACTION) {
+ }
+
+ env->CallStaticVoidMethod(gBinderOffsets.mClass, gBinderOffsets.mTransactionCallback, getpid(),
+ code, flags, err);
+
+ if (err == UNKNOWN_TRANSACTION) {
return JNI_FALSE;
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
new file mode 100644
index 000000000000..5498083b2182
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 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.internal.os;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.util.IntArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.nio.file.Files;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BinderfsStatsReaderTest {
+ private static final String BINDER_LOGS_STATS_HEADER = """
+ binder stats:
+ BC_TRANSACTION: 695756
+ BC_REPLY: 547779
+ BC_FREE_BUFFER: 1283223
+ BR_FAILED_REPLY: 4
+ BR_FROZEN_REPLY: 3
+ BR_ONEWAY_SPAM_SUSPECT: 1
+ proc: active 313 total 377
+ thread: active 3077 total 5227
+ """;
+ private static final String BINDER_LOGS_STATS_PROC1 = """
+ proc 14505
+ context binder
+ threads: 4
+ requested threads: 0+2/15
+ ready threads 0
+ free async space 520192
+ nodes: 9
+ refs: 29 s 29 w 29
+ buffers: 0
+ """;
+ private static final String BINDER_LOGS_STATS_PROC2 = """
+ proc 14461
+ context binder
+ threads: 8
+ requested threads: 0+2/15
+ ready threads 0
+ free async space 62
+ nodes: 30
+ refs: 51 s 51 w 51
+ buffers: 0
+ """;
+ private static final String BINDER_LOGS_STATS_PROC3 = """
+ proc 542
+ context binder
+ threads: 2
+ requested threads: 0+0/15
+ ready threads 0
+ free async space 519896
+ nodes: 1
+ refs: 2 s 3 w 2
+ buffers: 1
+ """;
+ private static final String BINDER_LOGS_STATS_PROC4 = """
+ proc 540
+ context binder
+ threads: 1
+ requested threads: 0+0/0
+ ready threads 1
+ free async space 44
+ nodes: 4
+ refs: 1 s 1 w 1
+ buffers: 0
+ """;
+ private File mStatsDirectory;
+ private int mFreezerBinderAsyncThreshold;
+ private IntArray mValidPids; // The pool of valid pids
+ private IntArray mStatsPids; // The pids read from binderfs stats that are also valid
+ private IntArray mStatsFree; // The free async space of the above pids
+ private boolean mHasError;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getContext();
+ mStatsDirectory = context.getDir("binder_logs", Context.MODE_PRIVATE);
+ mFreezerBinderAsyncThreshold = 1024;
+ mValidPids = IntArray.fromArray(new int[]{14505, 14461, 542, 540}, 4);
+ mStatsPids = new IntArray();
+ mStatsFree = new IntArray();
+ mHasError = false;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mStatsDirectory);
+ }
+
+ @Test
+ public void testNoneProc() throws Exception {
+ runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER);
+ assertFalse(mHasError);
+ assertEquals(0, mStatsPids.size());
+ assertEquals(0, mStatsFree.size());
+ }
+
+ @Test
+ public void testOneProc() throws Exception {
+ runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1);
+ assertFalse(mHasError);
+ assertEquals(0, mStatsPids.size());
+ assertEquals(0, mStatsFree.size());
+ }
+
+ @Test
+ public void testTwoProc() throws Exception {
+ runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1
+ + BINDER_LOGS_STATS_PROC2);
+ assertFalse(mHasError);
+ assertArrayEquals(mStatsPids.toArray(), new int[]{14461});
+ assertArrayEquals(mStatsFree.toArray(), new int[]{62});
+ }
+
+ @Test
+ public void testThreeProc() throws Exception {
+ runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1
+ + BINDER_LOGS_STATS_PROC2 + BINDER_LOGS_STATS_PROC3);
+ assertFalse(mHasError);
+ assertArrayEquals(mStatsPids.toArray(), new int[]{14461});
+ assertArrayEquals(mStatsFree.toArray(), new int[]{62});
+ }
+
+ @Test
+ public void testFourProc() throws Exception {
+ runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1
+ + BINDER_LOGS_STATS_PROC2 + BINDER_LOGS_STATS_PROC3 + BINDER_LOGS_STATS_PROC4);
+ assertFalse(mHasError);
+ assertArrayEquals(mStatsPids.toArray(), new int[]{14461, 540});
+ assertArrayEquals(mStatsFree.toArray(), new int[]{62, 44});
+ }
+
+ @Test
+ public void testInvalidProc() throws Exception {
+ mValidPids = new IntArray();
+ runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1
+ + BINDER_LOGS_STATS_PROC2 + BINDER_LOGS_STATS_PROC3 + BINDER_LOGS_STATS_PROC4);
+ assertFalse(mHasError);
+ assertEquals(0, mStatsPids.size());
+ assertEquals(0, mStatsFree.size());
+ }
+
+ private void runHandleBlockingFileLocks(String fileContents) throws Exception {
+ File tempFile = File.createTempFile("stats", null, mStatsDirectory);
+ Files.write(tempFile.toPath(), fileContents.getBytes());
+ new BinderfsStatsReader(tempFile.toString()).handleFreeAsyncSpace(
+ // Check if the current process is a valid one
+ pid -> mValidPids.indexOf(pid) != -1,
+
+ // Check if the current process is running out of async binder space
+ (pid, free) -> {
+ if (free < mFreezerBinderAsyncThreshold) {
+ mStatsPids.add(pid);
+ mStatsFree.add(free);
+ }
+ },
+
+ // Log the error if binderfs stats can't be accesses or correctly parsed
+ exception -> mHasError = true);
+ Files.delete(tempFile.toPath());
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 327421194f46..4dad2d560526 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -20122,7 +20122,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final long token = Binder.clearCallingIdentity();
try {
- return mOomAdjuster.mCachedAppOptimizer.isFreezerSupported();
+ return CachedAppOptimizer.isFreezerSupported();
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -20250,4 +20250,21 @@ public class ActivityManagerService extends IActivityManager.Stub
return index >= 0 && !mMediaProjectionTokenMap.valueAt(index).isEmpty();
}
}
+
+ /**
+ * Deal with binder transactions to frozen apps.
+ *
+ * @param debugPid The binder transaction sender
+ * @param code The binder transaction code
+ * @param flags The binder transaction flags
+ * @param err The binder transaction error
+ */
+ @Override
+ public void frozenBinderTransactionDetected(int debugPid, int code, int flags, int err) {
+ final ProcessRecord app;
+ synchronized (mPidsSelfLocked) {
+ app = mPidsSelfLocked.get(debugPid);
+ }
+ mOomAdjuster.mCachedAppOptimizer.binderError(debugPid, app, code, flags, err);
+ }
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index db933239874f..7574e91ceee8 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -52,6 +52,8 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityThread;
import android.app.ApplicationExitInfo;
+import android.app.ApplicationExitInfo.Reason;
+import android.app.ApplicationExitInfo.SubReason;
import android.app.IApplicationThread;
import android.database.ContentObserver;
import android.net.Uri;
@@ -67,6 +69,7 @@ import android.provider.DeviceConfig.OnPropertiesChangedListener;
import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.IntArray;
import android.util.Pair;
@@ -75,6 +78,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BinderfsStatsReader;
import com.android.internal.os.ProcLocksReader;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ServiceThread;
@@ -131,6 +135,12 @@ public final class CachedAppOptimizer {
"freeze_binder_offset";
@VisibleForTesting static final String KEY_FREEZER_BINDER_THRESHOLD =
"freeze_binder_threshold";
+ @VisibleForTesting static final String KEY_FREEZER_BINDER_CALLBACK_ENABLED =
+ "freeze_binder_callback_enabled";
+ @VisibleForTesting static final String KEY_FREEZER_BINDER_CALLBACK_THROTTLE =
+ "freeze_binder_callback_throttle";
+ @VisibleForTesting static final String KEY_FREEZER_BINDER_ASYNC_THRESHOLD =
+ "freeze_binder_async_threshold";
static final int UNFREEZE_REASON_NONE =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_NONE;
@@ -269,6 +279,9 @@ public final class CachedAppOptimizer {
@VisibleForTesting static final long DEFAULT_FREEZER_BINDER_DIVISOR = 4;
@VisibleForTesting static final int DEFAULT_FREEZER_BINDER_OFFSET = 500;
@VisibleForTesting static final long DEFAULT_FREEZER_BINDER_THRESHOLD = 1_000;
+ @VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED = true;
+ @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE = 10_000L;
+ @VisibleForTesting static final int DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD = 1_024;
@VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
Settings.Global.CACHED_APPS_FREEZER_ENABLED);
@@ -311,6 +324,7 @@ public final class CachedAppOptimizer {
static final int COMPACT_NATIVE_MSG = 5;
static final int UID_FROZEN_STATE_CHANGED_MSG = 6;
static final int DEADLOCK_WATCHDOG_MSG = 7;
+ static final int BINDER_ERROR_MSG = 8;
// When free swap falls below this percentage threshold any full (file + anon)
// compactions will be downgraded to file only compactions to reduce pressure
@@ -407,7 +421,10 @@ public final class CachedAppOptimizer {
} else if (KEY_FREEZER_BINDER_ENABLED.equals(name)
|| KEY_FREEZER_BINDER_DIVISOR.equals(name)
|| KEY_FREEZER_BINDER_THRESHOLD.equals(name)
- || KEY_FREEZER_BINDER_OFFSET.equals(name)) {
+ || KEY_FREEZER_BINDER_OFFSET.equals(name)
+ || KEY_FREEZER_BINDER_CALLBACK_ENABLED.equals(name)
+ || KEY_FREEZER_BINDER_CALLBACK_THROTTLE.equals(name)
+ || KEY_FREEZER_BINDER_ASYNC_THRESHOLD.equals(name)) {
updateFreezerBinderState();
}
}
@@ -479,7 +496,15 @@ public final class CachedAppOptimizer {
@VisibleForTesting volatile int mFreezerBinderOffset = DEFAULT_FREEZER_BINDER_OFFSET;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile long mFreezerBinderThreshold = DEFAULT_FREEZER_BINDER_THRESHOLD;
-
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting volatile boolean mFreezerBinderCallbackEnabled =
+ DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting volatile long mFreezerBinderCallbackThrottle =
+ DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting volatile int mFreezerBinderAsyncThreshold =
+ DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD;
// Handler on which compaction runs.
@VisibleForTesting
@@ -487,6 +512,7 @@ public final class CachedAppOptimizer {
private Handler mFreezeHandler;
@GuardedBy("mProcLock")
private boolean mFreezerOverride = false;
+ private long mFreezerBinderCallbackLast = -1;
@VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
@VisibleForTesting volatile boolean mFreezerExemptInstPkg = DEFAULT_FREEZER_EXEMPT_INST_PKG;
@@ -789,6 +815,12 @@ public final class CachedAppOptimizer {
pw.println(" " + KEY_FREEZER_BINDER_THRESHOLD + "=" + mFreezerBinderThreshold);
pw.println(" " + KEY_FREEZER_BINDER_DIVISOR + "=" + mFreezerBinderDivisor);
pw.println(" " + KEY_FREEZER_BINDER_OFFSET + "=" + mFreezerBinderOffset);
+ pw.println(" " + KEY_FREEZER_BINDER_CALLBACK_ENABLED + "="
+ + mFreezerBinderCallbackEnabled);
+ pw.println(" " + KEY_FREEZER_BINDER_CALLBACK_THROTTLE + "="
+ + mFreezerBinderCallbackThrottle);
+ pw.println(" " + KEY_FREEZER_BINDER_ASYNC_THRESHOLD + "="
+ + mFreezerBinderAsyncThreshold);
synchronized (mProcLock) {
int size = mFrozenProcesses.size();
pw.println(" Apps frozen: " + size);
@@ -1308,10 +1340,22 @@ public final class CachedAppOptimizer {
mFreezerBinderThreshold = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_FREEZER_BINDER_THRESHOLD, DEFAULT_FREEZER_BINDER_THRESHOLD);
+ mFreezerBinderCallbackEnabled = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+ KEY_FREEZER_BINDER_CALLBACK_ENABLED, DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED);
+ mFreezerBinderCallbackThrottle = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+ KEY_FREEZER_BINDER_CALLBACK_THROTTLE, DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE);
+ mFreezerBinderAsyncThreshold = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+ KEY_FREEZER_BINDER_ASYNC_THRESHOLD, DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD);
Slog.d(TAG_AM, "Freezer binder state set to enabled=" + mFreezerBinderEnabled
+ ", divisor=" + mFreezerBinderDivisor
+ ", offset=" + mFreezerBinderOffset
- + ", threshold=" + mFreezerBinderThreshold);
+ + ", threshold=" + mFreezerBinderThreshold
+ + ", callback enabled=" + mFreezerBinderCallbackEnabled
+ + ", callback throttle=" + mFreezerBinderCallbackThrottle
+ + ", async threshold=" + mFreezerBinderAsyncThreshold);
}
private boolean parseProcStateThrottle(String procStateThrottleString) {
@@ -2174,6 +2218,21 @@ public final class CachedAppOptimizer {
Slog.w(TAG_AM, "Unable to check file locks");
}
} break;
+ case BINDER_ERROR_MSG: {
+ IntArray pids = new IntArray();
+ // Copy the frozen pids to a local array to release mProcLock ASAP
+ synchronized (mProcLock) {
+ int size = mFrozenProcesses.size();
+ for (int i = 0; i < size; i++) {
+ pids.add(mFrozenProcesses.keyAt(i));
+ }
+ }
+
+ // Check binder errors to frozen processes with a local freezer lock
+ synchronized (mFreezerLock) {
+ binderErrorLocked(pids);
+ }
+ } break;
default:
return;
}
@@ -2479,4 +2538,115 @@ public final class CachedAppOptimizer {
return UNFREEZE_REASON_NONE;
}
}
+
+ /**
+ * Kill a frozen process with a specified reason
+ */
+ public void killProcess(int pid, String reason, @Reason int reasonCode,
+ @SubReason int subReason) {
+ mAm.mHandler.post(() -> {
+ synchronized (mAm) {
+ synchronized (mProcLock) {
+ ProcessRecord proc = mFrozenProcesses.get(pid);
+ // The process might have been killed or unfrozen by others
+ if (proc != null && proc.getThread() != null && !proc.isKilledByAm()) {
+ proc.killLocked(reason, reasonCode, subReason, true);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Sending binder transactions to frozen apps most likely indicates there's a bug. Log it and
+ * kill the frozen apps if they 1) receive sync binder transactions while frozen, or 2) miss
+ * async binder transactions due to kernel binder buffer running out.
+ *
+ * @param debugPid The binder transaction sender
+ * @param app The ProcessRecord of the sender
+ * @param code The binder transaction code
+ * @param flags The binder transaction flags
+ * @param err The binder transaction error
+ */
+ public void binderError(int debugPid, ProcessRecord app, int code, int flags, int err) {
+ Slog.w(TAG_AM, "pid " + debugPid + " " + (app == null ? "null" : app.processName)
+ + " sent binder code " + code + " with flags " + flags
+ + " to frozen apps and got error " + err);
+
+ // Do nothing if the binder error callback is not enabled.
+ // That means the frozen apps in a wrong state will be killed when they are unfrozen later.
+ if (!mFreezerBinderCallbackEnabled) {
+ return;
+ }
+
+ final long now = SystemClock.uptimeMillis();
+ if (now < mFreezerBinderCallbackLast + mFreezerBinderCallbackThrottle) {
+ Slog.d(TAG_AM, "Too many transaction errors, throttling freezer binder callback.");
+ return;
+ }
+ mFreezerBinderCallbackLast = now;
+
+ // Check all frozen processes in Freezer handler
+ mFreezeHandler.sendEmptyMessage(BINDER_ERROR_MSG);
+ }
+
+ private void binderErrorLocked(IntArray pids) {
+ // PIDs that run out of async binder buffer when being frozen
+ ArraySet<Integer> pidsAsync = (mFreezerBinderAsyncThreshold < 0) ? null : new ArraySet<>();
+
+ for (int i = 0; i < pids.size(); i++) {
+ int current = pids.get(i);
+ try {
+ int freezeInfo = getBinderFreezeInfo(current);
+
+ if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
+ killProcess(current, "Sync transaction while frozen",
+ ApplicationExitInfo.REASON_FREEZER,
+ ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION);
+
+ // No need to check async transactions in this case
+ continue;
+ }
+
+ if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
+ if (pidsAsync != null) {
+ pidsAsync.add(current);
+ }
+ if (DEBUG_FREEZER) {
+ Slog.w(TAG_AM, "pid " + current
+ + " received async transactions while frozen");
+ }
+ }
+ } catch (Exception e) {
+ // The process has died. No need to kill it again.
+ Slog.w(TAG_AM, "Unable to query binder frozen stats for pid " + current);
+ }
+ }
+
+ // TODO: when kernel binder driver supports, poll the binder status directly.
+ // Binderfs stats, like other debugfs files, is not a reliable interface. But it's the
+ // only true source for now. The following code checks all frozen PIDs. If any of them
+ // is running out of async binder buffer, kill it. Otherwise it will be killed at a
+ // later time when AMS unfreezes it, which causes race issues.
+ if (pidsAsync == null || pidsAsync.size() == 0) {
+ return;
+ }
+ new BinderfsStatsReader().handleFreeAsyncSpace(
+ // Check if the frozen process has pending async calls
+ pidsAsync::contains,
+
+ // Kill the current process if it's running out of async binder space
+ (current, free) -> {
+ if (free < mFreezerBinderAsyncThreshold) {
+ Slog.w(TAG_AM, "pid " + current
+ + " has " + free + " free async space, killing");
+ killProcess(current, "Async binder space running out while frozen",
+ ApplicationExitInfo.REASON_FREEZER,
+ ApplicationExitInfo.SUBREASON_FREEZER_BINDER_ASYNC_FULL);
+ }
+ },
+
+ // Log the error if binderfs stats can't be accesses or correctly parsed
+ exception -> Slog.e(TAG_AM, "Unable to parse binderfs stats"));
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3a7cb677bf30..c15f595525b4 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -65,6 +65,7 @@ import android.os.Environment;
import android.os.FactoryTest;
import android.os.FileUtils;
import android.os.IBinder;
+import android.os.IBinderCallback;
import android.os.IIncidentManager;
import android.os.Looper;
import android.os.Message;
@@ -969,6 +970,14 @@ public final class SystemServer implements Dumpable {
}
}
+ // Set binder transaction callback after starting system services
+ Binder.setTransactionCallback(new IBinderCallback() {
+ @Override
+ public void onTransactionError(int pid, int code, int flags, int err) {
+ mActivityManagerService.frozenBinderTransactionDetected(pid, code, flags, err);
+ }
+ });
+
// Loop forever.
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");