Monday, October 11, 2010

Java URLConnection provides no fail-safe timeout on reads

While it does sport a setReadTimeout() call, it does not work if the server returns some data and then gets stuck. There are servers like this in the real world, for example try this:

curl http://www.geo-mobile.com/nuke/index.php

You have to use a separate timer thread to timeout any reads.


                try {
                    URLConnection con = urlObj.openConnection();
                    con.setConnectTimeout(5000);
                    con.setReadTimeout(5000);
                    new Thread(new InterruptThread(Thread.currentThread(), con)).start();
                    retVal = new Source(con);
                } catch (IOException e) {
                    System.err.println("aborted parsing due to I/O: " + e.getMessage());
                    throw new IndexingException("aborted parsing due to timeout - " + aUrl);
                }

In this example, the Source constructor, using the Jericho parser retrieves a web page and it can get stuck on a socket read call. The timeouts don't have any effect. And it is not possible to interrupt a thread waiting on I/O using Thread.interrupt().

The solution is the hand-crafted InterruptThread class:


public class InterruptThread implements Runnable {
    Thread parent;
    URLConnection con;
    public InterruptThread(Thread parent, URLConnection con) {
        this.parent = parent;
        this.con = con;
    }

    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {

        }
        System.out.println("Timer thread forcing parent to quit connection");
        ((HttpURLConnection)con).disconnect();
        System.out.println("Timer thread closed connection held by parent, exiting");
    }
}

Here, the InterruptThread forcibly closes the URLConnection held by the parent thread, forcing the Source constructor of the parent thread to throw an IOException, which is handled by its catch block, thus avoiding the hang.

12 comments:

Anonymous said...

Thanks a lot, it seems to solve my problem with stuck connections.

Bye

Frédéric Chopin said...

Thanks men!...

Anonymous said...

any other solution besides using a new thread??

thushara said...

It doesn't have to be a "new" thread. If you can pass the connection to an existing thread, that would work too. but I can't think of how to tackle this with only the main thread of execution.

:) said...

I've created a post with an example:
http://leakfromjavaheap.blogspot.com/2013/12/are-connecttimeout-and-readtimeout.html

Anonymous said...

Thank you so much for this trick. I have tried it on Android URLConnection and works fine.

Unknown said...

In my case the problem is that it didn't raise the IOException. What could be happening?
Here is the log proving that was called.
11-12 10:36:23.902: I/System.out(11985): Timer thread forcing parent to quit connection
11-12 10:36:23.912: I/System.out(11985): Timer thread closed connection held by parent, exiting

XuanBoy said...

Thanks so much. But I see that you create but never use Parent Thread

thushara said...

You mean the passed in reference to the parent thread is not used? This can be used for diagnostics - the parent thread id miht be useful specially if you use this class with multiple concurrent threads.

ff said...

I think it is:
public void run() {
try {
Thread.sleep(5000);
System.out.println("Timer thread forcing parent to quit connection");
((HttpURLConnection)con).disconnect();
System.out.println("Timer thread closed connection held by parent, exiting");
} catch (InterruptedException e) {

}

}

Roger Pack said...

I thought that readTimeout worked OK though?

logidelic said...

FWIW, HttpURLConnection docs state that it is not thread safe, so it is not safe to call disconnect() from another thread while who-knows-what is going on. That said, there are no other (good) solutions to this problem.? Lovely. :)