1 /*
   2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   3  *
   4  * This code is free software; you can redistribute it and/or modify it
   5  * under the terms of the GNU General Public License version 2 only, as
   6  * published by the Free Software Foundation.
   7  *
   8  * This code is distributed in the hope that it will be useful, but WITHOUT
   9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  11  * version 2 for more details (a copy is included in the LICENSE file that
  12  * accompanied this code).
  13  *
  14  * You should have received a copy of the GNU General Public License version
  15  * 2 along with this work; if not, write to the Free Software Foundation,
  16  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  17  *
  18  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  19  * or visit www.oracle.com if you need additional information or have any
  20  * questions.
  21  */
  22 
  23 /*
  24  * This file is available under and governed by the GNU General Public
  25  * License version 2 only, as published by the Free Software Foundation.
  26  * However, the following notice accompanied the original version of this
  27  * file:
  28  *
  29  * Written by Martin Buchholz with assistance from members of JCP
  30  * JSR-166 Expert Group and released to the public domain, as
  31  * explained at http://creativecommons.org/publicdomain/zero/1.0/
  32  */
  33 
  34 /*
  35  * @test
  36  * @modules java.base/java.util.concurrent:open
  37  * @run testng WhiteBox
  38  * @summary White box tests of implementation details
  39  */
  40 
  41 import static org.testng.Assert.*;
  42 import org.testng.annotations.DataProvider;
  43 import org.testng.annotations.Test;
  44 
  45 import java.io.ByteArrayInputStream;
  46 import java.io.ByteArrayOutputStream;
  47 import java.io.ObjectInputStream;
  48 import java.io.ObjectOutputStream;
  49 import java.lang.invoke.MethodHandles;
  50 import java.lang.invoke.VarHandle;
  51 import java.util.ArrayList;
  52 import java.util.Iterator;
  53 import java.util.List;
  54 import java.util.concurrent.LinkedTransferQueue;
  55 import java.util.concurrent.ThreadLocalRandom;
  56 import java.util.concurrent.TimeUnit;
  57 import static java.util.stream.Collectors.toList;
  58 import java.util.function.Consumer;
  59 
  60 @Test
  61 public class WhiteBox {
  62     final ThreadLocalRandom rnd = ThreadLocalRandom.current();
  63     final VarHandle HEAD, TAIL, ITEM, NEXT;
  64     final int SWEEP_THRESHOLD;
  65 
  66     public WhiteBox() throws ReflectiveOperationException {
  67         Class<?> qClass = LinkedTransferQueue.class;
  68         Class<?> nodeClass = Class.forName(qClass.getName() + "$Node");
  69         MethodHandles.Lookup lookup
  70             = MethodHandles.privateLookupIn(qClass, MethodHandles.lookup());
  71         HEAD = lookup.findVarHandle(qClass, "head", nodeClass);
  72         TAIL = lookup.findVarHandle(qClass, "tail", nodeClass);
  73         NEXT = lookup.findVarHandle(nodeClass, "next", nodeClass);
  74         ITEM = lookup.findVarHandle(nodeClass, "item", Object.class);
  75         SWEEP_THRESHOLD = (int)
  76             lookup.findStaticVarHandle(qClass, "SWEEP_THRESHOLD", int.class)
  77             .get();
  78     }
  79 
  80     Object head(LinkedTransferQueue q) { return HEAD.getVolatile(q); }
  81     Object tail(LinkedTransferQueue q) { return TAIL.getVolatile(q); }
  82     Object item(Object node)           { return ITEM.getVolatile(node); }
  83     Object next(Object node)           { return NEXT.getVolatile(node); }
  84 
  85     int nodeCount(LinkedTransferQueue q) {
  86         int i = 0;
  87         for (Object p = head(q); p != null; ) {
  88             i++;
  89             if (p == (p = next(p))) p = head(q);
  90         }
  91         return i;
  92     }
  93 
  94     int tailCount(LinkedTransferQueue q) {
  95         int i = 0;
  96         for (Object p = tail(q); p != null; ) {
  97             i++;
  98             if (p == (p = next(p))) p = head(q);
  99         }
 100         return i;
 101     }
 102 
 103     Object findNode(LinkedTransferQueue q, Object e) {
 104         for (Object p = head(q); p != null; ) {
 105             if (item(p) != null && e.equals(item(p)))
 106                 return p;
 107             if (p == (p = next(p))) p = head(q);
 108         }
 109         throw new AssertionError("not found");
 110     }
 111 
 112     Iterator iteratorAt(LinkedTransferQueue q, Object e) {
 113         for (Iterator it = q.iterator(); it.hasNext(); )
 114             if (it.next().equals(e))
 115                 return it;
 116         throw new AssertionError("not found");
 117     }
 118 
 119     void assertIsSelfLinked(Object node) {
 120         assertSame(next(node), node);
 121         assertNull(item(node));
 122     }
 123     void assertIsNotSelfLinked(Object node) {
 124         assertNotSame(node, next(node));
 125     }
 126 
 127     @Test
 128     public void addRemove() {
 129         LinkedTransferQueue q = new LinkedTransferQueue();
 130         assertInvariants(q);
 131         assertNull(next(head(q)));
 132         assertNull(item(head(q)));
 133         q.add(1);
 134         assertEquals(nodeCount(q), 2);
 135         assertInvariants(q);
 136         q.remove(1);
 137         assertEquals(nodeCount(q), 1);
 138         assertInvariants(q);
 139     }
 140 
 141     /**
 142      * Traversal actions that visit every node and do nothing, but
 143      * have side effect of squeezing out dead nodes.
 144      */
 145     @DataProvider
 146     public Object[][] traversalActions() {
 147         return List.<Consumer<LinkedTransferQueue>>of(
 148             q -> q.forEach(e -> {}),
 149             q -> assertFalse(q.contains(new Object())),
 150             q -> assertFalse(q.remove(new Object())),
 151             q -> q.spliterator().forEachRemaining(e -> {}),
 152             q -> q.stream().collect(toList()),
 153             q -> assertFalse(q.removeIf(e -> false)),
 154             q -> assertFalse(q.removeAll(List.of())))
 155             .stream().map(x -> new Object[]{ x }).toArray(Object[][]::new);
 156     }
 157 
 158     @Test(dataProvider = "traversalActions")
 159     public void traversalOperationsCollapseLeadingNodes(
 160         Consumer<LinkedTransferQueue> traversalAction) {
 161         LinkedTransferQueue q = new LinkedTransferQueue();
 162         Object oldHead;
 163         int n = 1 + rnd.nextInt(5);
 164         for (int i = 0; i < n; i++) q.add(i);
 165         assertEquals(nodeCount(q), n + 1);
 166         oldHead = head(q);
 167         traversalAction.accept(q);
 168         assertInvariants(q);
 169         assertEquals(nodeCount(q), n);
 170         assertIsSelfLinked(oldHead);
 171     }
 172 
 173     @Test(dataProvider = "traversalActions")
 174     public void traversalOperationsCollapseInteriorNodes(
 175         Consumer<LinkedTransferQueue> traversalAction) {
 176         LinkedTransferQueue q = new LinkedTransferQueue();
 177         int n = 6;
 178         for (int i = 0; i < n; i++) q.add(i);
 179 
 180         // We must be quite devious to reliably create an interior dead node
 181         Object p0 = findNode(q, 0);
 182         Object p1 = findNode(q, 1);
 183         Object p2 = findNode(q, 2);
 184         Object p3 = findNode(q, 3);
 185         Object p4 = findNode(q, 4);
 186         Object p5 = findNode(q, 5);
 187 
 188         Iterator it1 = iteratorAt(q, 1);
 189         Iterator it2 = iteratorAt(q, 2);
 190 
 191         it2.remove(); // causes it2's ancestor to advance to 1
 192         assertSame(next(p1), p3);
 193         assertSame(next(p2), p3);
 194         assertNull(item(p2));
 195         it1.remove(); // removes it2's ancestor
 196         assertSame(next(p0), p3);
 197         assertSame(next(p1), p3);
 198         assertSame(next(p2), p3);
 199         assertNull(item(p1));
 200         assertEquals(it2.next(), 3);
 201         it2.remove(); // it2's ancestor can't unlink
 202 
 203         assertSame(next(p0), p3); // p3 is now interior dead node
 204         assertSame(next(p1), p4); // it2 uselessly CASed p1.next
 205         assertSame(next(p2), p3);
 206         assertSame(next(p3), p4);
 207         assertInvariants(q);
 208 
 209         int c = nodeCount(q);
 210         traversalAction.accept(q);
 211         assertEquals(nodeCount(q), c - 1);
 212 
 213         assertSame(next(p0), p4);
 214         assertSame(next(p1), p4);
 215         assertSame(next(p2), p3);
 216         assertSame(next(p3), p4);
 217         assertInvariants(q);
 218 
 219         // trailing nodes are not unlinked
 220         Iterator it5 = iteratorAt(q, 5); it5.remove();
 221         traversalAction.accept(q);
 222         assertSame(next(p4), p5);
 223         assertNull(next(p5));
 224         assertEquals(nodeCount(q), c - 1);
 225     }
 226 
 227     /**
 228      * Checks that traversal operations collapse a random pattern of
 229      * dead nodes as could normally only occur with a race.
 230      */
 231     @Test(dataProvider = "traversalActions")
 232     public void traversalOperationsCollapseRandomNodes(
 233         Consumer<LinkedTransferQueue> traversalAction) {
 234         LinkedTransferQueue q = new LinkedTransferQueue();
 235         int n = rnd.nextInt(6);
 236         for (int i = 0; i < n; i++) q.add(i);
 237         ArrayList nulledOut = new ArrayList();
 238         for (Object p = head(q); p != null; p = next(p))
 239             if (rnd.nextBoolean()) {
 240                 nulledOut.add(item(p));
 241                 ITEM.setVolatile(p, null);
 242             }
 243         traversalAction.accept(q);
 244         int c = nodeCount(q);
 245         assertEquals(q.size(), c - (q.contains(n - 1) ? 0 : 1));
 246         for (int i = 0; i < n; i++)
 247             assertTrue(nulledOut.contains(i) ^ q.contains(i));
 248     }
 249 
 250     /**
 251      * Traversal actions that remove every element, and are also
 252      * expected to squeeze out dead nodes.
 253      */
 254     @DataProvider
 255     public Object[][] bulkRemovalActions() {
 256         return List.<Consumer<LinkedTransferQueue>>of(
 257             q -> q.clear(),
 258             q -> assertTrue(q.removeIf(e -> true)),
 259             q -> assertTrue(q.retainAll(List.of())))
 260             .stream().map(x -> new Object[]{ x }).toArray(Object[][]::new);
 261     }
 262 
 263     @Test(dataProvider = "bulkRemovalActions")
 264     public void bulkRemovalOperationsCollapseNodes(
 265         Consumer<LinkedTransferQueue> bulkRemovalAction) {
 266         LinkedTransferQueue q = new LinkedTransferQueue();
 267         int n = 1 + rnd.nextInt(5);
 268         for (int i = 0; i < n; i++) q.add(i);
 269         bulkRemovalAction.accept(q);
 270         assertEquals(nodeCount(q), 1);
 271         assertInvariants(q);
 272     }
 273 
 274     /**
 275      * Actions that remove the first element, and are expected to
 276      * leave at most one slack dead node at head.
 277      */
 278     @DataProvider
 279     public Object[][] pollActions() {
 280         return List.<Consumer<LinkedTransferQueue>>of(
 281             q -> assertNotNull(q.poll()),
 282             q -> { try { assertNotNull(q.poll(1L, TimeUnit.DAYS)); }
 283                 catch (Throwable x) { throw new AssertionError(x); }},
 284             q -> { try { assertNotNull(q.take()); }
 285                 catch (Throwable x) { throw new AssertionError(x); }},
 286             q -> assertNotNull(q.remove()))
 287             .stream().map(x -> new Object[]{ x }).toArray(Object[][]::new);
 288     }
 289 
 290     @Test(dataProvider = "pollActions")
 291     public void pollActionsOneNodeSlack(
 292         Consumer<LinkedTransferQueue> pollAction) {
 293         LinkedTransferQueue q = new LinkedTransferQueue();
 294         int n = 1 + rnd.nextInt(5);
 295         for (int i = 0; i < n; i++) q.add(i);
 296         assertEquals(nodeCount(q), n + 1);
 297         for (int i = 0; i < n; i++) {
 298             int c = nodeCount(q);
 299             boolean slack = item(head(q)) == null;
 300             if (slack) assertNotNull(item(next(head(q))));
 301             pollAction.accept(q);
 302             assertEquals(nodeCount(q), q.isEmpty() ? 1 : c - (slack ? 2 : 0));
 303         }
 304         assertInvariants(q);
 305     }
 306 
 307     /**
 308      * Actions that append an element, and are expected to
 309      * leave at most one slack node at tail.
 310      */
 311     @DataProvider
 312     public Object[][] addActions() {
 313         return List.<Consumer<LinkedTransferQueue>>of(
 314             q -> q.add(1),
 315             q -> q.offer(1))
 316             .stream().map(x -> new Object[]{ x }).toArray(Object[][]::new);
 317     }
 318 
 319     @Test(dataProvider = "addActions")
 320     public void addActionsOneNodeSlack(
 321         Consumer<LinkedTransferQueue> addAction) {
 322         LinkedTransferQueue q = new LinkedTransferQueue();
 323         int n = 1 + rnd.nextInt(9);
 324         for (int i = 0; i < n; i++) {
 325             boolean slack = next(tail(q)) != null;
 326             addAction.accept(q);
 327             if (slack)
 328                 assertNull(next(tail(q)));
 329             else {
 330                 assertNotNull(next(tail(q)));
 331                 assertNull(next(next(tail(q))));
 332             }
 333             assertInvariants(q);
 334         }
 335     }
 336 
 337     byte[] serialBytes(Object o) {
 338         try {
 339             ByteArrayOutputStream bos = new ByteArrayOutputStream();
 340             ObjectOutputStream oos = new ObjectOutputStream(bos);
 341             oos.writeObject(o);
 342             oos.flush();
 343             oos.close();
 344             return bos.toByteArray();
 345         } catch (Exception fail) {
 346             throw new AssertionError(fail);
 347         }
 348     }
 349 
 350     @SuppressWarnings("unchecked")
 351     <T> T serialClone(T o) {
 352         try {
 353             ObjectInputStream ois = new ObjectInputStream
 354                 (new ByteArrayInputStream(serialBytes(o)));
 355             T clone = (T) ois.readObject();
 356             assertNotSame(o, clone);
 357             assertSame(o.getClass(), clone.getClass());
 358             return clone;
 359         } catch (Exception fail) {
 360             throw new AssertionError(fail);
 361         }
 362     }
 363 
 364     @Test
 365     public void testSerialization() {
 366         LinkedTransferQueue q = serialClone(new LinkedTransferQueue());
 367         assertInvariants(q);
 368     }
 369 
 370     public void cancelledNodeSweeping() throws Throwable {
 371         assertEquals(SWEEP_THRESHOLD & (SWEEP_THRESHOLD - 1), 0);
 372         LinkedTransferQueue q = new LinkedTransferQueue();
 373         Thread blockHead = null;
 374         if (rnd.nextBoolean()) {
 375             blockHead = new Thread(
 376                 () -> { try { q.take(); } catch (InterruptedException ok) {}});
 377             blockHead.start();
 378             while (nodeCount(q) != 2) { Thread.yield(); }
 379             assertTrue(q.hasWaitingConsumer());
 380             assertEquals(q.getWaitingConsumerCount(), 1);
 381         }
 382         int initialNodeCount = nodeCount(q);
 383 
 384         // Some dead nodes do in fact accumulate ...
 385         if (blockHead != null)
 386             while (nodeCount(q) < initialNodeCount + SWEEP_THRESHOLD / 2)
 387                 q.poll(1L, TimeUnit.MICROSECONDS);
 388 
 389         // ... but no more than SWEEP_THRESHOLD nodes accumulate
 390         for (int i = rnd.nextInt(SWEEP_THRESHOLD * 10); i-->0; )
 391             q.poll(1L, TimeUnit.MICROSECONDS);
 392         assertTrue(nodeCount(q) <= initialNodeCount + SWEEP_THRESHOLD);
 393 
 394         if (blockHead != null) {
 395             blockHead.interrupt();
 396             blockHead.join();
 397         }
 398     }
 399 
 400     /** Checks conditions which should always be true. */
 401     void assertInvariants(LinkedTransferQueue q) {
 402         assertNotNull(head(q));
 403         assertNotNull(tail(q));
 404         // head is never self-linked (but tail may!)
 405         for (Object h; next(h = head(q)) == h; )
 406             assertNotSame(h, head(q)); // must be update race
 407     }
 408 }