• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 
17 package com.android.server.pm.utils;
18 
19 import android.annotation.NonNull;
20 import android.os.Handler;
21 
22 import com.android.server.IoThread;
23 
24 import java.util.concurrent.atomic.AtomicInteger;
25 import java.util.function.Supplier;
26 
27 /**
28  * Loose throttle latest behavior for success/fail requests, with options to schedule or force a
29  * request through. Throttling is implicit and not configurable. This means requests are dispatched
30  * to the {@link Handler} immediately when received, and only batched while waiting on the next
31  * message execution or running request.
32  *
33  * This also means there is no explicit debouncing. Implicit debouncing is available through the
34  * same runtime delays in the {@link Handler} instance and the request execution, where multiple
35  * requests prior to the execution point are collapsed.
36  *
37  * Callers provide a {@link Handler} with which to schedule tasks on. This may be a highly
38  * contentious thread like {@link IoThread#getHandler()}, but note that there are no guarantees
39  * that the request will be handled before the system server dies. Ideally callers should handle
40  * re-initialization from stale state with no consequences to the user.
41  *
42  * This class will retry requests if they don't succeed, as provided by a true/false response from
43  * the block provided to run the request. This uses an exponential backoff mechanism, assuming that
44  * state write should be attempted immediately, but not retried so heavily as to potentially block
45  * other system server callers. Exceptions are not considered and will not result in a retry if
46  * thrown from inside the block. Caller should wrap with try-catch and rollback and transaction
47  * state before returning false to signal a retry.
48  *
49  * The caller is strictly responsible for data synchronization, as this class will not synchronize
50  * the request block, potentially running it multiple times or on multiple threads simultaneously
51  * if requests come in asynchronously.
52  */
53 public class RequestThrottle {
54 
55     private static final int DEFAULT_RETRY_MAX_ATTEMPTS = 5;
56     private static final int DEFAULT_DELAY_MS = 1000;
57     private static final int DEFAULT_BACKOFF_BASE = 2;
58 
59     private final AtomicInteger mLastRequest = new AtomicInteger(0);
60     private final AtomicInteger mLastCommitted = new AtomicInteger(-1);
61 
62     private final int mMaxAttempts;
63     private final int mFirstDelay;
64     private final int mBackoffBase;
65 
66     private final AtomicInteger mCurrentRetry = new AtomicInteger(0);
67 
68     @NonNull
69     private final Handler mHandler;
70 
71     @NonNull
72     private final Supplier<Boolean> mBlock;
73 
74     @NonNull
75     private final Runnable mRunnable;
76 
77     /**
78      * @see #RequestThrottle(Handler, int, int, int, Supplier)
79      */
RequestThrottle(@onNull Handler handler, @NonNull Supplier<Boolean> block)80     public RequestThrottle(@NonNull Handler handler, @NonNull Supplier<Boolean> block) {
81         this(handler, DEFAULT_RETRY_MAX_ATTEMPTS, DEFAULT_DELAY_MS, DEFAULT_BACKOFF_BASE,
82                 block);
83     }
84 
85     /**
86      * Backoff timing is calculated as firstDelay * (backoffBase ^ retryAttempt).
87      *
88      * @param handler     Representing the thread to run the provided block.
89      * @param block       The action to run when scheduled, returning whether or not the request was
90      *                    successful. Note that any thrown exceptions will be ignored and not
91      *                    retried, since it's not easy to tell how destructive or retry-able an
92      *                    exception is.
93      * @param maxAttempts Number of times to re-attempt any single request.
94      * @param firstDelay  The first delay used after the initial attempt.
95      * @param backoffBase The base of the backoff calculation, where retry attempt count is the
96      *                    exponent.
97      */
RequestThrottle(@onNull Handler handler, int maxAttempts, int firstDelay, int backoffBase, @NonNull Supplier<Boolean> block)98     public RequestThrottle(@NonNull Handler handler, int maxAttempts, int firstDelay,
99             int backoffBase, @NonNull Supplier<Boolean> block) {
100         mHandler = handler;
101         mBlock = block;
102         mMaxAttempts = maxAttempts;
103         mFirstDelay = firstDelay;
104         mBackoffBase = backoffBase;
105         mRunnable = this::runInternal;
106     }
107 
108     /**
109      * Schedule the intended action on the provided {@link Handler}.
110      */
schedule()111     public void schedule() {
112         // To avoid locking the Handler twice by pre-checking hasCallbacks, instead just queue
113         // the Runnable again. It will no-op if the request has already been written to disk.
114         mLastRequest.incrementAndGet();
115         mHandler.post(mRunnable);
116     }
117 
118     /**
119      * Run the intended action immediately on the calling thread. Note that synchronization and
120      * deadlock between threads is not handled. This will immediately call the request block, and
121      * also potentially schedule a retry. The caller must not block itself.
122      *
123      * @return true if the write succeeded or the last request was already written
124      */
runNow()125     public boolean runNow() {
126         mLastRequest.incrementAndGet();
127         return runInternal();
128     }
129 
runInternal()130     private boolean runInternal() {
131         int lastRequest = mLastRequest.get();
132         int lastCommitted = mLastCommitted.get();
133         if (lastRequest == lastCommitted) {
134             return true;
135         }
136 
137         if (mBlock.get()) {
138             mCurrentRetry.set(0);
139             mLastCommitted.set(lastRequest);
140             return true;
141         } else {
142             int currentRetry = mCurrentRetry.getAndIncrement();
143             if (currentRetry < mMaxAttempts) {
144                 long nextDelay =
145                         (long) (mFirstDelay * Math.pow(mBackoffBase, currentRetry));
146                 mHandler.postDelayed(mRunnable, nextDelay);
147             } else {
148                 mCurrentRetry.set(0);
149             }
150 
151             return false;
152         }
153     }
154 }
155