| | |
| | | /** The window as this JVM was started with it, put back after every test that varies it. */ |
| | | private static final long CONFIGURED_ALIVE_BYPASS_NANOS = CachedConnection.aliveBypassNanos; |
| | | |
| | | /** The standing read bound as this JVM was started with it, put back after every test that varies it. */ |
| | | private static final int CONFIGURED_READ_TIMEOUT_MILLIS = CachedConnection.readTimeoutMillis; |
| | | |
| | | @BeforeClass |
| | | public void registerStubDriver() throws Exception { |
| | | DriverManager.registerDriver(stub); |
| | |
| | | System.clearProperty(CachedConnection.POOL_MAX_PROPERTY); |
| | | System.clearProperty(CachedConnection.TTL_PROPERTY); |
| | | System.clearProperty(CachedConnection.ALIVE_BYPASS_PROPERTY); |
| | | System.clearProperty(CachedConnection.READ_TIMEOUT_PROPERTY); |
| | | // what has been reported once is remembered for the life of the jvm: left standing, the key |
| | | // of one test is what the next one finds when it asserts that it reported something itself |
| | | CachedConnection.warnedOnce.clear(); |
| | | CachedConnection.aliveBypassNanos = CONFIGURED_ALIVE_BYPASS_NANOS; |
| | | CachedConnection.readTimeoutMillis = CONFIGURED_READ_TIMEOUT_MILLIS; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * What a connection carries once the login is through, where a deployment asked for a read |
| | | * bound of its own: that bound rather than the bound of the login, which is a value nothing |
| | | * slower than a connect is meant to be measured against. Without one, this is the lift above - |
| | | * the behaviour of every connection this backend established before the property existed. |
| | | */ |
| | | @Test(timeOut = 120000) |
| | | public void testAnEstablishedConnectionCarriesTheReadBoundAskedFor() throws Exception { |
| | | final String url = StubDriver.PREFIX + "standing-read-bound"; |
| | | CachedConnection.readTimeoutMillis = 90000; |
| | | final Connection parent = mock(Connection.class); |
| | | stub.answerWith(parent); |
| | | |
| | | CachedConnection.connect(url, CachedConnection.ConnectDialect.MYSQL, 30, |
| | | CachedConnection.poolOf(url), false); |
| | | |
| | | verify(parent).setNetworkTimeout(any(Executor.class), eq(90000)); |
| | | verify(parent, never()).setNetworkTimeout(any(Executor.class), eq(0)); |
| | | } |
| | | |
| | | /** |
| | | * And it carries it whether or not the login had a bound of its own to lift. The read bound of |
| | | * a login is only ever set where the connect is bounded, so a deployment that runs with |
| | | * {@code connect.timeout=0} - the one setting that leaves a connect to the deadline of the |
| | | * borrow alone - would otherwise set this property and get nothing for it. |
| | | */ |
| | | @Test(timeOut = 120000) |
| | | public void testTheReadBoundIsSetWhereTheLoginHadNoneToLift() throws Exception { |
| | | final String url = StubDriver.PREFIX + "read-bound-without-a-login-bound"; |
| | | CachedConnection.readTimeoutMillis = 90000; |
| | | final Connection parent = mock(Connection.class); |
| | | stub.answerWith(parent); |
| | | |
| | | CachedConnection.connect(url, CachedConnection.ConnectDialect.MYSQL, 0, |
| | | CachedConnection.poolOf(url), false); |
| | | |
| | | verify(parent).setNetworkTimeout(any(Executor.class), eq(90000)); |
| | | } |
| | | |
| | | /** |
| | | * A read bound standing in the connection string is the deployment's own: the connect does not |
| | | * replace it with this one, exactly as it does not set the read bound of a login on top of it. |
| | | */ |
| | | @Test(timeOut = 120000) |
| | | public void testAReadBoundOfTheUrlIsNotReplacedByTheConfiguredOne() throws Exception { |
| | | final String url = StubDriver.PREFIX + "own-read-bound?socketTimeout=1000"; |
| | | CachedConnection.readTimeoutMillis = 90000; |
| | | final Connection parent = mock(Connection.class); |
| | | stub.answerWith(parent); |
| | | |
| | | CachedConnection.connect(url, CachedConnection.ConnectDialect.MYSQL, 30, |
| | | CachedConnection.poolOf(url), false); |
| | | |
| | | verify(parent, never()).setNetworkTimeout(any(Executor.class), anyInt()); |
| | | } |
| | | |
| | | /** |
| | | * Which connections carry a read bound of this backend's own making, as the backstop of |
| | | * {@code JDBCStorage} has to know it: a statement of an unbounded class takes that bound off |
| | | * for as long as it runs, and it may only take off what this class put on. A bound of the url |
| | | * is the deployment's, and a driver whose property names are not known here was never given |
| | | * one - lifting either would leave the connection unbounded for the rest of its life. |
| | | */ |
| | | @Test(timeOut = 120000) |
| | | public void testOnlyTheReadBoundThisClassSetsIsItsOwnToLift() { |
| | | CachedConnection.readTimeoutMillis = 90000; |
| | | assertEquals(CachedConnection.standingReadBoundMillis("jdbc:mysql://localhost:3306/db"), 90000, |
| | | "the bound this class sets on a connection of a dialect it knows"); |
| | | assertEquals(CachedConnection.standingReadBoundMillis("jdbc:mysql://localhost:3306/db?socketTimeout=1000"), 0, |
| | | "a read bound of the url was reported as this backend's own"); |
| | | assertEquals(CachedConnection.standingReadBoundMillis("jdbc:h2:mem:db"), 0, |
| | | "a driver this class sets no read bound on was reported as bounded by it"); |
| | | |
| | | CachedConnection.readTimeoutMillis = 0; |
| | | assertEquals(CachedConnection.standingReadBoundMillis("jdbc:mysql://localhost:3306/db"), 0, |
| | | "a connection carries no standing bound where none is configured"); |
| | | } |
| | | |
| | | /** |
| | | * The bound is configured in seconds and reaches the driver in milliseconds; 0, a negative |
| | | * value and a value that is no number all leave a connection unbounded, which is what this |
| | | * backend did before the property existed. A value past the ceiling of a socket read timeout is |
| | | * taken down to it rather than left to overflow the {@code int} of setNetworkTimeout, where it |
| | | * would arrive as a negative timeout - a value outside the contract, and one a driver is free |
| | | * to read as anything at all. |
| | | */ |
| | | @Test(timeOut = 120000) |
| | | public void testTheReadBoundIsConfiguredInSeconds() { |
| | | assertEquals(CachedConnection.getReadTimeoutMillis(), 0, "a connection is unbounded by default"); |
| | | |
| | | System.setProperty(CachedConnection.READ_TIMEOUT_PROPERTY, "90"); |
| | | assertEquals(CachedConnection.getReadTimeoutMillis(), 90000); |
| | | |
| | | System.setProperty(CachedConnection.READ_TIMEOUT_PROPERTY, "0"); |
| | | assertEquals(CachedConnection.getReadTimeoutMillis(), 0); |
| | | |
| | | System.setProperty(CachedConnection.READ_TIMEOUT_PROPERTY, "-1"); |
| | | assertEquals(CachedConnection.getReadTimeoutMillis(), 0, "a negative value was not read as no bound"); |
| | | |
| | | System.setProperty(CachedConnection.READ_TIMEOUT_PROPERTY, "a minute and a half"); |
| | | assertEquals(CachedConnection.getReadTimeoutMillis(), 0, |
| | | "a value that is no number was not ignored in favour of the default"); |
| | | |
| | | System.setProperty(CachedConnection.READ_TIMEOUT_PROPERTY, Integer.toString(Integer.MAX_VALUE)); |
| | | assertEquals(CachedConnection.getReadTimeoutMillis(), JDBCStorage.MAX_BOUND_SECONDS * 1000, |
| | | "a value past the ceiling of a socket read timeout was not taken down to it"); |
| | | } |
| | | |
| | | /** |
| | | * A driver that will not take the standing bound leaves a connection with no bound of ours on |
| | | * it, which is what every connection of this pool carried before the property existed and no |
| | | * reason to keep this one out of the pool. The connection that must not be pooled is the one |
| | | * still carrying the read bound of its login: there the call that failed was a call to take |
| | | * something off, and the bound left on it fails every statement slower than a connect. |
| | | */ |
| | | @Test(timeOut = 120000) |
| | | public void testAConnectionThatWouldNotTakeTheStandingBoundIsStillPooled() throws Exception { |
| | | final String url = StubDriver.PREFIX + "unsettable-standing-bound"; |
| | | CachedConnection.readTimeoutMillis = 90000; |
| | | final Connection parent = mock(Connection.class); |
| | | doThrow(new SQLException("setNetworkTimeout is not supported")) |
| | | .when(parent).setNetworkTimeout(any(Executor.class), anyInt()); |
| | | stub.answerWith(parent); |
| | | |
| | | final CachedConnection.Pool pool = CachedConnection.poolOf(url); |
| | | // Metered, and holding a permit of the pool as a borrow does: an unmetered connection is |
| | | // closed rather than pooled whatever bound it carries, which would answer this on the |
| | | // accounting of the pool instead of on the bound the case is about. |
| | | assertTrue(pool.tryReserve(), "the pool of this url would not reserve a place for the connection"); |
| | | final CachedConnection borrowed = CachedConnection.connect(url, CachedConnection.ConnectDialect.MYSQL, 0, |
| | | pool, true); |
| | | borrowed.close(); |
| | | |
| | | verify(parent, never()).close(); |
| | | assertEquals(pool.idleCount(), 1, |
| | | "a connection carrying no bound of ours was kept out of the pool"); |
| | | } |
| | | |
| | | /** |
| | | * A read parameter of the url set to 0 is no bound of the deployment's: 0 is what every one of |
| | | * these drivers reads as "wait as long as it takes", which is the default this property exists |
| | | * to replace. It tells the two bounds apart, and only on postgresql, where a parameter of the |
| | | * url outranks the property this class supplies: the login there is left carrying no bound of |
| | | * ours, and rightly so, while the bound of this property is no property of a connect at all - it |
| | | * is a setNetworkTimeout of an established connection, which no url outranks. Read as a bound of |
| | | * theirs, a "socketTimeout=0" - the default of pgjdbc, written out - would leave a deployment |
| | | * that asked for this one with no bound and no report of why. |
| | | */ |
| | | @Test(timeOut = 120000) |
| | | public void testAReadParameterOfTheUrlSetToZeroIsNoBoundOfTheDeployments() { |
| | | CachedConnection.readTimeoutMillis = 90000; |
| | | assertEquals(CachedConnection.standingReadBoundMillis("jdbc:postgresql://localhost/db?socketTimeout=0"), 90000, |
| | | "a postgresql url turning the read bound off was read as a bound of the deployment's own"); |
| | | assertEquals(CachedConnection.standingReadBoundMillis("jdbc:mysql://localhost:3306/db?socketTimeout=0"), 90000, |
| | | "a mysql url turning the read bound off was read as a bound of the deployment's own"); |
| | | assertEquals(CachedConnection.standingReadBoundMillis("jdbc:postgresql://localhost/db?socketTimeout=30"), 0, |
| | | "a read bound of a postgresql url is the deployment's own and stands"); |
| | | } |
| | | |
| | | /** |
| | | * The predicate deciding whether a url bounds the read reads the same set of names as the one |
| | | * deciding whether it declares it. It used to stop at the first name present even where the |
| | | * value there was a zero, so a url naming the bound under both names of the oracle driver - the |
| | | * dotted one turned off, the last segment set - was declared() and not bounds(): the login kept |
| | | * the administrator's value, because a property of ours is not supplied over a declared one, and |
| | | * a bound of ours then went on top of it with setNetworkTimeout. Contrived, but "the bound taken |
| | | * off is the bound that was set" holds only while the two look at the same names. |
| | | */ |
| | | @Test(timeOut = 120000) |
| | | public void testAReadBoundUnderEitherNameOfTheUrlIsTheDeploymentsOwn() { |
| | | CachedConnection.readTimeoutMillis = 90000; |
| | | assertEquals(CachedConnection.standingReadBoundMillis( |
| | | "jdbc:oracle:thin:@//localhost:1521/db?oracle.jdbc.ReadTimeout=0&ReadTimeout=600"), 0, |
| | | "a bound standing under the last segment of the name was read as no bound at all"); |
| | | assertEquals(CachedConnection.standingReadBoundMillis( |
| | | "jdbc:oracle:thin:@//localhost:1521/db?oracle.jdbc.ReadTimeout=0&ReadTimeout=0"), 90000, |
| | | "a url turning the read bound off under both of its names is no bound of the deployment's"); |
| | | } |
| | | |
| | | /** |
| | | * With nothing configured this is the lift and nothing else - the read bound of the login comes |
| | | * off and no bound of ours goes on top of it, which is what every connection of this pool |
| | | * carried before the property existed. The default of this property is what makes the change |
| | | * that introduced it no change at all for a deployment that does not ask for one. |
| | | */ |
| | | @Test(timeOut = 120000) |
| | | public void testTheDefaultTakesTheBoundOfTheLoginOffAndPutsNothingOnTopOfIt() throws Exception { |
| | | final String url = StubDriver.PREFIX + "default-read-bound"; |
| | | CachedConnection.readTimeoutMillis = 0; |
| | | final Connection parent = mock(Connection.class); |
| | | stub.answerWith(parent); |
| | | |
| | | CachedConnection.connect(url, CachedConnection.ConnectDialect.MYSQL, 30, |
| | | CachedConnection.poolOf(url), false); |
| | | |
| | | verify(parent).setNetworkTimeout(any(Executor.class), eq(0)); |
| | | verify(parent, times(1)).setNetworkTimeout(any(Executor.class), anyInt()); |
| | | } |
| | | |
| | | /** |
| | | * A driver that would take neither the standing bound nor the lift leaves the connection that |
| | | * must not be pooled: what it is left carrying is the read bound of a connect, and every borrow |
| | | * after this one would meet it - which is the case above, reached by the other of the two paths |
| | | * that call for a setNetworkTimeout once the login is through. |
| | | */ |
| | | @Test(timeOut = 120000) |
| | | public void testAConnectionThatWouldTakeNeitherTheStandingBoundNorTheLiftIsNotPooled() throws Exception { |
| | | final String url = StubDriver.PREFIX + "unsettable-over-a-login-bound"; |
| | | CachedConnection.readTimeoutMillis = 90000; |
| | | final Connection parent = mock(Connection.class); |
| | | doThrow(new SQLException("setNetworkTimeout is not supported")) |
| | | .when(parent).setNetworkTimeout(any(Executor.class), anyInt()); |
| | | stub.answerWith(parent); |
| | | |
| | | final CachedConnection.Pool pool = CachedConnection.poolOf(url); |
| | | // Metered, and holding a permit of the pool as a borrow does: an unmetered connection is |
| | | // closed rather than pooled whatever bound it carries, which would answer this on the |
| | | // accounting of the pool instead of on the bound the case is about. |
| | | assertTrue(pool.tryReserve(), "the pool of this url would not reserve a place for the connection"); |
| | | final CachedConnection borrowed = CachedConnection.connect(url, CachedConnection.ConnectDialect.MYSQL, 30, |
| | | pool, true); |
| | | borrowed.close(); |
| | | |
| | | verify(parent).close(); |
| | | assertEquals(pool.idleCount(), 0, |
| | | "a connection still carrying the read bound of its login went back into the pool"); |
| | | } |
| | | |
| | | /** |
| | | * The initializer reads this property the way it reads the two windows above - a value that is |
| | | * no number, or a negative one, is reported once and ignored in favour of the default - and |
| | | * reporting it has to leave the class usable: the set that report is deduplicated through is |
| | | * declared above every field whose initializer can reach it (JLS 12.4.2), so a field of this |
| | | * one moved above that set would turn a typo in a property into an ExceptionInInitializerError |
| | | * that no test of a class already initialized would ever meet. |
| | | */ |
| | | @Test(timeOut = 120000, dataProvider = "readBoundsWorthWarningAbout") |
| | | public void testAReadBoundWorthWarningAboutStillInitializesTheClass(String configured) throws Exception { |
| | | System.setProperty(CachedConnection.READ_TIMEOUT_PROPERTY, configured); |
| | | |
| | | final Class<?> reloaded = loadedAfresh(CachedConnection.class); |
| | | |
| | | assertNotSame(reloaded, CachedConnection.class, "the class under test was not loaded afresh"); |
| | | final Field field = reloaded.getDeclaredField("readTimeoutMillis"); |
| | | field.setAccessible(true); |
| | | assertEquals(field.getInt(null), 0, "the bound the reloaded class settled on"); |
| | | } |
| | | |
| | | @DataProvider |
| | | public Object[][] readBoundsWorthWarningAbout() { |
| | | return new Object[][]{{"a minute and a half"}, {"-1"}}; |
| | | } |
| | | |
| | | /** |
| | | * The case the window exists for: the connection this borrow takes out answered the database a |
| | | * moment ago, and asking it again costs the round trip the operation came to make. |
| | | */ |