1
2
3
4
5 package oshi.util;
6
7 import static org.hamcrest.MatcherAssert.assertThat;
8 import static org.hamcrest.Matchers.greaterThan;
9 import static org.hamcrest.Matchers.is;
10 import static org.hamcrest.Matchers.lessThan;
11 import static org.hamcrest.Matchers.lessThanOrEqualTo;
12 import static org.hamcrest.Matchers.not;
13 import static org.hamcrest.Matchers.notNullValue;
14 import static oshi.util.Memoizer.memoize;
15
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Locale;
19 import java.util.Random;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.ExecutorService;
22 import java.util.concurrent.Future;
23 import java.util.concurrent.LinkedBlockingQueue;
24 import java.util.concurrent.ThreadPoolExecutor;
25 import java.util.concurrent.TimeUnit;
26 import java.util.function.Supplier;
27
28 import org.junit.jupiter.api.AfterEach;
29 import org.junit.jupiter.api.BeforeEach;
30 import org.junit.jupiter.api.Test;
31 import org.junit.jupiter.api.condition.DisabledOnOs;
32 import org.junit.jupiter.api.condition.OS;
33
34 @DisabledOnOs(OS.SOLARIS)
35 final class MemoizerTest {
36
37 private static final int numberOfThreads = Math.max(5, Runtime.getRuntime().availableProcessors() + 2);
38
39 private ExecutorService ex;
40
41 @BeforeEach
42 void before() {
43 final ThreadPoolExecutor ex = new ThreadPoolExecutor(numberOfThreads, numberOfThreads, 0L,
44 TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
45 ex.allowCoreThreadTimeOut(false);
46 ex.prestartAllCoreThreads();
47
48 this.ex = ex;
49 }
50
51 @AfterEach
52 void after() throws InterruptedException {
53 ex.shutdownNow();
54 ex.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
55 }
56
57 @Test
58 void get() throws Throwable {
59
60
61
62 int x = new Random().nextInt(50);
63
64 int refreshIters = 200;
65 int noRefreshIters = 2_000;
66 if (x == 0) {
67
68 noRefreshIters = 20_000;
69 } else if (x == 1) {
70
71 refreshIters = 4000;
72 } else if (x < 12) {
73
74 noRefreshIters = 5_000;
75 } else if (x < 22) {
76
77 refreshIters = 500;
78 }
79
80 long iterationDurationNanos = TimeUnit.MILLISECONDS.toNanos(1);
81 long ttlNanos = -1;
82 for (int r = 0; r < noRefreshIters; r++) {
83 run(iterationDurationNanos, ttlNanos);
84 }
85
86 iterationDurationNanos = TimeUnit.MILLISECONDS.toNanos(10);
87 ttlNanos = iterationDurationNanos / 100;
88 assertThat("ttlNanos should not be zero", ttlNanos, is(not(0L)));
89 for (int r = 0; r < refreshIters; r++) {
90 run(iterationDurationNanos, ttlNanos);
91 }
92 }
93
94 private void run(final long iterationDurationNanos, final long ttlNanos) throws Throwable {
95 final Supplier<Long> s = new Supplier<Long>() {
96 private long value;
97
98
99
100 @Override
101 public Long get() {
102 return ++value;
103 }
104 };
105
106 final Supplier<Long> m = memoize(s, ttlNanos);
107
108 final Collection<Future<Void>> results = new ArrayList<>();
109
110 final long beginNanos = System.nanoTime();
111 for (int tid = 0; tid < numberOfThreads; tid++) {
112 results.add(ex.submit(() -> {
113
114
115 Long previousValue = m.get();
116 assertThat("previousValue should not be null", previousValue, is(notNullValue()));
117 assertThat("previousValue should be greater than zero", previousValue, is(greaterThan(0L)));
118
119
120
121 final long firstSupplierCallNanos = System.nanoTime();
122
123
124 boolean guaranteedIteration = false;
125 long now;
126 while ((now = System.nanoTime()) - beginNanos < iterationDurationNanos
127 || now - firstSupplierCallNanos < ttlNanos || (guaranteedIteration = !guaranteedIteration)) {
128
129
130
131 if (Thread.currentThread().isInterrupted()) {
132 throw new InterruptedException();
133 }
134 final Long newValue = m.get();
135
136 assertThat("newValue should not be null", newValue, is(notNullValue()));
137
138 assertThat("newValue shuld be larger", newValue, is(not(lessThan(previousValue))));
139 previousValue = newValue;
140 }
141 return null;
142 }));
143 }
144
145
146
147 finishAllThreads(results);
148
149
150
151
152
153
154 final long actualNumberOfIncrements = s.get() - 1;
155 testIncrementCounts(actualNumberOfIncrements, iterationDurationNanos, ttlNanos);
156
157 }
158
159 private static void finishAllThreads(Collection<Future<Void>> results)
160 throws InterruptedException, ExecutionException {
161 for (final Future<Void> result : results) {
162 result.get();
163 }
164 }
165
166 private static void testIncrementCounts(long actualNumberOfIncrements, long iterationDurationNanos, long ttlNanos) {
167 if (ttlNanos < 0) {
168 assertThat(String.format(Locale.ROOT, "ttlNanos=%d", ttlNanos), actualNumberOfIncrements, is(1L));
169 } else {
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186 final long minExpectedNumberOfIncrements = 2L;
187 final long maxExpectedNumberOfIncrements = (iterationDurationNanos / ttlNanos) + 2L * numberOfThreads;
188
189 assertThat(String.format(Locale.ROOT, "ttlNanos=%s", ttlNanos), minExpectedNumberOfIncrements,
190 is(lessThanOrEqualTo(actualNumberOfIncrements)));
191 assertThat(String.format(Locale.ROOT, "ttlNanos=%s", ttlNanos), actualNumberOfIncrements,
192 is(lessThanOrEqualTo(maxExpectedNumberOfIncrements)));
193 }
194 }
195 }