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