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

Commit ece4ef73 authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Fix concurrency of BatteryStats cleanup after user removal

Bug: 193742542
Bug: 180015146
Test: atest --rerun-until-failure 20 FrameworksCoreTests:BatteryStatsUserLifecycleTests#testNoCpuDataForRemovedUser

Change-Id: If45cbf0be6c29fc99771053c6a0ec34c60e293a8
parent 59c65885
Loading
Loading
Loading
Loading
+72 −39
Original line number Diff line number Diff line
@@ -300,9 +300,9 @@ public class BatteryStatsImpl extends BatteryStats {
    @VisibleForTesting
    public final class UidToRemove {
        int startUid;
        int endUid;
        long mTimeAddedInQueueMs;
        private final int mStartUid;
        private final int mEndUid;
        private final long mUidRemovalTimestamp;
        /** Remove just one UID */
        public UidToRemove(int uid, long timestamp) {
@@ -311,38 +311,18 @@ public class BatteryStatsImpl extends BatteryStats {
        /** Remove a range of UIDs, startUid must be smaller than endUid. */
        public UidToRemove(int startUid, int endUid, long timestamp) {
            this.startUid = startUid;
            this.endUid = endUid;
            mTimeAddedInQueueMs = timestamp;
            mStartUid = startUid;
            mEndUid = endUid;
            mUidRemovalTimestamp = timestamp;
        }
        void remove() {
            if (startUid == endUid) {
                mCpuUidUserSysTimeReader.removeUid(startUid);
                mCpuUidFreqTimeReader.removeUid(startUid);
                if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
                    mCpuUidActiveTimeReader.removeUid(startUid);
                    mCpuUidClusterTimeReader.removeUid(startUid);
                }
                if (mKernelSingleUidTimeReader != null) {
                    mKernelSingleUidTimeReader.removeUid(startUid);
                }
                mNumUidsRemoved++;
            } else if (startUid < endUid) {
                mCpuUidFreqTimeReader.removeUidsInRange(startUid, endUid);
                mCpuUidUserSysTimeReader.removeUidsInRange(startUid, endUid);
                if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
                    mCpuUidActiveTimeReader.removeUidsInRange(startUid, endUid);
                    mCpuUidClusterTimeReader.removeUidsInRange(startUid, endUid);
                }
                if (mKernelSingleUidTimeReader != null) {
                    mKernelSingleUidTimeReader.removeUidsInRange(startUid, endUid);
                }
                // Treat as one. We don't know how many uids there are in between.
                mNumUidsRemoved++;
            } else {
                Slog.w(TAG, "End UID " + endUid + " is smaller than start UID " + startUid);
        public long getUidRemovalTimestamp() {
            return mUidRemovalTimestamp;
        }
        @GuardedBy("BatteryStatsImpl.this")
        void removeLocked() {
            removeCpuStatsForUidRangeLocked(mStartUid, mEndUid);
        }
    }
@@ -526,11 +506,16 @@ public class BatteryStatsImpl extends BatteryStats {
        }
    }
    public void clearPendingRemovedUids() {
    /**
     * Removes kernel CPU stats for removed UIDs, in the order they were added to the
     * mPendingRemovedUids queue.
     */
    @GuardedBy("this")
    public void clearPendingRemovedUidsLocked() {
        long cutOffTimeMs = mClocks.elapsedRealtime() - mConstants.UID_REMOVE_DELAY_MS;
        while (!mPendingRemovedUids.isEmpty()
                && mPendingRemovedUids.peek().mTimeAddedInQueueMs < cutOffTimeMs) {
            mPendingRemovedUids.poll().remove();
                && mPendingRemovedUids.peek().getUidRemovalTimestamp() < cutOffTimeMs) {
            mPendingRemovedUids.poll().removeLocked();
        }
    }
@@ -694,6 +679,8 @@ public class BatteryStatsImpl extends BatteryStats {
        Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis);
        void cancelCpuSyncDueToWakelockChange();
        Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis);
        /** Schedule removal of UIDs corresponding to a removed user */
        Future<?> scheduleCleanupDueToRemovedUser(int userId);
    }
    public Handler mHandler;
@@ -14390,6 +14377,18 @@ public class BatteryStatsImpl extends BatteryStats {
    }
    public void onUserRemovedLocked(int userId) {
        if (mExternalSync != null) {
            // Clear out the removed user's UIDs after a short delay. The delay is needed
            // because at the point that this method is called, some activities are still
            // being wrapped up by those UIDs
            mExternalSync.scheduleCleanupDueToRemovedUser(userId);
        }
    }
    /**
     * Removes battery stats for UIDs corresponding to a removed user.
     */
    public void clearRemovedUserUidsLocked(int userId) {
        final int firstUidForUser = UserHandle.getUid(userId, 0);
        final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
        mUidStats.put(firstUidForUser, null);
@@ -14403,6 +14402,7 @@ public class BatteryStatsImpl extends BatteryStats {
            }
        }
        mUidStats.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
        removeCpuStatsForUidRangeLocked(firstUidForUser, lastUidForUser);
    }
    /**
@@ -14425,6 +14425,39 @@ public class BatteryStatsImpl extends BatteryStats {
        mPendingRemovedUids.add(new UidToRemove(uid, elapsedRealtimeMs));
    }
    /**
     * Removes the data for the deleted UIDs from the underlying kernel eBPF tables.
     */
    @GuardedBy("this")
    private void removeCpuStatsForUidRangeLocked(int startUid, int endUid) {
        if (startUid == endUid) {
            mCpuUidUserSysTimeReader.removeUid(startUid);
            mCpuUidFreqTimeReader.removeUid(startUid);
            if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
                mCpuUidActiveTimeReader.removeUid(startUid);
                mCpuUidClusterTimeReader.removeUid(startUid);
            }
            if (mKernelSingleUidTimeReader != null) {
                mKernelSingleUidTimeReader.removeUid(startUid);
            }
            mNumUidsRemoved++;
        } else if (startUid < endUid) {
            mCpuUidFreqTimeReader.removeUidsInRange(startUid, endUid);
            mCpuUidUserSysTimeReader.removeUidsInRange(startUid, endUid);
            if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
                mCpuUidActiveTimeReader.removeUidsInRange(startUid, endUid);
                mCpuUidClusterTimeReader.removeUidsInRange(startUid, endUid);
            }
            if (mKernelSingleUidTimeReader != null) {
                mKernelSingleUidTimeReader.removeUidsInRange(startUid, endUid);
            }
            // Treat as one. We don't know how many uids there are in between.
            mNumUidsRemoved++;
        } else {
            Slog.w(TAG, "End UID " + endUid + " is smaller than start UID " + startUid);
        }
    }
    /**
     * Retrieve the statistics object for a particular process, creating
     * if needed.
@@ -14729,7 +14762,7 @@ public class BatteryStatsImpl extends BatteryStats {
        private void updateUidRemoveDelay(long newTimeMs) {
            UID_REMOVE_DELAY_MS = newTimeMs;
            clearPendingRemovedUids();
            clearPendingRemovedUidsLocked();
        }
        public void dumpLocked(PrintWriter pw) {
+5 −5
Original line number Diff line number Diff line
@@ -1263,21 +1263,21 @@ public class BatteryStatsCpuTimesTest {
                mBatteryStatsImpl.new UidToRemove(1, mClocks.elapsedRealtime()));
        mBatteryStatsImpl.getPendingRemovedUids().add(
                mBatteryStatsImpl.new UidToRemove(5, 10, mClocks.elapsedRealtime()));
        mBatteryStatsImpl.clearPendingRemovedUids();
        mBatteryStatsImpl.clearPendingRemovedUidsLocked();
        assertEquals(2, mBatteryStatsImpl.getPendingRemovedUids().size());

        mClocks.realtime = mClocks.uptime = 100_000;
        mBatteryStatsImpl.clearPendingRemovedUids();
        mBatteryStatsImpl.clearPendingRemovedUidsLocked();
        assertEquals(2, mBatteryStatsImpl.getPendingRemovedUids().size());

        mClocks.realtime = mClocks.uptime = 200_000;
        mBatteryStatsImpl.getPendingRemovedUids().add(
                mBatteryStatsImpl.new UidToRemove(100, mClocks.elapsedRealtime()));
        mBatteryStatsImpl.clearPendingRemovedUids();
        mBatteryStatsImpl.clearPendingRemovedUidsLocked();
        assertEquals(3, mBatteryStatsImpl.getPendingRemovedUids().size());

        mClocks.realtime = mClocks.uptime = 400_000;
        mBatteryStatsImpl.clearPendingRemovedUids();
        mBatteryStatsImpl.clearPendingRemovedUidsLocked();
        assertEquals(1, mBatteryStatsImpl.getPendingRemovedUids().size());
        verify(mCpuUidActiveTimeReader).removeUid(1);
        verify(mCpuUidActiveTimeReader).removeUidsInRange(5, 10);
@@ -1289,7 +1289,7 @@ public class BatteryStatsCpuTimesTest {
        verify(mCpuUidUserSysTimeReader).removeUidsInRange(5, 10);

        mClocks.realtime = mClocks.uptime = 800_000;
        mBatteryStatsImpl.clearPendingRemovedUids();
        mBatteryStatsImpl.clearPendingRemovedUidsLocked();
        assertEquals(0, mBatteryStatsImpl.getPendingRemovedUids().size());
        verify(mCpuUidActiveTimeReader).removeUid(100);
        verify(mCpuUidClusterTimeReader).removeUid(100);
+2 −2
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ public class BatteryStatsUserLifecycleTests {
    private static final long POLL_INTERVAL_MS = 500;
    private static final long USER_REMOVE_TIMEOUT_MS = 5_000;
    private static final long STOP_USER_TIMEOUT_MS = 10_000;
    private static final long USER_UIDS_REMOVE_TIMEOUT_MS = 15_000;
    private static final long BATTERYSTATS_POLLING_TIMEOUT_MS = 5_000;

    private static final String CPU_DATA_TAG = "cpu";
@@ -78,7 +79,6 @@ public class BatteryStatsUserLifecycleTests {
    }

    @Test
    @SkipPresubmit("b/180015146")
    public void testNoCpuDataForRemovedUser() throws Exception {
        mIam.startUserInBackground(mTestUserId);
        waitUntilTrue("No uids for started user " + mTestUserId,
@@ -108,7 +108,7 @@ public class BatteryStatsUserLifecycleTests {
            return true;
        }, USER_REMOVE_TIMEOUT_MS);
        waitUntilTrue("Uids still found for removed user " + mTestUserId,
                () -> getNumberOfUidsInBatteryStats() == 0, BATTERYSTATS_POLLING_TIMEOUT_MS);
                () -> getNumberOfUidsInBatteryStats() == 0, USER_UIDS_REMOVE_TIMEOUT_MS);
    }

    @After
+5 −0
Original line number Diff line number Diff line
@@ -189,6 +189,11 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
            return null;
        }

        @Override
        public Future<?> scheduleCleanupDueToRemovedUser(int userId) {
            return null;
        }

        @Override
        public Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
            return null;
+15 −1
Original line number Diff line number Diff line
@@ -79,6 +79,9 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
    // There is some accuracy error in wifi reports so allow some slop in the results.
    private static final long MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS = 750;

    // Delay for clearing out battery stats for UIDs corresponding to a removed user
    public static final int UID_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS = 10_000;

    private final ScheduledExecutorService mExecutorService =
            Executors.newSingleThreadScheduledExecutor(
                    (ThreadFactory) r -> {
@@ -346,6 +349,17 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
        }
    }

    @Override
    public Future<?> scheduleCleanupDueToRemovedUser(int userId) {
        synchronized (BatteryExternalStatsWorker.this) {
            return mExecutorService.schedule(() -> {
                synchronized (mStats) {
                    mStats.clearRemovedUserUidsLocked(userId);
                }
            }, UID_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS, TimeUnit.MILLISECONDS);
        }
    }

    /**
     * Schedule a sync {@param syncRunnable} with a delay. If there's already a scheduled sync, a
     * new sync won't be scheduled unless it is being scheduled to run immediately (delayMillis=0).
@@ -481,7 +495,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
                        mStats.removeIsolatedUidLocked(uid, SystemClock.elapsedRealtime(),
                                SystemClock.uptimeMillis());
                    }
                    mStats.clearPendingRemovedUids();
                    mStats.clearPendingRemovedUidsLocked();
                }
            } catch (Exception e) {
                Slog.wtf(TAG, "Error updating external stats: ", e);