1 /* 2 * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 4636628 27 * @summary HttpURLConnection duplicates HTTP GET requests when used with multiple threads 28 */ 29 30 /* 31 * This tests keep-alive behavior using chunkedinputstreams 32 * It checks that keep-alive connections are used and also 33 * that requests are not being repeated (due to errors) 34 * 35 * It also checks that the keepalive connections are closed eventually 36 * because the test will not terminate if the connections 37 * are not closed by the keep-alive timer. 38 */ 39 40 import java.net.*; 41 import java.io.*; 42 import java.time.Duration; 43 import java.util.ArrayList; 44 import java.util.List; 45 46 public class MultiThreadTest extends Thread { 47 48 /* 49 * Is debugging enabled - start with -d to enable. 50 */ 51 static boolean debug = true; // disable debug once stability proven 52 53 static Object threadlock = new Object (); 54 static int threadCounter = 0; 55 56 static Object getLock() { return threadlock; } 57 58 static void debug(String msg) { 59 if (debug) 60 System.out.println(msg); 61 } 62 63 static int reqnum = 0; 64 65 void doRequest(String uri) throws Exception { 66 URL url = new URL(uri + "?foo="+reqnum); 67 reqnum ++; 68 HttpURLConnection http = (HttpURLConnection)url.openConnection(); 69 70 InputStream in = http.getInputStream(); 71 byte b[] = new byte[100]; 72 int total = 0; 73 int n; 74 do { 75 n = in.read(b); 76 if (n > 0) total += n; 77 } while (n > 0); 78 debug ("client: read " + total + " bytes"); 79 in.close(); 80 http.disconnect(); 81 } 82 83 String uri; 84 byte[] b; 85 int requests; 86 87 MultiThreadTest(int port, int requests) throws Exception { 88 uri = "http://localhost:" + 89 port + "/foo.html"; 90 91 b = new byte [256]; 92 this.requests = requests; 93 94 synchronized (threadlock) { 95 threadCounter ++; 96 } 97 } 98 99 public void run () { 100 long start = System.nanoTime(); 101 102 try { 103 for (int i=0; i<requests; i++) { 104 doRequest (uri); 105 } 106 } catch (Exception e) { 107 throw new RuntimeException (e.getMessage()); 108 } finally { 109 synchronized (threadlock) { 110 threadCounter --; 111 if (threadCounter == 0) { 112 threadlock.notifyAll(); 113 } 114 } 115 } 116 debug("client: end - " + Duration.ofNanos(System.nanoTime() - start)); 117 } 118 119 static int threads=5; 120 121 public static void main(String args[]) throws Exception { 122 long start = System.nanoTime(); 123 124 int x = 0, arg_len = args.length; 125 int requests = 20; 126 127 if (arg_len > 0 && args[0].equals("-d")) { 128 debug = true; 129 x = 1; 130 arg_len --; 131 } 132 if (arg_len > 0) { 133 threads = Integer.parseInt (args[x]); 134 requests = Integer.parseInt (args[x+1]); 135 } 136 137 /* start the server */ 138 ServerSocket ss = new ServerSocket(0); 139 Server svr = new Server(ss); 140 svr.start(); 141 142 Object lock = MultiThreadTest.getLock(); 143 synchronized (lock) { 144 for (int i=0; i<threads; i++) { 145 MultiThreadTest t = new MultiThreadTest(ss.getLocalPort(), requests); 146 t.start (); 147 } 148 try { 149 lock.wait(); 150 } catch (InterruptedException e) {} 151 } 152 153 // shutdown server - we're done. 154 svr.shutdown(); 155 156 int cnt = svr.connectionCount(); 157 MultiThreadTest.debug("Connections = " + cnt); 158 int reqs = Worker.getRequests (); 159 MultiThreadTest.debug("Requests = " + reqs); 160 System.out.println ("Connection count = " + cnt + " Request count = " + reqs); 161 if (cnt > threads) { // could be less 162 throw new RuntimeException ("Expected "+threads + " connections: used " +cnt); 163 } 164 if (reqs != threads*requests) { 165 throw new RuntimeException ("Expected "+ threads*requests+ " requests: got " +reqs); 166 } 167 for (Thread worker : svr.workers()) { 168 worker.join(60_000); 169 } 170 171 debug("main thread end - " + Duration.ofNanos(System.nanoTime() - start)); 172 } 173 } 174 175 /* 176 * Server thread to accept connection and create worker threads 177 * to service each connection. 178 */ 179 class Server extends Thread { 180 ServerSocket ss; 181 int connectionCount; 182 boolean shutdown = false; 183 private List<Worker> workers = new ArrayList<>(); 184 185 Server(ServerSocket ss) { 186 this.ss = ss; 187 } 188 189 public synchronized List<Worker> workers() { 190 return workers; 191 } 192 193 public synchronized int connectionCount() { 194 return connectionCount; 195 } 196 197 public synchronized void shutdown() { 198 shutdown = true; 199 } 200 201 public void run() { 202 try { 203 ss.setSoTimeout(2000); 204 205 for (;;) { 206 Socket s; 207 try { 208 MultiThreadTest.debug("server: calling accept."); 209 s = ss.accept(); 210 MultiThreadTest.debug("server: return accept."); 211 } catch (SocketTimeoutException te) { 212 MultiThreadTest.debug("server: STE"); 213 synchronized (this) { 214 if (shutdown) { 215 MultiThreadTest.debug("server: Shuting down."); 216 return; 217 } 218 } 219 continue; 220 } 221 222 int id; 223 Worker w; 224 synchronized (this) { 225 id = connectionCount++; 226 w = new Worker(s, id); 227 workers.add(w); 228 } 229 w.start(); 230 MultiThreadTest.debug("server: Started worker " + id); 231 } 232 233 } catch (Exception e) { 234 e.printStackTrace(); 235 } finally { 236 try { 237 ss.close(); 238 } catch (Exception e) { } 239 } 240 } 241 } 242 243 /* 244 * Worker thread to service single connection - can service 245 * multiple http requests on same connection. 246 */ 247 class Worker extends Thread { 248 Socket s; 249 int id; 250 251 Worker(Socket s, int id) { 252 this.s = s; 253 this.id = id; 254 } 255 256 static int requests = 0; 257 static Object rlock = new Object(); 258 259 public static int getRequests () { 260 synchronized (rlock) { 261 return requests; 262 } 263 } 264 public static void incRequests () { 265 synchronized (rlock) { 266 requests++; 267 } 268 } 269 270 int readUntil (InputStream in, char[] seq) throws IOException { 271 int i=0, count=0; 272 while (true) { 273 int c = in.read(); 274 if (c == -1) 275 return -1; 276 count++; 277 if (c == seq[i]) { 278 i++; 279 if (i == seq.length) 280 return count; 281 continue; 282 } else { 283 i = 0; 284 } 285 } 286 } 287 288 public void run() { 289 long start = System.nanoTime(); 290 291 try { 292 int max = 400; 293 byte b[] = new byte[1000]; 294 InputStream in = new BufferedInputStream (s.getInputStream()); 295 // response to client 296 PrintStream out = new PrintStream( 297 new BufferedOutputStream( 298 s.getOutputStream() )); 299 300 for (;;) { 301 302 // read entire request from client 303 int n=0; 304 305 n = readUntil (in, new char[] {'\r','\n', '\r','\n'}); 306 307 if (n <= 0) { 308 MultiThreadTest.debug("worker: " + id + ": Shutdown"); 309 s.close(); 310 return; 311 } 312 313 MultiThreadTest.debug("worker " + id + 314 ": Read request from client " + 315 "(" + n + " bytes)."); 316 317 incRequests(); 318 out.print("HTTP/1.1 200 OK\r\n"); 319 out.print("Transfer-Encoding: chunked\r\n"); 320 out.print("Content-Type: text/html\r\n"); 321 out.print("Connection: Keep-Alive\r\n"); 322 out.print ("Keep-Alive: timeout=15, max="+max+"\r\n"); 323 out.print("\r\n"); 324 out.print ("6\r\nHello \r\n"); 325 out.print ("5\r\nWorld\r\n"); 326 out.print ("0\r\n\r\n"); 327 out.flush(); 328 329 if (--max == 0) { 330 s.close(); 331 return; 332 } 333 } 334 335 } catch (Exception e) { 336 e.printStackTrace(); 337 } finally { 338 try { 339 s.close(); 340 } catch (Exception e) { } 341 MultiThreadTest.debug("worker: " + id + " end - " + 342 Duration.ofNanos(System.nanoTime() - start)); 343 } 344 } 345 }