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

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

PackageManager lock reduction utilities

Bug: 161323622

Define a Snapshotable interface that marks a class as supporting the
snapshot() methods.  A snapshot is an immutable copy of a class that
supports read-only methods.  Class attributes that are not required
for read-only methods are not coiped into the snapshot.

Watchable classes that are are Snapshotable exploit the Watchable
machinery to throw an exception if code attempts to modify a snapshot.

The Snapshotable functionality is tested in the existing WatcherTest
unit test.

Test: atest
 * FrameworksServicesTests:WatcherTest

Change-Id: I279b378ddf71c3e753fcc4776e22fe859123bc14
parent b9fa3af4
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.server.utils;

import android.annotation.NonNull;

/**
 * A class that implements Snappable can generate a read-only copy its instances.  A
 * snapshot is like a clone except that it is only required to support read-only class
 * methods.  Snapshots are immutable.  Attempts to modify the state of a snapshot throw
 * {@link UnsupporteOperationException}.
 * @param <T> The type returned by the snapshot() method.
 */
public interface Snappable<T> {

    /**
     * Create an immutable copy of the object, suitable for read-only methods.  A snapshot
     * is free to omit state that is only needed for mutating methods.
     */
    @NonNull T snapshot();
}
+133 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.server.utils;

import android.annotation.NonNull;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.SparseSetArray;

/**
 * A collection of useful methods for manipulating Snapshot classes.  This is similar to
 * java.util.Objects or java.util.Arrays.
 */
public class Snapshots {

    /**
     * Return the snapshot of an object, if the object extends {@link Snapper}, or the object
     * itself.
     * @param o The object to be copied
     * @return A snapshot of the object, if the object extends {@link Snapper}
     */
    public static <T> T maybeSnapshot(T o) {
        if (o instanceof Snappable) {
            return ((Snappable<T>) o).snapshot();
        } else {
            return o;
        }
    }

    /**
     * Copy a SparseArray in a manner suitable for a snapshot.  The destination must be
     * empty.  This is not a snapshot because the elements are copied by reference even if
     * they are {@link Snappable}.
     * @param dst The destination array.  It must be empty.
     * @param src The source array
     */
    public <E> void copy(@NonNull SparseArray<E> dst, @NonNull SparseArray<E> src) {
        if (dst.size() != 0) {
            throw new IllegalArgumentException("copy destination is not empty");
        }
        final int end = src.size();
        for (int i = 0; i < end; i++) {
            dst.put(src.keyAt(i), src.valueAt(i));
        }
    }

    /**
     * Copy a SparseSetArray in a manner suitable for a snapshot.  The destination must be
     * empty.  This is not a snapshot because the elements are copied by reference even if
     * they are {@link Snappable}.
     * @param dst The destination array.  It must be empty.
     * @param src The source array
     */
    public static <E> void copy(@NonNull SparseSetArray<E> dst, @NonNull SparseSetArray<E> src) {
        if (dst.size() != 0) {
            throw new IllegalArgumentException("copy destination is not empty");
        }
        final int end = src.size();
        for (int i = 0; i < end; i++) {
            final int size = src.sizeAt(i);
            for (int j = 0; j < size; j++) {
                dst.add(src.keyAt(i), src.valueAt(i, j));
            }
        }
    }

    /**
     * Make <dst> a snapshot of <src> .
     * @param dst The destination array.  It must be empty.
     * @param src The source array
     */
    public void snapshot(@NonNull SparseIntArray dst, @NonNull SparseIntArray src) {
        if (dst.size() != 0) {
            throw new IllegalArgumentException("snapshot destination is not empty");
        }
        final int end = src.size();
        for (int i = 0; i < end; i++) {
            dst.put(src.keyAt(i), src.valueAt(i));
        }
    }

    /**
     * Make <dst> a "snapshot" of <src>.  <dst> mst be empty.  The destination is just a
     * copy of the source except that if the source elements implement Snappable, then
     * the elements in the destination will be snapshots of elements from the source.
     * @param dst The destination array.  It must be empty.
     * @param src The source array
     */
    public static <E extends Snappable<E>> void snapshot(@NonNull SparseArray<E> dst,
            @NonNull SparseArray<E> src) {
        if (dst.size() != 0) {
            throw new IllegalArgumentException("snapshot destination is not empty");
        }
        final int end = src.size();
        for (int i = 0; i < end; i++) {
            dst.put(src.keyAt(i), src.valueAt(i).snapshot());
        }
    }

    /**
     * Make <dst> a "snapshot" of <src>.  <dst> mst be empty.  The destination is a
     * copy of the source except that snapshots are taken of the elements.
     * @param dst The destination array.  It must be empty.
     * @param src The source array
     */
    public static <E extends Snappable<E>> void snapshot(@NonNull SparseSetArray<E> dst,
            @NonNull SparseSetArray<E> src) {
        if (dst.size() != 0) {
            throw new IllegalArgumentException("snapshot destination is not empty");
        }
        final int end = src.size();
        for (int i = 0; i < end; i++) {
            final int size = src.sizeAt(i);
            for (int j = 0; j < size; j++) {
                dst.add(src.keyAt(i), src.valueAt(i, j).snapshot());
            }
        }
    }
}
+32 −1
Original line number Diff line number Diff line
@@ -19,11 +19,15 @@ package com.android.server.utils;
import android.annotation.NonNull;
import android.annotation.Nullable;

import com.android.internal.annotations.GuardedBy;

import java.util.ArrayList;
import java.util.Objects;

/**
 * A concrete implementation of {@link Watchable}
 * A concrete implementation of {@link Watchable}.  This includes one commonly needed feature:
 * the Watchable may be sealed, so that it throws an {@link IllegalStateException} if
 * a change is detected.
 */
public class WatchableImpl implements Watchable {
    /**
@@ -78,10 +82,37 @@ public class WatchableImpl implements Watchable {
    @Override
    public void dispatchChange(@Nullable Watchable what) {
        synchronized (mObservers) {
            if (mSealed) {
                throw new IllegalStateException("attempt to change a sealed object");
            }
            final int end = mObservers.size();
            for (int i = 0; i < end; i++) {
                mObservers.get(i).onChange(what);
            }
        }
    }

    /**
     * True if the object is sealed.
     */
    @GuardedBy("mObservers")
    private boolean mSealed = false;

    /**
     * Freeze the {@link Watchable}.
     **/
    public void seal() {
        synchronized (mObservers) {
            mSealed = true;
        }
    }

    /**
     * Return the sealed state.
     */
    public boolean isFrozen() {
        synchronized (mObservers) {
            return mSealed;
        }
    }
}
+39 −4
Original line number Diff line number Diff line
@@ -18,9 +18,7 @@ package com.android.server.utils;

import android.annotation.NonNull;
import android.annotation.Nullable;

import android.util.ArrayMap;
import android.util.Log;

import java.util.Collection;
import java.util.Collections;
@@ -31,14 +29,17 @@ import java.util.Set;
 * WatchedArrayMap is an {@link android.util.ArrayMap} that can report changes to itself.  If its
 * values are {@link Watchable} then the WatchedArrayMap will also report changes to the values.
 * A {@link Watchable} is notified only once, no matter how many times it is stored in the array.
 * @param <K> The key type.
 * @param <V> The value type.
 */
public class WatchedArrayMap<K, V> extends WatchableImpl implements Map<K, V> {
public class WatchedArrayMap<K, V> extends WatchableImpl
        implements Map<K, V>, Snappable {

    // The storage
    private final ArrayMap<K, V> mStorage;

    // If true, the array is watching its children
    private boolean mWatching = false;
    private volatile boolean mWatching = false;

    // The local observer
    private final Watcher mObserver = new Watcher() {
@@ -386,4 +387,38 @@ public class WatchedArrayMap<K, V> extends WatchableImpl implements Map<K, V> {
        onChanged();
        return result;
    }

    /**
     * Create a copy of the array.  If the element is a subclass of Snapper then the copy
     * contains snapshots of the elements.  Otherwise the copy contains references to the
     * elements.  The returned snapshot is immutable.
     * @return A new array whose elements are the elements of <this>.
     */
    public WatchedArrayMap<K, V> snapshot() {
        WatchedArrayMap<K, V> l = new WatchedArrayMap<>();
        snapshot(l, this);
        return l;
    }

    /**
     * Make the destination a copy of the source.  If the element is a subclass of Snapper then the
     * copy contains snapshots of the elements.  Otherwise the copy contains references to the
     * elements.  The destination must be initially empty.  Upon return, the destination is
     * immutable.
     * @param dst The destination array.  It must be empty.
     * @param src The source array.  It is not modified.
     */
    public static <K, V> void snapshot(@NonNull WatchedArrayMap<K, V> dst,
            @NonNull WatchedArrayMap<K, V> src) {
        if (dst.size() != 0) {
            throw new IllegalArgumentException("snapshot destination is not empty");
        }
        final int end = src.size();
        for (int i = 0; i < end; i++) {
            final V val = Snapshots.maybeSnapshot(src.valueAt(i));
            final K key = src.keyAt(i);
            dst.put(key, val);
        }
        dst.seal();
    }
}
+39 −3
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.server.utils;

import android.annotation.NonNull;
import android.annotation.Nullable;

import android.util.SparseArray;

import java.util.ArrayList;
@@ -28,14 +27,16 @@ import java.util.ArrayList;
 * array registers with the {@link Watchable}.  The array registers only once with each
 * {@link Watchable} no matter how many times the {@link Watchable} is stored in the
 * array.
 * @param <E> The element type, stored in the array.
 */
public class WatchedSparseArray<E> extends WatchableImpl {
public class WatchedSparseArray<E> extends WatchableImpl
        implements Snappable {

    // The storage
    private final SparseArray<E> mStorage;

    // If true, the array is watching its children
    private boolean mWatching = false;
    private volatile boolean mWatching = false;

    // The local observer
    private final Watcher mObserver = new Watcher() {
@@ -398,4 +399,39 @@ public class WatchedSparseArray<E> extends WatchableImpl {
    public String toString() {
        return mStorage.toString();
    }

    /**
     * Create a copy of the array.  If the element is a subclass of Snapper then the copy
     * contains snapshots of the elements.  Otherwise the copy contains references to the
     * elements.  The returned snapshot is immutable.
     * @return A new array whose elements are the elements of <this>.
     */
    public WatchedSparseArray<E> snapshot() {
        WatchedSparseArray<E> l = new WatchedSparseArray<>();
        snapshot(l, this);
        return l;
    }

    /**
     * Make the destination a copy of the source.  If the element is a subclass of Snapper then the
     * copy contains snapshots of the elements.  Otherwise the copy contains references to the
     * elements.  The destination must be initially empty.  Upon return, the destination is
     * immutable.
     * @param dst The destination array.  It must be empty.
     * @param src The source array.  It is not modified.
     */
    public static <E> void snapshot(@NonNull WatchedSparseArray<E> dst,
            @NonNull WatchedSparseArray<E> src) {
        if (dst.size() != 0) {
            throw new IllegalArgumentException("snapshot destination is not empty");
        }
        final int end = src.size();
        for (int i = 0; i < end; i++) {
            final E val = Snapshots.maybeSnapshot(src.valueAt(i));
            final int key = src.keyAt(i);
            dst.put(key, val);
        }
        dst.seal();
    }

}
Loading