4

I am here to solve an issue i am facing. I hope to get some help from the experts.

Below is a code of a simple file downloader in java. I want to detect an exception when the connection is broken in the middle of downloading a file. Now, I run the code and in the middle of downloading the file, i turn off my wifi with keyboard wifi off button. Doing this the program hangs forever without throwing any exceptions and without terminating. It seems it blocks forever. Firstly my confusion is why is exception not thrown? Secondly, in the following code you can see this line //con.setReadTimeout(2000); which is currently commented out. Removing the comment and running the program, now if connection breaks in the middle by turning off wifi then it waits for 2 seconds and then if it cannot read then it terminates without throwing exception. So, again in this case why is it just terminating and not throwing any exception? I am very puzzled by this behavior. I hope i can get some help. Thank you very much!

import java.io.FileOutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.*;

    public class Main {
        public static void main(String[] args) {

            try{

                URL website = new URL("http://128f1.downloadming1.com/bollywood%20mp3/Jai%20Ho%20(2014)/06%20-%20Naacho%20Re%20-%20DownloadMing.SE.mp3");

                URLConnection con = website.openConnection();


                //con.setReadTimeout(2000);



                ReadableByteChannel rbc = Channels.newChannel(con.getInputStream());
                FileOutputStream fos = new FileOutputStream("song.mp3");

                fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);


            }
            catch(Exception e){

                System.out.println("got here");
                e.printStackTrace();
            }



            System.out.println("Done downloading...");

        }
    }
user3256520
  • 441
  • 1
  • 4
  • 17
  • 1
    For the timeout situation, the `FileChannel` implementation (from `fos.getChannel()`) might swallow the exception and return the number of bytes written, if any. For the other case, I'm not sure. Try checking the type of your streams and looking at their implementation. – Sotirios Delimanolis Jul 27 '14 at 18:12
  • The first half of this question was already asked by the same user one day earlier: http://stackoverflow.com/questions/24975455/how-do-you-detect-a-network-disconnection-when-downloading-a-file-in-java – Fabian Ritzmann Nov 05 '14 at 00:15

1 Answers1

1

TCP connections are designed to deal with unreliable networks and packet loss. If your network connection is gone, it will not know unless you set the SO_KEEPALIVE socket option (java.net.SocketOptions has some documentation of that option).

Unfortunately, URLConnection does not allow you to set this or any other socket option. And even if it did, the keep alive interval would be very high, two hours on most operating systems. The keep alive interval can only be changed in your operating system settings, it can not be changed through Java (the SocketOptions.SO_KEEPALIVE documentation is confusing in that regard).

Altogether, you don't get an exception because the underlying network protocol is not designed to detect whether your network medium was disrupted after the initial establishment of the connection.

As you found out, setReadTimeout helps somewhat, however you have bumped into an undocumented feature of the transferFrom method. The actual implementation of that method is in sun.nio.ch.FileChannelImpl.transferFromArbitraryChannel. I am pasting the JDK 7 code here:

private long transferFromArbitraryChannel(ReadableByteChannel src,
                                          long position, long count)
    throws IOException
{
    // Untrusted target: Use a newly-erased buffer
    int c = (int)Math.min(count, TRANSFER_SIZE);
    ByteBuffer bb = Util.getTemporaryDirectBuffer(c);
    long tw = 0;                    // Total bytes written
    long pos = position;
    try {
        Util.erase(bb);
        while (tw < count) {
            bb.limit((int)Math.min((count - tw), (long)TRANSFER_SIZE));
            // ## Bug: Will block reading src if this channel
            // ##      is asynchronously closed
            int nr = src.read(bb);
            if (nr <= 0)
                break;
            bb.flip();
            int nw = write(bb, pos);
            tw += nw;
            if (nw != nr)
                break;
            pos += nw;
            bb.clear();
        }
        return tw;
    } catch (IOException x) {
        if (tw > 0)
            return tw;
        throw x;
    } finally {
        Util.releaseTemporaryDirectBuffer(bb);
    }
}

As you can see in the catch block, if some data was already written to the target, it will ignore any IOException and just return the number of bytes that were written.

Fabian Ritzmann
  • 1,345
  • 9
  • 20