All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.grpc.Deadline Maven / Gradle / Ivy

/*
 * Copyright 2016 The gRPC Authors
 *
 * 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 io.grpc;

import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * An absolute point in time, generally for tracking when a task should be completed. A deadline is
 * immutable except for the passage of time causing it to expire.
 *
 * 

Many systems use timeouts, which are relative to the start of the operation. However, being * relative causes them to be poorly suited for managing higher-level tasks where there are many * components and sub-operations that may not know the time of the initial "start of the operation." * However, a timeout can be converted to a {@code Deadline} at the start of the operation and then * passed to the various components unambiguously. */ public final class Deadline implements Comparable { private static final SystemTicker SYSTEM_TICKER = new SystemTicker(); // nanoTime has a range of just under 300 years. Only allow up to 100 years in the past or future // to prevent wraparound as long as process runs for less than ~100 years. private static final long MAX_OFFSET = TimeUnit.DAYS.toNanos(100 * 365); private static final long MIN_OFFSET = -MAX_OFFSET; private static final long NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1); /** * Returns the ticker that's based on system clock. * *

This is EXPERIMENTAL API and may subject to change. If you'd like it to be * stabilized or have any feedback, please * let us know. * * @since 1.24.0 */ public static Ticker getSystemTicker() { return SYSTEM_TICKER; } /** * Create a deadline that will expire at the specified offset based on the {@link #getSystemTicker * system ticker}. * *

If the given offset is extraordinarily long, say 100 years, the actual deadline created * might saturate. * * @param duration A non-negative duration. * @param units The time unit for the duration. * @return A new deadline. */ public static Deadline after(long duration, TimeUnit units) { return after(duration, units, SYSTEM_TICKER); } /** * Create a deadline that will expire at the specified offset based on the given {@link Ticker}. * *

If the given offset is extraordinarily long, say 100 years, the actual deadline created * might saturate. * *

CAUTION: Only deadlines created with the same {@link Ticker} instance can * be compared by methods like {@link #minimum}, {@link #isBefore} and {@link #compareTo}. Custom * Tickers should only be used in tests where you fake out the clock. Always use the {@link * #getSystemTicker system ticker} in production, or serious errors may occur. * *

This is EXPERIMENTAL API and may subject to change. If you'd like it to be * stabilized or have any feedback, please * let us know. * * @param duration A non-negative duration. * @param units The time unit for the duration. * @param ticker Where this deadline refer the current time * @return A new deadline. * * @since 1.24.0 */ public static Deadline after(long duration, TimeUnit units, Ticker ticker) { checkNotNull(units, "units"); return new Deadline(ticker, units.toNanos(duration), true); } private final Ticker ticker; private final long deadlineNanos; private volatile boolean expired; private Deadline(Ticker ticker, long offset, boolean baseInstantAlreadyExpired) { this(ticker, ticker.nanoTime(), offset, baseInstantAlreadyExpired); } private Deadline(Ticker ticker, long baseInstant, long offset, boolean baseInstantAlreadyExpired) { this.ticker = ticker; // Clamp to range [MIN_OFFSET, MAX_OFFSET] offset = Math.min(MAX_OFFSET, Math.max(MIN_OFFSET, offset)); deadlineNanos = baseInstant + offset; expired = baseInstantAlreadyExpired && offset <= 0; } /** * Has this deadline expired * @return {@code true} if it has, otherwise {@code false}. */ public boolean isExpired() { if (!expired) { if (deadlineNanos - ticker.nanoTime() <= 0) { expired = true; } else { return false; } } return true; } /** * Is {@code this} deadline before another. Two deadlines must be created using the same {@link * Ticker}. */ public boolean isBefore(Deadline other) { checkTicker(other); return this.deadlineNanos - other.deadlineNanos < 0; } /** * Return the minimum deadline of {@code this} or an other deadline. They must be created using * the same {@link Ticker}. * * @param other deadline to compare with {@code this}. */ public Deadline minimum(Deadline other) { checkTicker(other); return isBefore(other) ? this : other; } /** * Create a new deadline that is offset from {@code this}. * *

If the given offset is extraordinarily long, say 100 years, the actual deadline created * might saturate. */ // TODO(ejona): This method can cause deadlines to grow too far apart. For example: // Deadline.after(100 * 365, DAYS).offset(100 * 365, DAYS) would be less than // Deadline.after(-100 * 365, DAYS) public Deadline offset(long offset, TimeUnit units) { // May already be expired if (offset == 0) { return this; } return new Deadline(ticker, deadlineNanos, units.toNanos(offset), isExpired()); } /** * How much time is remaining in the specified time unit. Internal units are maintained as * nanoseconds and conversions are subject to the constraints documented for * {@link TimeUnit#convert}. If there is no time remaining, the returned duration is how * long ago the deadline expired. */ public long timeRemaining(TimeUnit unit) { final long nowNanos = ticker.nanoTime(); if (!expired && deadlineNanos - nowNanos <= 0) { expired = true; } return unit.convert(deadlineNanos - nowNanos, TimeUnit.NANOSECONDS); } /** * Schedule a task to be run when the deadline expires. * *

Note if this deadline was created with a custom {@link Ticker}, the {@code scheduler}'s * underlying clock should be synchronized with that Ticker. Otherwise the task won't be run at * the expected point of time. * * @param task to run on expiration * @param scheduler used to execute the task * @return {@link ScheduledFuture} which can be used to cancel execution of the task */ public ScheduledFuture runOnExpiration(Runnable task, ScheduledExecutorService scheduler) { checkNotNull(task, "task"); checkNotNull(scheduler, "scheduler"); return scheduler.schedule(task, deadlineNanos - ticker.nanoTime(), TimeUnit.NANOSECONDS); } @Override public String toString() { long remainingNanos = timeRemaining(TimeUnit.NANOSECONDS); long seconds = Math.abs(remainingNanos) / NANOS_PER_SECOND; long nanos = Math.abs(remainingNanos) % NANOS_PER_SECOND; StringBuilder buf = new StringBuilder(); if (remainingNanos < 0) { buf.append('-'); } buf.append(seconds); if (nanos > 0) { buf.append(String.format(Locale.US, ".%09d", nanos)); } buf.append("s from now"); if (ticker != SYSTEM_TICKER) { buf.append(" (ticker=" + ticker + ")"); } return buf.toString(); } /** * {@inheritDoc} * *

Both deadlines must be created with the same {@link Ticker}. */ @Override public int compareTo(Deadline that) { checkTicker(that); long diff = this.deadlineNanos - that.deadlineNanos; if (diff < 0) { return -1; } else if (diff > 0) { return 1; } return 0; } @Override public int hashCode() { return Arrays.asList(this.ticker, this.deadlineNanos).hashCode(); } @Override public boolean equals(final Object o) { if (o == this) { return true; } if (!(o instanceof Deadline)) { return false; } final Deadline other = (Deadline) o; if (this.ticker == null ? other.ticker != null : this.ticker != other.ticker) { return false; } if (this.deadlineNanos != other.deadlineNanos) { return false; } return true; } /** * Time source representing nanoseconds since fixed but arbitrary point in time. * *

DO NOT use custom {@link Ticker} implementations in production, because deadlines created * with custom tickers are incompatible with those created with the system ticker. Always use * the {@link #getSystemTicker system ticker} whenever you need to provide one in production code. * *

This is EXPERIMENTAL API and may subject to change. If you'd like it to be * stabilized or have any feedback, please * let us know. * *

In general implementations should be thread-safe, unless it's implemented and used in a * localized environment (like unit tests) where you are sure the usages are synchronized. * * @since 1.24.0 */ public abstract static class Ticker { /** Returns the number of nanoseconds since this source's epoch. */ public abstract long nanoTime(); } private static class SystemTicker extends Ticker { @Override public long nanoTime() { return System.nanoTime(); } } private static T checkNotNull(T reference, Object errorMessage) { if (reference == null) { throw new NullPointerException(String.valueOf(errorMessage)); } return reference; } private void checkTicker(Deadline other) { if (ticker != other.ticker) { throw new AssertionError( "Tickers (" + ticker + " and " + other.ticker + ") don't match." + " Custom Ticker should only be used in tests!"); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy