1 |
/* |
2 |
* Copyright (c) 2006, 2007, Oracle and/or its affiliates. All rights reserved. |
3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 |
* |
5 |
* This code is free software; you can redistribute it and/or modify it |
6 |
* under the terms of the GNU General Public License version 2 only, as |
7 |
* published by the Free Software Foundation. |
8 |
* |
9 |
* This code is distributed in the hope that it will be useful, but WITHOUT |
10 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
11 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
12 |
* version 2 for more details (a copy is included in the LICENSE file that |
13 |
* accompanied this code). |
14 |
* |
15 |
* You should have received a copy of the GNU General Public License version |
16 |
* 2 along with this work; if not, write to the Free Software Foundation, |
17 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
18 |
* |
19 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
20 |
* or visit www.oracle.com if you need additional information or have any |
21 |
* questions. |
22 |
*/ |
23 |
|
24 |
/* |
25 |
* @test |
26 |
* @bug 6360946 6360948 |
27 |
* @summary Test various operations on concurrently mutating collections |
28 |
* @author Martin Buchholz |
29 |
*/ |
30 |
|
31 |
import static java.util.Collections.*; |
32 |
import java.util.*; |
33 |
import java.util.concurrent.*; |
34 |
|
35 |
public class RacingCollections { |
36 |
/** |
37 |
* How long to run each "race" (in milliseconds). |
38 |
* Turn this up to some higher value like 1000 for stress testing: |
39 |
* java -Dmillis=1000 RacingCollections |
40 |
*/ |
41 |
static final long defaultWorkTimeMillis = Long.getLong("millis", 10L); |
42 |
|
43 |
/** |
44 |
* Whether to print debug information. |
45 |
*/ |
46 |
static final boolean debug = Boolean.getBoolean("debug"); |
47 |
|
48 |
static final Integer one = 1; |
49 |
static final Integer two = 2; |
50 |
|
51 |
/** |
52 |
* A thread that mutates an object forever, alternating between |
53 |
* being empty and containing singleton "two" |
54 |
*/ |
55 |
static class Frobber extends CheckedThread { |
56 |
volatile boolean done = false; |
57 |
boolean keepGoing(int i) { return (i % 128 != 0) || ! done; } |
58 |
|
59 |
final Object elLoco; |
60 |
Frobber(Object elLoco) { |
61 |
this.elLoco = elLoco; |
62 |
this.start(); |
63 |
} |
64 |
|
65 |
@SuppressWarnings("unchecked") |
66 |
void clear(Object o) { |
67 |
if (o instanceof Collection) |
68 |
((Collection<?>)o).clear(); |
69 |
else |
70 |
((Map<?,?>)o).clear(); |
71 |
} |
72 |
|
73 |
@SuppressWarnings("unchecked") |
74 |
void realRun() { |
75 |
// Mutate elLoco wildly forever, checking occasionally for "done" |
76 |
clear(elLoco); |
77 |
if (elLoco instanceof List) { |
78 |
List<Integer> l = (List<Integer>) elLoco; |
79 |
for (int i = 0; keepGoing(i); i++) { |
80 |
switch (i%2) { |
81 |
case 0: l.add(two); break; |
82 |
case 1: l.add(0, two); break; |
83 |
} |
84 |
switch (i%2) { |
85 |
case 0: l.remove(two); break; |
86 |
case 1: l.remove(0); break; |
87 |
}}} |
88 |
else if (elLoco instanceof Deque) { |
89 |
Deque<Integer> q = (Deque<Integer>) elLoco; |
90 |
for (int i = 0; keepGoing(i); i++) { |
91 |
switch (i%6) { |
92 |
case 0: q.add(two); break; |
93 |
case 1: q.addFirst(two); break; |
94 |
case 2: q.addLast(two); break; |
95 |
case 3: q.offer(two); break; |
96 |
case 4: q.offerFirst(two); break; |
97 |
case 5: q.offerLast(two); break; |
98 |
} |
99 |
switch (i%6) { |
100 |
case 0: q.remove(two); break; |
101 |
case 1: q.removeFirst(); break; |
102 |
case 2: q.removeLast(); break; |
103 |
case 3: q.poll(); break; |
104 |
case 4: q.pollFirst(); break; |
105 |
case 5: q.pollLast(); break; |
106 |
}}} |
107 |
else if (elLoco instanceof Queue) { |
108 |
Queue<Integer> q = (Queue<Integer>) elLoco; |
109 |
for (int i = 0; keepGoing(i); i++) { |
110 |
switch (i%2) { |
111 |
case 0: q.add(two); break; |
112 |
case 1: q.offer(two); break; |
113 |
} |
114 |
switch (i%2) { |
115 |
case 0: q.remove(two); break; |
116 |
case 1: q.poll(); break; |
117 |
}}} |
118 |
else if (elLoco instanceof Map) { |
119 |
Map<Integer, Boolean> m = (Map<Integer, Boolean>) elLoco; |
120 |
for (int i = 0; keepGoing(i); i++) { |
121 |
m.put(two, true); |
122 |
m.remove(two); |
123 |
}} |
124 |
else if (elLoco instanceof Collection) { |
125 |
Collection<Integer> c = (Collection<Integer>) elLoco; |
126 |
for (int i = 0; keepGoing(i); i++) { |
127 |
c.add(two); |
128 |
c.remove(two); |
129 |
}} |
130 |
else { throw new Error("Huh? " + elLoco); } |
131 |
} |
132 |
|
133 |
void enoughAlready() { |
134 |
done = true; |
135 |
try { join(); } catch (Throwable t) { unexpected(t); } |
136 |
} |
137 |
} |
138 |
|
139 |
private static void checkEqualSanity(Object theRock, Object elLoco) { |
140 |
//equal(theRock, theRock); |
141 |
equal(elLoco, elLoco); |
142 |
|
143 |
// It would be nice someday to have theRock and elLoco never "equal", |
144 |
// although the meaning of "equal" for mutating collections |
145 |
// is a bit fuzzy. Uncomment when/if we fix: |
146 |
// 6374942: Improve thread safety of collection .equals() methods |
147 |
//notEqual(theRock, elLoco); |
148 |
//notEqual(elLoco, theRock); |
149 |
|
150 |
notEqual(theRock.toString(), elLoco.toString()); |
151 |
} |
152 |
|
153 |
static class Looper { |
154 |
final long quittingTime; |
155 |
int i = 0; |
156 |
Looper() { this(defaultWorkTimeMillis); } |
157 |
Looper(long workTimeMillis) { |
158 |
quittingTime = System.nanoTime() + workTimeMillis * 1024 * 1024; |
159 |
} |
160 |
boolean keepGoing() { |
161 |
return (i++ % 128 != 0) || (System.nanoTime() - quittingTime < 0); |
162 |
} |
163 |
} |
164 |
|
165 |
private static void frob(Object theRock, Object elLoco) { |
166 |
Frobber frobber = new Frobber(elLoco); |
167 |
try { |
168 |
if (theRock instanceof Collection) { |
169 |
@SuppressWarnings("unchecked") |
170 |
Collection<Integer> c = (Collection<Integer>) theRock; |
171 |
if (! c.contains(one)) |
172 |
c.add(one); |
173 |
} else { |
174 |
@SuppressWarnings("unchecked") |
175 |
Map<Integer, Boolean> m = (Map<Integer, Boolean>) theRock; |
176 |
if (! m.containsKey(one)) |
177 |
m.put(one, true); |
178 |
} |
179 |
for (Looper looper = new Looper(); looper.keepGoing(); ) |
180 |
checkEqualSanity(theRock, elLoco); |
181 |
} |
182 |
catch (Throwable t) { unexpected(t); } |
183 |
finally { frobber.enoughAlready(); } |
184 |
} |
185 |
|
186 |
private static List<Map<Integer, Boolean>> newConcurrentMaps() { |
187 |
List<Map<Integer, Boolean>> list = |
188 |
new ArrayList<Map<Integer, Boolean>>(); |
189 |
list.add(new ConcurrentHashMap<Integer, Boolean>()); |
190 |
list.add(new ConcurrentSkipListMap<Integer, Boolean>()); |
191 |
return list; |
192 |
} |
193 |
|
194 |
private static List<Map<Integer, Boolean>> maps() { |
195 |
List<Map<Integer, Boolean>> list = newConcurrentMaps(); |
196 |
list.add(new Hashtable<Integer, Boolean>()); |
197 |
list.add(new HashMap<Integer, Boolean>()); |
198 |
list.add(new TreeMap<Integer, Boolean>()); |
199 |
Comparator<Integer> cmp = new Comparator<Integer>() { |
200 |
public int compare(Integer x, Integer y) { |
201 |
return x - y; |
202 |
}}; |
203 |
list.add(new TreeMap<Integer, Boolean>(Collections.reverseOrder(cmp))); |
204 |
return list; |
205 |
} |
206 |
|
207 |
private static List<Set<Integer>> newConcurrentSets() { |
208 |
List<Set<Integer>> list = new ArrayList<Set<Integer>>(); |
209 |
list.add(new ConcurrentSkipListSet<Integer>()); |
210 |
list.add(new CopyOnWriteArraySet<Integer>()); |
211 |
return list; |
212 |
} |
213 |
|
214 |
private static List<Set<Integer>> newSets() { |
215 |
List<Set<Integer>> list = newConcurrentSets(); |
216 |
list.add(new HashSet<Integer>()); |
217 |
list.add(new TreeSet<Integer>()); |
218 |
list.add(new TreeSet<Integer>(Collections.reverseOrder())); |
219 |
return list; |
220 |
} |
221 |
|
222 |
private static List<List<Integer>> newConcurrentLists() { |
223 |
List<List<Integer>> list = new ArrayList<List<Integer>>(); |
224 |
list.add(new CopyOnWriteArrayList<Integer>()); |
225 |
return list; |
226 |
} |
227 |
|
228 |
private static List<List<Integer>> newLists() { |
229 |
List<List<Integer>> list = newConcurrentLists(); |
230 |
list.add(new Vector<Integer>()); |
231 |
list.add(new ArrayList<Integer>()); |
232 |
return list; |
233 |
} |
234 |
|
235 |
private static List<Queue<Integer>> newConcurrentQueues() { |
236 |
List<Queue<Integer>> list = |
237 |
new ArrayList<Queue<Integer>>(newConcurrentDeques()); |
238 |
list.add(new ArrayBlockingQueue<Integer>(10)); |
239 |
list.add(new LinkedBlockingQueue<Integer>(10)); |
240 |
list.add(new LinkedTransferQueue<Integer>()); |
241 |
list.add(new ConcurrentLinkedQueue<Integer>()); |
242 |
return list; |
243 |
} |
244 |
|
245 |
private static List<Queue<Integer>> newQueues() { |
246 |
List<Queue<Integer>> list = |
247 |
new ArrayList<Queue<Integer>>(newDeques()); |
248 |
list.add(new LinkedBlockingQueue<Integer>(10)); |
249 |
return list; |
250 |
} |
251 |
|
252 |
private static List<Deque<Integer>> newConcurrentDeques() { |
253 |
List<Deque<Integer>> list = new ArrayList<Deque<Integer>>(); |
254 |
list.add(new LinkedBlockingDeque<Integer>(10)); |
255 |
list.add(new ConcurrentLinkedDeque<Integer>()); |
256 |
return list; |
257 |
} |
258 |
|
259 |
private static List<Deque<Integer>> newDeques() { |
260 |
List<Deque<Integer>> list = newConcurrentDeques(); |
261 |
list.add(new ArrayDeque<Integer>()); |
262 |
list.add(new LinkedList<Integer>()); |
263 |
return list; |
264 |
} |
265 |
|
266 |
private static void describe(Class<?> k, Object x, Object y) { |
267 |
if (debug) |
268 |
System.out.printf("%s: %s, %s%n", k.getSimpleName(), |
269 |
x.getClass().getSimpleName(), |
270 |
y.getClass().getSimpleName()); |
271 |
} |
272 |
|
273 |
private static void realMain(String[] args) { |
274 |
for (Map<Integer, Boolean> x : maps()) |
275 |
for (Map<Integer, Boolean> y : newConcurrentMaps()) { |
276 |
describe(Map.class, x, y); |
277 |
x.put(one, true); |
278 |
frob(x, y); |
279 |
frob(unmodifiableMap(x), y); |
280 |
frob(synchronizedMap(x), y); |
281 |
frob(x, synchronizedMap(y)); |
282 |
frob(checkedMap(x, Integer.class, Boolean.class), y); |
283 |
frob(x, checkedMap(y, Integer.class, Boolean.class)); |
284 |
x.clear(); |
285 |
frob(newSetFromMap(x), newSetFromMap(y)); |
286 |
frob(x.keySet(), newSetFromMap(y)); |
287 |
} |
288 |
|
289 |
for (Set<Integer> x : newSets()) |
290 |
for (Set<Integer> y : newConcurrentSets()) { |
291 |
describe(Set.class, x, y); |
292 |
frob(x, y); |
293 |
frob(unmodifiableSet(x), y); |
294 |
frob(synchronizedSet(x), y); |
295 |
frob(x, synchronizedSet(y)); |
296 |
frob(checkedSet(x, Integer.class), y); |
297 |
frob(x, checkedSet(y, Integer.class)); |
298 |
} |
299 |
|
300 |
for (List<Integer> x : newLists()) |
301 |
for (List<Integer> y : newConcurrentLists()) { |
302 |
describe(List.class, x, y); |
303 |
frob(x, y); |
304 |
frob(unmodifiableList(x), y); |
305 |
frob(synchronizedList(x), y); |
306 |
frob(x, synchronizedList(y)); |
307 |
frob(checkedList(x, Integer.class), y); |
308 |
frob(x, checkedList(y, Integer.class)); |
309 |
} |
310 |
|
311 |
for (Queue<Integer> x : newQueues()) |
312 |
for (Queue<Integer> y : newConcurrentQueues()) { |
313 |
describe(Queue.class, x, y); |
314 |
frob(x, y); |
315 |
} |
316 |
|
317 |
for (Deque<Integer> x : newDeques()) |
318 |
for (Deque<Integer> y : newConcurrentDeques()) { |
319 |
describe(Deque.class, x, y); |
320 |
frob(asLifoQueue(x), y); |
321 |
frob(x, asLifoQueue(y)); |
322 |
} |
323 |
} |
324 |
|
325 |
//--------------------- Infrastructure --------------------------- |
326 |
static volatile int passed = 0, failed = 0; |
327 |
static void pass() {passed++;} |
328 |
static void fail() {failed++; Thread.dumpStack();} |
329 |
static void fail(String msg) {System.out.println(msg); fail();} |
330 |
static void unexpected(Throwable t) {failed++; t.printStackTrace();} |
331 |
static void check(boolean cond) {if (cond) pass(); else fail();} |
332 |
static String toString(Object x) { |
333 |
return ((x instanceof Collection) || (x instanceof Map)) ? |
334 |
x.getClass().getName() : x.toString();} |
335 |
static void equal(Object x, Object y) { |
336 |
if (x == null ? y == null : x.equals(y)) pass(); |
337 |
else fail(toString(x) + " not equal to " + toString(y));} |
338 |
static void notEqual(Object x, Object y) { |
339 |
if (x == null ? y == null : x.equals(y)) |
340 |
fail(toString(x) + " equal to " + toString(y)); |
341 |
else pass();} |
342 |
public static void main(String[] args) throws Throwable { |
343 |
try {realMain(args);} catch (Throwable t) {unexpected(t);} |
344 |
System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); |
345 |
if (failed > 0) throw new AssertionError("Some tests failed");} |
346 |
private abstract static class CheckedThread extends Thread { |
347 |
abstract void realRun() throws Throwable; |
348 |
public void run() { |
349 |
try { realRun(); } catch (Throwable t) { unexpected(t); }}} |
350 |
} |