InvocationGateTest.java
/*
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2026, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v2.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.core.util;
import ch.qos.logback.core.testUtil.AbstractMultiThreadedHarness;
import ch.qos.logback.core.testUtil.RunnableWithCounterAndDone;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class InvocationGateTest {
private static final int ONCE_EVERY = 100;
private static final int MAX_TRAVERSAL_COUNT = 10_000;
private static final int THREAD_COUNT = 16;
static final int MASK = 0xAFF;
AtomicLong currentTime = new AtomicLong(1);
@Test
public void smoke() {
InvocationGate sig = new SimpleInvocationGate();
long currentTime = SimpleInvocationGate.DEFAULT_INCREMENT.getMilliseconds() + 1;
assertFalse(sig.isTooSoon(currentTime));
currentTime++;
assertTrue(sig.isTooSoon(currentTime));
}
@Disabled
@Test
void checkThreadSafety() throws InterruptedException {
InvocationGate sig = new SimpleInvocationGate(Duration.buildByMilliseconds(1));
long initialTime = currentTime.get();
sig.isTooSoon(initialTime); // sync invocation gate with current time
AtomicInteger traversalCount = new AtomicInteger(0);
RunnableWithCounterAndDone[] runnables = buildRunnables(sig, traversalCount);
SimpleInvocationGateHarness harness = new SimpleInvocationGateHarness(traversalCount);
harness.execute(runnables);
int tc = traversalCount.get();
long ct = currentTime.get();
long diff = ct - initialTime - MAX_TRAVERSAL_COUNT;
int traversalCountMismatch = tc - MAX_TRAVERSAL_COUNT;
assertTrue(traversalCountMismatch >=0, "traversalCountMismatch must be a positive number");
int tolerance = 6;
assertTrue(traversalCountMismatch < tolerance, "traversalCountMismatch must be less than "+tolerance+ " actual value "+traversalCountMismatch);
assertTrue(diff >=0, "time difference must be a positive number");
assertTrue(diff < tolerance, "time difference must be less than "+tolerance+" actual value "+diff);
}
private RunnableWithCounterAndDone[] buildRunnables(InvocationGate invocationGate, AtomicInteger traversalCount ) {
RunnableWithCounterAndDone[] runnables = new RunnableWithCounterAndDone[THREAD_COUNT + 1];
runnables[0] = new TimeUpdater(currentTime);
for(int i = 1; i < runnables.length; i++) {
runnables[i] = new InvocationGateChecker(invocationGate, traversalCount);
}
return runnables;
}
class SimpleInvocationGateHarness extends AbstractMultiThreadedHarness {
AtomicInteger traversalCount;
public SimpleInvocationGateHarness(AtomicInteger traversalCount) {
this.traversalCount = traversalCount;
}
public void waitUntilEndCondition() throws InterruptedException {
while(traversalCount.get() < MAX_TRAVERSAL_COUNT) {
Thread.yield();
}
}
}
private class TimeUpdater extends RunnableWithCounterAndDone {
Random random = new Random(69923259L);
AtomicLong currentTime;
public TimeUpdater(AtomicLong currentTime) {
this.currentTime = currentTime;
}
@Override
public void run() {
sleep(10);
while(!isDone()) {
if (0 == random.nextInt(ONCE_EVERY)) {
long ct = currentTime.incrementAndGet();
if((ct & MASK) == MASK) {
System.out.println("Time increment ct="+ct);
}
}
Thread.yield();
}
}
private void sleep(int duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private class InvocationGateChecker extends RunnableWithCounterAndDone {
InvocationGate invocationGate;
AtomicInteger traversalCount;
public InvocationGateChecker(InvocationGate invocationGate, AtomicInteger traversalCount) {
this.invocationGate = invocationGate;
this.traversalCount = traversalCount;
}
@Override
public void run() {
while(!isDone()) {
if (!invocationGate.isTooSoon(currentTime.get())) {
int tc = traversalCount.incrementAndGet();
if((tc & MASK) == MASK) {
System.out.println("traversalCount="+tc);
}
}
Thread.yield();
}
}
}
}