3

I am using Apache HttpClient 4 to communicate with a REST API and most of the time I do lengthy PUT operations. Since these may happen over an unstable Internet connection I need to detect if the connection is interrupted and possibly need to retry (with a resume request).

To try my routines in the real world I started a PUT operation and then I flipped the Wi-Fi switch of my laptop, causing an immediate total interruption of any data flow. However it takes a looong time (maybe 5 minutes or so) until eventually a SocketException is thrown.

How can I speed up to process? I'd like to set a timeout of maybe something around 30 seconds.

Update:

To clarify, my request is a PUT operation. So for a very long time (possibly hours) the only operation is a write() operation and there are no read operations. There is a timeout setting for read() operations, but I could not find one for write operations.

I am using my own Entity implementation and thus I write directly to an OutputStream which will pretty much immediately block once the Internet connection is interrupted. If OutputStreams had a timeout parameter so I could write out.write(nextChunk, 30000); I could detect such a problem myself. Actually I tried that:

public class TimeoutHttpEntity extends HttpEntityWrapper {

  public TimeoutHttpEntity(HttpEntity wrappedEntity) {
    super(wrappedEntity);
  }

  @Override
  public void writeTo(OutputStream outstream) throws IOException {
    try(TimeoutOutputStreamWrapper wrapper = new TimeoutOutputStreamWrapper(outstream, 30000)) {
      super.writeTo(wrapper);
    }
  }
}


public class TimeoutOutputStreamWrapper extends OutputStream {
  private final OutputStream delegate;
  private final long timeout;
  private final ExecutorService executorService = Executors.newSingleThreadExecutor();

  public TimeoutOutputStreamWrapper(OutputStream delegate, long timeout) {
    this.delegate = delegate;
    this.timeout = timeout;
  }

  @Override
  public void write(int b) throws IOException {
    executeWithTimeout(() -> {
      delegate.write(b);
      return null;
    });
  }

  @Override
  public void write(byte[] b) throws IOException {
    executeWithTimeout(() -> {
      delegate.write(b);
      return null;
    });
  }

  @Override
  public void write(byte[] b, int off, int len) throws IOException {
    executeWithTimeout(() -> {
      delegate.write(b, off, len);
      return null;
    });
  }

  @Override
  public void close() throws IOException {
    try {
      executeWithTimeout(() -> {
        delegate.close();
        return null;
      });
    } finally {
      executorService.shutdown();
    }
  }

  private void executeWithTimeout(final Callable<?> task) throws IOException {
    try {
      executorService.submit(task).get(timeout, TimeUnit.MILLISECONDS);
    } catch (TimeoutException e) {
      throw new IOException(e);
    } catch (ExecutionException e) {
      final Throwable cause = e.getCause();
      if (cause instanceof IOException) {
        throw (IOException)cause;
      }
      throw new Error(cause);
    } catch (InterruptedException e) {
      throw new Error(e);
    }
  }
}

public class TimeoutOutputStreamWrapperTest {
  private static final byte[] DEMO_ARRAY = new byte[]{1,2,3};
  private TimeoutOutputStreamWrapper streamWrapper;
  private OutputStream delegateOutput;

  public void setUp(long timeout) {
    delegateOutput = mock(OutputStream.class);
    streamWrapper = new TimeoutOutputStreamWrapper(delegateOutput, timeout);
  }

  @AfterMethod
  public void teardown() throws Exception {
    streamWrapper.close();
  }

  @Test
  public void write_writesByte() throws Exception {
    // Setup
    setUp(Long.MAX_VALUE);

    // Execution
    streamWrapper.write(DEMO_ARRAY);

    // Evaluation
    verify(delegateOutput).write(DEMO_ARRAY);
  }

  @Test(expectedExceptions = DemoIOException.class)
  public void write_passesThruException() throws Exception {
    // Setup
    setUp(Long.MAX_VALUE);
    doThrow(DemoIOException.class).when(delegateOutput).write(DEMO_ARRAY);

    // Execution
    streamWrapper.write(DEMO_ARRAY);

    // Evaluation performed by expected exception
  }

  @Test(expectedExceptions = IOException.class)
  public void write_throwsIOException_onTimeout() throws Exception {
    // Setup
    final CountDownLatch executionDone = new CountDownLatch(1);
    setUp(100);
    doAnswer(new Answer<Void>() {
      @Override
      public Void answer(InvocationOnMock invocation) throws Throwable {
        executionDone.await();
        return null;
      }
    }).when(delegateOutput).write(DEMO_ARRAY);

    // Execution
    try {
      streamWrapper.write(DEMO_ARRAY);
    } finally {
      executionDone.countDown();
    }

    // Evaluation performed by expected exception
  }

  public static class DemoIOException extends IOException {

  }
}

This is somewhat complicated, but it works quite well in my unit tests. And it works in real life as well, except that the HttpRequestExecutor catches the exception in line 127 and tries to close the connection. However when trying to close the connection it first tries to flush the connection which again blocks.

I might be able to dig deeper in HttpClient and figure out how to prevent this flush operation, but it is already a not too pretty solution, and it is just about to get even worse.

UPDATE:

It looks like this can't be done on the Java level. Can I do it on another level? (I am using Linux).

yankee
  • 38,872
  • 15
  • 103
  • 162
  • Did you check [this](http://stackoverflow.com/questions/6764035/apache-httpclient-timeout)? – Kevin Rave Aug 19 '14 at 16:23
  • @KevinRave: I read it. But I did not see anything which helps. What do you suggest? Use my above posted approach to detect lost connection, and combine it with the accepted answer (get connection manager and shutdown the connection)? How would that look like? The IOException thrown in my code above already causes a connection close, but it is ineffective. – yankee Aug 19 '14 at 16:58
  • There are two types of timeouts in HTTP Connection. If those wouldn't work out, (and I think is the likely in your case), then you could follow the approach you mentioned. – Kevin Rave Aug 19 '14 at 18:46

3 Answers3

5

Java blocking I/O does not support socket timeout for write operations. You are entirely at the mercy of the OS / JRE to unblock the thread blocked by the write operation. Moreover, this behavior tends to be OS / JRE specific.

This might be a legitimate case to consider using a HTTP client based on non-blocking I/O (NIO) such as Apache HttpAsyncClient.

ok2c
  • 26,450
  • 5
  • 63
  • 71
  • DO you happen to know how I tune my OS (Linux)? – yankee Aug 25 '14 at 16:22
  • @yankee I have been maintaining HttpClient for 10+ years. I have never come across an effective way of aborting a write op other than socket shutdown. I am not aware of any platform specific tweaks either – ok2c Aug 25 '14 at 17:29
  • @oleg can you use HttpClient w/o the socket implementation? It may be easier for OP just to create a NIO socket and use HttpClient to build/read the packets. – bond Aug 25 '14 at 18:36
  • @misterbiscuit it just does not work this way as there is no such thing as NIO socket. NIO has a different abstraction called Channels – ok2c Aug 25 '14 at 19:55
  • @oleg at the OS all you have is a file descriptor; which api you use to read and write to that FD determines how events are triggered. The term "Channel" used in Java for NIO means absolutely nothing. Both Blocking and Non Blocking IO in Java are abstractions to use one or the other networking apis. – bond Aug 25 '14 at 20:10
  • @misterbiscuit this is all fine and dandy but HttpClient relies on those meaningless abstractions – ok2c Aug 25 '14 at 20:32
  • @oleg my original question was: can HttpClient be used without a Socket/Channel? If so OP could assemble a very simple non-threaded NIO adaption for HttpClient that would give a very robust timeout mechanism. – bond Aug 25 '14 at 21:41
  • @misterbiscuit HC can be customized to read from and write to arbitrary streams, but those streams need to be bound to a system resource of some sort. – ok2c Aug 25 '14 at 21:58
0

You can configure the socket timeout using RequestConfig:

RequestConfig myRequestConfig = RequestConfig.custom()
    .setSocketTimeout(5000)  // 5 seconds
    .build();

When, when you do the call, just assign your new configuration. For instance,

HttpPut httpPut = new HttpPut("...");
httpPut.setConfig(requestConfig);
...
HttpClientContext context = HttpClientContext.create();
....
httpclient.execute(httpPut, context);

For more information regarthing timeout configurations, here there is a good explanation.

ipinyol
  • 336
  • 2
  • 12
  • The socket timeout refers to the SoTimeout. The [documentation](http://docs.oracle.com/javase/7/docs/api/java/net/Socket.html#setSoTimeout%28int%29) says: `With this option set to a non-zero timeout, a read() call on the InputStream associated with this Socket will block for only this amount of time`. But I am not doing any read calls. Just write calls. I tried it nevertheless, but it did not have the desired effect :-(. – yankee Aug 07 '14 at 18:47
  • @yankee The only way to know that the write has finished is to obtain the HTTP response 200 OK from the server. Blocking IO almost never blocks on writes. HttpClient is blocking on the read. – bond Aug 25 '14 at 18:34
0

Her is one of the link i came across which talks connection eviction policy : here

public static class IdleConnectionMonitorThread extends Thread {

private final HttpClientConnectionManager connMgr;
private volatile boolean shutdown;

public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
    super();
    this.connMgr = connMgr;
}

@Override
public void run() {
    try {
        while (!shutdown) {
            synchronized (this) {
                wait(5000);
                // Close expired connections
                connMgr.closeExpiredConnections();
                // Optionally, close connections
                // that have been idle longer than 30 sec
                connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
            }
        }
    } catch (InterruptedException ex) {
        // terminate
    }
}

public void shutdown() {
    shutdown = true;
    synchronized (this) {
        notifyAll();
    }
}}

I think you might want to look at this.

Madhusudan Joshi
  • 4,438
  • 3
  • 26
  • 42