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

com.google.appengine.repackaged.com.google.common.flogger.DurationRateLimiter Maven / Gradle / Ivy

There is a newer version: 2.0.27
Show newest version
/*
 * Copyright (C) 2014 The Flogger 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 com.google.common.flogger;

import static com.google.common.flogger.LogContext.Key.LOG_AT_MOST_EVERY;
import static com.google.common.flogger.RateLimitStatus.DISALLOW;
import static com.google.common.flogger.util.Checks.checkArgument;
import static com.google.common.flogger.util.Checks.checkNotNull;
import static java.lang.Math.max;

import com.google.common.flogger.backend.LogData;
import com.google.common.flogger.backend.Metadata;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;

/**
 * Rate limiter to support {@code atMostEvery(N, units)} functionality.
 *
 * 

Instances of this class are created for each unique {@link LogSiteKey} for which rate limiting * via the {@code LOG_AT_MOST_EVERY} metadata key is required. This class implements {@code * RateLimitStatus} as a mechanism for resetting the rate limiter state. * *

Instances of this class are thread safe. */ final class DurationRateLimiter extends RateLimitStatus { private static final LogSiteMap map = new LogSiteMap() { @Override protected DurationRateLimiter initialValue() { return new DurationRateLimiter(); } }; /** * Creates a period for rate limiting for the specified duration. This is invoked by the {@link * LogContext#atMostEvery(int, TimeUnit)} method to create a metadata value. */ static RateLimitPeriod newRateLimitPeriod(int n, TimeUnit unit) { // We could cache commonly used values here if we wanted. return new RateLimitPeriod(n, unit); } /** * Returns whether the log site should log based on the value of the {@code LOG_AT_MOST_EVERY} * metadata value and the current log site timestamp. */ @NullableDecl static RateLimitStatus check(Metadata metadata, LogSiteKey logSiteKey, long timestampNanos) { RateLimitPeriod rateLimitPeriod = metadata.findValue(LOG_AT_MOST_EVERY); if (rateLimitPeriod == null) { // Without rate limiter specific metadata, this limiter has no effect. return null; } return map.get(logSiteKey, metadata).checkLastTimestamp(timestampNanos, rateLimitPeriod); } /** * Immutable metadata for rate limiting based on a fixed count. This corresponds to the * LOG_AT_MOST_EVERY metadata key in {@link LogData}. Unlike the metadata for {@code every(N)}, we * need to use a wrapper class here to preserve the time unit information. */ static final class RateLimitPeriod { private final int n; private final TimeUnit unit; private RateLimitPeriod(int n, TimeUnit unit) { // This code will work with a zero length time period, but it's nonsensical to try. if (n <= 0) { throw new IllegalArgumentException("time period must be positive: " + n); } this.n = n; this.unit = checkNotNull(unit, "time unit"); } private long toNanos() { // Since nanoseconds are the smallest level of precision a TimeUnit can express, we are // guaranteed that "unit.toNanos(n) >= n > 0". This is important for correctness (see comment // in checkLastTimestamp()) because it ensures the new timestamp that indicates when logging // should occur always differs from the previous timestamp. return unit.toNanos(n); } @Override public String toString() { return n + " " + unit; } @Override public int hashCode() { // Rough and ready. We don't expect this be be needed much at all. return (n * 37) ^ unit.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof RateLimitPeriod) { RateLimitPeriod that = (RateLimitPeriod) obj; return this.n == that.n && this.unit == that.unit; } return false; } } private final AtomicLong lastTimestampNanos = new AtomicLong(-1L); // Visible for testing. DurationRateLimiter() {} /** * Checks whether the current time stamp is after the rate limiting period and if so, updates the * time stamp and returns true. This is invoked during post-processing if a rate limiting duration * was set via {@link LoggingApi#atMostEvery(int, TimeUnit)}. */ // Visible for testing. RateLimitStatus checkLastTimestamp(long timestampNanos, RateLimitPeriod period) { checkArgument(timestampNanos >= 0, "timestamp cannot be negative"); // If this is negative, we are in the pending state and will return "allow" until we are reset. // The value held here is updated to be the most recent negated timestamp, and is negated again // (making it positive and setting us into the rate limiting state) when we are reset. long lastNanos = lastTimestampNanos.get(); if (lastNanos >= 0) { long deadlineNanos = lastNanos + period.toNanos(); // Check for negative deadline to avoid overflow for ridiculous durations. Assume overflow // always means "no logging". if (deadlineNanos < 0 || timestampNanos < deadlineNanos) { return DISALLOW; } } // When logging is triggered, negate the timestamp to move us into the "pending" state and // return our reset status. // We don't want to race with the reset function (which may have already set a new timestamp). lastTimestampNanos.compareAndSet(lastNanos, -timestampNanos); return this; } // Reset function called to move the limiter out of the "pending" state. We do this by negating // the timestamp (which was already negated when we entered the pending state, so we restore it // to a positive value which moves us back into the "limiting" state). @Override public void reset() { // Only one thread at a time can reset a rate limiter, so this can be unconditional. We should // only be able to get here if the timestamp was set to a negative value above. However use // max() to make sure we always move out of the pending state. lastTimestampNanos.set(max(-lastTimestampNanos.get(), 0)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy