JDK-8222968 : ByteArrayPublisher is not thread-safe resulting in broken re-use of HttpRequests
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.net
  • Affected Version: 11,12,13
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_10
  • CPU: x86_64
  • Submitted: 2019-04-18
  • Updated: 2020-06-01
  • Resolved: 2019-06-28
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 13 JDK 14
11.0.5Fixed 13.0.2Fixed 14 b04Fixed
Description
A DESCRIPTION OF THE PROBLEM :
When using ByteArrayPublisher for sending out HttpRequest data, asynchronously / from multiple threads, reusing the same instance of HttpRequest built with ByteArrayPublisher, some requests would be broken because publisher is not thread safe and may wrongly reuse same instance of delegate for multiple requests.

Root cause is in the following lines:
        @Override
        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
            List<ByteBuffer> copy = copy(content, offset, length);
            >> this.delegate << = new PullPublisher<>(copy);
            delegate.subscribe(subscriber);
        }

delegate should not have been assigned to a member since a context-switch between delegate assignment and call to subscribe, might cause later delegate to be wrongly reused. Instead it should have simply been a local variable.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run test code, several times.

We've observed failure every 4-5 runs.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Program completes without an issue
ACTUAL -
At times, the following is being reported:

java.util.concurrent.CompletionException: java.io.IOException: SSLTube(SocketTube(76)) [HttpClient-1-Worker-79] Too few bytes returned by the publisher (0/1)


---------- BEGIN SOURCE ----------
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;

public class SimpleRunner {
    private static final HttpRequest.BodyPublisher BODY_PUBLISHER = HttpRequest.BodyPublishers.ofByteArray("a".getBytes());

    public static void main(String[] args) throws Exception {
        HttpClient client = createClient();

        Collection<CompletableFuture> futures = new ArrayList<>();
        for (int i=0;i<100;i++) {
            futures.add(client.sendAsync(createRequest(), HttpResponse.BodyHandlers.discarding()).exceptionally(t -> {t.printStackTrace(); return null;}));
        }
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
    }

    private static HttpRequest createRequest() throws URISyntaxException {
        HttpRequest.Builder builder = HttpRequest.newBuilder(new URI("https://www.oracle.com"))
                .method("POST", BODY_PUBLISHER)
                .version(HttpClient.Version.HTTP_1_1);
        builder.header("content-type", "text/plain");
        return builder.build();
    }

    private static HttpClient createClient() {
        return HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_1_1)
                .build();

    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Use HttpRequest.BodyPublishers.ofByteArrays instead replacing a single line of code:

    private static final HttpRequest.BodyPublisher BODY_PUBLISHER = HttpRequest.BodyPublishers.ofByteArrays(Collections.singletonList("a".getBytes()));


FREQUENCY : occasionally



Comments
Fix Request I think we should port this to a JDK 13 Update release also (too late for 13 GA) It's a reliability fix. Patch applies cleanly. Testing in progress.
22-08-2019

Fix Request I would like to request this be backported to 11u, to increase reliability in multithreaded applications. The problem is not confined to Windows. I was able to duplicate the issue in macOS on jdk11u, and the fix worked as described. The included test case passed. This patch applies cleanly to jdk11u and seems low risk.
21-08-2019

This issue is intermittently reproducible. The submitter had reported the issue on JDK 11.0.3. But with JDK 11, 11.0.2, 11.0.3 , JDK 12, I get the exception "java.util.concurrent.CompletionException: java.io.IOException: HTTP/1.1 header parser received no bytes" (The bug JDK-8217094 seems to have addressed the issue. ) With JDK 13-ea+17, I received the exception that the submitter has reported in the bug "java.util.concurrent.CompletionException: java.io.IOException: SSLTube(SocketTube(71)) [HttpClient-1-Worker-13] Too few bytes returned by the publisher (0/1)"(attached output log file :out_13.log) and just once the exception "java.util.concurrent.CompletionException: java.io.IOException: HTTP/1.1 header parser received no bytes" as well (attached output log file out_13_error2.log) To reproduce the issue, run the attached test case: JDK 11- Fail JDK 11.0.3 - Fail JDK 12 - Fail JDK 13-ea+17 - Fail Output logs attached.
25-04-2019