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