// ================================================================================================= // Copyright 2014 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or 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 com.twitter.common.util; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import java.io.Closeable; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import com.twitter.common.quantity.Amount; import com.twitter.common.quantity.Time; /** * Low resolution implementation of a {@link com.twitter.common.util.Clock}, * optimized for fast reads at the expense of precision. * It works by caching the result of the system clock for a * {@code resolution} amount of time. */ public class LowResClock implements Clock, Closeable { private static final ScheduledExecutorService GLOBAL_SCHEDULER = Executors.newScheduledThreadPool(1, new ThreadFactory() { public Thread newThread(Runnable r) { Thread t = new Thread(r, "LowResClock"); t.setDaemon(true); return t; } }); private volatile long time; private final ScheduledFuture<?> updaterHandler; private final Clock underlying; @VisibleForTesting LowResClock(Amount<Long, Time> resolution, ScheduledExecutorService executor, Clock clock) { long sleepTimeMs = resolution.as(Time.MILLISECONDS); Preconditions.checkArgument(sleepTimeMs > 0); underlying = clock; Runnable ticker = new Runnable() { @Override public void run() { time = underlying.nowMillis(); } }; // Ensure the constructing thread sees a LowResClock with a valid (low-res) time by executing a // blocking call now. ticker.run(); updaterHandler = executor.scheduleAtFixedRate(ticker, sleepTimeMs, sleepTimeMs, TimeUnit.MILLISECONDS); } /** * Construct a LowResClock which wraps the system clock. * This constructor will also schedule a periodic task responsible for * updating the time every {@code resolution}. */ public LowResClock(Amount<Long, Time> resolution) { this(resolution, GLOBAL_SCHEDULER, Clock.SYSTEM_CLOCK); } /** * Terminate the underlying updater task. * Any subsequent usage of the clock will throw an {@link IllegalStateException}. */ public void close() { updaterHandler.cancel(true); } @Override public long nowMillis() { checkNotClosed(); return time; } @Override public long nowNanos() { return nowMillis() * 1000 * 1000; } @Override public void waitFor(long millis) throws InterruptedException { checkNotClosed(); underlying.waitFor(millis); } private void checkNotClosed() { if (updaterHandler.isCancelled()) { throw new IllegalStateException("LowResClock invoked after being closed!"); } } }