Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 71d75c09 authored by Tim Murray's avatar Tim Murray
Browse files

CachedAppOptimizer: kill apps that spam binder transactions

Spamming binder transactions prevents the freeze binder ioctl from
succeeding, which allows apps that spam binder calls for whatever
reason to stay out of the freezer. This can have a negative effect on
battery life.

To address this issue, the following approach is introduced:

1. Wait for default freeze timeout
2. If the process failed the binder freeze ioctl, adjust the timeout
to be ((timeout / divisor) + rand(-offset, +offset))
3. If the new timeout is above the kill threshold, wait for the new
timeout and either freeze or goto 2
4. If the new timeout is below the kill threshold, assume the app is
broken and kill the app with REASON_EXCESSIVE_RESOURCE_USAGE

The retry approach is configurable with flags to enable the new
approach and set the divisor, offset, and kill threshold.

Test: myfreezertest is killed correctly
Bug: 286750917

Change-Id: I389200237c3b412f80f630bb4fd12961f52a849e
parent dea20a43
Loading
Loading
Loading
Loading
+104 −11
Original line number Diff line number Diff line
@@ -123,7 +123,14 @@ public final class CachedAppOptimizer {
            "freeze_debounce_timeout";
    @VisibleForTesting static final String KEY_FREEZER_EXEMPT_INST_PKG =
            "freeze_exempt_inst_pkg";

    @VisibleForTesting static final String KEY_FREEZER_BINDER_ENABLED =
            "freeze_binder_enabled";
    @VisibleForTesting static final String KEY_FREEZER_BINDER_DIVISOR =
            "freeze_binder_divisor";
    @VisibleForTesting static final String KEY_FREEZER_BINDER_OFFSET =
            "freeze_binder_offset";
    @VisibleForTesting static final String KEY_FREEZER_BINDER_THRESHOLD =
            "freeze_binder_threshold";

    static final int UNFREEZE_REASON_NONE =
            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_NONE;
@@ -237,8 +244,8 @@ public final class CachedAppOptimizer {
    @VisibleForTesting static final boolean ENABLE_FILE_COMPACT = false;

    // Defaults for phenotype flags.
    @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = true;
    @VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = true;
    @VisibleForTesting static final boolean DEFAULT_USE_COMPACTION = true;
    @VisibleForTesting static final boolean DEFAULT_USE_FREEZER = true;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
@@ -257,7 +264,11 @@ public final class CachedAppOptimizer {
    @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
            String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
    @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 10_000L;
    @VisibleForTesting static final Boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = true;
    @VisibleForTesting static final boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = true;
    @VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_ENABLED = true;
    @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 Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
                Settings.Global.CACHED_APPS_FREEZER_ENABLED);
@@ -393,6 +404,11 @@ public final class CachedAppOptimizer {
                                updateFreezerDebounceTimeout();
                            } else if (KEY_FREEZER_EXEMPT_INST_PKG.equals(name)) {
                                updateFreezerExemptInstPkg();
                            } 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)) {
                                updateFreezerBinderState();
                            }
                        }
                    }
@@ -455,6 +471,16 @@ public final class CachedAppOptimizer {
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting final Set<Integer> mProcStateThrottle;

    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile boolean mFreezerBinderEnabled = DEFAULT_FREEZER_BINDER_ENABLED;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mFreezerBinderDivisor = DEFAULT_FREEZER_BINDER_DIVISOR;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile int mFreezerBinderOffset = DEFAULT_FREEZER_BINDER_OFFSET;
    @GuardedBy("mPhenotypeFlagLock")
    @VisibleForTesting volatile long mFreezerBinderThreshold = DEFAULT_FREEZER_BINDER_THRESHOLD;


    // Handler on which compaction runs.
    @VisibleForTesting
    Handler mCompactionHandler;
@@ -759,6 +785,10 @@ public final class CachedAppOptimizer {
            pw.println("  " + KEY_FREEZER_STATSD_SAMPLE_RATE + "=" + mFreezerStatsdSampleRate);
            pw.println("  " + KEY_FREEZER_DEBOUNCE_TIMEOUT + "=" + mFreezerDebounceTimeout);
            pw.println("  " + KEY_FREEZER_EXEMPT_INST_PKG + "=" + mFreezerExemptInstPkg);
            pw.println("  " + KEY_FREEZER_BINDER_ENABLED + "=" + mFreezerBinderEnabled);
            pw.println("  " + KEY_FREEZER_BINDER_THRESHOLD + "=" + mFreezerBinderThreshold);
            pw.println("  " + KEY_FREEZER_BINDER_DIVISOR + "=" + mFreezerBinderDivisor);
            pw.println("  " + KEY_FREEZER_BINDER_OFFSET + "=" + mFreezerBinderOffset);
            synchronized (mProcLock) {
                int size = mFrozenProcesses.size();
                pw.println("  Apps frozen: " + size);
@@ -1264,6 +1294,26 @@ public final class CachedAppOptimizer {
        Slog.d(TAG_AM, "Freezer exemption set to " + mFreezerExemptInstPkg);
    }

    @GuardedBy("mPhenotypeFlagLock")
    private void updateFreezerBinderState() {
        mFreezerBinderEnabled = DeviceConfig.getBoolean(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_BINDER_ENABLED, DEFAULT_FREEZER_BINDER_ENABLED);
        mFreezerBinderDivisor = DeviceConfig.getLong(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_BINDER_DIVISOR, DEFAULT_FREEZER_BINDER_DIVISOR);
        mFreezerBinderOffset = DeviceConfig.getInt(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_BINDER_OFFSET, DEFAULT_FREEZER_BINDER_OFFSET);
        mFreezerBinderThreshold = DeviceConfig.getLong(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                KEY_FREEZER_BINDER_THRESHOLD, DEFAULT_FREEZER_BINDER_THRESHOLD);
        Slog.d(TAG_AM, "Freezer binder state set to enabled=" + mFreezerBinderEnabled
                + ", divisor=" + mFreezerBinderDivisor
                + ", offset=" + mFreezerBinderOffset
                + ", threshold=" + mFreezerBinderThreshold);
    }

    private boolean parseProcStateThrottle(String procStateThrottleString) {
        String[] procStates = TextUtils.split(procStateThrottleString, ",");
        mProcStateThrottle.clear();
@@ -1352,6 +1402,7 @@ public final class CachedAppOptimizer {
                }
            }
        }
        app.mOptRecord.setLastUsedTimeout(delayMillis);
        mFreezeHandler.sendMessageDelayed(
                mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
                delayMillis);
@@ -2121,12 +2172,54 @@ public final class CachedAppOptimizer {
        }

        @GuardedBy({"mAm", "mProcLock"})
        private void rescheduleFreeze(final ProcessRecord proc, final String reason,
                @UnfreezeReason int reasonCode) {
            Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid()
                    + " " + proc.processName + " (" + reason + ")");
            unfreezeAppLSP(proc, reasonCode);
        private void handleBinderFreezerFailure(final ProcessRecord proc, final String reason) {
            if (!mFreezerBinderEnabled) {
                // Just reschedule indefinitely.
                unfreezeAppLSP(proc, UNFREEZE_REASON_BINDER_TXNS);
                freezeAppAsyncLSP(proc);
                return;
            }
            /*
             * This handles the case where a process couldn't be frozen due to pending binder
             * transactions. In order to prevent apps from avoiding the freezer by spamming binder
             * transactions, there is an exponential decrease in freezer retry times plus a random
             * offset per attempt to avoid phase issues. Once the last-attempted timeout is below a
             * threshold, we assume that the app is spamming binder calls and can never be frozen,
             * and we will then crash the app.
             */
            if (proc.mOptRecord.getLastUsedTimeout() <= mFreezerBinderThreshold) {
                // We've given the app plenty of chances, assume broken. Time to die.
                Slog.d(TAG_AM, "Kill app due to repeated failure to freeze binder: "
                        + proc.getPid() + " " + proc.processName);
                mAm.mHandler.post(() -> {
                    synchronized (mAm) {
                        // Crash regardless of procstate in case the app has found another way
                        // to abuse oom_adj
                        if (proc.getThread() == null) {
                            return;
                        }
                        proc.killLocked("excessive binder traffic during cached",
                                ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
                                ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
                                true);
                    }
                });
                return;
            }

            long timeout = proc.mOptRecord.getLastUsedTimeout() / mFreezerBinderDivisor;
            // range is [-mFreezerBinderOffset, +mFreezerBinderOffset]
            int offset = mRandom.nextInt(mFreezerBinderOffset * 2) - mFreezerBinderOffset;
            timeout = Math.max(timeout + offset, mFreezerBinderThreshold);

            Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid()
                    + " " + proc.processName + " (" + reason  + "), timeout=" + timeout);
            Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_FREEZER_TRACK,
                    "Reschedule freeze " + proc.processName + ":" + proc.getPid()
                    + " timeout=" + timeout + ", reason=" + reason);

            unfreezeAppLSP(proc, UNFREEZE_REASON_BINDER_TXNS);
            freezeAppAsyncLSP(proc, timeout);
        }

        /**
@@ -2173,7 +2266,7 @@ public final class CachedAppOptimizer {
                // transactions that might be pending.
                try {
                    if (freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) {
                        rescheduleFreeze(proc, "outstanding txns", UNFREEZE_REASON_BINDER_TXNS);
                        handleBinderFreezerFailure(proc, "outstanding txns");
                        return;
                    }
                } catch (RuntimeException e) {
@@ -2234,7 +2327,7 @@ public final class CachedAppOptimizer {

                if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) {
                    synchronized (mProcLock) {
                        rescheduleFreeze(proc, "new pending txns", UNFREEZE_REASON_BINDER_TXNS);
                        handleBinderFreezerFailure(proc, "new pending txns");
                    }
                    return;
                }
+16 −0
Original line number Diff line number Diff line
@@ -127,6 +127,12 @@ final class ProcessCachedOptimizerRecord {
    @GuardedBy("mProcLock")
    private @UptimeMillisLong long mEarliestFreezableTimeMillis;

    /**
     * This is the most recently used timeout for freezing the app in millis
     */
    @GuardedBy("mProcLock")
    private long mLastUsedTimeout;

    @GuardedBy("mProcLock")
    long getLastCompactTime() {
        return mLastCompactTime;
@@ -281,6 +287,16 @@ final class ProcessCachedOptimizerRecord {
        mEarliestFreezableTimeMillis = earliestFreezableTimeMillis;
    }

    @GuardedBy("mProcLock")
    long getLastUsedTimeout() {
        return mLastUsedTimeout;
    }

    @GuardedBy("mProcLock")
    void setLastUsedTimeout(long lastUsedTimeout) {
        mLastUsedTimeout = lastUsedTimeout;
    }

    @GuardedBy("mProcLock")
    boolean isFreezeExempt() {
        return mFreezeExempt;