Issue4810 (#4823)

* Pending requests are accessed concurrently

Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
diff --git a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/DestinationConnectionPool.java b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/DestinationConnectionPool.java
index a23cd98..976f7b4 100644
--- a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/DestinationConnectionPool.java
+++ b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/DestinationConnectionPool.java
@@ -20,7 +20,6 @@
 import java.net.CookieManager;
 import java.net.URI;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
@@ -37,7 +36,7 @@
     private final Queue<HttpConnection> idleConnections = new ConcurrentLinkedDeque<>();
     private final Set<HttpConnection> connections = Collections.newSetFromMap(new ConcurrentHashMap<>());
     private final Queue<RequestRecord> pendingRequests = new ConcurrentLinkedDeque<>();
-    private final Map<HttpConnection, RequestRecord> requestsInProgress = new HashMap<>();
+    private final Map<HttpConnection, RequestRecord> requestsInProgress = new ConcurrentHashMap<>();
     private final CookieManager cookieManager;
     private final ScheduledExecutorService scheduler;
     private final ConnectionStateListener connectionStateListener;
diff --git a/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/StressTest.java b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/StressTest.java
new file mode 100644
index 0000000..ed43c08
--- /dev/null
+++ b/connectors/jdk-connector/src/test/java/org/glassfish/jersey/jdk/connector/internal/StressTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdk.connector.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.jdk.connector.JdkConnectorProvider;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.junit.Test;
+
+public class StressTest extends JerseyTest {
+
+    private static final int PARALLELISM = 50;
+    private static final int ITERATIONS = 1000;
+
+    @Path("/test")
+    public static class TestResource {
+
+        @GET
+        public String test() {
+            return "test";
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        enable(TestProperties.LOG_TRAFFIC);
+        enable(TestProperties.DUMP_ENTITY);
+        return new ResourceConfig(TestResource.class);
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JdkConnectorProvider());
+    }
+
+    @Test
+    public void randomnessStatus200() throws InterruptedException, ExecutionException {
+        ExecutorService executor = Executors.newFixedThreadPool(PARALLELISM,
+                new ThreadFactoryBuilder().setNameFormat("client-%d").build());
+        for (int i = 0; i < ITERATIONS; i++) {
+            System.out.println("Iteration " + i);
+            List<Future<Response>> responses = new ArrayList<>();
+            for (int j = 0; j < 100; j++) {
+                Future<Response> future = executor.submit(() -> target("/test").request().get());
+                responses.add(future);
+            }
+            for (Future<Response> response : responses) {
+                assertEquals(200, response.get().getStatus());
+            }
+        }
+        executor.shutdown();
+        assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
+    }
+}