/*
* 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.Ignore;
import org.junit.Rule;
import org.junit.Test;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.GraphDatabase;
import org.neo4j.driver.v1.Session;
import org.neo4j.driver.v1.exceptions.ClientException;
import org.neo4j.driver.v1.exceptions.DatabaseException;
import org.neo4j.driver.v1.util.TestNeo4j;
import static junit.framework.TestCase.fail;
@Ignore // TODO: re-enable this test when detecting a started server becomes more predictable
public class ConnectionPoolIT
{
@Rule
public TestNeo4j neo4j = new TestNeo4j();
private Driver driver;
private SessionGrabber sessionGrabber;
@Test
public void shouldRecoverFromDownedServer() throws Throwable
{
// Given a driver
driver = GraphDatabase.driver( neo4j.uri() );
// and given I'm heavily using it to acquire and release sessions
sessionGrabber = new SessionGrabber( driver );
sessionGrabber.start();
// When
neo4j.restart();
// Then we accept a hump with failing sessions, but demand that failures stop as soon as the server is back up.
sessionGrabber.assertSessionsAvailableWithin( 60 );
}
@After
public void cleanup() throws Exception
{
sessionGrabber.stop();
driver.close();
}
/**
* This is a background runner that will grab lots of sessions in one go, and then close them all, while tracking
* it's current state - is it currently able to acquire complete groups of sessions, or are there errors occurring?
*
* This can thus be used to judge the state of the driver - is it currently healthy or not?
*/
private class SessionGrabber implements Runnable
{
private final Driver driver;
private final CountDownLatch stopped = new CountDownLatch( 1 );
private volatile boolean sessionsAreAvailable = false;
private volatile boolean run = true;
private volatile Throwable lastExceptionFromDriver;
public SessionGrabber( Driver driver )
{
this.driver = driver;
}
public void start()
{
new Thread(this).start();
}
@Override
public void run()
{
try
{
while ( run )
{
try
{
// Try and launch 8 concurrent sessions
startAndCloseSessions( driver, 8 );
// Success! We created 8 sessions without failures
sessionsAreAvailable = true;
}
catch ( ClientException | DatabaseException e )
{
lastExceptionFromDriver = e;
sessionsAreAvailable = false;
}
catch ( Throwable e )
{
e.printStackTrace();
lastExceptionFromDriver = e;
throw new RuntimeException( e );
}
}
} finally
{
stopped.countDown();
}
}
private void startAndCloseSessions( Driver driver, int sessionCount )
{
LinkedList<Session> sessions = new LinkedList<>();
try
{
for ( int i = 0; i < sessionCount; i++ )
{
Session s = driver.session();
sessions.add( s );
s.run( "RETURN 1" ).consume();
}
}
finally
{
for ( Session session : sessions )
{
session.close();
}
}
}
public void assertSessionsAvailableWithin( int timeoutSeconds ) throws InterruptedException
{
long deadline = System.currentTimeMillis() + 1000 * timeoutSeconds;
while( System.currentTimeMillis() < deadline )
{
if( sessionsAreAvailable )
{
// Success!
return;
}
Thread.sleep( 100 );
}
// Failure - timeout :(
lastExceptionFromDriver.printStackTrace();
fail( "sessions did not become available from the driver after the db restart within the specified " +
"timeout. Last failure was: " + lastExceptionFromDriver.getMessage() );
}
public void stop() throws InterruptedException
{
run = false;
stopped.await(10, TimeUnit.SECONDS );
}
}
}