/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2026 3A Systems, LLC. */ package org.opends.server.util; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * Bounds how often a repeating failure is reported, so that a failure which a retry loop * or a stream of unwanted connections reproduces many times a second is still visible in * the error log without filling it. *

* One failure is logged per interval and the failures in between are counted, so that the * next one logged reports how many it stands for and a single line cannot be mistaken for * a single failure. That count looks backwards only: the failures following the last one * logged of a burst are counted but never reported, as nothing flushes the count when the * failures stop. *

* The first failure is always logged, however long the throttle has existed before it. *

* Safe to record from several threads, and exact for one: a failure recorded while * another thread is opening a new interval may be counted in the interval which is * closing or in the one which opens, so the count a caller reports can be off by the * failures which raced with it. It is a count of failures, not a ledger. */ public final class FailureLogThrottle { /** Minimum interval, in nanoseconds, between two failures logged. */ private final long intervalNanos; /** * Value of {@link System#nanoTime()} at which the last failure was logged. It starts one * interval in the past so that the first failure is logged. */ private final AtomicLong lastLogNanos; /** Number of failures suppressed since the last one logged. */ private final AtomicLong suppressed = new AtomicLong(); /** * Creates a throttle logging at most one failure per provided interval. * * @param interval * The interval between two failures logged. * @param unit * The unit the interval is expressed in. */ public FailureLogThrottle(final long interval, final TimeUnit unit) { this.intervalNanos = unit.toNanos(interval); this.lastLogNanos = new AtomicLong(System.nanoTime() - intervalNanos); } /** * Records a failure which happened at the provided time and tells how it must be logged, * together with the number of failures suppressed since the previous one logged. * * @param nowNanos * The value of {@link System#nanoTime()} at which the failure happened. * @return A number greater than or equal to zero if this failure is to be logged, which is * then the number of failures suppressed since the previous one logged, or * {@code -count - 1} if this failure is itself to be suppressed, where * {@code count} is the number of failures suppressed since the previous one * logged, this one included. */ public long record(final long nowNanos) { final long lastLog = lastLogNanos.get(); if (nowNanos - lastLog >= intervalNanos && lastLogNanos.compareAndSet(lastLog, nowNanos)) { return suppressed.getAndSet(0); } return -suppressed.incrementAndGet() - 1; } }