File: LogManagerAppContextDeadlock.java

package info (click to toggle)
openjdk-11 11.0.4%2B11-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 757,028 kB
  • sloc: java: 5,016,041; xml: 1,191,974; cpp: 934,731; ansic: 555,697; sh: 24,299; objc: 12,703; python: 3,602; asm: 3,415; makefile: 2,772; awk: 351; sed: 172; perl: 114; jsp: 24; csh: 3
file content (379 lines) | stat: -rw-r--r-- 14,336 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
/*
 * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.Enumeration;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import jdk.internal.misc.JavaAWTAccess;
import jdk.internal.misc.SharedSecrets;

/**
 * @test
 * @bug 8065991
 * @summary check that when LogManager is initialized, a deadlock similar
 *          to that described in 8065709 will not occur.
 * @modules java.base/jdk.internal.misc
 *          java.logging
 *          java.management
 * @run main/othervm LogManagerAppContextDeadlock UNSECURE
 * @run main/othervm LogManagerAppContextDeadlock SECURE
 *
 * @author danielfuchs
 */
public class LogManagerAppContextDeadlock {

    public static final Semaphore sem = new Semaphore(0);
    public static final Semaphore sem2 = new Semaphore(0);
    public static final Semaphore sem3 = new Semaphore(-2);
    public static volatile boolean goOn = true;
    public static volatile Exception thrown;

    // Emulate EventQueue
    static class FakeEventQueue {
        static final Logger logger = Logger.getLogger("foo");
    }

    // Emulate AppContext
    static class FakeAppContext {

        static final AtomicInteger numAppContexts = new AtomicInteger(0);
        static final class FakeAppContextLock {}
        static final FakeAppContextLock lock = new FakeAppContextLock();
        static volatile FakeAppContext appContext;

        final FakeEventQueue queue;
        FakeAppContext() {
            appContext = this;
            numAppContexts.incrementAndGet();
            // release sem2 to let Thread t2 call Logger.getLogger().
            sem2.release();
            try {
                // Wait until we JavaAWTAccess is called by LogManager.
                // Thread 2 will call Logger.getLogger() which will
                // trigger a call to JavaAWTAccess - which will release
                // sem, thus ensuring that Thread #2 is where we want it.
                sem.acquire();
                System.out.println("Sem acquired: Thread #2 has called JavaAWTAccess");
            } catch(InterruptedException x) {
                Thread.interrupted();
            }
            queue = new FakeEventQueue();
        }

        static FakeAppContext getAppContext() {
            synchronized (lock) {
                if (numAppContexts.get() == 0) {
                    return new FakeAppContext();
                }
                return appContext;
            }
        }

        static {
            SharedSecrets.setJavaAWTAccess(new JavaAWTAccess() {
                @Override
                public Object getAppletContext() {
                    if (numAppContexts.get() == 0) return null;
                    // We are in JavaAWTAccess, we can release sem and let
                    // FakeAppContext constructor proceeed.
                    System.out.println("Releasing Sem");
                    sem.release();
                    return getAppContext();
                }

            });
        }

    }


    // Test with or without a security manager
    public static enum TestCase {
        UNSECURE, SECURE;
        public void run() throws Exception {
            System.out.println("Running test case: " + name());
            Configure.setUp(this);
            test(this);
        }
    }

    public static void test(TestCase test) throws Exception {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                sem3.release();
                System.out.println("FakeAppContext.getAppContext()");
                FakeAppContext.getAppContext();
                System.out.println("Done: FakeAppContext.getAppContext()");
            }
        };
        t1.setDaemon(true);
        t1.start();
        Thread t2 = new Thread() {
            public Object logger;
            public void run() {
                sem3.release();
                try {
                    // Wait until Thread1 is in FakeAppContext constructor
                    sem2.acquire();
                    System.out.println("Sem2 acquired: Thread #1 will be waiting to acquire Sem");
                } catch (InterruptedException ie) {
                    Thread.interrupted();
                }
                System.out.println("Logger.getLogger(name).info(name)");
                // stick the logger in an instance variable to prevent it
                // from being garbage collected before the main thread
                // calls LogManager.getLogger() below.
                logger = Logger.getLogger(test.name());//.info(name);
                System.out.println("Done: Logger.getLogger(name).info(name)");
            }
        };
        t2.setDaemon(true);
        t2.start();
        System.out.println("Should exit now...");
        Thread detector = new DeadlockDetector();
        detector.start();

        // Wait for the 3 threads to start
        sem3.acquire();

        // Now wait for t1 & t2 to finish, or for a deadlock to be detected.
        while (goOn && (t1.isAlive() || t2.isAlive())) {
            if (t2.isAlive()) t2.join(1000);
            if (test == TestCase.UNSECURE && System.getSecurityManager() == null) {
                // if there's no security manager, AppContext.getAppContext() is
                // not called -  so Thread t2 will not end up calling
                // sem.release(). In that case we must release the semaphore here
                // so that t1 can proceed.
                if (LogManager.getLogManager().getLogger(TestCase.UNSECURE.name()) != null) {
                    // means Thread t2 has created the logger
                    sem.release();
                }
            }
            if (t1.isAlive()) t1.join(1000);
        }
        if (thrown != null) {
            throw thrown;
        }
    }

    // Thrown by the deadlock detector
    static final class DeadlockException extends RuntimeException {
        public DeadlockException(String message) {
            super(message);
        }
        @Override
        public void printStackTrace() {
        }
    }

    public static void main(String[] args) throws Exception {

        if (args.length == 0) {
            args = new String[] { "SECURE" };
        }

        // If we don't initialize LogManager here, there will be
        // a deadlock.
        // See <https://bugs.openjdk.java.net/browse/JDK-8065709?focusedCommentId=13582038&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13582038>
        // for more details.
        Logger.getLogger("main").info("starting...");
        try {
            TestCase.valueOf(args[0]).run();
            System.out.println("Test "+args[0]+" Passed");
        } catch(Throwable t) {
            System.err.println("Test " + args[0] +" failed: " + t);
            t.printStackTrace();
        }
    }

    // Called by the deadlock detector when a deadlock is found.
    static void fail(Exception x) {
        x.printStackTrace();
        if (thrown == null) {
            thrown = x;
        }
        goOn = false;
    }

    // A thread that detect deadlocks.
    static final class DeadlockDetector extends Thread {

        public DeadlockDetector() {
            this.setDaemon(true);
        }

        @Override
        public void run() {
            sem3.release();
            Configure.doPrivileged(this::loop);
        }
        public void loop() {
            while(goOn) {
                try {
                    long[] ids = ManagementFactory.getThreadMXBean().findDeadlockedThreads();
                    ids = ids == null ? new long[0] : ids;
                    if (ids.length == 1) {
                        throw new RuntimeException("Found 1 deadlocked thread: "+ids[0]);
                    } else if (ids.length > 0) {
                        ThreadInfo[] infos = ManagementFactory.getThreadMXBean().getThreadInfo(ids, Integer.MAX_VALUE);
                        System.err.println("Found "+ids.length+" deadlocked threads: ");
                        for (ThreadInfo inf : infos) {
                            System.err.println(inf);
                        }
                        throw new DeadlockException("Found "+ids.length+" deadlocked threads");
                    }
                    Thread.sleep(100);
                } catch(InterruptedException | RuntimeException x) {
                    fail(x);
                }
            }
        }

    }

    // A helper class to configure the security manager for the test,
    // and bypass it when needed.
    static class Configure {
        static Policy policy = null;
        static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
            @Override
            protected AtomicBoolean initialValue() {
                return  new AtomicBoolean(false);
            }
        };
        static void setUp(TestCase test) {
            switch (test) {
                case SECURE:
                    if (policy == null && System.getSecurityManager() != null) {
                        throw new IllegalStateException("SecurityManager already set");
                    } else if (policy == null) {
                        policy = new SimplePolicy(TestCase.SECURE, allowAll);
                        Policy.setPolicy(policy);
                        System.setSecurityManager(new SecurityManager());
                    }
                    if (System.getSecurityManager() == null) {
                        throw new IllegalStateException("No SecurityManager.");
                    }
                    if (policy == null) {
                        throw new IllegalStateException("policy not configured");
                    }
                    break;
                case UNSECURE:
                    if (System.getSecurityManager() != null) {
                        throw new IllegalStateException("SecurityManager already set");
                    }
                    break;
                default:
                    new InternalError("No such testcase: " + test);
            }
        }
        static void doPrivileged(Runnable run) {
            allowAll.get().set(true);
            try {
                run.run();
            } finally {
                allowAll.get().set(false);
            }
        }
    }

    // A Helper class to build a set of permissions.
    static final class PermissionsBuilder {
        final Permissions perms;
        public PermissionsBuilder() {
            this(new Permissions());
        }
        public PermissionsBuilder(Permissions perms) {
            this.perms = perms;
        }
        public PermissionsBuilder add(Permission p) {
            perms.add(p);
            return this;
        }
        public PermissionsBuilder addAll(PermissionCollection col) {
            if (col != null) {
                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
                    perms.add(e.nextElement());
                }
            }
            return this;
        }
        public Permissions toPermissions() {
            final PermissionsBuilder builder = new PermissionsBuilder();
            builder.addAll(perms);
            return builder.perms;
        }
    }

    // Policy for the test...
    public static class SimplePolicy extends Policy {

        final Permissions permissions;
        final Permissions allPermissions;
        final ThreadLocal<AtomicBoolean> allowAll; // actually: this should be in a thread locale
        public SimplePolicy(TestCase test, ThreadLocal<AtomicBoolean> allowAll) {
            this.allowAll = allowAll;
            // we don't actually need any permission to create our
            // FileHandlers because we're passing invalid parameters
            // which will make the creation fail...
            permissions = new Permissions();
            permissions.add(new RuntimePermission("accessClassInPackage.jdk.internal.misc"));

            // these are used for configuring the test itself...
            allPermissions = new Permissions();
            allPermissions.add(new java.security.AllPermission());

        }

        @Override
        public boolean implies(ProtectionDomain domain, Permission permission) {
            if (allowAll.get().get()) return allPermissions.implies(permission);
            return permissions.implies(permission);
        }

        @Override
        public PermissionCollection getPermissions(CodeSource codesource) {
            return new PermissionsBuilder().addAll(allowAll.get().get()
                    ? allPermissions : permissions).toPermissions();
        }

        @Override
        public PermissionCollection getPermissions(ProtectionDomain domain) {
            return new PermissionsBuilder().addAll(allowAll.get().get()
                    ? allPermissions : permissions).toPermissions();
        }
    }

}