diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e12181a08db377debc26615ff6b5099d9efa6162..3b6ea14ab0b6afa62d1d760ba68cdf567de85a27 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -135,6 +135,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; @@ -7274,6 +7275,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 d15c79f41d3f08c13c237f47f711f76353126299..24cb9ea87a12b78a12210a174c58eba46c8f64b2 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -477,6 +477,16 @@ public final class ApplicationExitInfo implements Parcelable { */ 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. @@ -654,6 +664,7 @@ public final class ApplicationExitInfo implements Parcelable { SUBREASON_UNDELIVERED_BROADCAST, SUBREASON_EXCESSIVE_BINDER_OBJECTS, SUBREASON_OOM_KILL, + SUBREASON_FREEZER_BINDER_ASYNC_FULL, }) @Retention(RetentionPolicy.SOURCE) public @interface SubReason {} @@ -1383,6 +1394,8 @@ public final class ApplicationExitInfo implements Parcelable { 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 03baf26713140b94506cecb8e756577dede3d329..520bf7dc890cd28b6bf8497b7a2c939534e71707 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -939,4 +939,14 @@ interface IActivityManager { int[] getUidFrozenState(in int[] uids); int checkPermissionForDevice(in String permission, int pid, int uid, int deviceId); + + /** + * 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 218d4bb8514fce3eab493c8ff25deaf5cfd5cf77..3db1cb0500f97f1cbf71a7104a57bfe11831c99a 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -665,6 +665,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 0000000000000000000000000000000000000000..e4be5b02e7e021449c6f5ec59b7bd196ffc57968 --- /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 0000000000000000000000000000000000000000..9cc4a35b5c6591cc45ab92d7544d41fb3bd21ef1 --- /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 bfd80a9e4f741ccb1a0765e7cace57458fbd33f5..d2d5186eb8a1b5fe5a3097dfad195c41242ae49f 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -73,6 +73,7 @@ static struct bindernative_offsets_t jclass mClass; jmethodID mExecTransact; jmethodID mGetInterfaceDescriptor; + jmethodID mTransactionCallback; // Object state. jfieldID mObject; @@ -1173,6 +1174,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( @@ -1387,7 +1390,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 0000000000000000000000000000000000000000..e9f6450c13aabe0b63b4a3147728f326227db68e --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java @@ -0,0 +1,194 @@ +/* + * 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 + mValidPids::contains, + + // 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 e88d0c6baf268186faf882f4149c9699ae062ea3..ae79f19f70f115f05bf25a42d913a1e458042493 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -20305,7 +20305,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long token = Binder.clearCallingIdentity(); try { - return mOomAdjuster.mCachedAppOptimizer.isFreezerSupported(); + return CachedAppOptimizer.isFreezerSupported(); } finally { Binder.restoreCallingIdentity(token); } @@ -20433,4 +20433,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 6005b64ca1bcd70ea68dc4df500eb9d7909d8c06..68af626869b775907c8cdabf5ec75d73cdf2d350 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; @@ -270,6 +280,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); @@ -312,6 +325,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 @@ -408,7 +422,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(); } } @@ -480,7 +497,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 @@ -488,6 +513,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; @@ -790,6 +816,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); @@ -1309,10 +1341,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) { @@ -2182,6 +2226,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; } @@ -2487,4 +2546,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 0d024d6a4c587e8043ce20a2696969eebd5f999a..56e385d535a0f39d1c9357acbe041ec8804faca8 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; @@ -985,6 +986,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");