/* * Copyright (C) 2023 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.car.internal; import android.annotation.Nullable; import android.car.builtin.os.HandlerHelper; import android.car.builtin.util.Slogf; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.LongSparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; /** * A pending request pool with timeout. The request uses {@code long} request ID. * * Note that this pool is not thread-safe. Caller must use lock to guard this object. * * @param The request info type. * * @hide */ public final class LongPendingRequestPool { private static final String TAG = LongPendingRequestPool.class.getSimpleName(); private static final int REQUESTS_TIMEOUT_MESSAGE_TYPE = 1; private final TimeoutCallback mTimeoutCallback; private final Handler mTimeoutHandler; private final Object mRequestIdsLock = new Object(); // A map to store system uptime in ms to the request IDs that will timeout at this time. @GuardedBy("mRequestIdsLock") private final LongSparseArray> mTimeoutUptimeMsToRequestIds = new LongSparseArray<>(); private final LongSparseArray mRequestIdToRequestInfo = new LongSparseArray<>(); private class MessageHandler implements Handler.Callback { @Override public boolean handleMessage(Message msg) { if (msg.what != REQUESTS_TIMEOUT_MESSAGE_TYPE) { Slogf.e(TAG, "Received unexpected msg with what: %d", msg.what); return true; } List requestIdsCopy; synchronized (mRequestIdsLock) { Long timeoutUptimeMs = (Long) msg.obj; List requestIds = mTimeoutUptimeMsToRequestIds.get(timeoutUptimeMs); if (requestIds == null) { Slogf.d(TAG, "handle a timeout msg, but all requests that should timeout " + "has been removed"); return true; } requestIdsCopy = new ArrayList<>(requestIds); mTimeoutUptimeMsToRequestIds.remove(timeoutUptimeMs); } mTimeoutCallback.onRequestsTimeout(requestIdsCopy); return true; } } /** * Interface for timeout callback. */ public interface TimeoutCallback { /** * Callback called when requests timeout. * * Note that caller must obtain lock to the request pool, call * {@link PendingRequestPool#getRequestIfFound} to check the request has not been removed, * and call {@link PendingRequestPool#removeRequest}, then release the lock. * * Similarly when a request is finished normally, caller should follow the same process. */ void onRequestsTimeout(List requestIds); } public LongPendingRequestPool(Looper looper, TimeoutCallback callback) { mTimeoutCallback = callback; mTimeoutHandler = new Handler(looper, new MessageHandler()); } /** * Get the number of pending requests. */ public int size() { return mRequestIdToRequestInfo.size(); } /** * Get the pending request ID at the specific index. */ public long keyAt(int index) { return mRequestIdToRequestInfo.keyAt(index); } /** * Get the pending request info at the specific index. */ public T valueAt(int index) { return mRequestIdToRequestInfo.valueAt(index); } /** * Add a list of new pending requests to the pool. * * If the request timeout before being removed from the pool, the timeout callback will be * called. The caller must remove the request from the pool during the callback. */ public void addPendingRequests(List requestsInfo) { LongSparseArray messagesToSend = new LongSparseArray<>(); synchronized (mRequestIdsLock) { for (T requestInfo : requestsInfo) { long requestId = requestInfo.getRequestId(); long timeoutUptimeMs = requestInfo.getTimeoutUptimeMs(); mRequestIdToRequestInfo.put(requestId, requestInfo); if (mTimeoutUptimeMsToRequestIds.get(timeoutUptimeMs) == null) { mTimeoutUptimeMsToRequestIds.put(timeoutUptimeMs, new ArrayList<>()); } List requestIds = mTimeoutUptimeMsToRequestIds.get(timeoutUptimeMs); requestIds.add(requestId); if (requestIds.size() == 1) { // This is the first time we register a timeout handler for the specific // {@code timeoutUptimeMs}. Message message = new Message(); message.what = REQUESTS_TIMEOUT_MESSAGE_TYPE; message.obj = Long.valueOf(timeoutUptimeMs); messagesToSend.put(timeoutUptimeMs, message); } } } for (int i = 0; i < messagesToSend.size(); i++) { mTimeoutHandler.sendMessageAtTime(messagesToSend.valueAt(i), messagesToSend.keyAt(i)); } } /** * Get the pending request info for the specific request ID if found. */ public @Nullable T getRequestIfFound(long requestId) { return mRequestIdToRequestInfo.get(requestId); } /** * Remove the pending request and the timeout handler for it. */ public void removeRequest(long requestId) { synchronized (mRequestIdsLock) { T requestInfo = mRequestIdToRequestInfo.get(requestId); if (requestInfo == null) { Slogf.w(TAG, "No pending requests for request ID: %d", requestId); return; } mRequestIdToRequestInfo.remove(requestId); long timeoutUptimeMs = requestInfo.getTimeoutUptimeMs(); List requestIds = mTimeoutUptimeMsToRequestIds.get(timeoutUptimeMs); if (requestIds == null) { Slogf.w(TAG, "No pending request that will timeout at: %d ms", timeoutUptimeMs); return; } if (!requestIds.remove(Long.valueOf(requestId))) { Slogf.w(TAG, "No pending request for request ID: %d, timeout at: %d ms", requestId, timeoutUptimeMs); return; } if (requestIds.isEmpty()) { mTimeoutUptimeMsToRequestIds.remove(timeoutUptimeMs); HandlerHelper.removeEqualMessages(mTimeoutHandler, REQUESTS_TIMEOUT_MESSAGE_TYPE, Long.valueOf(timeoutUptimeMs)); } } } /** * Returns whether the pool is empty and all stored data are cleared. */ @VisibleForTesting public boolean isEmpty() { synchronized (mRequestIdsLock) { return mTimeoutUptimeMsToRequestIds.size() == 0 && mRequestIdToRequestInfo.size() == 0; } } }