@Test
  public void testExceptionFromResponseHandler() throws Exception {
    TestingTicker ticker = new TestingTicker();
    AtomicReference<Duration> tickerIncrement =
        new AtomicReference<>(new Duration(0, TimeUnit.SECONDS));

    TestingHttpClient.Processor processor =
        (input) -> {
          Duration delta = tickerIncrement.get();
          ticker.increment(delta.toMillis(), TimeUnit.MILLISECONDS);
          throw new RuntimeException("Foo");
        };

    CyclicBarrier requestComplete = new CyclicBarrier(2);
    TestingClientCallback callback = new TestingClientCallback(requestComplete);

    URI location = URI.create("http://localhost:8080");
    HttpPageBufferClient client =
        new HttpPageBufferClient(
            new TestingHttpClient(processor, executor),
            new DataSize(10, Unit.MEGABYTE),
            new Duration(1, TimeUnit.MINUTES),
            location,
            callback,
            blockEncodingManager,
            executor,
            Stopwatch.createUnstarted(ticker));

    assertStatus(client, location, "queued", 0, 0, 0, 0, "not scheduled");

    // request processor will throw exception, verify the request is marked a completed
    // this starts the error stopwatch
    client.scheduleRequest();
    requestComplete.await(10, TimeUnit.SECONDS);
    assertEquals(callback.getPages().size(), 0);
    assertEquals(callback.getCompletedRequests(), 1);
    assertEquals(callback.getFinishedBuffers(), 0);
    assertEquals(callback.getFailedBuffers(), 0);
    assertStatus(client, location, "queued", 0, 1, 1, 1, "not scheduled");

    // advance time forward, but not enough to fail the client
    tickerIncrement.set(new Duration(30, TimeUnit.SECONDS));

    // verify that the client has not failed
    client.scheduleRequest();
    requestComplete.await(10, TimeUnit.SECONDS);
    assertEquals(callback.getPages().size(), 0);
    assertEquals(callback.getCompletedRequests(), 2);
    assertEquals(callback.getFinishedBuffers(), 0);
    assertEquals(callback.getFailedBuffers(), 0);
    assertStatus(client, location, "queued", 0, 2, 2, 2, "not scheduled");

    // advance time forward beyond the minimum error duration
    tickerIncrement.set(new Duration(31, TimeUnit.SECONDS));

    // verify that the client has failed
    client.scheduleRequest();
    requestComplete.await(10, TimeUnit.SECONDS);
    assertEquals(callback.getPages().size(), 0);
    assertEquals(callback.getCompletedRequests(), 3);
    assertEquals(callback.getFinishedBuffers(), 0);
    assertEquals(callback.getFailedBuffers(), 1);
    assertInstanceOf(callback.getFailure(), PageTransportTimeoutException.class);
    assertContains(
        callback.getFailure().getMessage(),
        WORKER_NODE_ERROR + " (http://localhost:8080/0 - requests failed for 61.00s)");
    assertStatus(client, location, "queued", 0, 3, 3, 3, "not scheduled");
  }
  @Test
  public void testHappyPath() throws Exception {
    Page expectedPage = new Page(100);

    DataSize expectedMaxSize = new DataSize(11, Unit.MEGABYTE);
    MockExchangeRequestProcessor processor = new MockExchangeRequestProcessor(expectedMaxSize);

    CyclicBarrier requestComplete = new CyclicBarrier(2);

    TestingClientCallback callback = new TestingClientCallback(requestComplete);

    URI location = URI.create("http://localhost:8080");
    HttpPageBufferClient client =
        new HttpPageBufferClient(
            new TestingHttpClient(processor, executor),
            expectedMaxSize,
            new Duration(1, TimeUnit.MINUTES),
            location,
            callback,
            blockEncodingManager,
            executor,
            Stopwatch.createUnstarted());

    assertStatus(client, location, "queued", 0, 0, 0, 0, "not scheduled");

    // fetch a page and verify
    processor.addPage(location, expectedPage);
    callback.resetStats();
    client.scheduleRequest();
    requestComplete.await(10, TimeUnit.SECONDS);

    assertEquals(callback.getPages().size(), 1);
    assertPageEquals(expectedPage, callback.getPages().get(0));
    assertEquals(callback.getCompletedRequests(), 1);
    assertEquals(callback.getFinishedBuffers(), 0);
    assertStatus(client, location, "queued", 1, 1, 1, 0, "not scheduled");

    // fetch no data and verify
    callback.resetStats();
    client.scheduleRequest();
    requestComplete.await(10, TimeUnit.SECONDS);

    assertEquals(callback.getPages().size(), 0);
    assertEquals(callback.getCompletedRequests(), 1);
    assertEquals(callback.getFinishedBuffers(), 0);
    assertStatus(client, location, "queued", 1, 2, 2, 0, "not scheduled");

    // fetch two more pages and verify
    processor.addPage(location, expectedPage);
    processor.addPage(location, expectedPage);
    callback.resetStats();
    client.scheduleRequest();
    requestComplete.await(10, TimeUnit.SECONDS);

    assertEquals(callback.getPages().size(), 2);
    assertPageEquals(expectedPage, callback.getPages().get(0));
    assertPageEquals(expectedPage, callback.getPages().get(1));
    assertEquals(callback.getCompletedRequests(), 1);
    assertEquals(callback.getFinishedBuffers(), 0);
    assertEquals(callback.getFailedBuffers(), 0);
    callback.resetStats();
    assertStatus(client, location, "queued", 3, 3, 3, 0, "not scheduled");

    // finish and verify
    callback.resetStats();
    processor.setComplete(location);
    client.scheduleRequest();
    requestComplete.await(10, TimeUnit.SECONDS);

    // get the buffer complete signal
    assertEquals(callback.getPages().size(), 0);
    assertEquals(callback.getCompletedRequests(), 1);

    // schedule the delete call to the buffer
    callback.resetStats();
    client.scheduleRequest();
    requestComplete.await(10, TimeUnit.SECONDS);
    assertEquals(callback.getFinishedBuffers(), 1);

    assertEquals(callback.getPages().size(), 0);
    assertEquals(callback.getCompletedRequests(), 0);
    assertEquals(callback.getFailedBuffers(), 0);

    assertStatus(client, location, "closed", 3, 5, 5, 0, "not scheduled");
  }
  @Test
  public void testInvalidResponses() throws Exception {
    CyclicBarrier beforeRequest = new CyclicBarrier(1);
    CyclicBarrier afterRequest = new CyclicBarrier(1);
    StaticRequestProcessor processor = new StaticRequestProcessor(beforeRequest, afterRequest);

    CyclicBarrier requestComplete = new CyclicBarrier(2);
    TestingClientCallback callback = new TestingClientCallback(requestComplete);

    URI location = URI.create("http://localhost:8080");
    HttpPageBufferClient client =
        new HttpPageBufferClient(
            new TestingHttpClient(processor, executor),
            new DataSize(10, Unit.MEGABYTE),
            new Duration(1, TimeUnit.MINUTES),
            location,
            callback,
            blockEncodingManager,
            executor,
            Stopwatch.createUnstarted());

    assertStatus(client, location, "queued", 0, 0, 0, 0, "not scheduled");

    // send not found response and verify response was ignored
    processor.setResponse(
        new TestingResponse(
            HttpStatus.NOT_FOUND,
            ImmutableListMultimap.of(CONTENT_TYPE, PRESTO_PAGES),
            new byte[0]));
    client.scheduleRequest();
    requestComplete.await(10, TimeUnit.SECONDS);
    assertEquals(callback.getPages().size(), 0);
    assertEquals(callback.getCompletedRequests(), 1);
    assertEquals(callback.getFinishedBuffers(), 0);
    assertEquals(callback.getFailedBuffers(), 1);
    assertInstanceOf(callback.getFailure(), PageTransportErrorException.class);
    assertContains(
        callback.getFailure().getMessage(),
        "Expected response code to be 200, but was 404 Not Found");
    assertStatus(client, location, "queued", 0, 1, 1, 1, "not scheduled");

    // send invalid content type response and verify response was ignored
    callback.resetStats();
    processor.setResponse(
        new TestingResponse(
            HttpStatus.OK, ImmutableListMultimap.of(CONTENT_TYPE, "INVALID_TYPE"), new byte[0]));
    client.scheduleRequest();
    requestComplete.await(10, TimeUnit.SECONDS);
    assertEquals(callback.getPages().size(), 0);
    assertEquals(callback.getCompletedRequests(), 1);
    assertEquals(callback.getFinishedBuffers(), 0);
    assertEquals(callback.getFailedBuffers(), 1);
    assertInstanceOf(callback.getFailure(), PageTransportErrorException.class);
    assertContains(
        callback.getFailure().getMessage(),
        "Expected application/x-presto-pages response from server but got INVALID_TYPE");
    assertStatus(client, location, "queued", 0, 2, 2, 2, "not scheduled");

    // send unexpected content type response and verify response was ignored
    callback.resetStats();
    processor.setResponse(
        new TestingResponse(
            HttpStatus.OK, ImmutableListMultimap.of(CONTENT_TYPE, "text/plain"), new byte[0]));
    client.scheduleRequest();
    requestComplete.await(10, TimeUnit.SECONDS);
    assertEquals(callback.getPages().size(), 0);
    assertEquals(callback.getCompletedRequests(), 1);
    assertEquals(callback.getFinishedBuffers(), 0);
    assertEquals(callback.getFailedBuffers(), 1);
    assertInstanceOf(callback.getFailure(), PageTransportErrorException.class);
    assertContains(
        callback.getFailure().getMessage(),
        "Expected application/x-presto-pages response from server but got text/plain");
    assertStatus(client, location, "queued", 0, 3, 3, 3, "not scheduled");

    // close client and verify
    client.close();
    requestComplete.await(10, TimeUnit.SECONDS);
    assertStatus(client, location, "closed", 0, 3, 4, 3, "not scheduled");
  }