1 |
/* |
2 |
* Written by Martin Buchholz with assistance from members of JCP |
3 |
* JSR-166 Expert Group and released to the public domain, as |
4 |
* explained at http://creativecommons.org/publicdomain/zero/1.0/ |
5 |
*/ |
6 |
|
7 |
/* |
8 |
* @test |
9 |
* @modules java.base/java.util.concurrent:open |
10 |
* @run testng WhiteBox |
11 |
* @summary White box tests of implementation details |
12 |
*/ |
13 |
|
14 |
import static org.testng.Assert.*; |
15 |
import org.testng.annotations.DataProvider; |
16 |
import org.testng.annotations.Test; |
17 |
|
18 |
import java.io.ByteArrayInputStream; |
19 |
import java.io.ByteArrayOutputStream; |
20 |
import java.io.ObjectInputStream; |
21 |
import java.io.ObjectOutputStream; |
22 |
import java.lang.invoke.MethodHandles; |
23 |
import java.lang.invoke.VarHandle; |
24 |
import java.util.ArrayList; |
25 |
import java.util.Iterator; |
26 |
import java.util.List; |
27 |
import java.util.concurrent.LinkedTransferQueue; |
28 |
import java.util.concurrent.ThreadLocalRandom; |
29 |
import java.util.concurrent.TimeUnit; |
30 |
import static java.util.stream.Collectors.toList; |
31 |
import java.util.function.Consumer; |
32 |
|
33 |
@Test |
34 |
public class WhiteBox { |
35 |
final ThreadLocalRandom rnd = ThreadLocalRandom.current(); |
36 |
final VarHandle HEAD, TAIL, ITEM, NEXT; |
37 |
|
38 |
public WhiteBox() throws ReflectiveOperationException { |
39 |
Class<?> qClass = LinkedTransferQueue.class; |
40 |
Class<?> nodeClass = Class.forName(qClass.getName() + "$Node"); |
41 |
MethodHandles.Lookup lookup |
42 |
= MethodHandles.privateLookupIn(qClass, MethodHandles.lookup()); |
43 |
HEAD = lookup.findVarHandle(qClass, "head", nodeClass); |
44 |
TAIL = lookup.findVarHandle(qClass, "tail", nodeClass); |
45 |
NEXT = lookup.findVarHandle(nodeClass, "next", nodeClass); |
46 |
ITEM = lookup.findVarHandle(nodeClass, "item", Object.class); |
47 |
} |
48 |
|
49 |
Object head(LinkedTransferQueue q) { return HEAD.getVolatile(q); } |
50 |
Object tail(LinkedTransferQueue q) { return TAIL.getVolatile(q); } |
51 |
Object item(Object node) { return ITEM.getVolatile(node); } |
52 |
Object next(Object node) { return NEXT.getVolatile(node); } |
53 |
|
54 |
int nodeCount(LinkedTransferQueue q) { |
55 |
int i = 0; |
56 |
for (Object p = head(q); p != null; ) { |
57 |
i++; |
58 |
if (p == (p = next(p))) p = head(q); |
59 |
} |
60 |
return i; |
61 |
} |
62 |
|
63 |
int tailCount(LinkedTransferQueue q) { |
64 |
int i = 0; |
65 |
for (Object p = tail(q); p != null; ) { |
66 |
i++; |
67 |
if (p == (p = next(p))) p = head(q); |
68 |
} |
69 |
return i; |
70 |
} |
71 |
|
72 |
Object findNode(LinkedTransferQueue q, Object e) { |
73 |
for (Object p = head(q); p != null; ) { |
74 |
if (item(p) != null && e.equals(item(p))) |
75 |
return p; |
76 |
if (p == (p = next(p))) p = head(q); |
77 |
} |
78 |
throw new AssertionError("not found"); |
79 |
} |
80 |
|
81 |
Iterator iteratorAt(LinkedTransferQueue q, Object e) { |
82 |
for (Iterator it = q.iterator(); it.hasNext(); ) |
83 |
if (it.next().equals(e)) |
84 |
return it; |
85 |
throw new AssertionError("not found"); |
86 |
} |
87 |
|
88 |
void assertIsSelfLinked(Object node) { |
89 |
assertSame(next(node), node); |
90 |
assertNull(item(node)); |
91 |
} |
92 |
void assertIsNotSelfLinked(Object node) { |
93 |
assertNotSame(node, next(node)); |
94 |
} |
95 |
|
96 |
@Test |
97 |
public void addRemove() { |
98 |
LinkedTransferQueue q = new LinkedTransferQueue(); |
99 |
assertInvariants(q); |
100 |
assertNull(next(head(q))); |
101 |
assertNull(item(head(q))); |
102 |
q.add(1); |
103 |
assertEquals(nodeCount(q), 2); |
104 |
assertInvariants(q); |
105 |
q.remove(1); |
106 |
assertEquals(nodeCount(q), 1); |
107 |
assertInvariants(q); |
108 |
} |
109 |
|
110 |
/** |
111 |
* Traversal actions that visit every node and do nothing, but |
112 |
* have side effect of squeezing out dead nodes. |
113 |
*/ |
114 |
@DataProvider |
115 |
public Object[][] traversalActions() { |
116 |
return List.<Consumer<LinkedTransferQueue>>of( |
117 |
q -> q.forEach(e -> {}), |
118 |
q -> assertFalse(q.contains(new Object())), |
119 |
q -> assertFalse(q.remove(new Object())), |
120 |
q -> q.spliterator().forEachRemaining(e -> {}), |
121 |
q -> q.stream().collect(toList()), |
122 |
q -> assertFalse(q.removeIf(e -> false)), |
123 |
q -> assertFalse(q.removeAll(List.of()))) |
124 |
.stream().map(x -> new Object[]{ x }).toArray(Object[][]::new); |
125 |
} |
126 |
|
127 |
@Test(dataProvider = "traversalActions") |
128 |
public void traversalOperationsCollapseLeadingNodes( |
129 |
Consumer<LinkedTransferQueue> traversalAction) { |
130 |
LinkedTransferQueue q = new LinkedTransferQueue(); |
131 |
Object oldHead; |
132 |
int n = 1 + rnd.nextInt(5); |
133 |
for (int i = 0; i < n; i++) q.add(i); |
134 |
assertEquals(nodeCount(q), n + 1); |
135 |
oldHead = head(q); |
136 |
traversalAction.accept(q); |
137 |
assertInvariants(q); |
138 |
assertEquals(nodeCount(q), n); |
139 |
assertIsSelfLinked(oldHead); |
140 |
} |
141 |
|
142 |
@Test(dataProvider = "traversalActions") |
143 |
public void traversalOperationsCollapseInteriorNodes( |
144 |
Consumer<LinkedTransferQueue> traversalAction) { |
145 |
LinkedTransferQueue q = new LinkedTransferQueue(); |
146 |
int n = 6; |
147 |
for (int i = 0; i < n; i++) q.add(i); |
148 |
|
149 |
// We must be quite devious to reliably create an interior dead node |
150 |
Object p0 = findNode(q, 0); |
151 |
Object p1 = findNode(q, 1); |
152 |
Object p2 = findNode(q, 2); |
153 |
Object p3 = findNode(q, 3); |
154 |
Object p4 = findNode(q, 4); |
155 |
Object p5 = findNode(q, 5); |
156 |
|
157 |
Iterator it1 = iteratorAt(q, 1); |
158 |
Iterator it2 = iteratorAt(q, 2); |
159 |
|
160 |
it2.remove(); // causes it2's ancestor to advance to 1 |
161 |
assertSame(next(p1), p3); |
162 |
assertSame(next(p2), p3); |
163 |
assertNull(item(p2)); |
164 |
it1.remove(); // removes it2's ancestor |
165 |
assertSame(next(p0), p3); |
166 |
assertSame(next(p1), p3); |
167 |
assertSame(next(p2), p3); |
168 |
assertNull(item(p1)); |
169 |
assertEquals(it2.next(), 3); |
170 |
it2.remove(); // it2's ancestor can't unlink |
171 |
|
172 |
assertSame(next(p0), p3); // p3 is now interior dead node |
173 |
assertSame(next(p1), p4); // it2 uselessly CASed p1.next |
174 |
assertSame(next(p2), p3); |
175 |
assertSame(next(p3), p4); |
176 |
assertInvariants(q); |
177 |
|
178 |
int c = nodeCount(q); |
179 |
traversalAction.accept(q); |
180 |
assertEquals(nodeCount(q), c - 1); |
181 |
|
182 |
assertSame(next(p0), p4); |
183 |
assertSame(next(p1), p4); |
184 |
assertSame(next(p2), p3); |
185 |
assertSame(next(p3), p4); |
186 |
assertInvariants(q); |
187 |
|
188 |
// trailing nodes are not unlinked |
189 |
Iterator it5 = iteratorAt(q, 5); it5.remove(); |
190 |
traversalAction.accept(q); |
191 |
assertSame(next(p4), p5); |
192 |
assertNull(next(p5)); |
193 |
assertEquals(nodeCount(q), c - 1); |
194 |
} |
195 |
|
196 |
/** |
197 |
* Checks that traversal operations collapse a random pattern of |
198 |
* dead nodes as could normally only occur with a race. |
199 |
*/ |
200 |
@Test(dataProvider = "traversalActions") |
201 |
public void traversalOperationsCollapseRandomNodes( |
202 |
Consumer<LinkedTransferQueue> traversalAction) { |
203 |
LinkedTransferQueue q = new LinkedTransferQueue(); |
204 |
int n = rnd.nextInt(6); |
205 |
for (int i = 0; i < n; i++) q.add(i); |
206 |
ArrayList nulledOut = new ArrayList(); |
207 |
for (Object p = head(q); p != null; p = next(p)) |
208 |
if (rnd.nextBoolean()) { |
209 |
nulledOut.add(item(p)); |
210 |
ITEM.setVolatile(p, null); |
211 |
} |
212 |
traversalAction.accept(q); |
213 |
int c = nodeCount(q); |
214 |
assertEquals(q.size(), c - (q.contains(n - 1) ? 0 : 1)); |
215 |
for (int i = 0; i < n; i++) |
216 |
assertTrue(nulledOut.contains(i) ^ q.contains(i)); |
217 |
} |
218 |
|
219 |
/** |
220 |
* Traversal actions that remove every element, and are also |
221 |
* expected to squeeze out dead nodes. |
222 |
*/ |
223 |
@DataProvider |
224 |
public Object[][] bulkRemovalActions() { |
225 |
return List.<Consumer<LinkedTransferQueue>>of( |
226 |
q -> q.clear(), |
227 |
q -> assertTrue(q.removeIf(e -> true)), |
228 |
q -> assertTrue(q.retainAll(List.of()))) |
229 |
.stream().map(x -> new Object[]{ x }).toArray(Object[][]::new); |
230 |
} |
231 |
|
232 |
@Test(dataProvider = "bulkRemovalActions") |
233 |
public void bulkRemovalOperationsCollapseNodes( |
234 |
Consumer<LinkedTransferQueue> bulkRemovalAction) { |
235 |
LinkedTransferQueue q = new LinkedTransferQueue(); |
236 |
int n = 1 + rnd.nextInt(5); |
237 |
for (int i = 0; i < n; i++) q.add(i); |
238 |
bulkRemovalAction.accept(q); |
239 |
assertEquals(nodeCount(q), 1); |
240 |
assertInvariants(q); |
241 |
} |
242 |
|
243 |
/** |
244 |
* Actions that remove the first element, and are expected to |
245 |
* leave at most one slack dead node at head. |
246 |
*/ |
247 |
@DataProvider |
248 |
public Object[][] pollActions() { |
249 |
return List.<Consumer<LinkedTransferQueue>>of( |
250 |
q -> assertNotNull(q.poll()), |
251 |
q -> { try { assertNotNull(q.poll(1L, TimeUnit.DAYS)); } |
252 |
catch (Throwable x) { throw new AssertionError(x); }}, |
253 |
q -> { try { assertNotNull(q.take()); } |
254 |
catch (Throwable x) { throw new AssertionError(x); }}, |
255 |
q -> assertNotNull(q.remove())) |
256 |
.stream().map(x -> new Object[]{ x }).toArray(Object[][]::new); |
257 |
} |
258 |
|
259 |
@Test(dataProvider = "pollActions") |
260 |
public void pollActionsOneNodeSlack( |
261 |
Consumer<LinkedTransferQueue> pollAction) { |
262 |
LinkedTransferQueue q = new LinkedTransferQueue(); |
263 |
int n = 1 + rnd.nextInt(5); |
264 |
for (int i = 0; i < n; i++) q.add(i); |
265 |
assertEquals(nodeCount(q), n + 1); |
266 |
for (int i = 0; i < n; i++) { |
267 |
int c = nodeCount(q); |
268 |
boolean slack = item(head(q)) == null; |
269 |
if (slack) assertNotNull(item(next(head(q)))); |
270 |
pollAction.accept(q); |
271 |
assertEquals(nodeCount(q), q.isEmpty() ? 1 : c - (slack ? 2 : 0)); |
272 |
} |
273 |
assertInvariants(q); |
274 |
} |
275 |
|
276 |
/** |
277 |
* Actions that append an element, and are expected to |
278 |
* leave at most one slack node at tail. |
279 |
*/ |
280 |
@DataProvider |
281 |
public Object[][] addActions() { |
282 |
return List.<Consumer<LinkedTransferQueue>>of( |
283 |
q -> q.add(1), |
284 |
q -> q.offer(1)) |
285 |
.stream().map(x -> new Object[]{ x }).toArray(Object[][]::new); |
286 |
} |
287 |
|
288 |
@Test(dataProvider = "addActions") |
289 |
public void addActionsOneNodeSlack( |
290 |
Consumer<LinkedTransferQueue> addAction) { |
291 |
LinkedTransferQueue q = new LinkedTransferQueue(); |
292 |
int n = 1 + rnd.nextInt(9); |
293 |
for (int i = 0; i < n; i++) { |
294 |
boolean slack = next(tail(q)) != null; |
295 |
addAction.accept(q); |
296 |
if (slack) |
297 |
assertNull(next(tail(q))); |
298 |
else { |
299 |
assertNotNull(next(tail(q))); |
300 |
assertNull(next(next(tail(q)))); |
301 |
} |
302 |
assertInvariants(q); |
303 |
} |
304 |
} |
305 |
|
306 |
byte[] serialBytes(Object o) { |
307 |
try { |
308 |
ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
309 |
ObjectOutputStream oos = new ObjectOutputStream(bos); |
310 |
oos.writeObject(o); |
311 |
oos.flush(); |
312 |
oos.close(); |
313 |
return bos.toByteArray(); |
314 |
} catch (Exception fail) { |
315 |
throw new AssertionError(fail); |
316 |
} |
317 |
} |
318 |
|
319 |
@SuppressWarnings("unchecked") |
320 |
<T> T serialClone(T o) { |
321 |
try { |
322 |
ObjectInputStream ois = new ObjectInputStream |
323 |
(new ByteArrayInputStream(serialBytes(o))); |
324 |
T clone = (T) ois.readObject(); |
325 |
assertNotSame(o, clone); |
326 |
assertSame(o.getClass(), clone.getClass()); |
327 |
return clone; |
328 |
} catch (Exception fail) { |
329 |
throw new AssertionError(fail); |
330 |
} |
331 |
} |
332 |
|
333 |
@Test |
334 |
public void testSerialization() { |
335 |
LinkedTransferQueue q = serialClone(new LinkedTransferQueue()); |
336 |
assertInvariants(q); |
337 |
} |
338 |
|
339 |
/** Checks conditions which should always be true. */ |
340 |
void assertInvariants(LinkedTransferQueue q) { |
341 |
assertNotNull(head(q)); |
342 |
assertNotNull(tail(q)); |
343 |
// head is never self-linked (but tail may!) |
344 |
for (Object h; next(h = head(q)) == h; ) |
345 |
assertNotSame(h, head(q)); // must be update race |
346 |
} |
347 |
} |