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

Commit a6a60b77 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

Cache more users in UserHandle.of()

We pass around more and more UserHandle these days because of mainline
modules, because even @SystemApi are not allowed to use integer user
IDs.

So let's make sure even in extreme cases we don't keep allocating
new UserHandle's for the same user.

Test: atest $ANDROID_BUILD_TOP/frameworks/base/core/tests/coretests/src/android/os/UserHandleTest.java
Test: atest $ANDROID_BUILD_TOP/cts/tests/app/src/android/app/cts/UserHandleTest.java
Change-Id: Iae34e1d11638ced53246d68d310ad62e058dba93
parent 43a7ff5f
Loading
Loading
Loading
Loading
+60 −10
Original line number Diff line number Diff line
@@ -23,10 +23,15 @@ import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Representation of a user on the device.
@@ -119,19 +124,46 @@ public final class UserHandle implements Parcelable {
    public static final int MIN_SECONDARY_USER_ID = 10;

    /**
     * Arbitrary user handle cache size. We use the cache even when {@link #MU_ENABLED} is false
     * anyway, so we can always assume in CTS that UserHandle.of(10) returns a cached instance
     * even on non-multiuser devices.
     * (Arbitrary) user handle cache size.
     * {@link #CACHED_USER_HANDLES} caches user handles in the range of
     * [{@link #MIN_SECONDARY_USER_ID}, {@link #MIN_SECONDARY_USER_ID} + {@link #NUM_CACHED_USERS}).
     *
     * For other users, we cache UserHandles in {link #sExtraUserHandleCache}.
     *
     * Normally, {@link #CACHED_USER_HANDLES} should cover all existing users, but use
     * {link #sExtraUserHandleCache} to ensure {@link UserHandle#of} will not cause too many
     * object allocations even if the device happens to have a secondary user with a large number
     * (e.g. the user kept creating and removing the guest user?).
     */
    private static final int NUM_CACHED_USERS = 4;
    private static final int NUM_CACHED_USERS = MU_ENABLED ? 8 : 0;

    private static final UserHandle[] CACHED_USER_INFOS = new UserHandle[NUM_CACHED_USERS];
    /** @see #NUM_CACHED_USERS} */
    private static final UserHandle[] CACHED_USER_HANDLES = new UserHandle[NUM_CACHED_USERS];

    /**
     * Extra cache for users beyond CACHED_USER_HANDLES.
     *
     * @see #NUM_CACHED_USERS
     * @hide
     */
    @GuardedBy("sExtraUserHandleCache")
    @VisibleForTesting
    public static final SparseArray<UserHandle> sExtraUserHandleCache = new SparseArray<>(0);

    /**
     * Max size of {@link #sExtraUserHandleCache}. Once it reaches this size, we select
     * an element to remove at random.
     *
     * @hide
     */
    @VisibleForTesting
    public static final int MAX_EXTRA_USER_HANDLE_CACHE_SIZE = 32;

    static {
        // Not lazily initializing the cache, so that we can share them across processes.
        // (We'll create them in zygote.)
        for (int i = 0; i < CACHED_USER_INFOS.length; i++) {
            CACHED_USER_INFOS[i] = new UserHandle(MIN_SECONDARY_USER_ID + i);
        for (int i = 0; i < CACHED_USER_HANDLES.length; i++) {
            CACHED_USER_HANDLES[i] = new UserHandle(MIN_SECONDARY_USER_ID + i);
        }
    }

@@ -302,13 +334,31 @@ public final class UserHandle implements Parcelable {
                return CURRENT_OR_SELF;
        }
        if (userId >= MIN_SECONDARY_USER_ID
                && userId < (MIN_SECONDARY_USER_ID + CACHED_USER_INFOS.length)) {
            return CACHED_USER_INFOS[userId - MIN_SECONDARY_USER_ID];
                && userId < (MIN_SECONDARY_USER_ID + CACHED_USER_HANDLES.length)) {
            return CACHED_USER_HANDLES[userId - MIN_SECONDARY_USER_ID];
        }
        if (userId == USER_NULL) { // Not common.
            return NULL;
        }
        return new UserHandle(userId);
        return getUserHandleFromExtraCache(userId);
    }

    /** @hide */
    @VisibleForTesting
    public static UserHandle getUserHandleFromExtraCache(@UserIdInt int userId) {
        synchronized (sExtraUserHandleCache) {
            final UserHandle extraCached = sExtraUserHandleCache.get(userId);
            if (extraCached != null) {
                return extraCached;
            }
            if (sExtraUserHandleCache.size() >= MAX_EXTRA_USER_HANDLE_CACHE_SIZE) {
                sExtraUserHandleCache.removeAt(
                        (new Random()).nextInt(MAX_EXTRA_USER_HANDLE_CACHE_SIZE));
            }
            final UserHandle newHandle = new UserHandle(userId);
            sExtraUserHandleCache.put(userId, newHandle);
            return newHandle;
        }
    }

    /**
+30 −0
Original line number Diff line number Diff line
@@ -24,12 +24,15 @@ import static android.os.UserHandle.getUid;
import static android.os.UserHandle.getUserId;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;

import androidx.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Random;

@RunWith(AndroidJUnit4.class)
public class UserHandleTest {
    // NOTE: keep logic in sync with system/core/libcutils/tests/multiuser_test.cpp
@@ -117,4 +120,31 @@ public class UserHandleTest {
    private static int multiuser_get_app_id(int uid) {
        return getAppId(uid);
    }

    @Test
    public void testExtraCache() {
        assertEquals(0, UserHandle.sExtraUserHandleCache.size());

        // This shouldn't hit the extra cache.
        assertEquals(10, UserHandle.of(10).getIdentifier());

        assertEquals(0, UserHandle.sExtraUserHandleCache.size());

        // This should hit the cache
        assertEquals(20, UserHandle.of(20).getIdentifier());

        assertEquals(1, UserHandle.sExtraUserHandleCache.size());

        // Make sure the cache works.
        final Random rnd = new Random();
        for (int i = 0; i < 10000; i++) {
            final int userId = rnd.nextInt(100);
            assertEquals(userId, UserHandle.of(userId).getIdentifier());
            assertSame(UserHandle.of(userId), UserHandle.of(userId));
        }

        // Make sure the cache size doesn't exceed the max size.
        assertEquals(UserHandle.MAX_EXTRA_USER_HANDLE_CACHE_SIZE,
                UserHandle.sExtraUserHandleCache.size());
    }
}