Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8260366: ExtendedSocketOptions <clinit> can deadlock in some circumstances #2601

Closed
wants to merge 7 commits into from

Conversation

@jaikiran
Copy link
Member

@jaikiran jaikiran commented Feb 17, 2021

Can I please get a review for this change which proposes to fix the issue reported in https://bugs.openjdk.java.net/browse/JDK-8260366?

The issue relates to the concurrent classloading of sun.net.ext.ExtendedSocketOptions and jdk.net.ExtendedSocketOptions leading to a deadlock. This is because the sun.net.ext.ExtendedSocketOptions in its static block does a Class.forName("jdk.net.ExtendedSocketOptions"). The jdk.net.ExtendedSocketOptions in its own static block calls the register method on sun.net.ext.ExtendedSocketOptions. If 2 threads concurrently try loading these classes (one loading the sun.net.ext.ExtendedSocketOptions and the other loading jdk.net.ExtendedSocketOptions), it can so happen that each one ends up holding a classloading lock in the static block of the respective class, while waiting for the other thread to release the lock, thus leading to a deadlock. The stacktrace posted in the linked JBS issue has the necessary details on the deadlock.

The commit here breaks this deadlock by moving out the Class.forName("jdk.net.ExtendedSocketOptions") call from the static block of sun.net.ext.ExtendedSocketOptions to the getInstance() method, thus lazily loading (on first call to getInstance()) and registering the jdk.net.ExtendedSocketOptions.

Extra attention needs to be given to the sun.net.ext.ExtendedSocketOptions#register(ExtendedSocketOptions extOptions) method. Before the change in this PR, when the sun.net.ext.ExtendedSocketOptions would successfully complete loading, it was guaranteed that the registered ExtendedSocketOptions would either be the one registered from the jdk.net.ExtendedSocketOptions or a NoExtendedSocketOptions. There wasn't any window of chance for any code (be it in the JDK or in application code) to call the sun.net.ext.ExtendedSocketOptions#register to register any different/other implementation/instance of the ExtendedSocketOptions. However, with this change in the PR, there is now a window of chance where any code in the JDK (or even application code?) can potentially call the sun.net.ext.ExtendedSocketOptions#register before either the jdk.net.ExtendedSocketOptions is loaded or the sun.net.ext.ExtendedSocketOptions#getInstance() method is called, thus allowing for some other implementation of the ExtendedSocketOptions to be registered. However, I'm not sure if it's a practical scenario - although the sun.net.ext.ExtendedSocketOptions#register is marked public, the comment on that method and the fact that it resides in an internal, not exposed by default class/module, makes me believe that this register method isn't supposed to be called by anyone other than the jdk.net.ExtendedSocketOptions. If at all this register method is allowed to be called from other places, then due to the change in this PR, additional work needs to be probably done in its implementation to allow for the jdk.net.ExtendedSocketOptions to be given first priority(?) to be registered first. I'll need input on whether I should worry about this case or if it's fine in its current form in this PR.

This PR also contains a jtreg test which reproduces the issue and verifies the fix.


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed

Issue

  • JDK-8260366: ExtendedSocketOptions can deadlock in some circumstances

Reviewers

Download

$ git fetch https://git.openjdk.java.net/jdk pull/2601/head:pull/2601
$ git checkout pull/2601

@bridgekeeper
Copy link

@bridgekeeper bridgekeeper bot commented Feb 17, 2021

👋 Welcome back jpai! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

Loading

@openjdk openjdk bot added the rfr label Feb 17, 2021
@openjdk
Copy link

@openjdk openjdk bot commented Feb 17, 2021

@jaikiran The following label will be automatically applied to this pull request:

  • net

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

Loading

@openjdk openjdk bot added the net label Feb 17, 2021
@mlbridge
Copy link

@mlbridge mlbridge bot commented Feb 17, 2021

Loading

Copy link
Member

@dfuch dfuch left a comment

Hi Jaikiran,

Thanks for taking a go at this one.

At a glance - the proposed fix seems reasonable to me. It would be good to have another pair of eyes (and analysis) on this though. Some issues with the test.

Loading

* @bug 8260366
* @summary Verify that concurrent classloading of sun.net.ext.ExtendedSocketOptions and
* jdk.net.ExtendedSocketOptions doesn't lead to a deadlock
* @run testng/othervm --add-exports=java.base/sun.net.ext=ALL-UNNAMED ExtendedSocketOptionsTest
Copy link
Member

@dfuch dfuch Feb 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --add-exports should not be needed - have you tried simply adding:

@modules java.base/sun.net.ext:+open
                  jdk.net

before @run? This also has the additional benefit to declare which modules are required to run the test.

Loading

Copy link
Member

@dfuch dfuch Feb 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the test should be fast, I'd also advise to stick several identical @run lines (maybe e.g. 5 of them) to increase the probability of the deadlock to happen...

Loading

Copy link
Member Author

@jaikiran jaikiran Feb 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the test should be fast, I'd also advise to stick several identical @run lines (maybe e.g. 5 of them) to increase the probability of the deadlock to happen...

Done. The PR has been updated to run this test multiple times now.

Loading

Copy link
Member Author

@jaikiran jaikiran Feb 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --add-exports should not be needed - have you tried simply adding:

@modules java.base/sun.net.ext:+open
                  jdk.net

before @run? This also has the additional benefit to declare which modules are required to run the test.

I had forgotten about the @modules directive. I have now updated the PR use that instead. The only minor difference between what you suggested and my updated PR is that I decided to use :open instead of :+open for the sun.net.ext package, since I don't use the types in that package at compile time, in that test.

Loading

* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
Copy link
Member

@dfuch dfuch Feb 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests do not need - nor should they have - the "Classpath" exception - please see Copyright notice of other tests in the vicinity.

Loading

Copy link
Member Author

@jaikiran jaikiran Feb 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for catching this. I've fixed it in the latest update of the PR.

Loading

@jaikiran
Copy link
Member Author

@jaikiran jaikiran commented Feb 17, 2021

Hello Daniel, thank you for the review. I have updated this PR to incorporate your review suggestions for the test.

Loading

@vyommani
Copy link
Contributor

@vyommani vyommani commented Feb 17, 2021

Changes looks OK to me, any specific reason for removing "final" specifier from "getInstance" & "register" methods ?.

Loading

@@ -167,27 +167,34 @@ protected ExtendedSocketOptions(Set<SocketOption<?>> options) {

private static volatile ExtendedSocketOptions instance;

public static final ExtendedSocketOptions getInstance() { return instance; }
public static ExtendedSocketOptions getInstance() {
if (instance != null) {
Copy link
Contributor

@turbanoff turbanoff Feb 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be it's worth to avoid reading volatile field twice?

Loading

Copy link
Member Author

@jaikiran jaikiran Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @turbanoff, do you mean why read it twice - once here and once inside the synchronized block?

Loading

Copy link
Contributor

@turbanoff turbanoff Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Once here and once inside synchronized block.
Reading volatile fields cost something on some architectures, so I think we could optimize it a bit.

Loading

Copy link
Member Author

@jaikiran jaikiran Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @turbanoff, the double read is necessary to correctly avoid any race conditions and is a typical strategy used in cases like these. I am not aware of any other alternate more performant strategy, for code like this.

Loading

Copy link
Contributor

@turbanoff turbanoff Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me be more clear: I think that it's enough to have only 2 volatile field reads in worst case. We can use local variable to avoid more than needed reads.
Current code can perform read 4 times in worst case: twice outside synchronized block and twice inside synchronized block.

There are many examples of similar code in the JDK:
https://github.com/openjdk/jdk/blob/master/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Util.java#L48
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/StackFrameInfo.java#L125

Loading

Copy link
Member Author

@jaikiran jaikiran Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! of course, I now see what you mean. I misunderstood your previous comment. I'll update this PR shortly.

Loading

Copy link
Member Author

@jaikiran jaikiran Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now updated this PR which this suggested change.

Loading

@jaikiran
Copy link
Member Author

@jaikiran jaikiran commented Feb 18, 2021

Changes looks OK to me, any specific reason for removing "final" specifier from "getInstance" & "register" methods ?.

Hello Vyom, thank you for the review. Since those two methods are static, the final was redundant on them and since this patch was already changing those 2 methods, I decided to remove it while I was at it. However, if you and others feel that this patch shouldn't change it, I will introduce it back.

Loading

@vyommani
Copy link
Contributor

@vyommani vyommani commented Feb 18, 2021

Changes looks OK to me, any specific reason for removing "final" specifier from "getInstance" & "register" methods ?.

Hello Vyom, thank you for the review. Since those two methods are static, the final was redundant on them and since this patch was already changing those 2 methods, I decided to remove it while I was at it. However, if you and others feel that this patch shouldn't change it, I will introduce it back.

I think it's OK for me. What about improving the test little bit, your test wants to load both classes at the same time. Please have a look on modified test.

/*

  • Copyright (c) 2021, 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 org.testng.Assert;
    import org.testng.annotations.Test;

import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**

  • @test

  • @bug 8260366

  • @summary Verify that concurrent classloading of

  • sun.net.ext.ExtendedSocketOptions and jdk.net.ExtendedSocketOptions doesn't

  • lead to a deadlock

  • @modules java.base/sun.net.ext:open

  • @run testng/othervm ExtendedSocketOptionsTest

  • @run testng/othervm ExtendedSocketOptionsTest

  • @run testng/othervm ExtendedSocketOptionsTest

  • @run testng/othervm ExtendedSocketOptionsTest

  • @run testng/othervm ExtendedSocketOptionsTest
    */
    public class ExtendedSocketOptionsTest {

    /**

    • Loads {@code jdk.net.ExtendedSocketOptions} and

    • {@code sun.net.ext.ExtendedSocketOptions} concurrently in a thread of

    • their own and expects the classloading of both those classes to

    • succeed.Additionally, after the classloading is successfully done, calls

    • the sun.net.ext.ExtendedSocketOptions#getInstance() and expects it to

    • return a registered ExtendedSocketOptions instance.

    • @throws java.lang.Exception
      */
      @test
      public void testConcurrentClassLoad() throws Exception {
      CountDownLatch latch = new CountDownLatch(1);
      final Callable<Class> task1 = new Task("jdk.net.ExtendedSocketOptions", latch); final Callable> task2 = new Task("sun.net.ext.ExtendedSocketOptions", latch);
      final ExecutorService executor = Executors.newFixedThreadPool(2);
      try {
      final Future<Class<?>>[] results = new Future[2];

       // submit
       results[0] = executor.submit(task1);
       results[1] = executor.submit(task2);
       latch.countDown();
      
       // wait for completion
       for (Future<Class<?>> result: results) {
           final Class<?> k = result.get();
           System.out.println("Completed loading " + k.getName());
       }
      

      } finally {
      executor.shutdownNow();
      }
      // check that the sun.net.ext.ExtendedSocketOptions#getInstance() does indeed return
      // the registered instance
      final Class<?> k = Class.forName("sun.net.ext.ExtendedSocketOptions");
      final Object extSocketOptions = k.getDeclaredMethod("getInstance").invoke(null);
      Assert.assertNotNull(extSocketOptions, "sun.net.ext.ExtendedSocketOptions#getInstance()"
      + " unexpectedly returned null");
      }

    private static class Task implements Callable<Class<?>> {

     private final String className;
     private final CountDownLatch latch;
    
     private Task(final String className, CountDownLatch latch) {
         this.className = className;
         this.latch = latch;
     }
    
     @Override
     public Class<?> call() {
         System.out.println(Thread.currentThread().getName() + " loading " + this.className);
         try {
             latch.await();
             return Class.forName(this.className);
         } catch (ClassNotFoundException | InterruptedException e) {
             System.err.println("Failed to load " + this.className);
             throw new RuntimeException(e);
         }
     }
    

    }
    }

Loading

@jaikiran
Copy link
Member Author

@jaikiran jaikiran commented Feb 18, 2021

What about improving the test little bit, your test wants to load both classes at the same time. Please have a look on modified test.

Hello Vyom, I think that's a good suggestion to use a latch for deciding when to trigger the classloading. I've taken your input and have made some relatively minor change to the way that latch gets used and updated my PR with that change. The latch now waits for both the tasks to reach the point where they are going to do a Class.forName on their respectively class names. This should make the test trigger that classloading in separate threads as simultaneously as possible.

Loading

@vyommani
Copy link
Contributor

@vyommani vyommani commented Feb 18, 2021

I think below change will address Andrey's concern
public static ExtendedSocketOptions getInstance() {
ExtendedSocketOptions temp = instance;
if (temp == null) {
synchronized (ExtendedSocketOptions.class) {
try {
// If the class is present, it will be initialized which
// triggers registration of the extended socket options.
Class<?> c = Class.forName("jdk.net.ExtendedSocketOptions");
} catch (ClassNotFoundException e) {
// the jdk.net module is not present => no extended socket options
instance = new NoExtendedSocketOptions();
}
temp = instance;
}
}
return temp;
}

Loading

instance = new NoExtendedSocketOptions();
}
}
return instance;
Copy link
Member

@dfuch dfuch Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest changing these two lines as well:

                // the jdk.net module is not present => no extended socket options
                ext = instance = new NoExtendedSocketOptions();
            }
        }
        return ext;

Loading

Copy link
Member Author

@jaikiran jaikiran Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello Daniel, I had thought about it in my previous commit. But this won't work, since in the normal case where the ClassNotFoundException doesn't get thrown, the instance is actually set in the register method which gets triggered due to the class load on jdk.net.ExtendedSocketOptions. As a result, returning the local ext variable won't work in that case, unless of course I do ext = instance in both the catch block and outside of it, which would, IMO, defeat the purpose of optimization in that last commit.

I decided to "prove" it with some test case and while doing so I just uncovered that my whole patch has just moved the deadlock to a new location - thread T1 calling sun.net.ext.ExtendedSocketOptions#getInstance() and thread T2 calling Class.forName("jdk.net.ExtendedSocketOptions") ends up in a deadlock. It's clears why that happens.

I am going to take a step back and come back with a different fix for this one. Thank you everyone for these reviews - they definitely helped.

Loading

Copy link
Contributor

@vyommani vyommani Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Jaikiran,

I tested with my suggested change and i did not see any deadlock at my local Linux environment. I just ran test in loop and it worked as expected.

Thanks,
Vyom

Loading

Copy link
Member Author

@jaikiran jaikiran Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello Vyom,

The trick is to change the test case to have something like this in the "task":

diff --git a/test/jdk/sun/net/ext/ExtendedSocketOptionsTest.java b/test/jdk/sun/net/ext/ExtendedSocketOptionsTest.java
index 0702abf5279..26c8a1384a2 100644
--- a/test/jdk/sun/net/ext/ExtendedSocketOptionsTest.java
+++ b/test/jdk/sun/net/ext/ExtendedSocketOptionsTest.java
@@ -96,7 +96,11 @@ public class ExtendedSocketOptionsTest {
                 classLoadingTriggerLatch.countDown();
                 // wait for the other task to let us know it's ready too, to load the class
                 classLoadingTriggerLatch.await();
-                return Class.forName(this.className);
+                final Class<?> c = Class.forName(this.className);
+                // let's call getInstance on sun.net.ext.ExtendedSocketOptions
+                final Class<?> k = Class.forName("sun.net.ext.ExtendedSocketOptions");
+                final Object extSocketOptions = k.getDeclaredMethod("getInstance").invoke(null);
+                return c;
             } catch (Exception e) {
                 System.err.println("Failed to load " + this.className);
                 throw new RuntimeException(e);

Essentially, trigger a call to sun.net.ext.ExtendedSocketOptions#getInstance and a classload of jdk.net.ExtendedSocketOptions simultaneously from different threads. That will end up with:


"pool-1-thread-1" #16 prio=5 os_prio=31 cpu=18.25ms elapsed=120.03s tid=0x00007ff9008c8a00 nid=0x6203 waiting for monitor entry  [0x0000700010c8b000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at sun.net.ext.ExtendedSocketOptions.register(java.base@17-internal/ExtendedSocketOptions.java:197)
	- waiting to lock <0x000000070f9f8e68> (a java.lang.Class for sun.net.ext.ExtendedSocketOptions)
	at jdk.net.ExtendedSocketOptions.<clinit>(jdk.net@17-internal/ExtendedSocketOptions.java:234)
	at java.lang.Class.forName0(java.base@17-internal/Native Method)
	at java.lang.Class.forName(java.base@17-internal/Class.java:375)
	at ExtendedSocketOptionsTest$Task.call(ExtendedSocketOptionsTest.java:100)
	at ExtendedSocketOptionsTest$Task.call(ExtendedSocketOptionsTest.java:84)
	at java.util.concurrent.FutureTask.run(java.base@17-internal/FutureTask.java:264)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17-internal/ThreadPoolExecutor.java:1135)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17-internal/ThreadPoolExecutor.java:635)
	at java.lang.Thread.run(java.base@17-internal/Thread.java:831)

"pool-1-thread-2" #17 prio=5 os_prio=31 cpu=8.76ms elapsed=120.03s tid=0x00007ff90102be00 nid=0x6403 in Object.wait()  [0x0000700010d8e000]
   java.lang.Thread.State: RUNNABLE
	at java.lang.Class.forName0(java.base@17-internal/Native Method)
	- waiting on the Class initialization monitor for jdk.net.ExtendedSocketOptions
	at java.lang.Class.forName(java.base@17-internal/Class.java:375)
	at sun.net.ext.ExtendedSocketOptions.getInstance(java.base@17-internal/ExtendedSocketOptions.java:185)
	- locked <0x000000070f9f8e68> (a java.lang.Class for sun.net.ext.ExtendedSocketOptions)
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base@17-internal/Native Method)
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base@17-internal/NativeMethodAccessorImpl.java:78)
	at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base@17-internal/DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(java.base@17-internal/Method.java:566)
	at ExtendedSocketOptionsTest$Task.call(ExtendedSocketOptionsTest.java:102)
	at ExtendedSocketOptionsTest$Task.call(ExtendedSocketOptionsTest.java:84)
	at java.util.concurrent.FutureTask.run(java.base@17-internal/FutureTask.java:264)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17-internal/ThreadPoolExecutor.java:1135)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17-internal/ThreadPoolExecutor.java:635)
	at java.lang.Thread.run(java.base@17-internal/Thread.java:831)

Loading

Copy link
Member

@dfuch dfuch Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello Daniel, I had thought about it in my previous commit. But this won't work, since in the normal case where the ClassNotFoundException doesn't get thrown, the instance is actually set in the register method which gets triggered due to the class load on jdk.net.ExtendedSocketOptions. As a result, returning the local ext variable won't work in that case, unless of course I do ext = instance in both the catch block and outside of it, which would, IMO, defeat the purpose of optimization in that last commit.

Oh - right - still - if you'd stick with that fix - I believe you should do that:

            try {
                // If the class is present, it will be initialized which
                // triggers registration of the extended socket options.
                Class<?> c = Class.forName("jdk.net.ExtendedSocketOptions");
                ext = instance;
            } catch (ClassNotFoundException e) {
                // the jdk.net module is not present => no extended socket options
                ext = instance = new NoExtendedSocketOptions();
            }
        }
        return ext;

Anyway: waiting for the next fix :-)

Loading

@jaikiran
Copy link
Member Author

@jaikiran jaikiran commented Feb 18, 2021

I'm closing this for now, for the reason stated here #2601 (comment).

Loading

@jaikiran jaikiran closed this Feb 18, 2021
…ce the testcase to be more robust in catching the deadlocks
@jaikiran
Copy link
Member Author

@jaikiran jaikiran commented Feb 19, 2021

After thinking a bit more about this issue and the patch I had proposed, I realized what I did wrong in this patch. The synchronized block in the sun.net.ext.ExtendedSocketOptions#getInstance() was scoped one statement too many. We don't (and shouldn't) need the Class.forName("jdk.net.ExtendedSocketOptions") to be part of that synchronized block. I've now modified and fixed that part to have the synchronized block only at the place where it should be needed. I've also enhanced the test to introduce a couple of additional concurrent tasks which now call sun.net.ext.ExtendedSocketOptions#getInstance(). So in total, the test now fires off 4 tasks, 2 of which load the respective classes and the other 2 call the getInstance() method, all concurrently. This should make the test a bit more robust and should catch any potential deadlocks. With this fix, the test now passes.

I've now updated and reopened this PR for further reviews.

Loading

@jaikiran jaikiran reopened this Feb 19, 2021
@vyommani
Copy link
Contributor

@vyommani vyommani commented Feb 19, 2021

After thinking a bit more about this issue and the patch I had proposed, I realized what I did wrong in this patch. The synchronized block in the sun.net.ext.ExtendedSocketOptions#getInstance() was scoped one statement too many. We don't (and shouldn't) need the Class.forName("jdk.net.ExtendedSocketOptions") to be part of that synchronized block. I've now modified and fixed that part to have the synchronized block only at the place where it should be needed. I've also enhanced the test to introduce a couple of additional concurrent tasks which now call sun.net.ext.ExtendedSocketOptions#getInstance(). So in total, the test now fires off 4 tasks, 2 of which load the respective classes and the other 2 call the getInstance() method, all concurrently. This should make the test a bit more robust and should catch any potential deadlocks. With this fix, the test now passes.

I've now updated and reopened this PR for further reviews.

Hi Jaikiran,

I looked into your previous patch and i believe why you were observing the deadlock because you make "sun.net.ext.ExtendedSocketOption.register" thread safe(synchronize). When you are loading both the classes simultaneously one thread is calling getInstance() and other thread while loading the class(jdk.net.ExtendedSocketOptions) calls register and you are observing the deadlock.

Although sun.net.ext.ExtendedSocketOptions.register is public method but it will be called only from jdk.net.ExtendedSocketOption static block, so we don't need it to be thread safe.

Let's wait for what others people say.

Loading

@jaikiran
Copy link
Member Author

@jaikiran jaikiran commented Feb 24, 2021

Any reviews/comments, in addition to what Vyom mentioned, to the updated PR please?

Loading

dfuch
dfuch approved these changes Feb 24, 2021
Copy link
Member

@dfuch dfuch left a comment

The fix and test looks good to me.

Loading

public static synchronized void register(ExtendedSocketOptions extOptions) {
if (instance != null)
throw new InternalError("Attempting to reregister extended options");

instance = extOptions;
}
Copy link
Member

@dfuch dfuch Feb 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguably, because instance is volatile, you could also use the double locking mechanism here to throw before synchronizing if instance is already set, and check again after synhronizing. However, it's probably not worth it since this method is expected to be called (and should always be called) only once - so I believe what you have here should be enough.

Loading

@openjdk
Copy link

@openjdk openjdk bot commented Feb 24, 2021

@jaikiran This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8260366: ExtendedSocketOptions <clinit> can deadlock in some circumstances

Reviewed-by: dfuchs

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 109 new commits pushed to the master branch:

  • 2c99bad: 8261920: [AIX] jshell command throws java.io.IOError on non English locales
  • 8c07063: 8262259: Remove unused variable in MethodLiveness::BasicBlock::compute_gen_kill_single
  • 0d2dbd2: 8262027: Improve how HttpConnection detects a closed channel when taking/returning a connection to the pool
  • 382e38d: 8256438: AArch64: Implement match rules with ROR shift register value
  • fac37bf: 8262269: javadoc test TestGeneratedClasses.java fails on Windows
  • 3e13b66: 8262157: LingeredApp.startAppExactJvmOpts does not print app output when launching fails
  • c769388: 8262266: JDK-8262049 fails validate-source
  • 03e781b: 8262265: ProblemList jdk/javadoc/doclet/testGeneratedClasses/TestGeneratedClasses.java on Windows
  • c6eae06: 8262049: [TESTBUG] Fix TestReferenceRefersTo.java for Shenandoah IU mode
  • e5304b3: 8253409: Double-rounding possibility in float fma
  • ... and 99 more: https://git.openjdk.java.net/jdk/compare/8ba390d1e243c5ec2e637263fa44907b664e8ceb...master

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

Loading

@openjdk openjdk bot added the ready label Feb 24, 2021
@jaikiran
Copy link
Member Author

@jaikiran jaikiran commented Feb 26, 2021

Thank you for the reviews, Daniel. I'll integrate this shortly.

Loading

@jaikiran
Copy link
Member Author

@jaikiran jaikiran commented Feb 26, 2021

/integrate

Loading

@openjdk openjdk bot closed this Feb 26, 2021
@openjdk openjdk bot added integrated and removed ready rfr labels Feb 26, 2021
@openjdk
Copy link

@openjdk openjdk bot commented Feb 26, 2021

@jaikiran Since your change was applied there have been 138 commits pushed to the master branch:

  • de3f519: 8258897: wrong translation of capturing local classes inside nested lambdas
  • d7efb4c: 8262199: issue in jli args.c
  • 7603278: 8260198: TypeInstPtr::dump2() emits multiple lines if Verbose is set
  • 0a4e710: 8261954: Dependencies: Improve iteration over class hierarchy under context class
  • 722142e: 8261520: JDK-8261302 breaks runtime/NMT/CheckForProperDetailStackTrace.java
  • bcca100: 4710675: JTextArea.setComponentOrientation does not work with correct timing
  • fce5765: 8262433: doclint: reference error in module jdk.incubator.foreign
  • 059ede0: 8262428: doclint warnings in java.xml module
  • 8256517: 8262421: doclint warnings in jdk.compiler module
  • 29c603f: 8262227: Change SystemDictionary::find() to return an InstanceKlass*.
  • ... and 128 more: https://git.openjdk.java.net/jdk/compare/8ba390d1e243c5ec2e637263fa44907b664e8ceb...master

Your commit was automatically rebased without conflicts.

Pushed as commit 240f2a1.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
4 participants