/* * 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.internal.net.pooling; import org.junit.Test; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionValidator; import org.neo4j.driver.internal.spi.PooledConnection; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.internal.util.Supplier; import org.neo4j.driver.v1.Logging; import org.neo4j.driver.v1.exceptions.ClientException; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.RETURNS_MOCKS; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.neo4j.driver.internal.net.BoltServerAddress.LOCAL_DEFAULT; public class PooledSocketConnectionTest { private static final ConnectionValidator<PooledConnection> VALID_CONNECTION = newFixedValidator( true, true ); private static final ConnectionValidator<PooledConnection> INVALID_CONNECTION = newFixedValidator( false, false ); @Test public void shouldDisposeConnectionIfNotValidConnection() throws Throwable { // Given final BlockingPooledConnectionQueue pool = newConnectionQueue(1); final boolean[] flags = {false}; Connection conn = mock( Connection.class ); PooledConnectionReleaseConsumer releaseConsumer = new PooledConnectionReleaseConsumer( pool, INVALID_CONNECTION ); PooledConnection pooledConnection = new PooledSocketConnection( conn, releaseConsumer, Clock.SYSTEM ) { @Override public void dispose() { flags[0] = true; } }; // When pooledConnection.close(); // Then assertThat( pool.size(), equalTo( 0 ) ); assertThat( flags[0], equalTo( true ) ); } @Test public void shouldReturnToThePoolIfIsValidConnectionAndIdlePoolIsNotFull() throws Throwable { // Given final BlockingPooledConnectionQueue pool = newConnectionQueue(1); final boolean[] flags = {false}; Connection conn = mock( Connection.class ); PooledConnectionReleaseConsumer releaseConsumer = new PooledConnectionReleaseConsumer( pool, VALID_CONNECTION ); PooledConnection pooledConnection = new PooledSocketConnection( conn, releaseConsumer, Clock.SYSTEM ) { @Override public void dispose() { flags[0] = true; } }; // When pooledConnection.close(); // Then assertTrue( pool.contains(pooledConnection)); assertThat( pool.size(), equalTo( 1 ) ); assertThat( flags[0], equalTo( false ) ); } @Test public void shouldDisposeConnectionIfValidConnectionAndIdlePoolIsFull() throws Throwable { // Given final BlockingPooledConnectionQueue pool = newConnectionQueue(1); final boolean[] flags = {false}; Connection conn = mock( Connection.class ); PooledConnectionReleaseConsumer releaseConsumer = new PooledConnectionReleaseConsumer( pool, VALID_CONNECTION); PooledConnection pooledConnection = new PooledSocketConnection( conn, releaseConsumer, Clock.SYSTEM ); PooledConnection shouldBeClosedConnection = new PooledSocketConnection( conn, releaseConsumer, Clock.SYSTEM ) { @Override public void dispose() { flags[0] = true; } }; // When pooledConnection.close(); shouldBeClosedConnection.close(); // Then assertTrue( pool.contains(pooledConnection) ); assertThat( pool.size(), equalTo( 1 ) ); assertThat( flags[0], equalTo( true ) ); } @Test @SuppressWarnings( "unchecked" ) public void shouldDisposeAcquiredConnectionsWhenPoolIsClosed() { PooledConnection connection = mock( PooledConnection.class ); BlockingPooledConnectionQueue pool = newConnectionQueue( 5 ); Supplier<PooledConnection> pooledConnectionFactory = mock( Supplier.class ); when( pooledConnectionFactory.get() ).thenReturn( connection ); PooledConnection acquiredConnection = pool.acquire( pooledConnectionFactory ); assertSame( acquiredConnection, connection ); pool.terminate(); verify( connection ).dispose(); } @Test @SuppressWarnings( "unchecked" ) public void shouldDisposeAcquiredAndIdleConnectionsWhenPoolIsClosed() { PooledConnection connection1 = mock( PooledConnection.class ); PooledConnection connection2 = mock( PooledConnection.class ); PooledConnection connection3 = mock( PooledConnection.class ); BlockingPooledConnectionQueue pool = newConnectionQueue( 5 ); Supplier<PooledConnection> pooledConnectionFactory = mock( Supplier.class ); when( pooledConnectionFactory.get() ) .thenReturn( connection1 ) .thenReturn( connection2 ) .thenReturn( connection3 ); PooledConnection acquiredConnection1 = pool.acquire( pooledConnectionFactory ); PooledConnection acquiredConnection2 = pool.acquire( pooledConnectionFactory ); PooledConnection acquiredConnection3 = pool.acquire( pooledConnectionFactory ); assertSame( acquiredConnection1, connection1 ); assertSame( acquiredConnection2, connection2 ); assertSame( acquiredConnection3, connection3 ); pool.offer( acquiredConnection2 ); pool.terminate(); verify( connection1 ).dispose(); verify( connection2 ).dispose(); verify( connection3 ).dispose(); } @Test public void shouldDisposeConnectionIfPoolAlreadyClosed() throws Throwable { // driver = GraphDatabase.driver(); // session = driver.session(); // ... // driver.close() -> clear the pools // session.close() -> well, close the connection directly without putting back to the pool // Given final BlockingPooledConnectionQueue pool = newConnectionQueue(1); pool.terminate(); final boolean[] flags = {false}; Connection conn = mock( Connection.class ); PooledConnectionReleaseConsumer releaseConsumer = new PooledConnectionReleaseConsumer( pool, VALID_CONNECTION); PooledConnection pooledConnection = new PooledSocketConnection( conn, releaseConsumer, Clock.SYSTEM ) { @Override public void dispose() { flags[0] = true; } }; // When pooledConnection.close(); // Then assertThat( pool.size(), equalTo( 0 ) ); assertThat( flags[0], equalTo( true ) ); // make sure that the dispose is called } @Test public void shouldDisposeConnectionIfPoolStoppedAfterPuttingConnectionBackToPool() throws Throwable { // Given final BlockingPooledConnectionQueue pool = newConnectionQueue(1); pool.terminate(); final boolean[] flags = {false}; Connection conn = mock( Connection.class ); PooledConnectionReleaseConsumer releaseConsumer = new PooledConnectionReleaseConsumer( pool, VALID_CONNECTION); PooledConnection pooledConnection = new PooledSocketConnection( conn, releaseConsumer, Clock.SYSTEM ) { @Override public void dispose() { flags[0] = true; } }; // When pooledConnection.close(); // Then assertThat( pool.size(), equalTo( 0 ) ); assertThat( flags[0], equalTo( true ) ); // make sure that the dispose is called } @Test public void shouldAckFailureOnRecoverableFailure() throws Throwable { // Given Connection conn = mock( Connection.class ); ClientException error = new ClientException( "Neo.ClientError", "a recoverable error" ); doThrow( error ).when( conn ).sync(); PooledConnection pooledConnection = new PooledSocketConnection( conn, mock( PooledConnectionReleaseConsumer.class ), mock( Clock.class ) ); // When try { pooledConnection.sync(); fail( "Should have thrown a recoverable error" ); } // Then catch( ClientException e ) { assertThat( e, equalTo( error ) ); } verify( conn, times( 1 ) ).ackFailure(); assertThat( pooledConnection.hasUnrecoverableErrors(), equalTo( false ) ); } @Test public void shouldNotAckFailureOnUnRecoverableFailure() { // Given Connection conn = mock( Connection.class ); ClientException error = new ClientException( "an unrecoverable error" ); doThrow( error ).when( conn ).sync(); PooledConnection pooledConnection = new PooledSocketConnection( conn, mock( PooledConnectionReleaseConsumer.class ), mock( Clock.class ) ); // When try { pooledConnection.sync(); fail( "Should have thrown an unrecoverable error" ); } //Then catch( ClientException e ) { assertThat( e, equalTo( error ) ); } verify( conn, times( 0 ) ).ackFailure(); assertThat( pooledConnection.hasUnrecoverableErrors(), equalTo( true ) ); } @Test public void shouldThrowExceptionIfFailureReceivedForAckFailure() { // Given Connection conn = mock( Connection.class ); ClientException error = new ClientException( "Neo.ClientError", "a recoverable error" ); ClientException failedToAckFailError = new ClientException( "Invalid server response message `FAILURE` received for client message `ACK_FAILURE`." ); doThrow( error ).doThrow( failedToAckFailError ).when( conn ).sync(); PooledConnection pooledConnection = new PooledSocketConnection( conn, mock( PooledConnectionReleaseConsumer.class ), mock( Clock.class ) ); // When & Then try { pooledConnection.sync(); fail( "Should have thrown a recoverable error" ); } catch( ClientException e ) { assertThat( e, equalTo( error ) ); } assertThat( pooledConnection.hasUnrecoverableErrors(), equalTo( false ) ); try { // sync ackFailure pooledConnection.sync(); fail( "Should have thrown an unrecoverable error" ); } catch( ClientException e ) { assertThat( e, equalTo( failedToAckFailError ) ); } verify( conn, times( 1 ) ).ackFailure(); assertThat( pooledConnection.hasUnrecoverableErrors(), equalTo( true ) ); } @Test public void hasNewLastUsedTimestampWhenCreated() { PooledConnectionReleaseConsumer releaseConsumer = mock( PooledConnectionReleaseConsumer.class ); Clock clock = when( mock( Clock.class ).millis() ).thenReturn( 42L ).getMock(); PooledConnection connection = new PooledSocketConnection( mock( Connection.class ), releaseConsumer, clock ); assertEquals( 42L, connection.lastUsedTimestamp() ); } @Test public void lastUsedTimestampUpdatedWhenConnectionClosed() { PooledConnectionReleaseConsumer releaseConsumer = mock( PooledConnectionReleaseConsumer.class ); Clock clock = when( mock( Clock.class ).millis() ) .thenReturn( 42L ).thenReturn( 4242L ).thenReturn( 424242L ).getMock(); PooledConnection connection = new PooledSocketConnection( mock( Connection.class ), releaseConsumer, clock ); assertEquals( 42, connection.lastUsedTimestamp() ); connection.close(); assertEquals( 4242, connection.lastUsedTimestamp() ); connection.close(); assertEquals( 424242, connection.lastUsedTimestamp() ); } private static BlockingPooledConnectionQueue newConnectionQueue( int capacity ) { return new BlockingPooledConnectionQueue( LOCAL_DEFAULT, capacity, mock( Logging.class, RETURNS_MOCKS ) ); } private static ConnectionValidator<PooledConnection> newFixedValidator( final boolean reusable, final boolean connected ) { return new ConnectionValidator<PooledConnection>() { @Override public boolean isReusable( PooledConnection connection ) { return reusable; } @Override public boolean isConnected( PooledConnection connection ) { return connected; } }; } }