2323
2424/*
2525 * @test
26- * @bug 6399443 8302899
26+ * @bug 6399443 8302899 8362123
2727 * @summary Test that Executors.newSingleThreadExecutor wraps an ExecutorService that
2828 * automatically shuts down and terminates when the wrapper is GC'ed
29+ * @library /test/lib/
2930 * @modules java.base/java.util.concurrent:+open
3031 * @run junit AutoShutdown
3132 */
3233
34+ import java .lang .ref .PhantomReference ;
35+ import java .lang .ref .Reference ;
36+ import java .lang .ref .ReferenceQueue ;
3337import java .lang .reflect .Field ;
3438import java .time .Duration ;
3539import java .util .concurrent .ExecutorService ;
3640import java .util .concurrent .Executors ;
41+ import java .util .concurrent .ThreadFactory ;
3742import java .util .concurrent .TimeUnit ;
3843import java .util .concurrent .atomic .AtomicInteger ;
44+ import java .util .function .Consumer ;
3945import java .util .function .Supplier ;
4046import java .util .stream .Stream ;
4147import java .util .stream .IntStream ;
4248
49+ import jdk .test .lib .Utils ;
50+ import jdk .test .lib .util .ForceGC ;
51+
4352import org .junit .jupiter .api .Test ;
4453import org .junit .jupiter .params .ParameterizedTest ;
4554import org .junit .jupiter .params .provider .MethodSource ;
@@ -61,6 +70,13 @@ private static Stream<Arguments> executorAndQueuedTaskCounts() {
6170 .mapToObj (i -> Arguments .of (s , i )));
6271 }
6372
73+ private static Stream <Arguments > shutdownMethods () {
74+ return Stream .<Consumer <ExecutorService >>of (
75+ e -> e .shutdown (),
76+ e -> e .shutdownNow ()
77+ ).map (Arguments ::of );
78+ }
79+
6480 /**
6581 * SingleThreadExecutor with no worker threads.
6682 */
@@ -110,6 +126,28 @@ void testActiveWorker(Supplier<ExecutorService> supplier,int queuedTaskCount) th
110126 assertEquals (ntasks , completedTaskCount .get ());
111127 }
112128
129+ @ ParameterizedTest
130+ @ MethodSource ("shutdownMethods" )
131+ void testShutdownUnlinksCleaner (Consumer <ExecutorService > shutdown ) throws Exception {
132+ ClassLoader classLoader =
133+ Utils .getTestClassPathURLClassLoader (ClassLoader .getPlatformClassLoader ());
134+
135+ ReferenceQueue <?> queue = new ReferenceQueue <>();
136+ Reference <?> reference = new PhantomReference (classLoader , queue );
137+ try {
138+ Class <?> isolatedClass = classLoader .loadClass ("AutoShutdown$IsolatedClass" );
139+ assertSame (isolatedClass .getClassLoader (), classLoader );
140+ isolatedClass .getDeclaredMethod ("shutdown" , Consumer .class ).invoke (null , shutdown );
141+
142+ isolatedClass = null ;
143+ classLoader = null ;
144+
145+ assertTrue (ForceGC .wait (() -> queue .poll () != null ));
146+ } finally {
147+ Reference .reachabilityFence (reference );
148+ }
149+ }
150+
113151 /**
114152 * Returns the delegate for the given ExecutorService. The given ExecutorService
115153 * must be a Executors$DelegatedExecutorService.
@@ -132,5 +170,22 @@ private void gcAndAwaitTermination(ExecutorService executor) throws Exception {
132170 terminated = executor .awaitTermination (100 , TimeUnit .MILLISECONDS );
133171 }
134172 }
135- }
136173
174+ public static class IsolatedClass {
175+
176+ private static final ExecutorService executor =
177+ Executors .newSingleThreadExecutor (new IsolatedThreadFactory ());
178+
179+ public static void shutdown (Consumer <ExecutorService > shutdown ) {
180+ shutdown .accept (executor );
181+ }
182+ }
183+
184+ public static class IsolatedThreadFactory implements ThreadFactory {
185+
186+ @ Override
187+ public Thread newThread (Runnable r ) {
188+ return new Thread (r );
189+ }
190+ }
191+ }
0 commit comments