/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.sqlite.inspection; import androidx.annotation.GuardedBy; /** * Throttler implementation ensuring that events are run not more frequently that specified * interval. Events submitted during the interval period are collapsed into one (i.e. only one is * executed). * * Thread safe. */ final class RequestCollapsingThrottler { private static final long NEVER = -1; private final Runnable mAction; private final long mMinIntervalMs; private final DeferredExecutor mExecutor; private final Object mLock = new Object(); @GuardedBy("mLock") private boolean mPendingDispatch = false; @GuardedBy("mLock") private long mLastSubmitted = NEVER; RequestCollapsingThrottler(long minIntervalMs, Runnable action, DeferredExecutor executor) { mExecutor = executor; mAction = action; mMinIntervalMs = minIntervalMs; } public void submitRequest() { synchronized (mLock) { if (mPendingDispatch) { return; } else { mPendingDispatch = true; // about to schedule } } long delayMs = mMinIntervalMs - sinceLast(); // delayMs < 0 is OK scheduleDispatch(delayMs); } // TODO: switch to ListenableFuture to react on failures @SuppressWarnings("FutureReturnValueIgnored") private void scheduleDispatch(long delayMs) { mExecutor.schedule(new Runnable() { @Override public void run() { try { mAction.run(); } finally { synchronized (mLock) { mLastSubmitted = now(); mPendingDispatch = false; } } } }, delayMs); } private static long now() { return System.currentTimeMillis(); } private long sinceLast() { synchronized (mLock) { final long lastSubmitted = mLastSubmitted; return lastSubmitted == NEVER ? (mMinIntervalMs + 1) // more than mMinIntervalMs : (now() - lastSubmitted); } } interface DeferredExecutor { void schedule(Runnable command, long delayMs); } }