• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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