Skip to content

Commit fcd7fb7

Browse files
author
Andrew Lu
committedJun 17, 2024
8331142: Add test for number of loader threads in BasicDirectoryModel
8331495: Limit BasicDirectoryModel/LoaderThreadCount.java to Windows only Reviewed-by: mdoerr Backport-of: b2fb5ea13ba5087411410519213fc953ecc44618
1 parent 686d69d commit fcd7fb7

File tree

1 file changed

+277
-0
lines changed

1 file changed

+277
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/*
2+
* Copyright (c) 2024, 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+
import java.io.IOException;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.nio.file.Paths;
28+
import java.util.ArrayList;
29+
import java.util.Arrays;
30+
import java.util.List;
31+
import java.util.Objects;
32+
import java.util.concurrent.BrokenBarrierException;
33+
import java.util.concurrent.CyclicBarrier;
34+
import java.util.concurrent.atomic.AtomicReference;
35+
import java.util.stream.LongStream;
36+
import java.util.stream.Stream;
37+
38+
import javax.swing.JFileChooser;
39+
40+
/*
41+
* @test
42+
* @bug 8325179
43+
* @requires os.family == "windows"
44+
* @summary Verifies there's only one BasicDirectoryModel.FilesLoader thread
45+
* at any given moment
46+
* @run main/othervm -Djava.awt.headless=true LoaderThreadCount
47+
*/
48+
public final class LoaderThreadCount extends ThreadGroup {
49+
/** Initial number of files. */
50+
private static final long NUMBER_OF_FILES = 500;
51+
52+
/**
53+
* Number of threads running {@code fileChooser.rescanCurrentDirectory()}.
54+
*/
55+
private static final int NUMBER_OF_THREADS = 5;
56+
57+
/** Number of snapshots with live threads. */
58+
private static final int SNAPSHOTS = 20;
59+
60+
/** The barrier to synchronise scanner threads and capturing live threads. */
61+
private static final CyclicBarrier start = new CyclicBarrier(NUMBER_OF_THREADS + 1);
62+
63+
/** List of scanner threads. */
64+
private static final List<Thread> threads = new ArrayList<>(NUMBER_OF_THREADS);
65+
66+
/**
67+
* Stores an exception caught by any of the threads.
68+
* If more exceptions are caught, they're added as suppressed exceptions.
69+
*/
70+
private static final AtomicReference<Throwable> exception =
71+
new AtomicReference<>();
72+
73+
/**
74+
* Stores an {@code IOException} thrown while removing the files.
75+
*/
76+
private static final AtomicReference<IOException> ioException =
77+
new AtomicReference<>();
78+
79+
80+
public static void main(String[] args) throws Throwable {
81+
try {
82+
// Start the test in its own thread group to catch and handle
83+
// all thrown exceptions, in particular in
84+
// BasicDirectoryModel.FilesLoader which is created by Swing.
85+
ThreadGroup threadGroup = new LoaderThreadCount();
86+
Thread runner = new Thread(threadGroup,
87+
LoaderThreadCount::wrapper,
88+
"Test Runner");
89+
runner.start();
90+
runner.join();
91+
} catch (Throwable throwable) {
92+
handleException(throwable);
93+
}
94+
95+
if (ioException.get() != null) {
96+
System.err.println("An error occurred while removing files:");
97+
ioException.get().printStackTrace();
98+
}
99+
100+
if (exception.get() != null) {
101+
throw exception.get();
102+
}
103+
}
104+
105+
private static void wrapper() {
106+
final long timeStart = System.currentTimeMillis();
107+
try {
108+
runTest(timeStart);
109+
} catch (Throwable throwable) {
110+
handleException(throwable);
111+
} finally {
112+
System.out.printf("Duration: %,d\n",
113+
(System.currentTimeMillis() - timeStart));
114+
}
115+
}
116+
117+
private static void runTest(final long timeStart) throws Throwable {
118+
final Path temp = Files.createDirectory(Paths.get("fileChooser-concurrency-" + timeStart));
119+
120+
try {
121+
createFiles(temp);
122+
123+
final JFileChooser fc = new JFileChooser(temp.toFile());
124+
125+
threads.addAll(Stream.generate(() -> new Thread(new Scanner(fc)))
126+
.limit(NUMBER_OF_THREADS)
127+
.toList());
128+
threads.forEach(Thread::start);
129+
130+
// Create snapshots of live threads
131+
List<Thread[]> threadsCapture =
132+
Stream.generate(LoaderThreadCount::getThreadSnapshot)
133+
.limit(SNAPSHOTS)
134+
.toList();
135+
136+
threads.forEach(Thread::interrupt);
137+
138+
List<Long> loaderCount =
139+
threadsCapture.stream()
140+
.map(ta -> Arrays.stream(ta)
141+
.filter(Objects::nonNull)
142+
.map(Thread::getName)
143+
.filter(tn -> tn.startsWith("Basic L&F File Loading Thread"))
144+
.count())
145+
.filter(c -> c > 0)
146+
.toList();
147+
148+
if (loaderCount.isEmpty()) {
149+
throw new RuntimeException("Invalid results: no loader threads detected");
150+
}
151+
152+
System.out.println("Number of snapshots: " + loaderCount.size());
153+
154+
long ones = loaderCount.stream()
155+
.filter(n -> n == 1)
156+
.count();
157+
long twos = loaderCount.stream()
158+
.filter(n -> n == 2)
159+
.count();
160+
long count = loaderCount.stream()
161+
.filter(n -> n > 2)
162+
.count();
163+
System.out.println("Number of snapshots where number of loader threads:");
164+
System.out.println(" = 1: " + ones);
165+
System.out.println(" = 2: " + twos);
166+
System.out.println(" > 2: " + count);
167+
if (count > 0) {
168+
throw new RuntimeException("Detected " + count + " snapshots "
169+
+ "with several loading threads");
170+
}
171+
} catch (Throwable e) {
172+
threads.forEach(Thread::interrupt);
173+
throw e;
174+
} finally {
175+
deleteFiles(temp);
176+
deleteFile(temp);
177+
}
178+
}
179+
180+
private static Thread[] getThreadSnapshot() {
181+
try {
182+
start.await();
183+
// Allow for the scanner threads to initiate re-scanning
184+
Thread.sleep(10);
185+
186+
Thread[] array = new Thread[Thread.activeCount()];
187+
Thread.currentThread()
188+
.getThreadGroup()
189+
.enumerate(array, false);
190+
191+
// Additional delay between captures
192+
Thread.sleep(500);
193+
194+
return array;
195+
} catch (InterruptedException | BrokenBarrierException e) {
196+
handleException(e);
197+
throw new RuntimeException("getThreadSnapshot is interrupted");
198+
}
199+
}
200+
201+
202+
private LoaderThreadCount() {
203+
super("bdmConcurrency");
204+
}
205+
206+
@Override
207+
public void uncaughtException(Thread t, Throwable e) {
208+
handleException(t, e);
209+
}
210+
211+
private static void handleException(Throwable throwable) {
212+
handleException(Thread.currentThread(), throwable);
213+
}
214+
215+
private static void handleException(final Thread thread,
216+
final Throwable throwable) {
217+
System.err.println("Exception in " + thread.getName() + ": "
218+
+ throwable.getClass()
219+
+ (throwable.getMessage() != null
220+
? ": " + throwable.getMessage()
221+
: ""));
222+
if (!exception.compareAndSet(null, throwable)) {
223+
exception.get().addSuppressed(throwable);
224+
}
225+
threads.stream()
226+
.filter(t -> t != thread)
227+
.forEach(Thread::interrupt);
228+
}
229+
230+
231+
private record Scanner(JFileChooser fileChooser)
232+
implements Runnable {
233+
234+
@Override
235+
public void run() {
236+
try {
237+
do {
238+
start.await();
239+
fileChooser.rescanCurrentDirectory();
240+
} while (!Thread.interrupted());
241+
} catch (InterruptedException | BrokenBarrierException e) {
242+
// Just exit the loop
243+
}
244+
}
245+
}
246+
247+
private static void createFiles(final Path parent) {
248+
LongStream.range(0, LoaderThreadCount.NUMBER_OF_FILES)
249+
.mapToObj(n -> parent.resolve(n + ".file"))
250+
.forEach(LoaderThreadCount::createFile);
251+
}
252+
253+
private static void createFile(final Path file) {
254+
try {
255+
Files.createFile(file);
256+
} catch (IOException e) {
257+
throw new RuntimeException(e);
258+
}
259+
}
260+
261+
private static void deleteFiles(final Path parent) throws IOException {
262+
try (var stream = Files.walk(parent)) {
263+
stream.filter(p -> p != parent)
264+
.forEach(LoaderThreadCount::deleteFile);
265+
}
266+
}
267+
268+
private static void deleteFile(final Path file) {
269+
try {
270+
Files.delete(file);
271+
} catch (IOException e) {
272+
if (!ioException.compareAndSet(null, e)) {
273+
ioException.get().addSuppressed(e);
274+
}
275+
}
276+
}
277+
}

1 commit comments

Comments
 (1)

openjdk-notifier[bot] commented on Jun 17, 2024

@openjdk-notifier[bot]
Please sign in to comment.