/*
* 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.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.neo4j.driver.internal.DriverFactory;
import org.neo4j.driver.internal.cluster.RoutingSettings;
import org.neo4j.driver.internal.retry.RetrySettings;
import org.neo4j.driver.internal.util.Clock;
import org.neo4j.driver.internal.util.DriverFactoryWithClock;
import org.neo4j.driver.internal.util.FakeClock;
import org.neo4j.driver.v1.Config;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.GraphDatabase;
import org.neo4j.driver.v1.Record;
import org.neo4j.driver.v1.Session;
import org.neo4j.driver.v1.exceptions.ServiceUnavailableException;
import org.neo4j.driver.v1.util.TestNeo4j;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.neo4j.driver.v1.util.Neo4jRunner.DEFAULT_AUTH_TOKEN;
import static org.neo4j.driver.v1.util.Neo4jRunner.DEFAULT_URI;
/**
* Mainly concerned about the connection pool - we want to make sure that bad connections are evacuated from the
* pool properly if the server dies, or all connections are lost for some other reason.
*/
@RunWith(Parameterized.class)
public class ServerKilledIT
{
@Rule
public TestNeo4j neo4j = new TestNeo4j();
@Parameters(name = "{0} connections")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ "plaintext", Config.build().withoutEncryption() },
{ "tls encrypted", Config.build().withEncryption() }
});
}
private Config.ConfigBuilder config;
public ServerKilledIT( String testName, Config.ConfigBuilder config )
{
this.config = config;
}
@Test
public void shouldRecoverFromServerRestart() throws Throwable
{
// Given config with sessionLivenessCheckTimeout not set, i.e. turned off
try ( Driver driver = GraphDatabase.driver( DEFAULT_URI, DEFAULT_AUTH_TOKEN, config.toConfig() ) )
{
acquireAndReleaseConnections( 4, driver );
// When
neo4j.forceRestart();
// Then we should be able to start using sessions again, at most O(numSessions) session calls later
int toleratedFailures = 4;
for ( int i = 0; i < 10; i++ )
{
try ( Session s = driver.session() )
{
s.run( "RETURN 'Hello, world!'" );
}
catch ( ServiceUnavailableException e )
{
if ( toleratedFailures-- == 0 )
{
fail( "Expected (for now) at most four failures, one for each old connection, but now I've " +
"gotten " + "five: " + e.getMessage() );
}
}
}
if ( toleratedFailures > 0 )
{
fail( "This query should have failed " + toleratedFailures + " times" );
}
}
}
@Test
public void shouldDropBrokenOldSessions() throws Throwable
{
// config with set liveness check timeout
int livenessCheckTimeoutMinutes = 10;
config.withConnectionLivenessCheckTimeout( livenessCheckTimeoutMinutes, TimeUnit.MINUTES );
FakeClock clock = new FakeClock();
try ( Driver driver = createDriver( clock, config.toConfig() ) )
{
acquireAndReleaseConnections( 5, driver );
// restart database to invalidate all idle connections in the pool
neo4j.forceRestart();
// move clock forward more than configured liveness check timeout
clock.progress( TimeUnit.MINUTES.toMillis( livenessCheckTimeoutMinutes + 1 ) );
// now all idle connections should be considered too old and will be verified during acquisition
// they will appear broken because of the database restart and new valid connection will be created
try ( Session session = driver.session() )
{
List<Record> records = session.run( "RETURN 1" ).list();
assertEquals( 1, records.size() );
assertEquals( 1, records.get( 0 ).get( 0 ).asInt() );
}
}
}
private static void acquireAndReleaseConnections( int count, Driver driver )
{
if ( count > 0 )
{
Session session = driver.session();
session.run( "RETURN 1" );
acquireAndReleaseConnections( count - 1, driver );
session.close();
}
}
private static Driver createDriver( Clock clock, Config config )
{
DriverFactory factory = new DriverFactoryWithClock( clock );
RoutingSettings routingSettings = new RoutingSettings( 1, 1, null );
RetrySettings retrySettings = RetrySettings.DEFAULT;
return factory.newInstance( DEFAULT_URI, DEFAULT_AUTH_TOKEN, routingSettings, retrySettings, config );
}
}