1 /* 2 * Copyright (c) 2011, 2019, 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 * @library lib/ 27 * @run testng/othervm LdapTimeoutTest 28 * @bug 7094377 8000487 6176036 7056489 8151678 29 * @summary Timeout tests for ldap 30 */ 31 32 import org.testng.Assert; 33 import org.testng.annotations.BeforeTest; 34 import org.testng.annotations.Test; 35 36 import javax.naming.Context; 37 import javax.naming.NamingException; 38 import javax.naming.directory.InitialDirContext; 39 import javax.naming.directory.SearchControls; 40 import java.io.IOException; 41 import java.io.OutputStream; 42 import java.net.Socket; 43 import java.util.ArrayList; 44 import java.util.Hashtable; 45 import java.util.List; 46 import java.util.Objects; 47 import java.util.concurrent.Callable; 48 import java.util.concurrent.CompletableFuture; 49 import java.util.concurrent.ExecutionException; 50 import java.util.concurrent.ExecutorService; 51 import java.util.concurrent.Executors; 52 import java.util.concurrent.Future; 53 import java.util.concurrent.FutureTask; 54 import java.util.concurrent.SynchronousQueue; 55 import java.util.concurrent.TimeUnit; 56 import java.util.concurrent.TimeoutException; 57 58 import static java.lang.String.format; 59 import static java.util.concurrent.TimeUnit.MILLISECONDS; 60 import static java.util.concurrent.TimeUnit.NANOSECONDS; 61 import static org.testng.Assert.assertTrue; 62 import static org.testng.Assert.expectThrows; 63 64 public class LdapTimeoutTest { 65 66 // ------ configure test timeouts here ------ 67 68 /* 69 * Practical representation of "indefinite" timeout. 70 */ 71 private static final long INFINITY_MILLIS = 20_000; 72 /* 73 * The lag. 74 * 75 * In other words, how long it takes to observe a timeout after it has 76 * occurred. This time is provisioned for things like stack unwinding and 77 * threads communication. 78 */ 79 private static final long RIGHT_MARGIN = 3_000; 80 /* 81 * The accuracy. 82 * 83 * Consider an activity that is supposed to take 10 seconds starts a little 84 * bit before we start measuring those 10 seconds. So from our point of view 85 * that 10-second activity may finish within, say, 9.7 seconds. 86 * 87 * Another example would be using a timed operation that returns prematurely. 88 */ 89 private static final long LEFT_MARGIN = 200; 90 91 private static final long CONNECT_MILLIS = 3_000; 92 private static final long READ_MILLIS = 10_000; 93 94 static { 95 // quick sanity check to make sure this timeouts configuration is 96 // consistent and the timeouts do not overlap 97 assert (LEFT_MARGIN >= 0 && RIGHT_MARGIN >= 0); 98 assert (2 * CONNECT_MILLIS + RIGHT_MARGIN < READ_MILLIS - LEFT_MARGIN); 99 assert (2 * CONNECT_MILLIS + READ_MILLIS + RIGHT_MARGIN < INFINITY_MILLIS - LEFT_MARGIN); 100 } 101 102 @BeforeTest 103 public void beforeTest() { 104 startAuxiliaryDiagnosticOutput(); 105 } 106 107 /* 108 * These are timeout tests and they are run in parallel to reduce the total 109 * amount of run time. 110 * 111 * Currently it doesn't seem possible to instruct JTREG to run TestNG test 112 * methods in parallel. That said, this JTREG test is still 113 * a "TestNG-flavored" test for the sake of having org.testng.Assert 114 * capability. 115 */ 116 @Test 117 public void test() throws Exception { 118 List<Future<?>> futures = new ArrayList<>(); 119 ExecutorService executorService = Executors.newCachedThreadPool(); 120 try { 121 futures.add(executorService.submit(() -> { test1(); return null; })); 122 futures.add(executorService.submit(() -> { test2(); return null; })); 123 futures.add(executorService.submit(() -> { test3(); return null; })); 124 futures.add(executorService.submit(() -> { test4(); return null; })); 125 futures.add(executorService.submit(() -> { test5(); return null; })); 126 futures.add(executorService.submit(() -> { test6(); return null; })); 127 futures.add(executorService.submit(() -> { test7(); return null; })); 128 } finally { 129 executorService.shutdown(); 130 } 131 int failedCount = 0; 132 for (var f : futures) { 133 try { 134 f.get(); 135 } catch (ExecutionException e) { 136 failedCount++; 137 e.getCause().printStackTrace(System.out); 138 } 139 } 140 if (failedCount > 0) 141 throw new RuntimeException(failedCount + " (sub)tests failed"); 142 } 143 144 static void test1() throws Exception { 145 Hashtable<Object, Object> env = new Hashtable<>(); 146 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 147 // Here and in the other tests it's important to close the server as 148 // calling `thread.interrupt` from assertion may not be enough 149 // (depending on where the blocking call has stuck) 150 try (TestServer server = new NotBindableServer()) { 151 env.put(Context.PROVIDER_URL, "ldap://localhost:" + server.getPort()); 152 server.start(); 153 // Here and in the other tests joining done purely to reduce timing 154 // jitter. Commenting out or removing that should not make the test 155 // incorrect. (ServerSocket can accept connection as soon as it is 156 // bound, not need to call `accept` before that.) 157 server.starting().join(); 158 assertIncompletion(INFINITY_MILLIS, () -> new InitialDirContext(env)); 159 } 160 } 161 162 static void test2() throws Exception { 163 Hashtable<Object, Object> env = new Hashtable<>(); 164 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 165 env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(CONNECT_MILLIS)); 166 try (TestServer server = new BindableButNotReadableServer()) { 167 env.put(Context.PROVIDER_URL, "ldap://localhost:" + server.getPort()); 168 server.start(); 169 server.starting().join(); 170 InitialDirContext ctx = new InitialDirContext(env); 171 SearchControls scl = new SearchControls(); 172 scl.setSearchScope(SearchControls.SUBTREE_SCOPE); 173 assertIncompletion(INFINITY_MILLIS, 174 () -> ctx.search("ou=People,o=JNDITutorial", "(objectClass=*)", scl)); 175 } 176 } 177 178 static void test3() throws Exception { 179 Hashtable<Object, Object> env = new Hashtable<>(); 180 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 181 try (TestServer server = new BindableButNotReadableServer()) { 182 env.put(Context.PROVIDER_URL, "ldap://localhost:" + server.getPort()); 183 server.start(); 184 server.starting().join(); 185 InitialDirContext ctx = new InitialDirContext(env); 186 SearchControls scl = new SearchControls(); 187 scl.setSearchScope(SearchControls.SUBTREE_SCOPE); 188 assertIncompletion(INFINITY_MILLIS, 189 () -> ctx.search("ou=People,o=JNDITutorial", "(objectClass=*)", scl)); 190 } 191 } 192 193 static void test4() throws Exception { 194 Hashtable<Object, Object> env = new Hashtable<>(); 195 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 196 env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(CONNECT_MILLIS)); 197 env.put("com.sun.jndi.ldap.read.timeout", String.valueOf(READ_MILLIS)); 198 try (TestServer server = new NotBindableServer()) { 199 env.put(Context.PROVIDER_URL, "ldap://localhost:" + server.getPort()); 200 server.start(); 201 server.starting().join(); 202 Assert.ThrowingRunnable completion = 203 () -> assertCompletion(CONNECT_MILLIS - LEFT_MARGIN, 204 2 * CONNECT_MILLIS + RIGHT_MARGIN, 205 () -> new InitialDirContext(env)); 206 NamingException e = expectThrows(NamingException.class, completion); 207 String msg = e.getMessage(); 208 assertTrue(msg != null && msg.contains("timeout") 209 && msg.contains(String.valueOf(CONNECT_MILLIS)), 210 msg); 211 } 212 } 213 214 static void test5() throws Exception { 215 Hashtable<Object, Object> env = new Hashtable<>(); 216 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 217 env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(CONNECT_MILLIS)); 218 env.put("com.sun.jndi.ldap.read.timeout", String.valueOf(READ_MILLIS)); 219 try (TestServer server = new BindableButNotReadableServer()) { 220 env.put(Context.PROVIDER_URL, "ldap://localhost:" + server.getPort()); 221 server.start(); 222 server.starting().join(); 223 InitialDirContext ctx = new InitialDirContext(env); 224 SearchControls scl = new SearchControls(); 225 scl.setSearchScope(SearchControls.SUBTREE_SCOPE); 226 Assert.ThrowingRunnable completion = 227 () -> assertCompletion(READ_MILLIS - LEFT_MARGIN, 228 READ_MILLIS + RIGHT_MARGIN, 229 () -> ctx.search("ou=People,o=JNDITutorial", "(objectClass=*)", scl)); 230 NamingException e = expectThrows(NamingException.class, completion); 231 String msg = e.getMessage(); 232 assertTrue(msg != null && msg.contains("timeout") 233 && msg.contains(String.valueOf(READ_MILLIS)), 234 msg); 235 } 236 } 237 238 static void test6() throws Exception { 239 Hashtable<Object, Object> env = new Hashtable<>(); 240 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 241 env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(CONNECT_MILLIS)); 242 env.put("com.sun.jndi.ldap.read.timeout", String.valueOf(READ_MILLIS)); 243 try (TestServer server = new NotBindableServer()) { 244 env.put(Context.PROVIDER_URL, "ldap://localhost:" + server.getPort()); 245 server.start(); 246 server.starting().join(); 247 Assert.ThrowingRunnable completion = 248 () -> assertCompletion(CONNECT_MILLIS - LEFT_MARGIN, 249 2 * CONNECT_MILLIS + RIGHT_MARGIN, 250 () -> new InitialDirContext(env)); 251 NamingException e = expectThrows(NamingException.class, completion); 252 String msg = e.getMessage(); 253 assertTrue(msg != null && msg.contains("timeout") 254 && msg.contains(String.valueOf(CONNECT_MILLIS)), 255 msg); 256 } 257 } 258 259 static void test7() throws Exception { 260 // 8000487: Java JNDI connection library on ldap conn is 261 // not honoring configured timeout 262 Hashtable<Object, Object> env = new Hashtable<>(); 263 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 264 env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(CONNECT_MILLIS)); 265 env.put("com.sun.jndi.ldap.read.timeout", String.valueOf(READ_MILLIS)); 266 env.put(Context.SECURITY_AUTHENTICATION, "simple"); 267 env.put(Context.SECURITY_PRINCIPAL, "user"); 268 env.put(Context.SECURITY_CREDENTIALS, "password"); 269 try (TestServer server = new NotBindableServer()) { 270 env.put(Context.PROVIDER_URL, "ldap://localhost:" + server.getPort()); 271 server.start(); 272 server.starting().join(); 273 Assert.ThrowingRunnable completion = 274 () -> assertCompletion(CONNECT_MILLIS - LEFT_MARGIN, 275 2 * CONNECT_MILLIS + RIGHT_MARGIN, 276 () -> new InitialDirContext(env)); 277 NamingException e = expectThrows(NamingException.class, completion); 278 String msg = e.getMessage(); 279 assertTrue(msg != null && msg.contains("timeout") 280 && msg.contains(String.valueOf(CONNECT_MILLIS)), 281 msg); 282 } 283 } 284 285 // ------ test stub servers ------ 286 287 static class TestServer extends BaseLdapServer { 288 289 private final CompletableFuture<Void> starting = new CompletableFuture<>(); 290 291 TestServer() throws IOException { } 292 293 @Override 294 protected void beforeAcceptingConnections() { 295 starting.completeAsync(() -> null); 296 } 297 298 public CompletableFuture<Void> starting() { 299 return starting.copy(); 300 } 301 } 302 303 static class BindableButNotReadableServer extends TestServer { 304 305 BindableButNotReadableServer() throws IOException { } 306 307 private static final byte[] bindResponse = { 308 0x30, 0x0C, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0A, 309 0x01, 0x00, 0x04, 0x00, 0x04, 0x00 310 }; 311 312 @Override 313 protected void handleRequest(Socket socket, 314 LdapMessage msg, 315 OutputStream out) 316 throws IOException { 317 switch (msg.getOperation()) { 318 case BIND_REQUEST: 319 out.write(bindResponse); 320 out.flush(); 321 default: 322 break; 323 } 324 } 325 } 326 327 static class NotBindableServer extends TestServer { 328 329 NotBindableServer() throws IOException { } 330 331 @Override 332 protected void beforeConnectionHandled(Socket socket) { 333 try { 334 TimeUnit.DAYS.sleep(Integer.MAX_VALUE); 335 } catch (InterruptedException e) { 336 Thread.currentThread().interrupt(); 337 } 338 } 339 } 340 341 // ------ timeouts check utilities ------ 342 343 /* 344 * Asserts that the specified executable yields a result or an exception 345 * within the specified time frame. Interrupts the executable 346 * unconditionally. 347 * 348 * If the executable yields a result or an exception within the specified 349 * time frame, the result will be returned and the exception will be 350 * rethrown respectively in a transparent fashion as if the executable was 351 * executed directly. 352 */ 353 public static <T> T assertCompletion(long loMillis, 354 long hiMillis, 355 Callable<T> code) 356 throws Throwable { 357 if (loMillis < 0 || hiMillis < 0 || loMillis > hiMillis) { 358 throw new IllegalArgumentException("loMillis=" + loMillis + 359 ", hiMillis=" + hiMillis); 360 } 361 Objects.requireNonNull(code); 362 363 // this queue acts both as an exchange point and a barrier 364 SynchronousQueue<Long> startTime = new SynchronousQueue<>(); 365 366 Callable<T> wrappedTask = () -> { 367 // by the time this value reaches the "stopwatch" thread it might be 368 // well outdated and that's okay, we will adjust the wait time 369 startTime.put(System.nanoTime()); 370 return code.call(); 371 }; 372 373 FutureTask<T> task = new FutureTask<>(wrappedTask); 374 Thread t = new Thread(task); 375 t.start(); 376 377 final long startNanos; 378 try { 379 startNanos = startTime.take(); // (1) wait for the initial time mark 380 } catch (Throwable e) { 381 t.interrupt(); 382 throw e; 383 } 384 385 final long waitTime = hiMillis - 386 NANOSECONDS.toMillis(System.nanoTime() - startNanos); // (2) adjust wait time 387 388 try { 389 T r = task.get(waitTime, MILLISECONDS); // (3) wait for the task to complete 390 long elapsed = NANOSECONDS.toMillis(System.nanoTime() - startNanos); 391 if (elapsed < loMillis || elapsed > hiMillis) { 392 throw new RuntimeException(format( 393 "After %s ms. returned result '%s'", elapsed, r)); 394 } 395 return r; 396 } catch (ExecutionException e) { 397 long elapsed = NANOSECONDS.toMillis(System.nanoTime() - startNanos); 398 if (elapsed < loMillis || elapsed > hiMillis) { 399 throw new RuntimeException(format( 400 "After %s ms. thrown exception", elapsed), e); 401 } 402 throw e.getCause(); 403 } catch (TimeoutException e) { 404 // We trust timed get not to throw TimeoutException prematurely 405 // (i.e. before the wait time elapses) 406 long elapsed = NANOSECONDS.toMillis(System.nanoTime() - startNanos); 407 throw new RuntimeException(format( 408 "After %s ms. is incomplete", elapsed)); 409 } finally { 410 t.interrupt(); 411 } 412 } 413 414 /* 415 * Asserts that the specified executable yields no result and no exception 416 * for at least the specified amount of time. Interrupts the executable 417 * unconditionally. 418 */ 419 public static void assertIncompletion(long millis, Callable<?> code) 420 throws Exception 421 { 422 if (millis < 0) { 423 throw new IllegalArgumentException("millis=" + millis); 424 } 425 Objects.requireNonNull(code); 426 427 // this queue acts both as an exchange point and a barrier 428 SynchronousQueue<Long> startTime = new SynchronousQueue<>(); 429 430 Callable<?> wrappedTask = () -> { 431 // by the time this value reaches the "stopwatch" thread it might be 432 // well outdated and that's okay, we will adjust the wait time 433 startTime.put(System.nanoTime()); 434 return code.call(); 435 }; 436 437 FutureTask<?> task = new FutureTask<>(wrappedTask); 438 Thread t = new Thread(task); 439 t.start(); 440 441 final long startNanos; 442 try { 443 startNanos = startTime.take(); // (1) wait for the initial time mark 444 } catch (Throwable e) { 445 t.interrupt(); 446 throw e; 447 } 448 449 final long waitTime = millis - 450 NANOSECONDS.toMillis(System.nanoTime() - startNanos); // (2) adjust wait time 451 452 try { 453 Object r = task.get(waitTime, MILLISECONDS); // (3) wait for the task to complete 454 long elapsed = NANOSECONDS.toMillis(System.nanoTime() - startNanos); 455 if (elapsed < waitTime) { 456 throw new RuntimeException(format( 457 "After %s ms. returned result '%s'", elapsed, r)); 458 } 459 } catch (ExecutionException e) { 460 long elapsed = NANOSECONDS.toMillis(System.nanoTime() - startNanos); 461 if (elapsed < waitTime) { 462 throw new RuntimeException(format( 463 "After %s ms. thrown exception", elapsed), e); 464 } 465 } catch (TimeoutException expected) { 466 } finally { 467 t.interrupt(); 468 } 469 } 470 471 // ------ miscellaneous utilities ------ 472 473 /* 474 * A diagnostic aid that might help with debugging timeout issues. The idea 475 * is to continuously measure accuracy and responsiveness of the system that 476 * runs this test. If the system is overwhelmed (with something else), it 477 * might affect the test run. At the very least we will have traces of that 478 * in the logs. 479 * 480 * This utility does not automatically scale up test timeouts, it simply 481 * gathers information. 482 */ 483 private static void startAuxiliaryDiagnosticOutput() { 484 System.out.printf("Starting diagnostic output (probe)%n"); 485 Thread t = new Thread(() -> { 486 for (int i = 0; ; i = ((i % 20) + 1)) { 487 // 500, 1_000, 1_500, ..., 9_500, 10_000, 500, 1_000, ... 488 long expected = i * 500; 489 long start = System.nanoTime(); 490 try { 491 MILLISECONDS.sleep(expected); 492 } catch (InterruptedException e) { 493 return; 494 } 495 long stop = System.nanoTime(); 496 long actual = NANOSECONDS.toMillis(stop - start); 497 System.out.printf("(probe) expected [ms.]: %s, actual [ms.]: %s%n", 498 expected, actual); 499 500 } 501 }, "probe"); 502 t.setDaemon(true); 503 t.start(); 504 } 505 }