Skip to content

Commit

Permalink
Prevent database healing in case of critical errors
Browse files Browse the repository at this point in the history
Do not allow to heal database health in case of critical exception was
observed.
So far only OutOfMemoryError considered as critical exception.
  • Loading branch information
MishaDemianenko committed Jul 20, 2017
1 parent 1566b7c commit 4a24b8c
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 12 deletions.
Expand Up @@ -20,6 +20,7 @@
package org.neo4j.kernel.internal; package org.neo4j.kernel.internal;


import org.neo4j.graphdb.event.ErrorState; import org.neo4j.graphdb.event.ErrorState;
import org.neo4j.helpers.Exceptions;
import org.neo4j.kernel.impl.core.DatabasePanicEventGenerator; import org.neo4j.kernel.impl.core.DatabasePanicEventGenerator;
import org.neo4j.logging.Log; import org.neo4j.logging.Log;


Expand All @@ -29,9 +30,9 @@ public class DatabaseHealth
{ {
private static final String panicMessage = "Database has encountered some problem, " private static final String panicMessage = "Database has encountered some problem, "
+ "please perform necessary action (tx recovery/restart)"; + "please perform necessary action (tx recovery/restart)";
private static final Class<?>[] CRITICAL_EXCEPTIONS = new Class[]{OutOfMemoryError.class};


// Keep that cozy name for legacy purposes private volatile boolean healthy = true;
private volatile boolean tmOk = true; // TODO rather skip volatile if possible here.
private final DatabasePanicEventGenerator dbpe; private final DatabasePanicEventGenerator dbpe;
private final Log log; private final Log log;
private Throwable causeOfPanic; private Throwable causeOfPanic;
Expand All @@ -51,7 +52,7 @@ public DatabaseHealth( DatabasePanicEventGenerator dbpe, Log log )
*/ */
public <EXCEPTION extends Throwable> void assertHealthy( Class<EXCEPTION> panicDisguise ) throws EXCEPTION public <EXCEPTION extends Throwable> void assertHealthy( Class<EXCEPTION> panicDisguise ) throws EXCEPTION
{ {
if ( !tmOk ) if ( !healthy )
{ {
EXCEPTION exception; EXCEPTION exception;
try try
Expand All @@ -78,7 +79,7 @@ public <EXCEPTION extends Throwable> void assertHealthy( Class<EXCEPTION> panicD


public void panic( Throwable cause ) public void panic( Throwable cause )
{ {
if ( !tmOk ) if ( !healthy )
{ {
return; return;
} }
Expand All @@ -88,21 +89,35 @@ public void panic( Throwable cause )
throw new IllegalArgumentException( "Must provide a cause for the database panic" ); throw new IllegalArgumentException( "Must provide a cause for the database panic" );
} }
this.causeOfPanic = cause; this.causeOfPanic = cause;
this.tmOk = false; this.healthy = false;
log.error( "Database panic: " + panicMessage, cause ); log.error( "Database panic: " + panicMessage, cause );
dbpe.generateEvent( ErrorState.TX_MANAGER_NOT_OK, causeOfPanic ); dbpe.generateEvent( ErrorState.TX_MANAGER_NOT_OK, causeOfPanic );
} }


public boolean isHealthy() public boolean isHealthy()
{ {
return tmOk; return healthy;
} }


public void healed() public boolean healed()
{ {
tmOk = true; if ( hasCriticalFailure() )
causeOfPanic = null; {
log.info( "Database health set to OK" ); log.error( "Database encountered a critical error and can't be healed. Restart required." );
return false;
}
else
{
healthy = true;
causeOfPanic = null;
log.info( "Database health set to OK" );
return true;
}
}

private boolean hasCriticalFailure()
{
return !isHealthy() && Exceptions.contains( causeOfPanic, CRITICAL_EXCEPTIONS );
} }


public Throwable cause() public Throwable cause()
Expand Down
Expand Up @@ -21,12 +21,16 @@


import org.junit.Test; import org.junit.Test;


import java.io.IOException;

import org.neo4j.kernel.impl.core.DatabasePanicEventGenerator; import org.neo4j.kernel.impl.core.DatabasePanicEventGenerator;
import org.neo4j.logging.AssertableLogProvider; import org.neo4j.logging.AssertableLogProvider;
import org.neo4j.logging.NullLogProvider; import org.neo4j.logging.NullLogProvider;


import static org.hamcrest.Matchers.sameInstance; import static org.hamcrest.Matchers.sameInstance;
import static org.hamcrest.core.Is.is; import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
Expand All @@ -36,7 +40,7 @@
public class DatabaseHealthTest public class DatabaseHealthTest
{ {
@Test @Test
public void shouldGenerateKernelPanicEvents() throws Exception public void shouldGenerateDatabasePanicEvents() throws Exception
{ {
// GIVEN // GIVEN
DatabasePanicEventGenerator generator = mock( DatabasePanicEventGenerator.class ); DatabasePanicEventGenerator generator = mock( DatabasePanicEventGenerator.class );
Expand All @@ -53,7 +57,7 @@ public void shouldGenerateKernelPanicEvents() throws Exception
} }


@Test @Test
public void shouldLogKernelPanicEvent() throws Exception public void shouldLogDatabasePanicEvent() throws Exception
{ {
// GIVEN // GIVEN
AssertableLogProvider logProvider = new AssertableLogProvider(); AssertableLogProvider logProvider = new AssertableLogProvider();
Expand All @@ -74,4 +78,40 @@ public void shouldLogKernelPanicEvent() throws Exception
) )
); );
} }

@Test
public void healDatabaseWithoutCriticalErrors()
{
AssertableLogProvider logProvider = new AssertableLogProvider();
DatabaseHealth databaseHealth = new DatabaseHealth( mock( DatabasePanicEventGenerator.class ),
logProvider.getLog( DatabaseHealth.class ) );

assertTrue( databaseHealth.isHealthy() );

databaseHealth.panic( new IOException( "Space exception." ) );

assertFalse( databaseHealth.isHealthy() );
assertTrue( databaseHealth.healed() );
logProvider.assertContainsLogCallContaining( "Database health set to OK" );
logProvider.assertNoMessagesContaining( "Database encountered a critical error and can't be healed. Restart required." );
}

@Test
public void databaseWithCriticalErrorsCanNotBeHealed()
{
AssertableLogProvider logProvider = new AssertableLogProvider();
DatabaseHealth databaseHealth = new DatabaseHealth( mock( DatabasePanicEventGenerator.class ),
logProvider.getLog( DatabaseHealth.class ) );

assertTrue( databaseHealth.isHealthy() );

IOException criticalException = new IOException( "Space exception.", new OutOfMemoryError( "Out of memory." ) );
databaseHealth.panic( criticalException );

assertFalse( databaseHealth.isHealthy() );
assertFalse( databaseHealth.healed() );
logProvider.assertNoMessagesContaining( "Database health set to OK" );
logProvider.assertContainsLogCallContaining(
"Database encountered a critical error and can't be healed. Restart required." );
}
} }

0 comments on commit 4a24b8c

Please sign in to comment.