Java Solaris Communities Sun Store Join SDN My Profile Why Join?
 
Bug Database
Bug Detail
Quick Lists
Top 25 Bugs
Top 25 RFE's
Recently Closed Bugs
Printable Page Printable Page


Bug Database
Bug ID: 5103988
Votes 4
Synopsis (fc) FileChannel.transferTo should return -1 for EAGAIN instead throws IOException
Category java:classes_nio
Reported Against tiger-rc
Release Fixed 7(b05), 6u18(b01) (Bug ID:2178831)
State 10-Fix Delivered, bug
Priority: 4-Low
Related Bugs
Submit Date 17-SEP-2004
Description


FULL PRODUCT VERSION :
java version "1.5.0-rc"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-rc-b63)
Java HotSpot(TM) Server VM (build 1.5.0-rc-b63, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Linux tao 2.6.5-gentoo-r1 #1 SMP Thu Jun 17 12:47:48 GMT 2004 i686  customer (R) Xeon(TM) CPU 1700MHz GenuineIntel GNU/Linux


A DESCRIPTION OF THE PROBLEM :
On Linux unlike Solaris,  FileChannel.transferTo when invoked on an outgoing
socket in non-blocking mode does not  return the correct  value of  -1  (EAGAIN)
in the situation that the socket buffer (send) is full.

  To reproduce is very simple. Just  connect to a server and set the server socket in non-blocking mode and attempt to do a file xfer larger than the max TCP send buffer (64K) .   If the other end is slow in reading  the socket buffer will fill up soon and cause sendfile()  to return EAGAIN (-1)  at the Linux system call level.

Unfortunately on Linux, the FileChannelImpl.c  just doesn't check if the return value is -1, it always throws a java.io.IOException with the cause as err msg corresponding to EAGAIN. On Solaris no such problem occurs as it correctly returns IOS_UNAVAILABLE status to the Java  code.

Obviously this makes transferTo unsuitable for non-blocking IO with NIO on Linux!!

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1.  Have a simple server that after accepting a connection doesn't do any reads or does the ready very slowly (add a long sleep)

2. Have the client (NIO)  connect with the server with non-blocking option on the connected socket.

3. Make the client invoke transferTo on a large file (> 400K for example) . Just need to ensure that send socket buffer fills up and EAGAIN is retruned. Make sure you keep looping  to complete the full xfer .

4. On Linux do a > strace  on the JVM, to verify sendfile() is returning -1 or EGAIN on the last invocation

You will see the transferTo function throw :

Resource temporarily unavailable
java.io.IOException: Resource temporarily unavailable
	at sun.nio.ch.FileChannelImpl.transferTo0(Native Method)
	at sun.nio.ch.FileChannelImpl.transferToDirectly(FileChannelImpl.java:416)
	at sun.nio.ch.FileChannelImpl.transferTo(FileChannelImpl.java:517)
	at org.nirala.tests.jvm.TestSendfile.testSendfile(TestSendfile.java:57)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)


We tried this on JDK1.4.2 and various 1.5.0 rels with the same problem.



EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Should return 0 if no more bytes can be  xfered due to EAGAIN retruned by the underlying Linux sendfile() impl. This will allow Java code to use a selector to poll for write readiness of the socket and attempt transferTo when socket is ready.

No such issues on Windows.
ACTUAL -
You will see the transferTo function throw :

Resource temporarily unavailable
java.io.IOException: Resource temporarily unavailable
	at sun.nio.ch.FileChannelImpl.transferTo0(Native Method)
	at sun.nio.ch.FileChannelImpl.transferToDirectly(FileChannelImpl.java:416)
	at sun.nio.ch.FileChannelImpl.transferTo(FileChannelImpl.java:517)
	at org.nirala.tests.jvm.TestSendfile.testSendfile(TestSendfile.java:57)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

We tried this on JDK1.4.2 and various 1.5.0 rels with the same problem.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
Here is a snippet from our tests :

//  Client driver :

 public void testSendfile() throws IOException {
    String host = "laptop";
    int port = 9011;
    SocketAddress sad = new InetSocketAddress(host, port);
    SocketChannel sc = SocketChannel.open();
    sc.connect(sad);
    sc.configureBlocking(block);

    String fname = "tmp-snefile-"+(new GUID());
    FileProposerExample.stuffFile(fname, fsize);
    FileChannel fc = Config.getFileChannel(fname);

    long nsent = 0, curnset = 0;
    while(nsent != fsize && nsent >= 0) {
      /*
      _logger.info("sendfile ("+nsent+", "+ ((fsize -nsent) - 2000) +")");
      curnset = fc.transferTo(nsent, (fsize - nsent) - 2000, sc);
      _logger.info(" actually sent = "+curnset);
      nsent += curnset;
      */
      _logger.info("sendfile ("+nsent+", "+ (fsize -nsent) +")");
      curnset =  fc.transferTo(nsent, fsize - nsent, sc);
       nsent += curnset;
      _logger.info(" actually sent = "+curnset);
    }
    fc.close();
    File f = new File(fname);
    f.delete();
  }


A simple server to simulate slow read or no read. just put a  customer  on read  and let the accept happen :

public class DevNullServer  {
  ServerSocketChannel listener = null;
  protected void mySetup()
  {
    InetSocketAddress listenAddr =  new InetSocketAddress(9011);

    try {
      listener = ServerSocketChannel.open();
      ServerSocket ss = listener.socket();
      ss.setReuseAddress(true);
      ss.bind(listenAddr);
      System.out.println("Listening on port : "+ listenAddr.toString());
    } catch (IOException e) {
      System.out.println("Failed to bind, is port : "+ listenAddr.toString()
          + " already in use ? Error Msg : "+e.getMessage());
      e.printStackTrace();
    }

  }

  public static void main(String[] args)
  {
    DevNullServer dns = new DevNullServer();
    dns.mySetup();
    dns.listen();
  }

  private void listen()
  {
    ByteBuffer dst = ByteBuffer.allocate(4096);
    try {
      while(true) {
        SocketChannel conn = listener.accept();
        System.out.println("Accepted : "+conn);
        conn.configureBlocking(true);
        int nread = 0;
        while (nread != -1)  {
          try {
          // make sure u  customer  here to prolong the read time and cause
          //  send/recv  TCP buffers to fill up.,
          nread = conn.read(dst);
          } catch (IOException e) {
            e.printStackTrace();
            nread = -1;
          }

          dst.rewind();
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Don't use transferTo() or catch IOException, check the error
string and if it says "Resource temporarily unavailable" presume
EAGAIN happened and retry the transferTo at the same position
after selector indicates Write Readiness.
(Incident Review ID: 310872) 
======================================================================
Posted Date : 2006-09-05 08:39:53.0
Work Around
N/A
Evaluation
Yes, this is a bug as we are missing the check for EGAIN on Linux.
Posted Date : 2006-09-05 08:39:53.0
Comments
  
  Include a link with my name & email   

Submitted On 25-SEP-2004
b_rahul
Actually my  bug report's title is not correct. TranferTo should return 0 at EAGAIN and not -1  to indicate no  bytes were transfered.


Submitted On 21-JUN-2006
Besides EAGAIN, sendfile() can also return EINTR resulting in a similar problem. See bug entry 6427312. It's probably easier to fix both together.


Submitted On 27-DEC-2007
scockroach
Still seeing this problem with Java(TM) SE Runtime Environment (build 1.6.0_02-b05) on Linux 2.6.22.


Submitted On 16-JAN-2008
b_rahul
It would be nice if in the future the actual J2Se version number was mentioned. After a bit of googling it appears J2SE7  is Dolphin release. So scockroach  J2SE6 won't have the bug fix.



PLEASE NOTE: JDK6 is formerly known as Project Mustang