/* * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 6360946 6360948 * @summary Test various operations on concurrently mutating collections * @author Martin Buchholz */ import static java.util.Collections.*; import java.util.*; import java.util.concurrent.*; public class RacingCollections { /** * How long to run each "race" (in milliseconds). * Turn this up to some higher value like 1000 for stress testing: * java -Dmillis=1000 RacingCollections */ final static long defaultWorkTimeMillis = Long.getLong("millis", 10L); /** * Whether to print debug information. */ final static boolean debug = Boolean.getBoolean("debug"); final static Integer one = 1; final static Integer two = 2; /** * A thread that mutates an object forever, alternating between * being empty and containing singleton "two" */ static class Frobber extends CheckedThread { volatile boolean done = false; boolean keepGoing(int i) { return (i % 128 != 0) || ! done; } final Object elLoco; Frobber(Object elLoco) { this.elLoco = elLoco; this.start(); } @SuppressWarnings("unchecked") void clear(Object o) { if (o instanceof Collection) ((Collection<?>)o).clear(); else ((Map<?,?>)o).clear(); } @SuppressWarnings("unchecked") void realRun() { // Mutate elLoco wildly forever, checking occasionally for "done" clear(elLoco); if (elLoco instanceof List) { List<Integer> l = (List<Integer>) elLoco; for (int i = 0; keepGoing(i); i++) { switch (i%2) { case 0: l.add(two); break; case 1: l.add(0, two); break; } switch (i%2) { case 0: l.remove(two); break; case 1: l.remove(0); break; }}} else if (elLoco instanceof Deque) { Deque<Integer> q = (Deque<Integer>) elLoco; for (int i = 0; keepGoing(i); i++) { switch (i%6) { case 0: q.add(two); break; case 1: q.addFirst(two); break; case 2: q.addLast(two); break; case 3: q.offer(two); break; case 4: q.offerFirst(two); break; case 5: q.offerLast(two); break; } switch (i%6) { case 0: q.remove(two); break; case 1: q.removeFirst(); break; case 2: q.removeLast(); break; case 3: q.poll(); break; case 4: q.pollFirst(); break; case 5: q.pollLast(); break; }}} else if (elLoco instanceof Queue) { Queue<Integer> q = (Queue<Integer>) elLoco; for (int i = 0; keepGoing(i); i++) { switch (i%2) { case 0: q.add(two); break; case 1: q.offer(two); break; } switch (i%2) { case 0: q.remove(two); break; case 1: q.poll(); break; }}} else if (elLoco instanceof Map) { Map<Integer, Boolean> m = (Map<Integer, Boolean>) elLoco; for (int i = 0; keepGoing(i); i++) { m.put(two, true); m.remove(two); }} else if (elLoco instanceof Collection) { Collection<Integer> c = (Collection<Integer>) elLoco; for (int i = 0; keepGoing(i); i++) { c.add(two); c.remove(two); }} else { throw new Error("Huh? " + elLoco); } } void enoughAlready() { done = true; try { join(); } catch (Throwable t) { unexpected(t); } } } private static void checkEqualSanity(Object theRock, Object elLoco) { //equal(theRock, theRock); equal(elLoco, elLoco); // It would be nice someday to have theRock and elLoco never "equal", // although the meaning of "equal" for mutating collections // is a bit fuzzy. Uncomment when/if we fix: // 6374942: Improve thread safety of collection .equals() methods //notEqual(theRock, elLoco); //notEqual(elLoco, theRock); notEqual(theRock.toString(), elLoco.toString()); } static class Looper { final long quittingTime; int i = 0; Looper() { this(defaultWorkTimeMillis); } Looper(long workTimeMillis) { quittingTime = System.nanoTime() + workTimeMillis * 1024 * 1024; } boolean keepGoing() { return (i++ % 128 != 0) || (System.nanoTime() < quittingTime); } } private static void frob(Object theRock, Object elLoco) { Frobber frobber = new Frobber(elLoco); try { if (theRock instanceof Collection) { @SuppressWarnings("unchecked") Collection<Integer> c = (Collection<Integer>) theRock; if (! c.contains(one)) c.add(one); } else { @SuppressWarnings("unchecked") Map<Integer, Boolean> m = (Map<Integer, Boolean>) theRock; if (! m.containsKey(one)) m.put(one, true); } for (Looper looper = new Looper(); looper.keepGoing(); ) checkEqualSanity(theRock, elLoco); } catch (Throwable t) { unexpected(t); } finally { frobber.enoughAlready(); } } private static List<Map<Integer, Boolean>> newConcurrentMaps() { List<Map<Integer, Boolean>> list = new ArrayList<Map<Integer, Boolean>>(); list.add(new ConcurrentHashMap<Integer, Boolean>()); list.add(new ConcurrentSkipListMap<Integer, Boolean>()); return list; } private static List<Map<Integer, Boolean>> maps() { List<Map<Integer, Boolean>> list = newConcurrentMaps(); list.add(new Hashtable<Integer, Boolean>()); list.add(new HashMap<Integer, Boolean>()); list.add(new TreeMap<Integer, Boolean>()); Comparator<Integer> cmp = new Comparator<Integer>() { public int compare(Integer x, Integer y) { return x - y; }}; list.add(new TreeMap<Integer, Boolean>(Collections.reverseOrder(cmp))); return list; } private static List<Set<Integer>> newConcurrentSets() { List<Set<Integer>> list = new ArrayList<Set<Integer>>(); list.add(new ConcurrentSkipListSet<Integer>()); list.add(new CopyOnWriteArraySet<Integer>()); return list; } private static List<Set<Integer>> newSets() { List<Set<Integer>> list = newConcurrentSets(); list.add(new HashSet<Integer>()); list.add(new TreeSet<Integer>()); list.add(new TreeSet<Integer>(Collections.reverseOrder())); return list; } private static List<List<Integer>> newConcurrentLists() { List<List<Integer>> list = new ArrayList<List<Integer>>(); list.add(new CopyOnWriteArrayList<Integer>()); return list; } private static List<List<Integer>> newLists() { List<List<Integer>> list = newConcurrentLists(); list.add(new Vector<Integer>()); list.add(new ArrayList<Integer>()); return list; } private static List<Queue<Integer>> newConcurrentQueues() { List<Queue<Integer>> list = new ArrayList<Queue<Integer>>(newConcurrentDeques()); list.add(new LinkedBlockingQueue<Integer>(10)); list.add(new LinkedTransferQueue<Integer>()); list.add(new ConcurrentLinkedQueue<Integer>()); return list; } private static List<Queue<Integer>> newQueues() { List<Queue<Integer>> list = new ArrayList<Queue<Integer>>(newDeques()); list.add(new LinkedBlockingQueue<Integer>(10)); return list; } private static List<Deque<Integer>> newConcurrentDeques() { List<Deque<Integer>> list = new ArrayList<Deque<Integer>>(); list.add(new LinkedBlockingDeque<Integer>(10)); list.add(new ConcurrentLinkedDeque<Integer>()); return list; } private static List<Deque<Integer>> newDeques() { List<Deque<Integer>> list = newConcurrentDeques(); list.add(new ArrayDeque<Integer>()); list.add(new LinkedList<Integer>()); return list; } private static void describe(Class<?> k, Object x, Object y) { if (debug) System.out.printf("%s: %s, %s%n", k.getSimpleName(), x.getClass().getSimpleName(), y.getClass().getSimpleName()); } private static void realMain(String[] args) { for (Map<Integer, Boolean> x : maps()) for (Map<Integer, Boolean> y : newConcurrentMaps()) { describe(Map.class, x, y); x.put(one, true); frob(x, y); frob(unmodifiableMap(x), y); frob(synchronizedMap(x), y); frob(x, synchronizedMap(y)); frob(checkedMap(x, Integer.class, Boolean.class), y); frob(x, checkedMap(y, Integer.class, Boolean.class)); x.clear(); frob(newSetFromMap(x), newSetFromMap(y)); frob(x.keySet(), newSetFromMap(y)); } for (Set<Integer> x : newSets()) for (Set<Integer> y : newConcurrentSets()) { describe(Set.class, x, y); frob(x, y); frob(unmodifiableSet(x), y); frob(synchronizedSet(x), y); frob(x, synchronizedSet(y)); frob(checkedSet(x, Integer.class), y); frob(x, checkedSet(y, Integer.class)); } for (List<Integer> x : newLists()) for (List<Integer> y : newConcurrentLists()) { describe(List.class, x, y); frob(x, y); frob(unmodifiableList(x), y); frob(synchronizedList(x), y); frob(x, synchronizedList(y)); frob(checkedList(x, Integer.class), y); frob(x, checkedList(y, Integer.class)); } for (Queue<Integer> x : newQueues()) for (Queue<Integer> y : newConcurrentQueues()) { describe(Queue.class, x, y); frob(x, y); } for (Deque<Integer> x : newDeques()) for (Deque<Integer> y : newConcurrentDeques()) { describe(Deque.class, x, y); frob(asLifoQueue(x), y); frob(x, asLifoQueue(y)); } } //--------------------- Infrastructure --------------------------- static volatile int passed = 0, failed = 0; static void pass() {passed++;} static void fail() {failed++; Thread.dumpStack();} static void fail(String msg) {System.out.println(msg); fail();} static void unexpected(Throwable t) {failed++; t.printStackTrace();} static void check(boolean cond) {if (cond) pass(); else fail();} static String toString(Object x) { return ((x instanceof Collection) || (x instanceof Map)) ? x.getClass().getName() : x.toString();} static void equal(Object x, Object y) { if (x == null ? y == null : x.equals(y)) pass(); else fail(toString(x) + " not equal to " + toString(y));} static void notEqual(Object x, Object y) { if (x == null ? y == null : x.equals(y)) fail(toString(x) + " equal to " + toString(y)); else pass();} public static void main(String[] args) throws Throwable { try {realMain(args);} catch (Throwable t) {unexpected(t);} System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); if (failed > 0) throw new AssertionError("Some tests failed");} private static abstract class CheckedThread extends Thread { abstract void realRun() throws Throwable; public void run() { try { realRun(); } catch (Throwable t) { unexpected(t); }}} }