| /* |
| * 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 |
| */ |
| static final long defaultWorkTimeMillis = Long.getLong("millis", 10L); |
| |
| /** |
| * Whether to print debug information. |
| */ |
| static final boolean debug = Boolean.getBoolean("debug"); |
| |
| static final Integer one = 1; |
| static final 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 < 0); |
| } |
| } |
| |
| 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<>(); |
| 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<>() { |
| 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<>(); |
| 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.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<>(newConcurrentDeques()); |
| list.add(new ArrayBlockingQueue<Integer>(10)); |
| 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<>(newDeques()); |
| list.add(new LinkedBlockingQueue<Integer>(10)); |
| return list; |
| } |
| |
| private static List<Deque<Integer>> newConcurrentDeques() { |
| List<Deque<Integer>> list = new ArrayList<>(); |
| 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 abstract static class CheckedThread extends Thread { |
| abstract void realRun() throws Throwable; |
| public void run() { |
| try { realRun(); } catch (Throwable t) { unexpected(t); }}} |
| } |