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

Commit 5674d0ac authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Limit the maximal number of pending requests in TCMS" into rvc-dev am: 0f54743c

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11795958

Change-Id: Ic2cdcd538e6cb70741d8d0780933f62aa282792d
parents 0cc8dbca 0f54743c
Loading
Loading
Loading
Loading
+103 −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.textclassifier;

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

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;

import java.util.ArrayDeque;
import java.util.Objects;
import java.util.Queue;

/**
 * A fixed-size queue which automatically evicts the oldest element from the queue when it is full.
 *
 * <p>This class does not accept null element.
 *
 * @param <E> the type of elements held in this queue
 */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class FixedSizeQueue<E> {

    private final Queue<E> mDelegate;

    @Nullable
    private final OnEntryEvictedListener<E> mOnEntryEvictedListener;

    private final int mMaxSize;

    public FixedSizeQueue(int maxSize, @Nullable OnEntryEvictedListener<E> onEntryEvictedListener) {
        Preconditions.checkArgument(maxSize > 0, "maxSize (%s) must > 0", maxSize);
        mDelegate = new ArrayDeque<>(maxSize);
        mMaxSize = maxSize;
        mOnEntryEvictedListener = onEntryEvictedListener;
    }

    /** Returns the number of items in the queue. */
    public int size() {
        return mDelegate.size();
    }

    /** Adds an element to the queue, evicts the oldest element if it reaches its max capacity. */
    public boolean add(@NonNull E element) {
        Objects.requireNonNull(element);
        if (size() == mMaxSize) {
            E removed = mDelegate.remove();
            if (mOnEntryEvictedListener != null) {
                mOnEntryEvictedListener.onEntryEvicted(removed);
            }
        }
        mDelegate.add(element);
        return true;
    }

    /**
     * Returns and removes the head of the queue, or returns null if this queue is empty.
     */
    @Nullable
    public E poll() {
        return mDelegate.poll();
    }

    /**
     * Removes an element from the queue, returns a boolean to indicate if an element is removed.
     */
    public boolean remove(@NonNull E element) {
        Objects.requireNonNull(element);
        return mDelegate.remove(element);
    }

    /** Returns whether the queue is empty. */
    public boolean isEmpty() {
        return mDelegate.isEmpty();
    }

    /**
     * A listener to get notified when an element is evicted.
     *
     * @param <E> the type of element
     */
    public interface OnEntryEvictedListener<E> {
        /**
         * Notifies that an element is evicted because the queue is reaching its max capacity.
         */
        void onEntryEvicted(@NonNull E element);
    }
}
+20 −7
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.service.textclassifier.TextClassifierService;
import android.service.textclassifier.TextClassifierService.ConnectionState;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.LruCache;
import android.util.Slog;
import android.util.SparseArray;
import android.view.textclassifier.ConversationAction;
@@ -68,12 +69,10 @@ import com.android.server.SystemService;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;

/**
 * A manager for TextClassifier services.
@@ -308,13 +307,15 @@ public final class TextClassificationManagerService extends ITextClassifierServi
        Objects.requireNonNull(classificationContext);
        Objects.requireNonNull(classificationContext.getSystemTextClassifierMetadata());

        synchronized (mLock) {
            mSessionCache.put(sessionId, classificationContext);
        }
        handleRequest(
                classificationContext.getSystemTextClassifierMetadata(),
                /* verifyCallingPackage= */ true,
                /* attemptToBind= */ false,
                service -> {
                    service.onCreateTextClassificationSession(classificationContext, sessionId);
                    mSessionCache.put(sessionId, classificationContext);
                },
                "onCreateTextClassificationSession",
                NO_OP_CALLBACK);
@@ -588,12 +589,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi
     * are cleaned up automatically when the client process is dead.
     */
    static final class SessionCache {
        private static final int MAX_CACHE_SIZE = 100;

        @NonNull
        private final Object mLock;
        @NonNull
        @GuardedBy("mLock")
        private final Map<TextClassificationSessionId, StrippedTextClassificationContext> mCache =
                new ArrayMap<>();
        private final LruCache<TextClassificationSessionId, StrippedTextClassificationContext>
                mCache = new LruCache<>(MAX_CACHE_SIZE);
        @NonNull
        @GuardedBy("mLock")
        private final Map<TextClassificationSessionId, DeathRecipient> mDeathRecipients =
@@ -775,6 +778,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi
    }

    private final class ServiceState {
        private static final int MAX_PENDING_REQUESTS = 20;

        @UserIdInt
        final int mUserId;
        @NonNull
@@ -786,7 +791,15 @@ public final class TextClassificationManagerService extends ITextClassifierServi
        final int mBindServiceFlags;
        @NonNull
        @GuardedBy("mLock")
        final Queue<PendingRequest> mPendingRequests = new ArrayDeque<>();
        final FixedSizeQueue<PendingRequest> mPendingRequests =
                new FixedSizeQueue<>(MAX_PENDING_REQUESTS,
                        request -> {
                            Slog.w(LOG_TAG,
                                    String.format("Pending request[%s] is dropped", request.mName));
                            if (request.mOnServiceFailure != null) {
                                request.mOnServiceFailure.run();
                            }
                        });
        @Nullable
        @GuardedBy("mLock")
        ITextClassifierService mService;
@@ -910,7 +923,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi
                pw.printPair("bindServiceFlags", mBindServiceFlags);
                pw.printPair("boundServiceUid", mBoundServiceUid);
                pw.printPair("binding", mBinding);
                pw.printPair("numberRequests", mPendingRequests.size());
                pw.printPair("numOfPendingRequests", mPendingRequests.size());
            }
        }

+109 −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.textclassifier;

import static com.google.common.truth.Truth.assertThat;

import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

public class FixedSizeQueueTest {

    @Test
    public void add_belowMaxCapacity() {
        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null);
        assertThat(queue.size()).isEqualTo(0);

        queue.add(1);

        assertThat(queue.size()).isEqualTo(1);
        assertThat(queue.poll()).isEqualTo(1);
    }

    @Test
    public void add_exceedMaxCapacity() {
        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(2, /* onEntryEvictedListener= */ null);

        queue.add(1);
        queue.add(2);
        queue.add(3);

        assertThat(queue.size()).isEqualTo(2);
        assertThat(queue.poll()).isEqualTo(2);
        assertThat(queue.poll()).isEqualTo(3);
    }

    @Test
    public void poll() {
        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null);

        queue.add(1);

        assertThat(queue.poll()).isEqualTo(1);
        assertThat(queue.poll()).isNull();
    }

    @Test
    public void remove() {
        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null);

        queue.add(1);

        assertThat(queue.remove(1)).isTrue();
        assertThat(queue.isEmpty()).isTrue();
    }

    @Test
    public void remove_noSuchElement() {
        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null);

        queue.add(1);

        assertThat(queue.remove(2)).isFalse();
    }

    @Test
    public void isEmpty_true() {
        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null);

        assertThat(queue.isEmpty()).isTrue();
    }

    @Test
    public void isEmpty_false() {
        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null);

        queue.add(1);

        assertThat(queue.isEmpty()).isFalse();
    }

    @Test
    public void onEntryEvicted() {
        List<Integer> onEntryEvictedElements = new ArrayList<>();
        FixedSizeQueue<Integer> queue =
                new FixedSizeQueue<>(1, onEntryEvictedElements::add);

        queue.add(1);
        queue.add(2);
        queue.add(3);

        assertThat(onEntryEvictedElements).containsExactly(1, 2).inOrder();
    }
}