/* * Copyright (c) 2002-2017 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.neo4j.driver.v1.integration; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import org.mockito.Mockito; import java.util.List; import org.neo4j.driver.internal.ConnectionSettings; import org.neo4j.driver.internal.DriverFactory; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.net.BoltServerAddress; import org.neo4j.driver.internal.net.pooling.PoolSettings; import org.neo4j.driver.internal.net.pooling.SocketConnectionPool; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.spi.Connector; import org.neo4j.driver.internal.spi.PooledConnection; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.v1.AuthToken; import org.neo4j.driver.v1.AuthTokens; import org.neo4j.driver.v1.Config; import org.neo4j.driver.v1.Driver; import org.neo4j.driver.v1.Logging; import org.neo4j.driver.v1.Record; import org.neo4j.driver.v1.Session; import org.neo4j.driver.v1.StatementResult; import org.neo4j.driver.v1.StatementRunner; import org.neo4j.driver.v1.Transaction; import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.summary.ResultSummary; import org.neo4j.driver.v1.util.TestNeo4j; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.neo4j.driver.v1.Config.defaultConfig; import static org.neo4j.driver.v1.Values.parameters; public class ConnectionHandlingIT { @ClassRule public static final TestNeo4j neo4j = new TestNeo4j(); private Driver driver; private MemorizingConnectionPool connectionPool; @Before public void createDriver() { DriverFactoryWithConnector driverFactory = new DriverFactoryWithConnector(); AuthToken auth = neo4j.authToken(); RoutingSettings routingSettings = new RoutingSettings( 1, 1, null ); RetrySettings retrySettings = RetrySettings.DEFAULT; driver = driverFactory.newInstance( neo4j.uri(), auth, routingSettings, retrySettings, defaultConfig() ); connectionPool = driverFactory.connectionPool; } @After public void closeDriver() { driver.close(); } @Test public void connectionUsedForSessionRunReturnedToThePoolWhenResultConsumed() { StatementResult result = createNodesInNewSession( 12 ); Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).close(); result.consume(); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); verify( connection1 ).close(); } @Test public void connectionUsedForSessionRunReturnedToThePoolWhenResultSummaryObtained() { StatementResult result = createNodesInNewSession( 5 ); Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).close(); ResultSummary summary = result.summary(); assertEquals( 5, summary.counters().nodesCreated() ); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); verify( connection1 ).close(); } @Test public void connectionUsedForSessionRunReturnedToThePoolWhenResultFetchedInList() { StatementResult result = createNodesInNewSession( 2 ); Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).close(); List<Record> records = result.list(); assertEquals( 2, records.size() ); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); verify( connection1 ).close(); } @Test public void connectionUsedForSessionRunReturnedToThePoolWhenSingleRecordFetched() { StatementResult result = createNodesInNewSession( 1 ); Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).close(); assertNotNull( result.single() ); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); verify( connection1 ).close(); } @Test public void connectionUsedForSessionRunReturnedToThePoolWhenResultFetchedAsIterator() { StatementResult result = createNodesInNewSession( 6 ); Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).close(); int seenRecords = 0; while ( result.hasNext() ) { assertNotNull( result.next() ); seenRecords++; } assertEquals( 6, seenRecords ); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); verify( connection1 ).close(); } @Test public void connectionUsedForSessionRunReturnedToThePoolWhenServerErrorDuringResultFetching() { Session session = driver.session(); // provoke division by zero StatementResult result = session.run( "UNWIND range(10, 0, -1) AS i CREATE (n {index: 10/i}) RETURN n" ); Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).close(); try { result.hasNext(); fail( "Exception expected" ); } catch ( Exception e ) { assertThat( e, instanceOf( ClientException.class ) ); } Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); verify( connection1 ).close(); } @Test public void previousSessionRunResultIsBufferedBeforeRunningNewStatement() { Session session = driver.session(); StatementResult result1 = createNodes( 3, session ); Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).close(); StatementResult result2 = createNodes( 2, session ); verify( connection1 ).close(); assertEquals( 3, result1.list().size() ); assertEquals( 2, result2.list().size() ); } @Test public void previousSessionRunResultIsBufferedBeforeStartingNewTransaction() { Session session = driver.session(); StatementResult result1 = createNodes( 3, session ); Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).close(); session.beginTransaction(); verify( connection1 ).close(); assertEquals( 3, result1.list().size() ); } @Test public void connectionUsedForTransactionReturnedToThePoolWhenTransactionCommitted() { Session session = driver.session(); Transaction tx = session.beginTransaction(); Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).close(); StatementResult result = createNodes( 5, tx ); tx.success(); tx.close(); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); verify( connection1 ).close(); assertEquals( 5, result.list().size() ); } @Test public void connectionUsedForTransactionReturnedToThePoolWhenTransactionRolledBack() { Session session = driver.session(); Transaction tx = session.beginTransaction(); Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).close(); StatementResult result = createNodes( 8, tx ); tx.failure(); tx.close(); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); verify( connection1 ).close(); assertEquals( 8, result.list().size() ); } @Test public void connectionUsedForTransactionReturnedToThePoolWhenTransactionFailsToCommitted() throws Exception { try ( Session session = driver.session() ) { session.run( "CREATE CONSTRAINT ON (book:Book) ASSERT exists(book.isbn)" ); } Session session = driver.session(); Transaction tx = session.beginTransaction(); Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1 ).close(); // connection previously used for constraint creation // property existence constraints are verified on commit, try to violate it tx.run( "CREATE (:Book)" ); tx.success(); try { tx.close(); fail( "Exception expected" ); } catch ( Exception e ) { assertThat( e, instanceOf( ClientException.class ) ); } Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); // connection should have been closed twice: for constraint creation and for node creation verify( connection1, times( 2 ) ).close(); } @Test public void connectionDisposedWhenItHasUnrecoverableError() { Session session = driver.session(); PooledConnection connection1; StatementResult result1; try ( Transaction tx = session.beginTransaction() ) { result1 = tx.run( "RETURN 42 AS answer" ); tx.success(); connection1 = connectionPool.lastAcquiredConnectionSpy; when( connection1.hasUnrecoverableErrors() ).thenReturn( true ); } verify( connection1 ).dispose(); assertEquals( 42, result1.single().get( "answer" ).asInt() ); PooledConnection connection2; StatementResult result2; try ( Transaction tx = session.beginTransaction() ) { result2 = tx.run( "RETURN 4242 AS answer" ); tx.success(); connection2 = connectionPool.lastAcquiredConnectionSpy; assertNotSame( connection1, connection2 ); } verify( connection2, never() ).dispose(); verify( connection2 ).close(); assertEquals( 4242, result2.single().get( "answer" ).asInt() ); } private StatementResult createNodesInNewSession( int nodesToCreate ) { return createNodes( nodesToCreate, driver.session() ); } private StatementResult createNodes( int nodesToCreate, StatementRunner statementRunner ) { return statementRunner.run( "UNWIND range(1, {nodesToCreate}) AS i CREATE (n {index: i}) RETURN n", parameters( "nodesToCreate", nodesToCreate ) ); } private static class DriverFactoryWithConnector extends DriverFactory { MemorizingConnectionPool connectionPool; @Override protected ConnectionPool createConnectionPool( AuthToken authToken, SecurityPlan securityPlan, Config config ) { ConnectionSettings connectionSettings = new ConnectionSettings( authToken, 1000 ); PoolSettings poolSettings = new PoolSettings( 10, 0 ); Connector connector = createConnector( connectionSettings, securityPlan, config.logging() ); connectionPool = new MemorizingConnectionPool( poolSettings, connector, createClock(), config.logging() ); return connectionPool; } } private static class MemorizingConnectionPool extends SocketConnectionPool { PooledConnection lastAcquiredConnectionSpy; MemorizingConnectionPool( PoolSettings poolSettings, Connector connector, Clock clock, Logging logging ) { super( poolSettings, connector, clock, logging ); } @Override public PooledConnection acquire( BoltServerAddress address ) { PooledConnection connection = super.acquire( address ); // this connection pool returns spies so spies will be returned to the pool // prevent spying on spies... if ( !Mockito.mockingDetails( connection ).isSpy() ) { connection = spy( connection ); } lastAcquiredConnectionSpy = connection; return connection; } } }