/** * Copyright (c) 2002-2014 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package concurrency; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.index.Index; import org.neo4j.index.impl.lucene.LuceneDataSource; import org.neo4j.kernel.GraphDatabaseAPI; import org.neo4j.kernel.impl.transaction.xaframework.XaContainer; import org.neo4j.test.AbstractSubProcessTestBase; import org.neo4j.test.subprocess.DebugInterface; import org.neo4j.test.subprocess.DebuggedThread; import org.neo4j.test.subprocess.BreakPoint; public class ShutdownRaceTest extends AbstractSubProcessTestBase { private final CountDownLatch restart = new CountDownLatch( 1 ), last = new CountDownLatch( 1 ); @Test public void canHaveShutdownWhileAccessingIndexWriters() throws Exception { run( new IndexTask() ); run( new BreakTask() ); restart.await(); restart(); last.await(); run( new IndexTask() ); } @Override protected BreakPoint[] breakpoints( int id ) { final AtomicReference<DebuggedThread> shutdownThread = new AtomicReference<DebuggedThread>(), indexThread = new AtomicReference<DebuggedThread>(); return new BreakPoint[] { new BreakPoint( XaContainer.class, "close" ) { @Override protected void callback( DebugInterface debug ) { if ( debug.matchCallingMethod( 1, LuceneDataSource.class, null ) ) { shutdownThread.set( debug.thread().suspend( this ) ); resume( indexThread.getAndSet( null ) ); this.disable(); } } @Override public void deadlock( DebuggedThread thread ) { shutdownThread.set( null ); thread.resume(); } }.enable(), new BreakPoint( BreakTask.class, "breakpoint1" ) { @Override protected void callback( DebugInterface debug ) { indexThread.set( debug.thread().suspend( this ) ); restart.countDown(); } }.enable(), new BreakPoint( BreakTask.class, "breakpoint2" ) { @Override protected void callback( DebugInterface debug ) { resume( shutdownThread.getAndSet( null ) ); last.countDown(); } }.enable() }; } static void resume( DebuggedThread thread ) { if ( thread != null ) thread.resume(); } @SuppressWarnings( "serial" ) private static class IndexTask implements Task { @Override public void run( final GraphDatabaseAPI graphdb ) { try { Transaction tx = graphdb.beginTx(); try { index( graphdb.index().forNodes( "name" ), graphdb.getReferenceNode() ); tx.success(); } finally { tx.finish(); } } finally { done(); } } private void index( Index<Node> index, Node node ) { enterIndex(); index.add( node, getClass().getSimpleName(), Thread.currentThread().getName() ); } protected void enterIndex() { // override } protected void done() { // override } } @SuppressWarnings( "serial" ) private static class BreakTask extends IndexTask { @Override public void run( final GraphDatabaseAPI graphdb ) { new Thread() { @Override public void run() { runTask( graphdb ); } }.start(); } void runTask( GraphDatabaseAPI graphdb ) { super.run( graphdb ); } @Override protected void enterIndex() { breakpoint1(); } @Override protected void done() { breakpoint2(); } private void breakpoint1() { // the debugger will break here } private void breakpoint2() { // the debugger will break here } } }