JDK-8138622 : (dc) Deadlock between Thread.interrupt() and DatagramChannel.send()
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 7,8u60,9
  • Priority: P4
  • Status: Resolved
  • Resolution: Duplicate
  • OS: windows_2012
  • CPU: x86
  • Submitted: 2015-09-15
  • Updated: 2018-02-28
  • Resolved: 2018-02-28
Related Reports
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
Both 1.7 and 1.8 JREs


ADDITIONAL OS VERSION INFORMATION :
Both Linux and Windows

A DESCRIPTION OF THE PROBLEM :
When we attempt to interrupt a thread sending data on a DatagramChannel, we sometimes encounter a deadlock between the thread doing the interrupt and the thread sending the data. We've determined that the locks in question are java.lang.Thread's blockerLock and sun.nio.ch.DatagramChannelImpl's stateLock.

I've included a test program which can reproduce it. ~40% of the time it happens on the first iteration, otherwise it could take some time.


REPRODUCIBILITY :
This bug can be reproduced occasionally.

---------- BEGIN SOURCE ----------
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DeadlockTest
{
  private static final Logger LOG = LoggerFactory.getLogger(DeadlockTest.class);
  private DatagramChannel receiver;
  private InetSocketAddress target = new InetSocketAddress("localhost", 42421);
  private boolean running = true;

  private class Sender implements Runnable
  {
    private Thread thisThread = null;

    /**
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run()
    {
      thisThread = Thread.currentThread();
      while (running)
      {
        try
        {
          DatagramChannel sender = DatagramChannel.open();
          sender.setOption(StandardSocketOptions.SO_REUSEADDR, true);
          sender.connect(target);

          LOG.info("Sending");
          sender.send(ByteBuffer.allocate(100).putInt(1), target);
          // Sooner or later you stop seeing this message
          LOG.info("Sent");
          Thread.sleep(ThreadLocalRandom.current().nextLong(0, 100));
        }
        catch (Exception e)
        {
          // Do Nothing
        }
        finally
        {
          if (Thread.interrupted())
          {
            LOG.info("Was interrupted");
          }
        }
      }
    }

    public void interruptThis()
    {
      if (thisThread != null)
      {
        thisThread.interrupt();
      }
    }
  }

  @Test
  public void testDeadlock() throws IOException, InterruptedException
  {
    receiver = DatagramChannel.open();
    receiver.setOption(StandardSocketOptions.SO_REUSEADDR, true);
    receiver.bind(new InetSocketAddress(42421));
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Sender senderActor = new Sender();
    executor.submit(senderActor);
    while (running)
    {
      LOG.info("Before interrupt");
      senderActor.interruptThis();
      // Sooner or later, you stop seeing this message.
      LOG.info("After interrupt");
      Thread.sleep(ThreadLocalRandom.current().nextLong(0, 100));
    }
  }
}
---------- END SOURCE ----------


Comments
The changes in JDK-8198562 change the locking in SocketChannel and ServerSocketChannel. A follow-on change will re-implement the locking in DatagramChannel so the deadlock observed here will not happen.
25-02-2018

I suspect if the following code in send method need synchronized stateLock for so large scope: synchronized (stateLock) { if (!isConnected()) { if (target == null) throw new NullPointerException(); SecurityManager sm = System.getSecurityManager(); if (sm != null) { if (ia.isMulticastAddress()) { sm.checkMulticast(ia); } else { sm.checkConnect(ia.getHostAddress(), isa.getPort()); } } } else { // Connected case; Check address then write if (!target.equals(remoteAddress)) { throw new IllegalArgumentException( "Connected address not equal to target address"); } return write(src); } } I attached a clumsy diff, it may fix the bug, I haven't tested it...
06-11-2015

Attached a thread dump to show the deadlock clearer. Obviously this line in DatagramChannelImpl.java's public int send(ByteBuffer src, SocketAddress target) method has problem: return write(src); It puts end() method in the scope of the stateLock. When someone try to interrupt the I/O thread, if it get blockerLock of the thread firstly, and then try to get stateLock in order to close the channel, but the I/O thread has got stateLock(because it is writing), and when it run end() finally, it need to get blockerLock, here deadlock. This bug is more similar to JDK-8024833.
06-11-2015

Could this be related to JDK-8054039 ?
05-11-2015

Attached test case run on Windows 7: JDK 7 - Fail JDK 7u80 - Fail JDK 8u60 -fail JDK 9 - Fail Moving across to dev-team for evaluation.
30-09-2015