LogManagerAppContextDeadlock.java revision 12745:f068a4ffddd2
1/*
2 * Copyright (c) 2013, 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
24import java.lang.management.ManagementFactory;
25import java.lang.management.ThreadInfo;
26import java.security.CodeSource;
27import java.security.Permission;
28import java.security.PermissionCollection;
29import java.security.Permissions;
30import java.security.Policy;
31import java.security.ProtectionDomain;
32import java.util.Enumeration;
33import java.util.concurrent.Semaphore;
34import java.util.concurrent.atomic.AtomicBoolean;
35import java.util.concurrent.atomic.AtomicInteger;
36import java.util.logging.LogManager;
37import java.util.logging.Logger;
38
39/**
40 * @test
41 * @bug 8065991
42 * @summary check that when LogManager is initialized, a deadlock similar
43 *          to that described in 8065709 will not occur.
44 * @modules java.base/sun.misc
45 * @run main/othervm LogManagerAppContextDeadlock UNSECURE
46 * @run main/othervm LogManagerAppContextDeadlock SECURE
47 *
48 * @author danielfuchs
49 */
50public class LogManagerAppContextDeadlock {
51
52    public static final Semaphore sem = new Semaphore(0);
53    public static final Semaphore sem2 = new Semaphore(0);
54    public static final Semaphore sem3 = new Semaphore(-2);
55    public static volatile boolean goOn = true;
56    public static volatile Exception thrown;
57
58    // Emulate EventQueue
59    static class FakeEventQueue {
60        static final Logger logger = Logger.getLogger("foo");
61    }
62
63    // Emulate AppContext
64    static class FakeAppContext {
65
66        static final AtomicInteger numAppContexts = new AtomicInteger(0);
67        static final class FakeAppContextLock {}
68        static final FakeAppContextLock lock = new FakeAppContextLock();
69        static volatile FakeAppContext appContext;
70
71        final FakeEventQueue queue;
72        FakeAppContext() {
73            appContext = this;
74            numAppContexts.incrementAndGet();
75            // release sem2 to let Thread t2 call Logger.getLogger().
76            sem2.release();
77            try {
78                // Wait until we JavaAWTAccess is called by LogManager.
79                // Thread 2 will call Logger.getLogger() which will
80                // trigger a call to JavaAWTAccess - which will release
81                // sem, thus ensuring that Thread #2 is where we want it.
82                sem.acquire();
83                System.out.println("Sem acquired: Thread #2 has called JavaAWTAccess");
84            } catch(InterruptedException x) {
85                Thread.interrupted();
86            }
87            queue = new FakeEventQueue();
88        }
89
90        static FakeAppContext getAppContext() {
91            synchronized (lock) {
92                if (numAppContexts.get() == 0) {
93                    return new FakeAppContext();
94                }
95                return appContext;
96            }
97        }
98
99        static {
100            sun.misc.SharedSecrets.setJavaAWTAccess(new sun.misc.JavaAWTAccess() {
101                @Override
102                public Object getAppletContext() {
103                    if (numAppContexts.get() == 0) return null;
104                    // We are in JavaAWTAccess, we can release sem and let
105                    // FakeAppContext constructor proceeed.
106                    System.out.println("Releasing Sem");
107                    sem.release();
108                    return getAppContext();
109                }
110
111            });
112        }
113
114    }
115
116
117    // Test with or without a security manager
118    public static enum TestCase {
119        UNSECURE, SECURE;
120        public void run() throws Exception {
121            System.out.println("Running test case: " + name());
122            Configure.setUp(this);
123            test(this);
124        }
125    }
126
127    public static void test(TestCase test) throws Exception {
128        Thread t1 = new Thread() {
129            @Override
130            public void run() {
131                sem3.release();
132                System.out.println("FakeAppContext.getAppContext()");
133                FakeAppContext.getAppContext();
134                System.out.println("Done: FakeAppContext.getAppContext()");
135            }
136        };
137        t1.setDaemon(true);
138        t1.start();
139        Thread t2 = new Thread() {
140            public void run() {
141                sem3.release();
142                try {
143                    // Wait until Thread1 is in FakeAppContext constructor
144                    sem2.acquire();
145                    System.out.println("Sem2 acquired: Thread #1 will be waiting to acquire Sem");
146                } catch (InterruptedException ie) {
147                    Thread.interrupted();
148                }
149                System.out.println("Logger.getLogger(name).info(name)");
150                Logger.getLogger(test.name());//.info(name);
151                System.out.println("Done: Logger.getLogger(name).info(name)");
152            }
153        };
154        t2.setDaemon(true);
155        t2.start();
156        System.out.println("Should exit now...");
157        Thread detector = new DeadlockDetector();
158        detector.start();
159
160        // Wait for the 3 threads to start
161        sem3.acquire();
162
163        // Now wait for t1 & t2 to finish, or for a deadlock to be detected.
164        while (goOn && (t1.isAlive() || t2.isAlive())) {
165            if (t2.isAlive()) t2.join(1000);
166            if (test == TestCase.UNSECURE && System.getSecurityManager() == null) {
167                // if there's no security manager, AppContext.getAppContext() is
168                // not called -  so Thread t2 will not end up calling
169                // sem.release(). In that case we must release the semaphore here
170                // so that t1 can proceed.
171                if (LogManager.getLogManager().getLogger(TestCase.UNSECURE.name()) != null) {
172                    // means Thread t2 has created the logger
173                    sem.release();
174                }
175            }
176            if (t1.isAlive()) t1.join(1000);
177        }
178        if (thrown != null) {
179            throw thrown;
180        }
181    }
182
183    // Thrown by the deadlock detector
184    static final class DeadlockException extends RuntimeException {
185        public DeadlockException(String message) {
186            super(message);
187        }
188        @Override
189        public void printStackTrace() {
190        }
191    }
192
193    public static void main(String[] args) throws Exception {
194
195        if (args.length == 0) {
196            args = new String[] { "SECURE" };
197        }
198
199        // If we don't initialize LogManager here, there will be
200        // a deadlock.
201        // See <https://bugs.openjdk.java.net/browse/JDK-8065709?focusedCommentId=13582038&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13582038>
202        // for more details.
203        Logger.getLogger("main").info("starting...");
204        try {
205            TestCase.valueOf(args[0]).run();
206            System.out.println("Test "+args[0]+" Passed");
207        } catch(Throwable t) {
208            System.err.println("Test " + args[0] +" failed: " + t);
209            t.printStackTrace();
210        }
211    }
212
213    // Called by the deadlock detector when a deadlock is found.
214    static void fail(Exception x) {
215        x.printStackTrace();
216        if (thrown == null) {
217            thrown = x;
218        }
219        goOn = false;
220    }
221
222    // A thread that detect deadlocks.
223    static final class DeadlockDetector extends Thread {
224
225        public DeadlockDetector() {
226            this.setDaemon(true);
227        }
228
229        @Override
230        public void run() {
231            sem3.release();
232            Configure.doPrivileged(this::loop);
233        }
234        public void loop() {
235            while(goOn) {
236                try {
237                    long[] ids = ManagementFactory.getThreadMXBean().findDeadlockedThreads();
238                    ids = ids == null ? new long[0] : ids;
239                    if (ids.length == 1) {
240                        throw new RuntimeException("Found 1 deadlocked thread: "+ids[0]);
241                    } else if (ids.length > 0) {
242                        ThreadInfo[] infos = ManagementFactory.getThreadMXBean().getThreadInfo(ids, Integer.MAX_VALUE);
243                        System.err.println("Found "+ids.length+" deadlocked threads: ");
244                        for (ThreadInfo inf : infos) {
245                            System.err.println(inf);
246                        }
247                        throw new DeadlockException("Found "+ids.length+" deadlocked threads");
248                    }
249                    Thread.sleep(100);
250                } catch(InterruptedException | RuntimeException x) {
251                    fail(x);
252                }
253            }
254        }
255
256    }
257
258    // A helper class to configure the security manager for the test,
259    // and bypass it when needed.
260    static class Configure {
261        static Policy policy = null;
262        static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
263            @Override
264            protected AtomicBoolean initialValue() {
265                return  new AtomicBoolean(false);
266            }
267        };
268        static void setUp(TestCase test) {
269            switch (test) {
270                case SECURE:
271                    if (policy == null && System.getSecurityManager() != null) {
272                        throw new IllegalStateException("SecurityManager already set");
273                    } else if (policy == null) {
274                        policy = new SimplePolicy(TestCase.SECURE, allowAll);
275                        Policy.setPolicy(policy);
276                        System.setSecurityManager(new SecurityManager());
277                    }
278                    if (System.getSecurityManager() == null) {
279                        throw new IllegalStateException("No SecurityManager.");
280                    }
281                    if (policy == null) {
282                        throw new IllegalStateException("policy not configured");
283                    }
284                    break;
285                case UNSECURE:
286                    if (System.getSecurityManager() != null) {
287                        throw new IllegalStateException("SecurityManager already set");
288                    }
289                    break;
290                default:
291                    new InternalError("No such testcase: " + test);
292            }
293        }
294        static void doPrivileged(Runnable run) {
295            allowAll.get().set(true);
296            try {
297                run.run();
298            } finally {
299                allowAll.get().set(false);
300            }
301        }
302    }
303
304    // A Helper class to build a set of permissions.
305    static final class PermissionsBuilder {
306        final Permissions perms;
307        public PermissionsBuilder() {
308            this(new Permissions());
309        }
310        public PermissionsBuilder(Permissions perms) {
311            this.perms = perms;
312        }
313        public PermissionsBuilder add(Permission p) {
314            perms.add(p);
315            return this;
316        }
317        public PermissionsBuilder addAll(PermissionCollection col) {
318            if (col != null) {
319                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
320                    perms.add(e.nextElement());
321                }
322            }
323            return this;
324        }
325        public Permissions toPermissions() {
326            final PermissionsBuilder builder = new PermissionsBuilder();
327            builder.addAll(perms);
328            return builder.perms;
329        }
330    }
331
332    // Policy for the test...
333    public static class SimplePolicy extends Policy {
334
335        final Permissions permissions;
336        final Permissions allPermissions;
337        final ThreadLocal<AtomicBoolean> allowAll; // actually: this should be in a thread locale
338        public SimplePolicy(TestCase test, ThreadLocal<AtomicBoolean> allowAll) {
339            this.allowAll = allowAll;
340            // we don't actually need any permission to create our
341            // FileHandlers because we're passing invalid parameters
342            // which will make the creation fail...
343            permissions = new Permissions();
344            permissions.add(new RuntimePermission("accessClassInPackage.sun.misc"));
345
346            // these are used for configuring the test itself...
347            allPermissions = new Permissions();
348            allPermissions.add(new java.security.AllPermission());
349
350        }
351
352        @Override
353        public boolean implies(ProtectionDomain domain, Permission permission) {
354            if (allowAll.get().get()) return allPermissions.implies(permission);
355            return permissions.implies(permission);
356        }
357
358        @Override
359        public PermissionCollection getPermissions(CodeSource codesource) {
360            return new PermissionsBuilder().addAll(allowAll.get().get()
361                    ? allPermissions : permissions).toPermissions();
362        }
363
364        @Override
365        public PermissionCollection getPermissions(ProtectionDomain domain) {
366            return new PermissionsBuilder().addAll(allowAll.get().get()
367                    ? allPermissions : permissions).toPermissions();
368        }
369    }
370
371}
372