blob: cf511101bf1511e35ed70b27a62ec9f0a68d83c5 [file] [log] [blame]
duke6e45e102007-12-01 00:00:00 +00001/*
ohairf5857212010-12-28 15:53:50 -08002 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
duke6e45e102007-12-01 00:00:00 +00003 * 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 *
ohair2283b9d2010-05-25 15:58:33 -070019 * 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.
duke6e45e102007-12-01 00:00:00 +000022 */
23/*
24 @test
25 @summary test Resource Bundle for bug 4168625
26 @build Bug4168625Class Bug4168625Getter Bug4168625Resource Bug4168625Resource3 Bug4168625Resource3_en Bug4168625Resource3_en_CA Bug4168625Resource3_en_IE Bug4168625Resource3_en_US Bug4168625Resource2_en_US Bug4168625Resource2
27 @run main/timeout=600 Bug4168625Test
naoto2071ff62010-10-22 11:32:26 -070028 @bug 4168625 6993339
duke6e45e102007-12-01 00:00:00 +000029*/
30/*
31 *
32 *
33 * (C) Copyright IBM Corp. 1999 - All Rights Reserved
34 *
duke6e45e102007-12-01 00:00:00 +000035 * The original version of this source code and documentation is
36 * copyrighted and owned by IBM. These materials are provided
37 * under terms of a License Agreement between IBM and Sun.
38 * This technology is protected by multiple US and International
39 * patents. This notice and attribution to IBM may not be removed.
40 *
41 */
42
43import java.util.*;
44import java.io.*;
45
46/**
naoto2071ff62010-10-22 11:32:26 -070047 * This test tries to correct two efficiency problems with the caching
48 * mechanism of ResourceBundle. It also allows concurrent loads
duke6e45e102007-12-01 00:00:00 +000049 * of resource bundles to be performed if the bundles are unrelated (ex. a
50 * load of a local system resource by one thread while another thread is
51 * doing a slow load over a network).
52 */
53public class Bug4168625Test extends RBTestFmwk {
54 public static void main(String[] args) throws Exception {
55 new Bug4168625Test().run(args);
56 }
57
58 /**
59 * Verify that getBundle will do something reasonable when part of the
60 * resource hierarchy is missing.
61 */
62 public void testMissingParent() throws Exception {
63 final Locale oldDefault = Locale.getDefault();
64 Locale.setDefault(new Locale("en", "US"));
65 try {
66 final Locale loc = new Locale("jf", "jf");
67 ResourceBundle bundle = ResourceBundle.getBundle("Bug4168625Resource2", loc);
68 final String s1 = bundle.getString("name");
69 if (!s1.equals("Bug4168625Resource2_en_US")) {
70 errln("getBundle did not find leaf bundle: "+bundle.getClass().getName());
71 }
72 final String s2 = bundle.getString("baseName");
73 if (!s2.equals("Bug4168625Resource2")) {
74 errln("getBundle did not set up proper inheritance chain");
75 }
76 } finally {
77 Locale.setDefault(oldDefault);
78 }
79 }
80
81 /**
82 * Previous versions of ResourceBundle have had the following
83 * caching behavior. Assume the classes
84 * Bug4168625Resource_fr_FR, Bug4168625Resource_fr,
85 * Bug4168625Resource_en_US, and Bug4168625Resource_en don't
86 * exist. The class Bug4168625Resource does. Assume the default
87 * locale is en_US.
88 * <P>
89 * <pre>
90 * getBundle("Bug4168625Resource", new Locale("fr", "FR"));
91 * -->try to load Bug4168625Resource_fr_FR
92 * -->try to load Bug4168625Resource_fr
93 * -->try to load Bug4168625Resource_en_US
94 * -->try to load Bug4168625Resource_en
95 * -->load Bug4168625Resource
96 * -->cache Bug4168625Resource as Bug4168625Resource
97 * -->cache Bug4168625Resource as Bug4168625Resource_en
98 * -->cache Bug4168625Resource as Bug4168625Resource_en_US
99 * -->return Bug4168625Resource
100 * getBundle("Bug4168625Resource", new Locale("fr", "FR"));
101 * -->try to load Bug4168625Resource_fr_FR
102 * -->try to load Bug4168625Resource_fr
103 * -->find cached Bug4168625Resource_en_US
104 * -->return Bug4168625Resource_en_US (which is realy Bug4168625Resource)
105 * </pre>
106 * <P>
107 * The second call causes two loads for Bug4168625Resource_fr_FR and
108 * Bug4168625Resource_en which have already been tried and failed. These
109 * two loads should have been cached as Bug4168625Resource by the first
110 * call.
111 *
112 * The following, more efficient behavior is desired:
113 * <P>
114 * <pre>
115 * getBundle("Bug4168625Resource", new Locale("fr", "FR"));
116 * -->try to load Bug4168625Resource_fr_FR
117 * -->try to load Bug4168625Resource_fr
118 * -->try to load Bug4168625Resource_en_US
119 * -->try to load Bug4168625Resource_en
120 * -->load Bug4168625Resource
121 * -->cache Bug4168625Resource as Bug4168625Resource
122 * -->cache Bug4168625Resource as Bug4168625Resource_en
123 * -->cache Bug4168625Resource as Bug4168625Resource_en_US
124 * -->cache Bug4168625Resource as Bug4168625Resource_fr
125 * -->cache Bug4168625Resource as Bug4168625Resource_fr_FR
126 * -->return Bug4168625Resource
127 * getBundle("Bug4168625Resource", new Locale("fr", "FR"));
128 * -->find cached Bug4168625Resource_fr_FR
129 * -->return Bug4168625Resource_en_US (which is realy Bug4168625Resource)
130 * </pre>
131 * <P>
132 *
133 */
134 public void testCacheFailures() throws Exception {
135 checkResourceLoading("Bug4168625Resource", new Locale("fr", "FR"));
136 }
137
138 /**
139 * Previous versions of ResourceBundle have had the following
140 * caching behavior. Assume the current locale is locale is en_US.
141 * The classes Bug4168625Resource_en_US, and Bug4168625Resource_en don't
142 * exist. The class Bug4168625Resource does.
143 * <P>
144 * <pre>
145 * getBundle("Bug4168625Resource", new Locale("en", "US"));
146 * -->try to load Bug4168625Resource_en_US
147 * -->try to load Bug4168625Resource_en
148 * -->try to load Bug4168625Resource_en_US
149 * -->try to load Bug4168625Resource_en
150 * -->load Bug4168625Resource
151 * -->cache Bug4168625Resource as Bug4168625Resource
152 * -->cache Bug4168625Resource as Bug4168625Resource_en
153 * -->cache Bug4168625Resource as Bug4168625Resource_en_US
154 * -->return Bug4168625Resource
155 * </pre>
156 * <P>
157 * The redundant loads of Bug4168625Resource_en_US and Bug4168625Resource_en
158 * should not occur. The desired behavior is as follows:
159 * <P>
160 * <pre>
161 * getBundle("Bug4168625Resource", new Locale("en", "US"));
162 * -->try to load Bug4168625Resource_en_US
163 * -->try to load Bug4168625Resource_en
164 * -->load Bug4168625Resource
165 * -->cache Bug4168625Resource as Bug4168625Resource
166 * -->cache Bug4168625Resource as Bug4168625Resource_en
167 * -->cache Bug4168625Resource as Bug4168625Resource_en_US
168 * -->return Bug4168625Resource
169 * </pre>
170 * <P>
171 */
172 public void testRedundantLoads() throws Exception {
173 checkResourceLoading("Bug4168625Resource", Locale.getDefault());
174 }
175
176 /**
177 * Ensure that resources are only loaded once and are cached correctly
178 */
179 private void checkResourceLoading(String resName, Locale l) throws Exception {
180 final Loader loader = new Loader( new String[] { "Bug4168625Class" }, new String[] { "Bug4168625Resource3_en_US", "Bug4168625Resource3_en_CA" });
181 final Class c = loader.loadClass("Bug4168625Class");
182 Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
183 final String resClassName;
184 if (l.toString().length() > 0) {
185 resClassName = resName+"_"+l;
186 } else {
187 resClassName = resName;
188 }
189
190 Object bundle = test.getResourceBundle(resName, l);
191 loader.logClasses("Initial lookup of "+resClassName+" generated the following loads:");
192
193 final Vector lastLoad = new Vector(loader.loadedClasses.size());
194 boolean dups = false;
195 for (int i = loader.loadedClasses.size() - 1; i >= 0 ; i--) {
196 final Object item = loader.loadedClasses.elementAt(i);
197 loader.loadedClasses.removeElementAt(i);
198 if (loader.loadedClasses.contains(item)) {
199 logln("Resource loaded more than once: "+item);
200 dups = true;
201 } else {
202 lastLoad.addElement(item);
203 }
204 }
205 if (dups) {
206 errln("ResourceBundle loaded some classes multiple times");
207 }
208
209 loader.loadedClasses.removeAllElements();
210 bundle = test.getResourceBundle(resName, l);
211 loader.logClasses("Second lookup of "+resClassName+" generated the following loads:");
212
213 dups = false;
214 for (int i = 0; i < loader.loadedClasses.size(); i++) {
215 Object item = loader.loadedClasses.elementAt(i);
216 if (lastLoad.contains(item)) {
217 logln("ResourceBundle did not cache "+item+" correctly");
218 dups = true;
219 }
220 }
221 if (dups) {
222 errln("Resource bundle not caching some classes properly");
223 }
224 }
225
duke6e45e102007-12-01 00:00:00 +0000226 private class ConcurrentLoadingThread extends Thread {
227 private Loader loader;
228 public Object bundle;
229 private Bug4168625Getter test;
230 private Locale locale;
231 private String resourceName = "Bug4168625Resource3";
232 public ConcurrentLoadingThread(Loader loader, Bug4168625Getter test, Locale l, String resourceName) {
233 this.loader = loader;
234 this.test = test;
235 this.locale = l;
236 this.resourceName = resourceName;
237 }
238 public ConcurrentLoadingThread(Loader loader, Bug4168625Getter test, Locale l) {
239 this.loader = loader;
240 this.test = test;
241 this.locale = l;
242 }
243 public void run() {
244 try {
245 logln(">>"+threadName()+">run");
246 bundle = test.getResourceBundle(resourceName, locale);
247 } catch (Exception e) {
248 errln("TEST CAUGHT UNEXPECTED EXCEPTION: "+e);
249 } finally {
250 logln("<<"+threadName()+"<run");
251 }
252 }
253 public synchronized void waitUntilPinged() {
254 logln(">>"+threadName()+">waitUntilPinged");
255 loader.notifyEveryone();
256 try {
257 wait(30000); //wait 30 seconds max.
258 } catch (InterruptedException e) {
259 logln("Test deadlocked.");
260 }
261 logln("<<"+threadName()+"<waitUntilPinged");
262 }
263 public synchronized void ping() {
264 logln(">>"+threadName()+">ping "+threadName(this));
265 notifyAll();
266 logln("<<"+threadName()+"<ping "+threadName(this));
267 }
268 };
269
270 /**
271 * This test ensures that multiple resources can be loading at the same
272 * time as long as they don't depend on each other in some way.
273 */
naoto2071ff62010-10-22 11:32:26 -0700274 public void testConcurrentLoading() throws Exception {
duke6e45e102007-12-01 00:00:00 +0000275 final Loader loader = new Loader( new String[] { "Bug4168625Class" }, new String[] { "Bug4168625Resource3_en_US", "Bug4168625Resource3_en_CA" });
276 final Class c = loader.loadClass("Bug4168625Class");
277 final Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
278
279 ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, new Locale("en", "CA"));
280 ConcurrentLoadingThread thread2 = new ConcurrentLoadingThread(loader, test, new Locale("en", "IE"));
281
282 thread1.start(); //start thread 1
283 loader.waitForNotify(1); //wait for thread1 to do getBundle & block in loader
284 thread2.start(); //start second thread
naoto14b6d412011-04-06 10:53:13 -0700285 thread2.join(); //wait until thread2 terminates.
duke6e45e102007-12-01 00:00:00 +0000286
287 //Thread1 should be blocked inside getBundle at the class loader
288 //Thread2 should have completed its getBundle call and terminated
289 if (!thread1.isAlive() || thread2.isAlive()) {
290 errln("ResourceBundle.getBundle not allowing legal concurrent loads");
291 }
292
293 thread1.ping(); //continue thread1
294 thread1.join();
duke6e45e102007-12-01 00:00:00 +0000295 }
296
297 /**
298 * This test ensures that a resource loads correctly (with all its parents)
299 * when memory is very low (ex. the cache gets purged during a load).
300 */
301 public void testLowMemoryLoad() throws Exception {
302 final String[] classToLoad = { "Bug4168625Class" };
303 final String[] classToWait = { "Bug4168625Resource3_en_US","Bug4168625Resource3_en","Bug4168625Resource3" };
304 final Loader loader = new Loader(classToLoad, classToWait);
305 final Class c = loader.loadClass("Bug4168625Class");
306 final Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
307 causeResourceBundleCacheFlush();
308
309 ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, new Locale("en", "US"));
310 thread1.start(); //start thread 1
311 loader.waitForNotify(1); //wait for thread1 to do getBundle(en_US) & block in loader
312 causeResourceBundleCacheFlush(); //cause a cache flush
313 thread1.ping(); //kick thread 1
314 loader.waitForNotify(2); //wait for thread1 to do getBundle(en) & block in loader
315 causeResourceBundleCacheFlush(); //cause a cache flush
316 thread1.ping(); //kick thread 1
317 loader.waitForNotify(3); //wait for thread1 to do getBundle(en) & block in loader
318 causeResourceBundleCacheFlush(); //cause a cache flush
319 thread1.ping(); //kick thread 1
naoto14b6d412011-04-06 10:53:13 -0700320 thread1.join(); //wait until thread1 terminates
duke6e45e102007-12-01 00:00:00 +0000321
322 ResourceBundle bundle = (ResourceBundle)thread1.bundle;
323 String s1 = bundle.getString("Bug4168625Resource3_en_US");
324 String s2 = bundle.getString("Bug4168625Resource3_en");
325 String s3 = bundle.getString("Bug4168625Resource3");
326 if ((s1 == null) || (s2 == null) || (s3 == null)) {
327 errln("Bundle not constructed correctly. The parent chain is incorrect.");
328 }
329 }
330
331 /**
332 * A simple class loader that loads classes from the current
333 * working directory. The loader will block the current thread
334 * of execution before it returns when it tries to load
335 * the class "Bug4168625Resource3_en_US".
336 */
337 private static final String CLASS_PREFIX = "";
338 private static final String CLASS_SUFFIX = ".class";
339
340 private static final class SimpleLoader extends ClassLoader {
341 private boolean network = false;
342
343 public SimpleLoader() {
mchung019ce7d2010-06-15 20:34:49 -0700344 super(SimpleLoader.class.getClassLoader());
duke6e45e102007-12-01 00:00:00 +0000345 this.network = false;
346 }
347 public SimpleLoader(boolean simulateNetworkLoad) {
mchung019ce7d2010-06-15 20:34:49 -0700348 super(SimpleLoader.class.getClassLoader());
duke6e45e102007-12-01 00:00:00 +0000349 this.network = simulateNetworkLoad;
350 }
351 public Class loadClass(final String className, final boolean resolveIt)
352 throws ClassNotFoundException {
353 Class result;
354 synchronized (this) {
355 result = findLoadedClass(className);
356 if (result == null) {
357 if (network) {
358 try {
359 Thread.sleep(100);
360 } catch (java.lang.InterruptedException e) {
361 }
362 }
mchung019ce7d2010-06-15 20:34:49 -0700363 result = getParent().loadClass(className);
duke6e45e102007-12-01 00:00:00 +0000364 if ((result != null) && resolveIt) {
365 resolveClass(result);
366 }
367 }
368 }
369 return result;
370 }
371 }
372
373 private final class Loader extends ClassLoader {
374 public final Vector loadedClasses = new Vector();
375 private String[] classesToLoad;
376 private String[] classesToWaitFor;
377
378 public Loader() {
mchung019ce7d2010-06-15 20:34:49 -0700379 super(Loader.class.getClassLoader());
duke6e45e102007-12-01 00:00:00 +0000380 classesToLoad = new String[0];
381 classesToWaitFor = new String[0];
382 }
383
384 public Loader(final String[] classesToLoadIn, final String[] classesToWaitForIn) {
mchung019ce7d2010-06-15 20:34:49 -0700385 super(Loader.class.getClassLoader());
duke6e45e102007-12-01 00:00:00 +0000386 classesToLoad = classesToLoadIn;
387 classesToWaitFor = classesToWaitForIn;
388 }
389
390 /**
391 * Load a class. Files we can load take preference over ones the system
392 * can load.
393 */
394 private byte[] getClassData(final String className) {
395 boolean shouldLoad = false;
396 for (int i = classesToLoad.length-1; i >= 0; --i) {
397 if (className.equals(classesToLoad[i])) {
398 shouldLoad = true;
399 break;
400 }
401 }
402
403 if (shouldLoad) {
404 final String name = CLASS_PREFIX+className+CLASS_SUFFIX;
405 try {
406 final InputStream fi = this.getClass().getClassLoader().getResourceAsStream(name);
407 final byte[] result = new byte[fi.available()];
408 fi.read(result);
409 return result;
410 } catch (Exception e) {
411 logln("Error loading test class: "+name);
412 logln(e.toString());
413 return null;
414 }
415 } else {
416 return null;
417 }
418 }
419
420 /**
421 * Load a class. Files we can load take preference over ones the system
422 * can load.
423 */
424 public Class loadClass(final String className, final boolean resolveIt)
425 throws ClassNotFoundException {
426 Class result;
427 synchronized (this) {
428 logln(">>"+threadName()+">load "+className);
429 loadedClasses.addElement(className);
430
431 result = findLoadedClass(className);
432 if (result == null) {
433 final byte[] classData = getClassData(className);
434 if (classData == null) {
435 //we don't have a local copy of this one
436 logln("Loading system class: "+className);
437 result = loadFromSystem(className);
438 } else {
439 result = defineClass(classData, 0, classData.length);
440 if (result == null) {
441 //there was an error defining the class
442 result = loadFromSystem(className);
443 }
444 }
445 if ((result != null) && resolveIt) {
446 resolveClass(result);
447 }
448 }
449 }
450 for (int i = classesToWaitFor.length-1; i >= 0; --i) {
451 if (className.equals(classesToWaitFor[i])) {
452 rendezvous();
453 break;
454 }
455 }
456 logln("<<"+threadName()+"<load "+className);
457 return result;
458 }
459
460 /**
mchung019ce7d2010-06-15 20:34:49 -0700461 * Delegate loading to its parent class loader that loads the test classes.
462 * In othervm mode, the parent class loader is the system class loader;
463 * in samevm mode, the parent class loader is the jtreg URLClassLoader.
duke6e45e102007-12-01 00:00:00 +0000464 */
465 private Class loadFromSystem(String className) throws ClassNotFoundException {
mchung019ce7d2010-06-15 20:34:49 -0700466 return getParent().loadClass(className);
duke6e45e102007-12-01 00:00:00 +0000467 }
468
469 public void logClasses(String title) {
470 logln(title);
471 for (int i = 0; i < loadedClasses.size(); i++) {
472 logln(" "+loadedClasses.elementAt(i));
473 }
474 logln("");
475 }
476
477 public int notifyCount = 0;
478 public int waitForNotify(int count) {
479 return waitForNotify(count, 0);
480 }
481 public synchronized int waitForNotify(int count, long time) {
482 logln(">>"+threadName()+">waitForNotify");
483 if (count > notifyCount) {
484 try {
485 wait(time);
486 } catch (InterruptedException e) {
487 }
488 } else {
489 logln(" count("+count+") > notifyCount("+notifyCount+")");
490 }
491 logln("<<"+threadName()+"<waitForNotify");
492 return notifyCount;
493 }
494 private synchronized void notifyEveryone() {
495 logln(">>"+threadName()+">notifyEveryone");
496 notifyCount++;
497 notifyAll();
498 logln("<<"+threadName()+"<notifyEveryone");
499 }
500 private void rendezvous() {
501 final Thread current = Thread.currentThread();
502 if (current instanceof ConcurrentLoadingThread) {
503 ((ConcurrentLoadingThread)current).waitUntilPinged();
504 }
505 }
506 }
507
508 private static String threadName() {
509 return threadName(Thread.currentThread());
510 }
511
512 private static String threadName(Thread t) {
513 String temp = t.toString();
514 int ndx = temp.indexOf("Thread[");
515 temp = temp.substring(ndx + "Thread[".length());
516 ndx = temp.indexOf(',');
517 temp = temp.substring(0, ndx);
518 return temp;
519 }
520
521 /** Fill memory to force all SoftReferences to be GCed */
522 private void causeResourceBundleCacheFlush() {
523 logln("Filling memory...");
524 int allocationSize = 1024;
525 Vector memoryHog = new Vector();
526 try {
527 while (true) {
528 memoryHog.addElement(new byte[allocationSize]);
529 allocationSize *= 2;
530 }
531 } catch (Throwable e) {
532 logln("Caught "+e+" filling memory");
533 } finally{
534 memoryHog = null;
535 System.gc();
536 }
537 logln("last allocation size: " + allocationSize);
538 }
539
540 /**
541 * NOTE: this problem is not externally testable and can only be
542 * verified through code inspection unless special code to force
543 * a task switch is inserted into ResourceBundle.
544 * The class Bug4168625Resource_sp exists. It's parent bundle
545 * (Bug4168625Resource) contains a resource string with the tag
546 * "language" but Bug4168625Resource_sp does not.
547 * Assume two threads are executing, ThreadA and ThreadB and they both
548 * load a resource Bug4168625Resource with from sp locale.
549 * ResourceBundle.getBundle adds a bundle to the bundle cache (in
550 * findBundle) before it sets the bundle's parent (in getBundle after
551 * returning from findBundle).
552 * <P>
553 * <pre>
554 * ThreadA.getBundle("Bug4168625Resource", new Locale("sp"));
555 * A-->load Bug4168625Resource_sp
556 * A-->find cached Bug4168625Resource
557 * A-->cache Bug4168625Resource_sp as Bug4168625Resource_sp
558 * ThreadB.getBundle("Bug4168625Resource", new Locale("sp"));
559 * B-->find cached Bug4168625Resource_sp
560 * B-->return Bug4168625Resource_sp
561 * ThreadB.bundle.getString("language");
562 * B-->try to find "language" in Bug4168625Resource_sp
563 * B-->Bug4168625Resource_sp does not have a parent, so return null;
564 * ThreadB.System.out.println("Some unknown country");
565 * A-->set parent of Bug4168625Resource_sp to Bug4168625Resource
566 * A-->return Bug4168625Resource_sp (the same bundle ThreadB got)
567 * ThreadA.bundle.getString("language");
568 * A-->try to find "language" in Bug4168625Resource_sp
569 * A-->try to find "language" in Bug4168625Resource (parent of Bug4168625Resource_sp)
570 * A-->return the string
571 * ThreadA.System.out.println("Langauge = "+country);
572 * ThreadB.bundle.getString("language");
573 * B-->try to find "language" in Bug4168625Resource_sp
574 * B-->try to find "language" in Bug4168625Resource (parent of Bug4168625Resource_sp)
575 * B-->return the string
576 * ThreadB.System.out.println("Langauge = "+country);
577 * </pre>
578 * <P>
579 * Note that the first call to getString() by ThreadB returns null, but the second
580 * returns a value. Thus to ThreadB, the bundle appears to change. ThreadA gets
581 * the expected results right away.
582 */
583}