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ülcü 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}