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

Commit c8bebaf2 authored by Lee Shombert's avatar Lee Shombert
Browse files

Create a PIC nonce-watcher for external clients

This creates a NonceWatcher feature that reports if a PIC nonce has
changed its value.  The feature is only effective in the same process
as the nonce server (e.g., system_server for MODULE_SYSTEM nonces),
but it is very fast in-process.

Clients wait for a nonce change by blocking on a semaphore.  This
isolates the PIC invalidation hot path from delays in the client
behavior.

Flag: android.app.pic_uses_shared_memory
Bug: 360897450
Test: atest
 * FrameworksCoreTests:PropertyInvalidatedCacheTests
 * FrameworksCoreTests:IpcDataCacheTest
 * CtsOsTestCases:IpcDataCacheTest
Change-Id: I56d89df8301e397860836dd06bf9fed14bc13d45
parent 7980a97f
Loading
Loading
Loading
Loading
+191 −17
Original line number Diff line number Diff line
@@ -61,6 +61,8 @@ import java.util.Random;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
@@ -680,12 +682,17 @@ public class PropertyInvalidatedCache<Query, Result> {
        @GuardedBy("mLock")
        private boolean mTestMode = false;

        /**
         * The local value of the handler, used during testing but also used directly by the
         * NonceLocal handler.
         */
        // This is the local value of the nonce, as last set by the NonceHandler.  It is always
        // updated by the setNonce() operation.  The getNonce() operation returns this value in
        // NonceLocal handlers and handlers in test mode.
        @GuardedBy("mLock")
        protected long mShadowNonce = NONCE_UNSET;

        // A list of watchers to be notified of changes.  This is null until at least one watcher
        // registers.  Checking for null is meant to be the fastest way the handler can determine
        // that there are no watchers to be notified.
        @GuardedBy("mLock")
        protected long mTestNonce = NONCE_UNSET;
        private ArrayList<Semaphore> mWatchers;

        /**
         * The methods to get and set a nonce from whatever storage is being used.  mLock may be
@@ -701,27 +708,60 @@ public class PropertyInvalidatedCache<Query, Result> {

        /**
         * Get a nonce from storage.  If the handler is in test mode, the nonce is returned from
         * the local mTestNonce.
         * the local mShadowNonce.
         */
        long getNonce() {
            synchronized (mLock) {
                if (mTestMode) return mTestNonce;
                if (mTestMode) return mShadowNonce;
            }
            return getNonceInternal();
        }

        /**
         * Write a nonce to storage.  If the handler is in test mode, the nonce is written to the
         * local mTestNonce and storage is not affected.
         * Write a nonce to storage.  The nonce is always written to the local mShadowNonce.  If
         * the handler is not in test mode the nonce is also written to storage.
         */
        void setNonce(long val) {
            synchronized (mLock) {
                if (mTestMode) {
                    mTestNonce = val;
                    return;
                mShadowNonce = val;
                if (!mTestMode) {
                    setNonceInternal(val);
                }
                wakeAllWatchersLocked();
            }
        }

        @GuardedBy("mLock")
        private void wakeAllWatchersLocked() {
            if (mWatchers != null) {
                for (int i = 0; i < mWatchers.size(); i++) {
                    mWatchers.get(i).release();
                }
            }
        }

        /**
         * Register a watcher to be notified when a nonce changes.  There is no check for
         * duplicates.  In general, this method is called only from {@link NonceWatcher}.
         */
        void registerWatcher(Semaphore s) {
            synchronized (mLock) {
                if (mWatchers == null) {
                    mWatchers = new ArrayList<>();
                }
                mWatchers.add(s);
            }
        }

        /**
         * Unregister a watcher.  Nothing happens if the watcher is not registered.
         */
        void unregisterWatcher(Semaphore s) {
            synchronized (mLock) {
                if (mWatchers != null) {
                    mWatchers.remove(s);
                }
            }
            setNonceInternal(val);
        }

        /**
@@ -854,7 +894,7 @@ public class PropertyInvalidatedCache<Query, Result> {
        void setTestMode(boolean mode) {
            synchronized (mLock) {
                mTestMode = mode;
                mTestNonce = NONCE_UNSET;
                mShadowNonce = NONCE_UNSET;
            }
        }

@@ -1028,7 +1068,7 @@ public class PropertyInvalidatedCache<Query, Result> {
    /**
     * SystemProperties and shared storage are protected and cannot be written by random
     * processes.  So, for testing purposes, the NonceLocal handler stores the nonce locally.  The
     * NonceLocal uses the mTestNonce in the superclass, regardless of test mode.
     * NonceLocal uses the mShadowNonce in the superclass, regardless of test mode.
     */
    private static class NonceLocal extends NonceHandler {
        // The saved nonce.
@@ -1040,13 +1080,127 @@ public class PropertyInvalidatedCache<Query, Result> {

        @Override
        long getNonceInternal() {
            return mTestNonce;
            return mShadowNonce;
        }

        @Override
        void setNonceInternal(long value) {
            mTestNonce = value;
            mShadowNonce = value;
        }
    }

    /**
     * A NonceWatcher lets an external client test if a nonce value has changed from the last time
     * the watcher was checked.
     * @hide
     */
    public static class NonceWatcher implements AutoCloseable {
        // The handler for the key.
        private final NonceHandler mHandler;

        // The last-seen value.  This is initialized to "unset".
        private long mLastSeen = NONCE_UNSET;

        // The semaphore that the watcher waits on.  A permit is released every time the nonce
        // changes.  Permits are acquired in the wait method.
        private final Semaphore mSem = new Semaphore(0);

        /**
         * Create a watcher for a handler.  The last-seen value is not set here and will be
         * "unset".  Therefore, a call to isChanged() will return true if the nonce has ever been
         * set, no matter when the watcher is first created.  Clients may want to flush that
         * change by calling isChanged() immediately after constructing the object.
         */
        private NonceWatcher(@NonNull NonceHandler handler) {
            mHandler = handler;
            mHandler.registerWatcher(mSem);
        }

        /**
         * Unregister to be notified when a nonce changes.  NonceHandler allows a call to
         * unregisterWatcher with a semaphore that is not registered, so there is no check inside
         * this method to guard against multiple closures.
         */
        @Override
        public void close() {
            mHandler.unregisterWatcher(mSem);
        }

        /**
         * Return the last seen value of the nonce.  This does not update that value.  Only
         * {@link #isChanged()} updates the value.
         */
        public long lastSeen() {
            return mLastSeen;
        }

        /**
         * Return true if the nonce has changed from the last time isChanged() was called.  The
         * method is not thread safe.
         * @hide
         */
        public boolean isChanged() {
            long current = mHandler.getNonce();
            if (current != mLastSeen) {
                mLastSeen = current;
                return true;
            }
            return false;
        }

        /**
         * Wait for the nonce value to change.  It is not guaranteed that the nonce has changed when
         * this returns: clients must confirm with {@link #isChanged}. The wait operation is only
         * effective in a process that writes the nonces.  The function returns the number of times
         * the nonce had changed since the last call to the method.
         * @hide
         */
        public int waitForChange() throws InterruptedException {
            mSem.acquire(1);
            return 1 + mSem.drainPermits();
        }

        /**
         * Wait for the nonce value to change.  It is not guaranteed that the nonce has changed when
         * this returns: clients must confirm with {@link #isChanged}. The wait operation is only
         * effective in a process that writes the nonces.  The function returns the number of times
         * the nonce changed since the last call to the method.  A return value of zero means the
         * timeout expired.  Beware that a timeout of 0 means the function will not wait at all.
         * @hide
         */
        public int waitForChange(long timeout, TimeUnit timeUnit) throws InterruptedException {
            if (mSem.tryAcquire(1, timeout, timeUnit)) {
                return 1 + mSem.drainPermits();
            } else {
                return 0;
            }
        }

        /**
         * Wake the watcher by releasing the semaphore.  This can be used to wake clients that are
         * blocked in {@link #waitForChange} without affecting the underlying nonce.
         * @hide
         */
        public void wakeUp() {
            mSem.release();
        }
    }

    /**
     * Return a NonceWatcher for the cache.
     * @hide
     */
    public NonceWatcher getNonceWatcher() {
        return new NonceWatcher(mNonce);
    }

    /**
     * Return a NonceWatcher for the given property.  If a handler does not exist for the
     * property, one is created.  This throws if the property name is not a valid cache key.
     * @hide
     */
    public static NonceWatcher getNonceWatcher(@NonNull String propertyName) {
        return new NonceWatcher(getNonceHandler(propertyName));
    }

    /**
@@ -1662,6 +1816,26 @@ public class PropertyInvalidatedCache<Query, Result> {
        mNonce.invalidate();
    }

    /**
     * Non-static version of corkInvalidations() for situations in which the cache instance is
     * available.  This is slightly faster than than the static versions because it does not have
     * to look up the NonceHandler for a given property name.
     * @hide
     */
    public void corkInvalidations() {
        mNonce.cork();
    }

    /**
     * Non-static version of uncorkInvalidations() for situations in which the cache instance is
     * available.  This is slightly faster than than the static versions because it does not have
     * to look up the NonceHandler for a given property name.
     * @hide
     */
    public void uncorkInvalidations() {
        mNonce.uncork();
    }

    /**
     * Invalidate caches in all processes that are keyed for the module and api.
     * @hide
+65 −4
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import static android.app.PropertyInvalidatedCache.NonceStore.INVALID_NONCE_INDE
import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
@@ -34,6 +35,8 @@ import static org.junit.Assert.fail;

import android.annotation.SuppressLint;
import android.app.PropertyInvalidatedCache.Args;
import android.app.PropertyInvalidatedCache.NonceWatcher;
import android.app.PropertyInvalidatedCache.NonceStore;
import android.os.Binder;
import com.android.internal.os.ApplicationSharedMemory;

@@ -45,11 +48,15 @@ import android.platform.test.ravenwood.RavenwoodRule;

import androidx.test.filters.SmallTest;

import com.android.internal.os.ApplicationSharedMemory;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

/**
 * Test for verifying the behavior of {@link PropertyInvalidatedCache}.  This test does
 * not use any actual binder calls - it is entirely self-contained.  This test also relies
@@ -490,6 +497,62 @@ public class PropertyInvalidatedCacheTests {
        }
    }

    // Verify that NonceWatcher change reporting works properly
    @Test
    public void testNonceWatcherChanged() {
        // Create a cache that will write a system nonce.
        TestCache sysCache = new TestCache(MODULE_SYSTEM, "watcher1");
        sysCache.testPropertyName();

        try (NonceWatcher watcher1 = sysCache.getNonceWatcher()) {

            // The property has never been invalidated so it is still unset.
            assertFalse(watcher1.isChanged());

            // Invalidate the cache.  The first call to isChanged will return true but the second
            // call will return false;
            sysCache.invalidateCache();
            assertTrue(watcher1.isChanged());
            assertFalse(watcher1.isChanged());

            // Invalidate the cache.  The first call to isChanged will return true but the second
            // call will return false;
            sysCache.invalidateCache();
            sysCache.invalidateCache();
            assertTrue(watcher1.isChanged());
            assertFalse(watcher1.isChanged());

            NonceWatcher watcher2 = sysCache.getNonceWatcher();
            // This watcher return isChanged() immediately because the nonce is not UNSET.
            assertTrue(watcher2.isChanged());
        }
    }

    // Verify that NonceWatcher wait-for-change works properly
    @Test
    public void testNonceWatcherWait() throws Exception {
        // Create a cache that will write a system nonce.
        TestCache sysCache = new TestCache(MODULE_TEST, "watcher1");

        // Use the watcher outside a try-with-resources block.
        NonceWatcher watcher1 = sysCache.getNonceWatcher();

        // Invalidate the cache and then "wait".
        sysCache.invalidateCache();
        assertEquals(watcher1.waitForChange(), 1);

        // Invalidate the cache three times and then "wait".
        sysCache.invalidateCache();
        sysCache.invalidateCache();
        sysCache.invalidateCache();
        assertEquals(watcher1.waitForChange(), 3);

        // Wait for a change.  It won't happen, but the code will time out after 10ms.
        assertEquals(watcher1.waitForChange(10, TimeUnit.MILLISECONDS), 0);

        watcher1.close();
    }

    // Verify the behavior of shared memory nonce storage.  This does not directly test the cache
    // storing nonces in shared memory.
    @RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED)
@@ -502,10 +565,8 @@ public class PropertyInvalidatedCacheTests {

        // Create a server-side store and a client-side store.  The server's store is mutable and
        // the client's store is not mutable.
        PropertyInvalidatedCache.NonceStore server =
                new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), true);
        PropertyInvalidatedCache.NonceStore client =
                new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), false);
        NonceStore server = new NonceStore(shmem.getSystemNonceBlock(), true);
        NonceStore client = new NonceStore(shmem.getSystemNonceBlock(), false);

        final String name1 = "name1";
        assertEquals(server.getHandleForName(name1), INVALID_NONCE_INDEX);