View Javadoc
1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2022, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
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); // sync invocation gate with current time
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 }