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