diff --git a/src/hotspot/src/share/vm/classfile/vmSymbols.hpp b/src/hotspot/src/share/vm/classfile/vmSymbols.hpp index 90ffcf9e..8c434ee1 100644 --- a/src/hotspot/src/share/vm/classfile/vmSymbols.hpp +++ b/src/hotspot/src/share/vm/classfile/vmSymbols.hpp @@ -607,6 +607,11 @@ /* trace signatures */ \ TRACE_TEMPLATES(template) \ \ + /* bugid 8194653: For early initialization */ \ + template(java_nio_file_FileSystems, "java/nio/file/FileSystems") \ + template(getDefault_name, "getDefault") \ + template(getDefault_signature, "()Ljava/nio/file/FileSystem;") \ + \ /*end*/ // Here are all the intrinsics known to the runtime and the CI. diff --git a/src/hotspot/src/share/vm/runtime/thread.cpp b/src/hotspot/src/share/vm/runtime/thread.cpp index a95850f6..3f6f9cc4 100644 --- a/src/hotspot/src/share/vm/runtime/thread.cpp +++ b/src/hotspot/src/share/vm/runtime/thread.cpp @@ -988,9 +988,15 @@ bool Thread::set_as_starting_thread() { return os::create_main_thread((JavaThread*)this); } +// Helper to initialize_class for callers that need the klass result +static Klass *initialize_class_and_return(Symbol* class_name, TRAPS) { + Klass* klass = SystemDictionary::resolve_or_fail(class_name, true, CHECK_NULL); + InstanceKlass::cast(klass)->initialize(CHECK_NULL); + return klass; +} + static void initialize_class(Symbol* class_name, TRAPS) { - Klass* klass = SystemDictionary::resolve_or_fail(class_name, true, CHECK); - InstanceKlass::cast(klass)->initialize(CHECK); + (void)initialize_class_and_return(class_name, CHECK); } @@ -3651,6 +3657,26 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK_0); } + // See : bugid 8194653 + // Background : Deadlock as described by https://bugs.openjdk.java.net/browse/JDK-8194653 + // Mitigation : Eliminates a race condition on call to + // java.nio.file.FileSystems::getDefault() where two threads (one inside + // a loadLibrary() call) could cause a deadlock if they both referenced this + // code path. Priming the code path during startup eliminates the deadlock + // ( and loadLibrary0 are single threaded). + // Note the JVM is at the end of initialization and the getDefault() call + // is safe to make. + // Future Fix : OpenJDK 9 beyond address the issue with a different (fixed) startup + // sequence. For OpenJDK 8 this should be sufficient. + { + Klass* k = initialize_class_and_return(vmSymbols::java_nio_file_FileSystems(), CHECK_0); + instanceKlassHandle klass (THREAD, k); + JavaValue result(T_OBJECT); + + JavaCalls::call_static(&result, klass, vmSymbols::getDefault_name(), + vmSymbols::getDefault_signature(), CHECK_0); + } + #if INCLUDE_MANAGEMENT Management::initialize(THREAD); #endif // INCLUDE_MANAGEMENT diff --git a/src/hotspot/test/runtime/8194653/FileSystemsDeadlockTest.java b/src/hotspot/test/runtime/8194653/FileSystemsDeadlockTest.java new file mode 100644 index 00000000..33682493 --- /dev/null +++ b/src/hotspot/test/runtime/8194653/FileSystemsDeadlockTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2018, Amazon 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.nio.file.FileSystems; + +/* + * Check for a possible scenario where 2 user threads early in JVM execution could take paths + * that would result in a deadlock. Thread1 must be running a found along the + * FileSystems.getDefault() path that consequently loads a native library. Thread2 must be in + * a native library load callback (through JNI_OnLoad) that then also references + * FileSystems.getDefault() which triggers the same path but blocks waiting for thread1 + * to complete. Thread1 is then also blocked on thread2 waiting for it to complete its native + * library load. + * + * The fix is to prime the FileSystems.getDefault() path at the end of JVM startup to prevent + * the path from being run by multithreaded user code, avoiding the deadlock. + */ + +/* @test + * @bug 8194653 + * @summary Accessing FileSystems.getDefaults() early in startup from two different threads should not deadlock. + * @run main/othervm FileSystemsDeadlockTest + */ +public class FileSystemsDeadlockTest { + static Thread thread1 = null; + static Thread thread2 = null; + + static volatile boolean thread1Ready = false; + + static volatile boolean thread1Interrupted = false; + static volatile boolean thread2Interrupted = false; + + public static void main(String[] args) { + // thread1 will attempt to directly access FileSystems.getDefault() which leads to a + // and eventual Runtime.getRuntime.loadLibrary0(). + thread1 = new Thread() { + public void run() { + try { + // Synchronize with thread1 so that everyone gets started roughly together + synchronized(this) { + thread1Ready = true; + thread1.notifyAll(); + thread1.wait(); + } + } catch(InterruptedException e) { + // Should never have gotten here - Failure + thread1Interrupted = true; + return ; + } + + // Run FileSystems.getDefault() which triggers a and a native library + // load. The fix would prevent the (and consequently the native library + // load) from happening because it was done during JVM startup. + FileSystems.getDefault(); + }; + }; + + // thread2 simulates being inside a loadLibrary0() call (ie: through a JNI_OnLoad call back) + // by synchronizing on Runtime.getRuntime() (what loadLibrary0() does) and then afer waiting + // sufficient time for thread1 to begin, then attempts to run FileSystems.getDefault(). This + // should result in thread2 blocking on the same that thread1 is running. + thread2 = new Thread() { + public void run() { + // Synchronize on Runtime.getRuntime() right upfront. This simulates the native + // library load and running code on a call back through JNI_OnLoad. + synchronized(Runtime.getRuntime()) { + try { + // Synchronize with thread1 so that everyone gets started roughly together + synchronized(thread1) { + while(!thread1Ready) { + thread1.wait(); + } + thread1.notifyAll(); + } + + // Wait long enough to let thread1 get going + Thread.sleep(5000l); + + // Now try and trigger the which thread1 should already be running. + // The fix would prevent the from happening because it was done + // during JVM startup. + FileSystems.getDefault(); + } catch(InterruptedException e) { + thread2Interrupted = true; + return ; + } + } + }; + }; + + thread1.start(); + thread2.start(); + + // Wait a set amount of time for the threads to complete. The test is successful if the threads complete without failures. + try { + thread1.join(10000l); + thread2.join(10000l); + } catch(InterruptedException e) { + // Should never have gotten here - Failure + throw new RuntimeException(); + } + + // Test that all threads have terminated without failures + if(thread1.isAlive() + || thread1Interrupted + || thread2.isAlive() + || thread2Interrupted) { + throw new RuntimeException(); + } + } +} -- 2.15.3.AMZN