Path: blob/master/test/jdk/com/sun/jndi/ldap/LdapTimeoutTest.java
41153 views
/*1* Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @library /test/lib26* lib/27* @run testng/othervm LdapTimeoutTest28* @bug 7094377 8000487 6176036 7056489 815167829* @summary Timeout tests for ldap30*/3132import org.testng.Assert;33import org.testng.annotations.BeforeTest;34import org.testng.annotations.Test;3536import javax.naming.Context;37import javax.naming.NamingException;38import javax.naming.directory.InitialDirContext;39import javax.naming.directory.SearchControls;40import java.io.IOException;41import java.io.OutputStream;42import java.net.Socket;43import java.util.ArrayList;44import java.util.Hashtable;45import java.util.List;46import java.util.Objects;47import java.util.concurrent.Callable;48import java.util.concurrent.CompletableFuture;49import java.util.concurrent.ExecutionException;50import java.util.concurrent.ExecutorService;51import java.util.concurrent.Executors;52import java.util.concurrent.Future;53import java.util.concurrent.FutureTask;54import java.util.concurrent.SynchronousQueue;55import java.util.concurrent.TimeUnit;56import java.util.concurrent.TimeoutException;5758import static java.lang.String.format;59import static java.util.concurrent.TimeUnit.MILLISECONDS;60import static java.util.concurrent.TimeUnit.NANOSECONDS;61import static jdk.test.lib.Utils.adjustTimeout;62import static org.testng.Assert.assertTrue;63import static org.testng.Assert.expectThrows;6465public class LdapTimeoutTest {6667// ------ configure test timeouts here ------6869/*70* Practical representation of an infinite timeout.71*/72private static final long INFINITY_MILLIS = adjustTimeout(20_000);73/*74* The acceptable variation in timeout measurements.75*/76private static final long TOLERANCE = adjustTimeout( 3_500);7778private static final long CONNECT_MILLIS = adjustTimeout( 3_000);79private static final long READ_MILLIS = adjustTimeout(10_000);8081static {82// a series of checks to make sure this timeouts configuration is83// consistent and the timeouts do not overlap8485assert (TOLERANCE >= 0);86// context creation87assert (2 * CONNECT_MILLIS + TOLERANCE < READ_MILLIS);88// context creation immediately followed by search89assert (2 * CONNECT_MILLIS + READ_MILLIS + TOLERANCE < INFINITY_MILLIS);90}9192@BeforeTest93public void beforeTest() {94startAuxiliaryDiagnosticOutput();95}9697/*98* These are timeout tests and they are run in parallel to reduce the total99* amount of run time.100*101* Currently it doesn't seem possible to instruct JTREG to run TestNG test102* methods in parallel. That said, this JTREG test is still103* a "TestNG-flavored" test for the sake of having org.testng.Assert104* capability.105*/106@Test107public void test() throws Exception {108List<Future<?>> futures = new ArrayList<>();109ExecutorService executorService = Executors.newCachedThreadPool();110try {111futures.add(executorService.submit(() -> { test1(); return null; }));112futures.add(executorService.submit(() -> { test2(); return null; }));113futures.add(executorService.submit(() -> { test3(); return null; }));114futures.add(executorService.submit(() -> { test4(); return null; }));115futures.add(executorService.submit(() -> { test5(); return null; }));116futures.add(executorService.submit(() -> { test6(); return null; }));117futures.add(executorService.submit(() -> { test7(); return null; }));118} finally {119executorService.shutdown();120}121int failedCount = 0;122for (var f : futures) {123try {124f.get();125} catch (ExecutionException e) {126failedCount++;127e.getCause().printStackTrace(System.out);128}129}130if (failedCount > 0)131throw new RuntimeException(failedCount + " (sub)tests failed");132}133134static void test1() throws Exception {135Hashtable<Object, Object> env = new Hashtable<>();136env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");137// Here and in the other tests it's important to close the server as138// calling `thread.interrupt` from assertion may not be enough139// (depending on where the blocking call has stuck)140try (TestServer server = new NotBindableServer()) {141env.put(Context.PROVIDER_URL, urlTo(server));142server.start();143// Here and in the other tests joining done purely to reduce timing144// jitter. Commenting out or removing that should not make the test145// incorrect. (ServerSocket can accept connection as soon as it is146// bound, not need to call `accept` before that.)147server.starting().join();148assertIncompletion(INFINITY_MILLIS, () -> new InitialDirContext(env));149}150}151152static void test2() throws Exception {153Hashtable<Object, Object> env = new Hashtable<>();154env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");155env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(CONNECT_MILLIS));156try (TestServer server = new BindableButNotReadableServer()) {157env.put(Context.PROVIDER_URL, urlTo(server));158server.start();159server.starting().join();160InitialDirContext ctx = new InitialDirContext(env);161SearchControls scl = new SearchControls();162scl.setSearchScope(SearchControls.SUBTREE_SCOPE);163assertIncompletion(INFINITY_MILLIS,164() -> ctx.search("ou=People,o=JNDITutorial", "(objectClass=*)", scl));165}166}167168static void test3() throws Exception {169Hashtable<Object, Object> env = new Hashtable<>();170env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");171try (TestServer server = new BindableButNotReadableServer()) {172env.put(Context.PROVIDER_URL, urlTo(server));173server.start();174server.starting().join();175InitialDirContext ctx = new InitialDirContext(env);176SearchControls scl = new SearchControls();177scl.setSearchScope(SearchControls.SUBTREE_SCOPE);178assertIncompletion(INFINITY_MILLIS,179() -> ctx.search("ou=People,o=JNDITutorial", "(objectClass=*)", scl));180}181}182183static void test4() throws Exception {184Hashtable<Object, Object> env = new Hashtable<>();185env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");186env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(CONNECT_MILLIS));187env.put("com.sun.jndi.ldap.read.timeout", String.valueOf(READ_MILLIS));188try (TestServer server = new NotBindableServer()) {189env.put(Context.PROVIDER_URL, urlTo(server));190server.start();191server.starting().join();192Assert.ThrowingRunnable completion =193() -> assertCompletion(CONNECT_MILLIS,1942 * CONNECT_MILLIS + TOLERANCE,195() -> new InitialDirContext(env));196NamingException e = expectThrows(NamingException.class, completion);197String msg = e.getMessage();198assertTrue(msg != null && msg.contains("timeout")199&& msg.contains(String.valueOf(CONNECT_MILLIS)),200msg);201}202}203204static void test5() throws Exception {205Hashtable<Object, Object> env = new Hashtable<>();206env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");207env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(CONNECT_MILLIS));208env.put("com.sun.jndi.ldap.read.timeout", String.valueOf(READ_MILLIS));209try (TestServer server = new BindableButNotReadableServer()) {210env.put(Context.PROVIDER_URL, urlTo(server));211server.start();212server.starting().join();213InitialDirContext ctx = new InitialDirContext(env);214SearchControls scl = new SearchControls();215scl.setSearchScope(SearchControls.SUBTREE_SCOPE);216Assert.ThrowingRunnable completion =217() -> assertCompletion(READ_MILLIS,218READ_MILLIS + TOLERANCE,219() -> ctx.search("ou=People,o=JNDITutorial", "(objectClass=*)", scl));220NamingException e = expectThrows(NamingException.class, completion);221String msg = e.getMessage();222assertTrue(msg != null && msg.contains("timeout")223&& msg.contains(String.valueOf(READ_MILLIS)),224msg);225}226}227228static void test6() throws Exception {229Hashtable<Object, Object> env = new Hashtable<>();230env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");231env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(CONNECT_MILLIS));232env.put("com.sun.jndi.ldap.read.timeout", String.valueOf(READ_MILLIS));233try (TestServer server = new NotBindableServer()) {234env.put(Context.PROVIDER_URL, urlTo(server));235server.start();236server.starting().join();237Assert.ThrowingRunnable completion =238() -> assertCompletion(CONNECT_MILLIS,2392 * CONNECT_MILLIS + TOLERANCE,240() -> new InitialDirContext(env));241NamingException e = expectThrows(NamingException.class, completion);242String msg = e.getMessage();243assertTrue(msg != null && msg.contains("timeout")244&& msg.contains(String.valueOf(CONNECT_MILLIS)),245msg);246}247}248249static void test7() throws Exception {250// 8000487: Java JNDI connection library on ldap conn is251// not honoring configured timeout252Hashtable<Object, Object> env = new Hashtable<>();253env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");254env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(CONNECT_MILLIS));255env.put("com.sun.jndi.ldap.read.timeout", String.valueOf(READ_MILLIS));256env.put(Context.SECURITY_AUTHENTICATION, "simple");257env.put(Context.SECURITY_PRINCIPAL, "user");258env.put(Context.SECURITY_CREDENTIALS, "password");259try (TestServer server = new NotBindableServer()) {260env.put(Context.PROVIDER_URL, urlTo(server));261server.start();262server.starting().join();263Assert.ThrowingRunnable completion =264() -> assertCompletion(CONNECT_MILLIS,2652 * CONNECT_MILLIS + TOLERANCE,266() -> new InitialDirContext(env));267NamingException e = expectThrows(NamingException.class, completion);268String msg = e.getMessage();269assertTrue(msg != null && msg.contains("timeout")270&& msg.contains(String.valueOf(CONNECT_MILLIS)),271msg);272}273}274275// ------ test stub servers ------276277static class TestServer extends BaseLdapServer {278279private final CompletableFuture<Void> starting = new CompletableFuture<>();280281TestServer() throws IOException { }282283@Override284protected void beforeAcceptingConnections() {285starting.completeAsync(() -> null);286}287288public CompletableFuture<Void> starting() {289return starting.copy();290}291}292293static class BindableButNotReadableServer extends TestServer {294295BindableButNotReadableServer() throws IOException { }296297private static final byte[] bindResponse = {2980x30, 0x0C, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0A,2990x01, 0x00, 0x04, 0x00, 0x04, 0x00300};301302@Override303protected void handleRequest(Socket socket,304LdapMessage msg,305OutputStream out)306throws IOException {307switch (msg.getOperation()) {308case BIND_REQUEST:309out.write(bindResponse);310out.flush();311default:312break;313}314}315}316317static class NotBindableServer extends TestServer {318319NotBindableServer() throws IOException { }320321@Override322protected void beforeConnectionHandled(Socket socket) {323try {324TimeUnit.DAYS.sleep(Integer.MAX_VALUE);325} catch (InterruptedException e) {326Thread.currentThread().interrupt();327}328}329}330331// ------ timeouts check utilities ------332333/*334* Asserts that the specified executable yields a result or an exception335* within the specified time frame. Interrupts the executable336* unconditionally.337*338* If the executable yields a result or an exception within the specified339* time frame, the result will be returned and the exception will be340* rethrown respectively in a transparent fashion as if the executable was341* executed directly.342*/343public static <T> T assertCompletion(long loMillis,344long hiMillis,345Callable<T> code)346throws Throwable {347if (loMillis < 0 || hiMillis < 0 || loMillis > hiMillis) {348throw new IllegalArgumentException("loMillis=" + loMillis +349", hiMillis=" + hiMillis);350}351Objects.requireNonNull(code);352353// this queue acts both as an exchange point and a barrier354SynchronousQueue<Long> startTime = new SynchronousQueue<>();355356Callable<T> wrappedTask = () -> {357// by the time this value reaches the "stopwatch" thread it might be358// well outdated and that's okay, we will adjust the wait time359startTime.put(System.nanoTime());360return code.call();361};362363FutureTask<T> task = new FutureTask<>(wrappedTask);364Thread t = new Thread(task);365t.start();366367final long startNanos;368try {369startNanos = startTime.take(); // (1) wait for the initial time mark370} catch (Throwable e) {371t.interrupt();372throw e;373}374375final long waitTime = hiMillis -376NANOSECONDS.toMillis(System.nanoTime() - startNanos); // (2) adjust wait time377378try {379T r = task.get(waitTime, MILLISECONDS); // (3) wait for the task to complete380long elapsed = NANOSECONDS.toMillis(System.nanoTime() - startNanos);381if (elapsed < loMillis || elapsed > hiMillis) {382throw new RuntimeException(format(383"After %s ms. (waitTime %s ms.) returned result '%s'", elapsed, waitTime, r));384}385return r;386} catch (ExecutionException e) {387long elapsed = NANOSECONDS.toMillis(System.nanoTime() - startNanos);388if (elapsed < loMillis || elapsed > hiMillis) {389throw new RuntimeException(format(390"After %s ms. (waitTime %s ms.) thrown exception", elapsed, waitTime), e);391}392throw e.getCause();393} catch (TimeoutException e) {394// We trust timed get not to throw TimeoutException prematurely395// (i.e. before the wait time elapses)396long elapsed = NANOSECONDS.toMillis(System.nanoTime() - startNanos);397throw new RuntimeException(format(398"After %s ms. (waitTime %s ms.) is incomplete", elapsed, waitTime));399} finally {400t.interrupt();401}402}403404/*405* Asserts that the specified executable yields no result and no exception406* for at least the specified amount of time. Interrupts the executable407* unconditionally.408*/409public static void assertIncompletion(long millis, Callable<?> code)410throws Exception411{412if (millis < 0) {413throw new IllegalArgumentException("millis=" + millis);414}415Objects.requireNonNull(code);416417// this queue acts both as an exchange point and a barrier418SynchronousQueue<Long> startTime = new SynchronousQueue<>();419420Callable<?> wrappedTask = () -> {421// by the time this value reaches the "stopwatch" thread it might be422// well outdated and that's okay, we will adjust the wait time423startTime.put(System.nanoTime());424return code.call();425};426427FutureTask<?> task = new FutureTask<>(wrappedTask);428Thread t = new Thread(task);429t.start();430431final long startNanos;432try {433startNanos = startTime.take(); // (1) wait for the initial time mark434} catch (Throwable e) {435t.interrupt();436throw e;437}438439final long waitTime = millis -440NANOSECONDS.toMillis(System.nanoTime() - startNanos); // (2) adjust wait time441442try {443Object r = task.get(waitTime, MILLISECONDS); // (3) wait for the task to complete444long elapsed = NANOSECONDS.toMillis(System.nanoTime() - startNanos);445if (elapsed < waitTime) {446throw new RuntimeException(format(447"After %s ms. (waitTime %s ms.) returned result '%s'", elapsed, waitTime, r));448}449} catch (ExecutionException e) {450long elapsed = NANOSECONDS.toMillis(System.nanoTime() - startNanos);451if (elapsed < waitTime) {452throw new RuntimeException(format(453"After %s ms. (waitTime %s ms.) thrown exception", elapsed, waitTime), e);454}455} catch (TimeoutException expected) {456} finally {457t.interrupt();458}459}460461// ------ miscellaneous utilities ------462463private static String urlTo(TestServer server) {464String hostAddress = server.getInetAddress().getHostAddress();465String addr;466if (hostAddress.contains(":")) { // IPv6467addr = '[' + hostAddress + ']';468} else { // IPv4469addr = hostAddress;470}471return "ldap://" + addr + ":" + server.getPort();472}473474/*475* A diagnostic aid that might help with debugging timeout issues. The idea476* is to continuously measure accuracy and responsiveness of the system that477* runs this test. If the system is overwhelmed (with something else), it478* might affect the test run. At the very least we will have traces of that479* in the logs.480*481* This utility does not automatically scale up test timeouts, it simply482* gathers information.483*/484private static void startAuxiliaryDiagnosticOutput() {485System.out.printf("Starting diagnostic output (probe)%n");486Thread t = new Thread(() -> {487for (int i = 0; ; i = ((i % 20) + 1)) {488// 500, 1_000, 1_500, ..., 9_500, 10_000, 500, 1_000, ...489long expected = i * 500;490long start = System.nanoTime();491try {492MILLISECONDS.sleep(expected);493} catch (InterruptedException e) {494return;495}496long stop = System.nanoTime();497long actual = NANOSECONDS.toMillis(stop - start);498System.out.printf("(probe) expected [ms.]: %s, actual [ms.]: %s%n",499expected, actual);500501}502}, "probe");503t.setDaemon(true);504t.start();505}506}507508509