Uploaded image for project: 'JBoss Enterprise Application Platform'
  1. JBoss Enterprise Application Platform
  2. JBEAP-4845

receiveFullBytes callbacks may not be called on error with some protocols

    Details

    • Target Release:
    • Steps to Reproduce:
      Hide

      Run the unit test above

      Show
      Run the unit test above
    • Sprint:
      EAP 7.0.1

      Description

      I've got a case where I can call receiveFullBytes but neither the success
      nor the error handler is ever called. My code looks like this:

      public static CompletableFuture<ByteSource>
      getCompleteBody(HttpServerExchange exchange) {
        log.debug("In getCompleteBody.");
        CompletableFuture<ByteSource> finalResult = new CompletableFuture<>();
        try {
          log.debug("Calling receiveFullBytes.");
          exchange.getRequestReceiver().receiveFullBytes(
              // Success case.
              (HttpServerExchange excng, byte[] bytes) -> {
                log.debug("getFullBytes completed with success.");
                finalResult.complete(ByteSource.wrap(bytes));
              },
      
              // Error case
              (HttpServerExchange exchng, IOException t) -> {
                log.warn("getFullBytes completed with an error:", t);
                finalResult.completeExceptionally(t);
              }
          );
        } catch (Throwable t) {
          log.warn("receiveFullBytes threw an exception:", t);
          finalResult.completeExceptionally(t);
        }
      
        return finalResult;
      }
      

      I've got a unit test that opens a socket, starts sending data, and
      then, intentionally throws an exception. The test then ensures that
      the CompletableFuture above completes with an exception. Except, it
      never completes; the test just hangs forever. You can see that the
      "log.debug("Calling receiveFullBytes.")" line does get called and that
      the neither of the log lines in either callback is ever called. The
      full test looks like this:

      @Test
      public void getCompleteBodyCompletesWithExceptionOnBodyFailure()
      throws Exception {
        AtomicBoolean futureRedeemedWithError = new AtomicBoolean(false);
        CountDownLatch waitForInputToFail = new CountDownLatch(1);
      
        HttpHandler handler = new HttpHandler() {
          @Override
          public void handleRequest(HttpServerExchange exchange) throws Exception {
            log.info("Handler called.");
            exchange.dispatch();
            log.info("Calling getCompleteBody");
            UndertowUtils.getCompleteBody(exchange).exceptionally((Throwable t) -> {
              log.debug("As expected, received an exception:", t);
              futureRedeemedWithError.set(true);
              waitForInputToFail.countDown();
              return null;
            });
          }
        };
        UndertowTestUtils.RunningServer server =
      UndertowTestUtils.startUndertowOnFreePort(handler);
      
        // A Body() implementation that will return a few chunks of data but the fail
        Body failingRequestBody = new Body() {
          private AtomicInteger readCalls = new AtomicInteger(0);
          @Override
          public long getContentLength() {
            return 1000;
          }
      
          @Override
          public long read(ByteBuffer buffer) throws IOException {
            if (readCalls.getAndIncrement() < 2) {
              byte[] toSend = "some data".getBytes(Charsets.UTF_8);
              buffer.put(toSend);
              return toSend.length;
            } else {
              throw new RuntimeException("Fake error on sending data.");
            }
          }
      
          @Override
          public void close() throws IOException {
          }
        };
      
        // A body generator using the above, busted Body.
        BodyGenerator failingBodyGenerator = new BodyGenerator() {
          @Override
          public Body createBody() throws IOException {
            return failingRequestBody;
          }
        };
      
        // This will start sending data so our handler gets invoked but it
      will then fail partway through.
        SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()
            .setUrl(String.format("http://localhost:%s", server.getPort()))
            .build();
      
        client.post(failingBodyGenerator);
      
      
        // Test hangs here forever
        waitForInputToFail.await();
      
        assertThat(futureRedeemedWithError.get()).isTrue();
      }
      

      This feels like a bug. I'd expect the contract of "receiveFullBytes" to be
      that either the success or failure handler gets called exactly once.

        Gliffy Diagrams

          Attachments

            Issue Links

              Activity

                People

                • Assignee:
                  swd847 Stuart Douglas
                  Reporter:
                  oliverdain Oliver Dain
                  Tester:
                  Jan Stourac
                • Votes:
                  0 Vote for this issue
                  Watchers:
                  3 Start watching this issue

                  Dates

                  • Created:
                    Updated:
                    Resolved: