001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v2.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014
015package ch.qos.logback.core.util;
016
017import java.util.concurrent.atomic.AtomicLong;
018
019/**
020 * A time-based {@link InvocationGate} with fixed-interval logic.
021 * <p>
022 * Callers on a hot path use {@link #isTooSoon(long)} to decide whether to skip
023 * a costly operation. At most one thread per {@linkplain #increment increment}
024 * interval is allowed to proceed (i.e. receives {@code false}); other threads
025 * in the same window receive {@code true} and should skip the work.
026 * </p>
027 * <p>
028 * Compared to {@link DefaultInvocationGate}, this implementation does not adapt
029 * a sampling mask. It only advances an atomic next-allowed timestamp by a fixed
030 * {@link Duration}.
031 * </p>
032 * <p>
033 * Typical use is size checks in rolling policies where file length is expensive
034 * relative to the logging call.
035 * </p>
036 *
037 * @author Ceki G&uuml;lc&uuml;
038 * @since 1.3.6/1.4.6 (formerly {@link SimpleInvocationGate})
039 * @see InvocationGate
040 * @see DefaultInvocationGate
041 * @see BatchedFixedIntervalInvocationGate
042 * @see ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy
043 */
044public class FixedIntervalInvocationGate implements InvocationGate {
045
046    /**
047     * Next time (milliseconds since the epoch) at or after which a caller may be
048     * allowed to proceed. Updated with compare-and-set so that only one thread
049     * wins per interval.
050     */
051    AtomicLong atomicNext = new AtomicLong(0);
052
053    /**
054     * Minimum time between allowed invocations.
055     */
056    final Duration increment;
057
058    /**
059     * Default increment: 60 seconds.
060     */
061    final public static Duration DEFAULT_INCREMENT = Duration.buildBySeconds(60);
062
063    /**
064     * Creates a gate with {@link #DEFAULT_INCREMENT}.
065     */
066    public FixedIntervalInvocationGate() {
067        this(DEFAULT_INCREMENT);
068    }
069
070    /**
071     * Creates a gate that allows at most one successful passage per
072     * {@code anIncrement} period.
073     *
074     * @param anIncrement duration between allowed invocations; must not be
075     *                    {@code null}
076     */
077    public FixedIntervalInvocationGate(Duration anIncrement) {
078        this.increment = anIncrement;
079    }
080
081    /**
082     * Returns {@code true} if the caller should skip further work; {@code false}
083     * if this call is allowed to proceed.
084     * <p>
085     * If {@code currentTime} is {@link InvocationGate#TIME_UNAVAILABLE}
086     * ({@code -1}), this method returns {@code false} so the caller can still
087     * perform the work when the clock is unavailable.
088     * </p>
089     * <p>
090     * Otherwise, when {@code currentTime} is strictly before the next allowed
091     * time, the method returns {@code true} (too soon). When
092     * {@code currentTime} has reached the next allowed time, this thread tries
093     * to advance that time by {@link #increment}. On success it returns
094     * {@code false} (proceed); if another thread already advanced it, this
095     * thread returns {@code true} so that only one passage per interval is
096     * granted.
097     * </p>
098     *
099     * @param currentTime current time in milliseconds, or
100     *                    {@link InvocationGate#TIME_UNAVAILABLE} if unknown
101     * @return {@code true} if further work should be skipped; {@code false} if
102     *         the caller may proceed
103     */
104    @Override
105    public boolean isTooSoon(long currentTime) {
106        if (currentTime == -1)
107            return false;
108
109        long localNext = atomicNext.get();
110        if (currentTime >= localNext) {
111            long next2 = currentTime + increment.getMilliseconds();
112            // if success, we were able to set the variable, otherwise some other thread beat us to it
113            boolean success = atomicNext.compareAndSet(localNext, next2);
114            // while we have crossed 'next', the other thread already returned true. There is
115            // no point in letting more than one thread per duration.
116            return !success;
117        } else {
118            return true;
119        }
120
121    }
122}