1 /*
2  * Copyright 2020 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 androidx.sqlite.inspection;
18 
19 import androidx.annotation.GuardedBy;
20 
21 /**
22  * Throttler implementation ensuring that events are run not more frequently that specified
23  * interval. Events submitted during the interval period are collapsed into one (i.e. only one is
24  * executed).
25  *
26  * Thread safe.
27  */
28 final class RequestCollapsingThrottler {
29     private static final long NEVER = -1;
30 
31     private final Runnable mAction;
32     private final long mMinIntervalMs;
33     private final DeferredExecutor mExecutor;
34     private final Object mLock = new Object();
35 
36     @GuardedBy("mLock") private boolean mPendingDispatch = false;
37     @GuardedBy("mLock") private long mLastSubmitted = NEVER;
38 
RequestCollapsingThrottler(long minIntervalMs, Runnable action, DeferredExecutor executor)39     RequestCollapsingThrottler(long minIntervalMs, Runnable action, DeferredExecutor executor) {
40         mExecutor = executor;
41         mAction = action;
42         mMinIntervalMs = minIntervalMs;
43     }
44 
submitRequest()45     public void submitRequest() {
46         synchronized (mLock) {
47             if (mPendingDispatch) {
48                 return;
49             } else {
50                 mPendingDispatch = true; // about to schedule
51             }
52         }
53         long delayMs = mMinIntervalMs - sinceLast(); // delayMs < 0 is OK
54         scheduleDispatch(delayMs);
55     }
56 
57     // TODO: switch to ListenableFuture to react on failures
58     @SuppressWarnings("FutureReturnValueIgnored")
scheduleDispatch(long delayMs)59     private void scheduleDispatch(long delayMs) {
60         mExecutor.schedule(new Runnable() {
61             @Override
62             public void run() {
63                 try {
64                     mAction.run();
65                 } finally {
66                     synchronized (mLock) {
67                         mLastSubmitted = now();
68                         mPendingDispatch = false;
69                     }
70                 }
71             }
72         }, delayMs);
73     }
74 
now()75     private static long now() {
76         return System.currentTimeMillis();
77     }
78 
sinceLast()79     private long sinceLast() {
80         synchronized (mLock) {
81             final long lastSubmitted = mLastSubmitted;
82             return lastSubmitted == NEVER
83                     ? (mMinIntervalMs + 1) // more than mMinIntervalMs
84                     : (now() - lastSubmitted);
85         }
86     }
87 
88     interface DeferredExecutor {
schedule(Runnable command, long delayMs)89         void schedule(Runnable command, long delayMs);
90     }
91 }
92