1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.internal; 17 18 import android.annotation.Nullable; 19 import android.car.builtin.os.HandlerHelper; 20 import android.car.builtin.util.Slogf; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.os.Message; 24 import android.util.LongSparseArray; 25 26 import com.android.internal.annotations.GuardedBy; 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * A pending request pool with timeout. The request uses {@code long} request ID. 34 * 35 * Note that this pool is not thread-safe. Caller must use lock to guard this object. 36 * 37 * @param <T> The request info type. 38 * 39 * @hide 40 */ 41 public final class LongPendingRequestPool<T extends LongRequestIdWithTimeout> { 42 private static final String TAG = LongPendingRequestPool.class.getSimpleName(); 43 private static final int REQUESTS_TIMEOUT_MESSAGE_TYPE = 1; 44 45 private final TimeoutCallback mTimeoutCallback; 46 private final Handler mTimeoutHandler; 47 private final Object mRequestIdsLock = new Object(); 48 // A map to store system uptime in ms to the request IDs that will timeout at this time. 49 @GuardedBy("mRequestIdsLock") 50 private final LongSparseArray<List<Long>> mTimeoutUptimeMsToRequestIds = 51 new LongSparseArray<>(); 52 private final LongSparseArray<T> mRequestIdToRequestInfo = new LongSparseArray<>(); 53 54 private class MessageHandler implements Handler.Callback { 55 @Override handleMessage(Message msg)56 public boolean handleMessage(Message msg) { 57 if (msg.what != REQUESTS_TIMEOUT_MESSAGE_TYPE) { 58 Slogf.e(TAG, "Received unexpected msg with what: %d", msg.what); 59 return true; 60 } 61 List<Long> requestIdsCopy; 62 synchronized (mRequestIdsLock) { 63 Long timeoutUptimeMs = (Long) msg.obj; 64 List<Long> requestIds = mTimeoutUptimeMsToRequestIds.get(timeoutUptimeMs); 65 if (requestIds == null) { 66 Slogf.d(TAG, "handle a timeout msg, but all requests that should timeout " 67 + "has been removed"); 68 return true; 69 } 70 requestIdsCopy = new ArrayList<>(requestIds); 71 mTimeoutUptimeMsToRequestIds.remove(timeoutUptimeMs); 72 } 73 mTimeoutCallback.onRequestsTimeout(requestIdsCopy); 74 return true; 75 } 76 } 77 78 /** 79 * Interface for timeout callback. 80 */ 81 public interface TimeoutCallback { 82 /** 83 * Callback called when requests timeout. 84 * 85 * Note that caller must obtain lock to the request pool, call 86 * {@link PendingRequestPool#getRequestIfFound} to check the request has not been removed, 87 * and call {@link PendingRequestPool#removeRequest}, then release the lock. 88 * 89 * Similarly when a request is finished normally, caller should follow the same process. 90 */ onRequestsTimeout(List<Long> requestIds)91 void onRequestsTimeout(List<Long> requestIds); 92 } 93 LongPendingRequestPool(Looper looper, TimeoutCallback callback)94 public LongPendingRequestPool(Looper looper, TimeoutCallback callback) { 95 mTimeoutCallback = callback; 96 mTimeoutHandler = new Handler(looper, new MessageHandler()); 97 } 98 99 /** 100 * Get the number of pending requests. 101 */ size()102 public int size() { 103 return mRequestIdToRequestInfo.size(); 104 } 105 106 /** 107 * Get the pending request ID at the specific index. 108 */ keyAt(int index)109 public long keyAt(int index) { 110 return mRequestIdToRequestInfo.keyAt(index); 111 } 112 113 /** 114 * Get the pending request info at the specific index. 115 */ valueAt(int index)116 public T valueAt(int index) { 117 return mRequestIdToRequestInfo.valueAt(index); 118 } 119 120 /** 121 * Add a list of new pending requests to the pool. 122 * 123 * If the request timeout before being removed from the pool, the timeout callback will be 124 * called. The caller must remove the request from the pool during the callback. 125 */ addPendingRequests(List<T> requestsInfo)126 public void addPendingRequests(List<T> requestsInfo) { 127 LongSparseArray<Message> messagesToSend = new LongSparseArray<>(); 128 synchronized (mRequestIdsLock) { 129 for (T requestInfo : requestsInfo) { 130 long requestId = requestInfo.getRequestId(); 131 long timeoutUptimeMs = requestInfo.getTimeoutUptimeMs(); 132 mRequestIdToRequestInfo.put(requestId, requestInfo); 133 if (mTimeoutUptimeMsToRequestIds.get(timeoutUptimeMs) == null) { 134 mTimeoutUptimeMsToRequestIds.put(timeoutUptimeMs, new ArrayList<>()); 135 } 136 List<Long> requestIds = mTimeoutUptimeMsToRequestIds.get(timeoutUptimeMs); 137 requestIds.add(requestId); 138 139 if (requestIds.size() == 1) { 140 // This is the first time we register a timeout handler for the specific 141 // {@code timeoutUptimeMs}. 142 Message message = new Message(); 143 message.what = REQUESTS_TIMEOUT_MESSAGE_TYPE; 144 message.obj = Long.valueOf(timeoutUptimeMs); 145 messagesToSend.put(timeoutUptimeMs, message); 146 } 147 } 148 } 149 for (int i = 0; i < messagesToSend.size(); i++) { 150 mTimeoutHandler.sendMessageAtTime(messagesToSend.valueAt(i), messagesToSend.keyAt(i)); 151 } 152 } 153 154 /** 155 * Get the pending request info for the specific request ID if found. 156 */ getRequestIfFound(long requestId)157 public @Nullable T getRequestIfFound(long requestId) { 158 return mRequestIdToRequestInfo.get(requestId); 159 } 160 161 /** 162 * Remove the pending request and the timeout handler for it. 163 */ removeRequest(long requestId)164 public void removeRequest(long requestId) { 165 synchronized (mRequestIdsLock) { 166 T requestInfo = mRequestIdToRequestInfo.get(requestId); 167 if (requestInfo == null) { 168 Slogf.w(TAG, "No pending requests for request ID: %d", requestId); 169 return; 170 } 171 mRequestIdToRequestInfo.remove(requestId); 172 long timeoutUptimeMs = requestInfo.getTimeoutUptimeMs(); 173 List<Long> requestIds = mTimeoutUptimeMsToRequestIds.get(timeoutUptimeMs); 174 if (requestIds == null) { 175 Slogf.w(TAG, "No pending request that will timeout at: %d ms", timeoutUptimeMs); 176 return; 177 } 178 if (!requestIds.remove(Long.valueOf(requestId))) { 179 Slogf.w(TAG, "No pending request for request ID: %d, timeout at: %d ms", 180 requestId, timeoutUptimeMs); 181 return; 182 } 183 if (requestIds.isEmpty()) { 184 mTimeoutUptimeMsToRequestIds.remove(timeoutUptimeMs); 185 HandlerHelper.removeEqualMessages(mTimeoutHandler, REQUESTS_TIMEOUT_MESSAGE_TYPE, 186 Long.valueOf(timeoutUptimeMs)); 187 } 188 } 189 } 190 191 /** 192 * Returns whether the pool is empty and all stored data are cleared. 193 */ 194 @VisibleForTesting isEmpty()195 public boolean isEmpty() { 196 synchronized (mRequestIdsLock) { 197 return mTimeoutUptimeMsToRequestIds.size() == 0 && mRequestIdToRequestInfo.size() == 0; 198 } 199 } 200 } 201