mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
2 days ago 0c29df8cefccbf0722819c81fbb64ddc0be99b53
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/*
 * 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.
 * <p>
 * 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.
 * <p>
 * The first failure is always logged, however long the throttle has existed before it.
 * <p>
 * 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;
  }
}