1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package ch.qos.logback.core.util;
16
17 import ch.qos.logback.core.contention.AbstractMultiThreadedHarness;
18 import ch.qos.logback.core.contention.RunnableWithCounterAndDone;
19 import org.junit.jupiter.api.Disabled;
20 import org.junit.jupiter.api.Test;
21
22 import java.util.Random;
23 import java.util.concurrent.atomic.AtomicInteger;
24 import java.util.concurrent.atomic.AtomicLong;
25
26 import static org.junit.jupiter.api.Assertions.assertEquals;
27 import static org.junit.jupiter.api.Assertions.assertFalse;
28 import static org.junit.jupiter.api.Assertions.assertTrue;
29
30 public class InvocationGateTest {
31
32 private static final int ONCE_EVERY = 100;
33 private static final int MAX_TRAVERSAL_COUNT = 10_000;
34 private static final int THREAD_COUNT = 16;
35
36
37 static final int MASK = 0xAFF;
38
39 AtomicLong currentTime = new AtomicLong(1);
40
41
42
43 @Test
44 public void smoke() {
45 InvocationGate sig = new SimpleInvocationGate();
46 long currentTime = SimpleInvocationGate.DEFAULT_INCREMENT.getMilliseconds() + 1;
47 assertFalse(sig.isTooSoon(currentTime));
48 currentTime++;
49 assertTrue(sig.isTooSoon(currentTime));
50 }
51
52 @Disabled
53 @Test
54 void checkThreadSafety() throws InterruptedException {
55 InvocationGate sig = new SimpleInvocationGate(Duration.buildByMilliseconds(1));
56
57 long initialTime = currentTime.get();
58 sig.isTooSoon(initialTime);
59
60 AtomicInteger traversalCount = new AtomicInteger(0);
61 RunnableWithCounterAndDone[] runnables = buildRunnables(sig, traversalCount);
62 SimpleInvocationGateHarness harness = new SimpleInvocationGateHarness(traversalCount);
63 harness.execute(runnables);
64
65 int tc = traversalCount.get();
66 long ct = currentTime.get();
67 long diff = ct - initialTime - MAX_TRAVERSAL_COUNT;
68 int traversalCountMismatch = tc - MAX_TRAVERSAL_COUNT;
69 assertTrue(traversalCountMismatch >=0, "traversalCountMismatch must be a positive number");
70 int tolerance = 6;
71 assertTrue(traversalCountMismatch < tolerance, "traversalCountMismatch must be less than "+tolerance+ " actual value "+traversalCountMismatch);
72
73 assertTrue(diff >=0, "time difference must be a positive number");
74 assertTrue(diff < tolerance, "time difference must be less than "+tolerance+" actual value "+diff);
75
76
77
78 }
79
80 private RunnableWithCounterAndDone[] buildRunnables(InvocationGate invocationGate, AtomicInteger traversalCount ) {
81
82 RunnableWithCounterAndDone[] runnables = new RunnableWithCounterAndDone[THREAD_COUNT + 1];
83 runnables[0] = new TimeUpdater(currentTime);
84 for(int i = 1; i < runnables.length; i++) {
85 runnables[i] = new InvocationGateChecker(invocationGate, traversalCount);
86 }
87 return runnables;
88 }
89
90 class SimpleInvocationGateHarness extends AbstractMultiThreadedHarness {
91
92 AtomicInteger traversalCount;
93
94 public SimpleInvocationGateHarness(AtomicInteger traversalCount) {
95 this.traversalCount = traversalCount;
96 }
97
98 public void waitUntilEndCondition() throws InterruptedException {
99 while(traversalCount.get() < MAX_TRAVERSAL_COUNT) {
100 Thread.yield();
101 }
102 }
103 }
104
105 private class TimeUpdater extends RunnableWithCounterAndDone {
106
107 Random random = new Random(69923259L);
108 AtomicLong currentTime;
109 public TimeUpdater(AtomicLong currentTime) {
110 this.currentTime = currentTime;
111 }
112 @Override
113 public void run() {
114 sleep(10);
115 while(!isDone()) {
116 if (0 == random.nextInt(ONCE_EVERY)) {
117 long ct = currentTime.incrementAndGet();
118 if((ct & MASK) == MASK) {
119 System.out.println("Time increment ct="+ct);
120 }
121 }
122 Thread.yield();
123 }
124 }
125
126 private void sleep(int duration) {
127 try {
128 Thread.sleep(duration);
129 } catch (InterruptedException e) {
130 throw new RuntimeException(e);
131 }
132 }
133 }
134
135 private class InvocationGateChecker extends RunnableWithCounterAndDone {
136
137 InvocationGate invocationGate;
138 AtomicInteger traversalCount;
139 public InvocationGateChecker(InvocationGate invocationGate, AtomicInteger traversalCount) {
140 this.invocationGate = invocationGate;
141 this.traversalCount = traversalCount;
142 }
143
144 @Override
145 public void run() {
146 while(!isDone()) {
147 if (!invocationGate.isTooSoon(currentTime.get())) {
148 int tc = traversalCount.incrementAndGet();
149 if((tc & MASK) == MASK) {
150 System.out.println("traversalCount="+tc);
151 }
152 }
153 Thread.yield();
154 }
155 }
156 }
157 }