16

I have the following code block:

try {
    URL url = new URL("http://site-to-test.com/nonexistingpage.html");

    HttpURLConnection urlc = (HttpURLConnection) url.openConnection();
    urlc.setRequestProperty("User-Agent", CoreProtocolPNames.USER_AGENT);
    urlc.setRequestProperty("Connection", "close");
    urlc.setConnectTimeout(500); // timeout is in milliseconds
    urlc.connect();

    if (urlc.getResponseCode() == 404) {
        // Server was reachable
        Log.i(TAG, "Server is reachable");
    }

} catch (MalformedURLException mue) {
    Log.e(TAG, "MalformedURLException: " + mue.getMessage());
} catch (IOException e) {
    Log.e(TAG, "IOException: " + e.getMessage());
}

When the site-to-test domain is not reachable through the current network, this code blocks for about 30-40 seconds before receiving the IOException. And I specifically set the timeout value to 500ms. What am I missing here? Shouldn't the above block terminate in half a second, regardless of the network state and the site's availability?

András Szepesházi
  • 6,483
  • 5
  • 45
  • 59

2 Answers2

14

It appears that Java URLConnection provides no fail-safe timeout on reads

The solution is, as the article explains, to use a separate Thread for timing, and disconnect the HttpURLConnection manually once the timer thread is done.

András Szepesházi
  • 6,483
  • 5
  • 45
  • 59
  • Is this workaround possible if I use an AsyncTask instead of a Thread? – NoBugs Aug 03 '12 at 04:21
  • I think I found a good solution for my AsyncTask problem in Android. I set a Timer to stop it after a certain delay, and reset the timer when it downloads another piece. This way it doesn't time out on slow connection, but times out if network is disconnected: http://stackoverflow.com/questions/6829801/httpurlconnection-setconnecttimeout-has-no-effect/11790633#11790633 – NoBugs Aug 06 '12 at 21:21
  • I have an `HttpURLConnection` object, get data and try to write it to a file using an `OutputStream` object. `output = connection.getOutputStream();`. After I had switched off Wi-Fi, `ConnectException` was thrown. Does it mean that I can listen to my network connection? – Maksim Dmitriev Jan 18 '13 at 15:42
  • @RedPlanet What version of Android or Java were you using, that gave the exception? On Froyo it doesn't give any exception, that's why I used solution I linked above. – NoBugs Apr 13 '13 at 23:43
  • Scary, but is it still true 10 years later? If `setReadTimeout()` and `setConnectTimeout()` are both called, is it still possible for `HttpURLConnection` to block indefinitely due to a stuck connection? – logidelic Apr 30 '20 at 23:43
  • @logidelic I certainly hope not, but can't confirm - I have been away from Android development for several years now. – András Szepesházi May 04 '20 at 15:02
  • 1
    Thanks for the response. I can confirm: It's still a problem. :) Which is frustrating because the solution proposed in the link assumes that HttpURLConnection is thread-safe; which it is not. :( – logidelic May 04 '20 at 15:09
1

After deep investigations and alot of trails, I found that the best way to implement a timer for AsyncTask (or Service, the object you used to perform background work) away from the HTTP connection class, as sometimes when you disconnect the HTTP connection, this doesn't interrupt the web call, I implemented this class to be used when you need timeout check for your HTTP connection

public abstract class AsyncTaskWithTimer<Params, Progress, Result> extends
    AsyncTask<Params, Progress, Result> {

    private static final int HTTP_REQUEST_TIMEOUT = 30000;

    @Override
    protected Result doInBackground(Params... params) {
        createTimeoutListener();
        return doInBackgroundImpl(params);
    }

    private void createTimeoutListener() {
        Thread timeout = new Thread() {
            public void run() {
                Looper.prepare();

                final Handler handler = new Handler();
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {

                        if (AsyncTaskWithTimer.this != null
                                && AsyncTaskWithTimer.this.getStatus() != Status.FINISHED)
                            AsyncTaskWithTimer.this.cancel(true);
                        handler.removeCallbacks(this);
                        Looper.myLooper().quit();
                    }
                }, HTTP_REQUEST_TIMEOUT);

                Looper.loop();
            }
        };
        timeout.start();
    }

    abstract protected Result doInBackgroundImpl(Params... params);
}

A Sample for this

public class AsyncTaskWithTimerSample extends AsyncTaskWithTimer<Void, Void, Void> {

    @Override
    protected void onCancelled(Void void) {
        Log.d(TAG, "Async Task onCancelled With Result");
        super.onCancelled(result);
    }

    @Override
    protected void onCancelled() {
        Log.d(TAG, "Async Task onCancelled");
        super.onCancelled();
    }

    @Override
    protected Void doInBackgroundImpl(Void... params) {
        // Do background work
        return null;
    };
 }
Michael Updike
  • 644
  • 1
  • 6
  • 13
Ayman Mahgoub
  • 4,152
  • 1
  • 30
  • 27