diff --git a/graph-algo/CHANGES.txt b/graph-algo/CHANGES.txt index 4d0925320..76b9a4b18 100644 --- a/graph-algo/CHANGES.txt +++ b/graph-algo/CHANGES.txt @@ -1,3 +1,7 @@ +1.4.M03 (2011-05-26) +-------------------- +o No Changes. + 1.3.M05 (2011-03-24) -------------------- o Fixed a bug regarding creation of invalid paths. diff --git a/jmx/CHANGES.txt b/jmx/CHANGES.txt index 3ba265127..c0d48cf30 100644 --- a/jmx/CHANGES.txt +++ b/jmx/CHANGES.txt @@ -1,3 +1,8 @@ +1.4.M03 (2011-05-25) +-------------------- +o No changes. + +-------------------- o Split up the management component in two, one ("jmx" - this component) with basic support and one ("management") with advanced support. 1.3.M05 (2011-03-24) diff --git a/kernel/CHANGES.txt b/kernel/CHANGES.txt index c583736fa..2c4a8a286 100644 --- a/kernel/CHANGES.txt +++ b/kernel/CHANGES.txt @@ -1,3 +1,12 @@ +1.4.M03 (2011-05-26) +-------------------- +o Kernel now supports self relationships or "loops". +o Added new relationship direction aware cache on nodes speeding up traversals by direction. +o Reduced memory usage by relationships. +o Reduced call stack removing the EventConsumer/ResourceConnection wrappers glue between nioneo and kernel. +o Fixed bug in global tx log that did not mark transactions finished when closed then crashed. +o Fixed cache invalidation when applying external transaction. + 1.4.M02 (2011-05-12) -------------------- o Changed the default implementation of the logical log to a DirectMappedLogBuffer. diff --git a/kernel/src/main/java/org/neo4j/kernel/impl/persistence/PersistenceManager.java b/kernel/src/main/java/org/neo4j/kernel/impl/persistence/PersistenceManager.java index ae70f6fb2..9d8589fc4 100644 --- a/kernel/src/main/java/org/neo4j/kernel/impl/persistence/PersistenceManager.java +++ b/kernel/src/main/java/org/neo4j/kernel/impl/persistence/PersistenceManager.java @@ -128,63 +128,63 @@ public RelationshipTypeData[] loadAllRelationshipTypes() public ArrayMap nodeDelete( long nodeId ) { - return getResource().nodeDelete( nodeId ); + return getResource( true ).nodeDelete( nodeId ); } public long nodeAddProperty( long nodeId, PropertyIndex index, Object value ) { - return getResource().nodeAddProperty( nodeId, index, value ); + return getResource( true ).nodeAddProperty( nodeId, index, value ); } public void nodeChangeProperty( long nodeId, long propertyId, Object value ) { - getResource().nodeChangeProperty( nodeId, propertyId, value ); + getResource( true ).nodeChangeProperty( nodeId, propertyId, value ); } public void nodeRemoveProperty( long nodeId, long propertyId ) { - getResource().nodeRemoveProperty( nodeId, propertyId ); + getResource( true ).nodeRemoveProperty( nodeId, propertyId ); } public void nodeCreate( long id ) { - getResource().nodeCreate( id ); + getResource( true ).nodeCreate( id ); } public void relationshipCreate( long id, int typeId, long startNodeId, long endNodeId ) { - getResource().relationshipCreate( id, typeId, startNodeId, endNodeId ); + getResource( true ).relationshipCreate( id, typeId, startNodeId, endNodeId ); } public ArrayMap relDelete( long relId ) { - return getResource().relDelete( relId ); + return getResource( true ).relDelete( relId ); } public long relAddProperty( long relId, PropertyIndex index, Object value ) { - return getResource().relAddProperty( relId, index, value ); + return getResource( true ).relAddProperty( relId, index, value ); } public void relChangeProperty( long relId, long propertyId, Object value ) { - getResource().relChangeProperty( relId, propertyId, value ); + getResource( true ).relChangeProperty( relId, propertyId, value ); } public void relRemoveProperty( long relId, long propertyId ) { - getResource().relRemoveProperty( relId, propertyId ); + getResource( true ).relRemoveProperty( relId, propertyId ); } public void createPropertyIndex( String key, int id ) { - getResource().createPropertyIndex( key, id ); + getResource( true ).createPropertyIndex( key, id ); } public void createRelationshipType( int id, String name ) { - getResource().createRelationshipType( id, name ); + getResource( false ).createRelationshipType( id, name ); } private NeoStoreTransaction getReadOnlyResource() @@ -208,7 +208,7 @@ private NeoStoreTransaction getReadOnlyResource() return con; } - private NeoStoreTransaction getResource() + private NeoStoreTransaction getResource( boolean registerEventHooks ) { NeoStoreTransaction con = null; @@ -233,7 +233,7 @@ private NeoStoreTransaction getResource() con = persistenceSource.createTransaction( xaConnection ); tx.registerSynchronization( new TxCommitHook( tx ) ); - registerTransactionEventHookIfNeeded(); + if ( registerEventHooks ) registerTransactionEventHookIfNeeded(); txConnectionMap.put( tx, con ); } catch ( javax.transaction.RollbackException re ) @@ -359,17 +359,17 @@ void releaseResourceConnectionsForTransaction( Transaction tx ) public RelIdArray getCreatedNodes() { - return getResource().getCreatedNodes(); + return getResource( true ).getCreatedNodes(); } public boolean isNodeCreated( long nodeId ) { - return getResource().isNodeCreated( nodeId ); + return getResource( true ).isNodeCreated( nodeId ); } public boolean isRelationshipCreated( long relId ) { - return getResource().isRelationshipCreated( relId ); + return getResource( true ).isRelationshipCreated( relId ); } public int getKeyIdForProperty( long propertyId ) diff --git a/kernel/src/test/java/org/neo4j/kernel/TestTransactionEventDeadlocks.java b/kernel/src/test/java/org/neo4j/kernel/TestTransactionEventDeadlocks.java new file mode 100644 index 000000000..0d2b933c3 --- /dev/null +++ b/kernel/src/test/java/org/neo4j/kernel/TestTransactionEventDeadlocks.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2002-2011 "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 . + */ +package org.neo4j.kernel; + +import static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.neo4j.graphdb.DynamicRelationshipType; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.event.TransactionData; +import org.neo4j.graphdb.event.TransactionEventHandler; +import org.neo4j.test.TargetDirectory; + +public class TestTransactionEventDeadlocks +{ + public TargetDirectory target = TargetDirectory.forTest( getClass() ); + private EmbeddedGraphDatabase graphdb; + + @Before + public void startGraphdb() + { + this.graphdb = new EmbeddedGraphDatabase( target.graphDbDir( true ).getPath() ); + } + + @After + public void stopGraphdb() + { + if ( graphdb != null ) graphdb.shutdown(); + graphdb = null; + } + + @Test + public void canAvoidDeadlockThatWouldHappenIfTheRelationshipTypeCreationTransactionModifiedData() throws Exception + { + final Node root = graphdb.getReferenceNode(); + Transaction tx = graphdb.beginTx(); + try + { + root.setProperty( "counter", Long.valueOf( 0L ) ); + tx.success(); + } + finally + { + tx.finish(); + } + + graphdb.registerTransactionEventHandler( new TransactionEventHandler() + { + @SuppressWarnings( "boxing" ) + @Override + public Void beforeCommit( TransactionData data ) throws Exception + { + root.setProperty( "counter", ( (Long) root.removeProperty( "counter" ) ) + 1 ); + return null; + } + + @Override + public void afterCommit( TransactionData data, Void state ) + { + // nothing + } + + @Override + public void afterRollback( TransactionData data, Void state ) + { + // nothing + } + } ); + + tx = graphdb.beginTx(); + try + { + root.setProperty( "state", "not broken yet" ); + root.createRelationshipTo( graphdb.createNode(), DynamicRelationshipType.withName( "TEST" ) ); + root.removeProperty( "state" ); + tx.success(); + } + finally + { + tx.finish(); + } + + assertEquals( 1L, root.getProperty( "counter" ) ); + } +} diff --git a/kernel/src/test/java/org/neo4j/test/AbstractSubProcessTestBase.java b/kernel/src/test/java/org/neo4j/test/AbstractSubProcessTestBase.java new file mode 100644 index 000000000..ac4197048 --- /dev/null +++ b/kernel/src/test/java/org/neo4j/test/AbstractSubProcessTestBase.java @@ -0,0 +1,200 @@ +/** + * Copyright (c) 2002-2011 "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 . + */ +package org.neo4j.test; + +import java.io.IOException; +import java.io.Serializable; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import org.junit.After; +import org.junit.Before; +import org.neo4j.kernel.AbstractGraphDatabase; +import org.neo4j.kernel.EmbeddedGraphDatabase; + +public class AbstractSubProcessTestBase +{ + private final TargetDirectory target; + protected final Instance[] instances; + + public AbstractSubProcessTestBase() + { + this( 1 ); + } + + protected AbstractSubProcessTestBase( int instances ) + { + this.instances = new Instance[instances]; + this.target = TargetDirectory.forTest( getClass() ); + } + + protected final void run( Task task ) + { + for ( Instance instance : instances ) + { + if ( instance != null ) instance.run( task ); + } + } + + protected final void restart() + { + for ( Instance instance : instances ) + { + if ( instance != null ) instance.restart(); + } + } + + /** + * @param id the id of the instance to get breakpoints for + */ + protected SubProcessBreakPoint[] breakpoints( int id ) + { + return null; + } + + protected interface Task extends Serializable + { + void run( AbstractGraphDatabase graphdb ); + } + + @Before + public final void startSubprocesses() throws IOException, InterruptedException + { + SubInstance prototype = new SubInstance(); + for ( int i = 0; i < instances.length; i++ ) + { + instances[i] = prototype.start( bootstrap( i ), breakpoints( i ) ); + } + for ( Instance instance : instances ) + { + if ( instance != null ) instance.awaitStarted(); + } + } + + protected Bootstrapper bootstrap( int id ) throws IOException + { + return new Bootstrapper( this, id ); + } + + @After + public final void stopSubprocesses() + { + synchronized ( instances ) + { + for ( int i = 0; i < instances.length; i++ ) + { + Instance instance = instances[i]; + if ( instance != null ) SubProcess.stop( instance ); + instances[i] = null; + } + } + } + + public interface Instance + { + void run( Task task ); + + void awaitStarted() throws InterruptedException; + + void restart(); + + // T getMBean( Class beanType ); + } + + @SuppressWarnings( "serial" ) + protected static class Bootstrapper implements Serializable + { + protected final String storeDir; + + protected Bootstrapper( AbstractSubProcessTestBase test, int instance ) throws IOException + { + this.storeDir = test.target.directory( "graphdb." + instance, true ).getCanonicalPath(); + } + + protected AbstractGraphDatabase startup() + { + return new EmbeddedGraphDatabase( storeDir ); + } + } + + @SuppressWarnings( { "hiding", "serial" } ) + private static class SubInstance extends SubProcess implements Instance + { + private volatile AbstractGraphDatabase graphdb; + private static final AtomicReferenceFieldUpdater GRAPHDB = AtomicReferenceFieldUpdater + .newUpdater( SubInstance.class, AbstractGraphDatabase.class, "graphdb" ); + private volatile Bootstrapper bootstrap; + + @Override + protected synchronized void startup( Bootstrapper bootstrap ) throws Throwable + { + this.bootstrap = bootstrap; + graphdb = bootstrap.startup(); + } + + @Override + public void awaitStarted() throws InterruptedException + { + while ( graphdb == null ) + { + Thread.sleep( 1 ); + } + } + + @Override + public void run( Task task ) + { + task.run( graphdb ); + } + + @Override + protected void shutdown() + { + AbstractGraphDatabase graphdb; + graphdb = GRAPHDB.getAndSet( this, null ); + this.bootstrap = null; + if ( graphdb != null ) graphdb.shutdown(); + super.shutdown(); + } + + @Override + public void restart() + { + AbstractGraphDatabase graphdb; + Bootstrapper bootstrap = this.bootstrap; + while ( ( graphdb = GRAPHDB.getAndSet( this, null ) ) == null ) + { + if ( ( bootstrap = this.bootstrap ) == null ) + throw new IllegalStateException( "instance has been shut down" ); + } + graphdb.shutdown(); + this.graphdb = bootstrap.startup(); + } + + public T getMBean( Class beanType ) + { + AbstractGraphDatabase graphdb; + while ( ( graphdb = this.graphdb ) == null ) + { + if ( this.bootstrap == null ) throw new IllegalStateException( "instance has been shut down" ); + } + return graphdb.getManagementBean( beanType ); + } + } +} diff --git a/kernel/src/test/java/org/neo4j/test/SubProcess.java b/kernel/src/test/java/org/neo4j/test/SubProcess.java index ef81d9022..d7b7d9df3 100644 --- a/kernel/src/test/java/org/neo4j/test/SubProcess.java +++ b/kernel/src/test/java/org/neo4j/test/SubProcess.java @@ -53,10 +53,6 @@ import org.neo4j.helpers.Predicate; -import com.sun.jdi.ReferenceType; -import com.sun.jdi.event.EventQueue; -import com.sun.jdi.event.LocatableEvent; -import com.sun.jdi.request.ClassPrepareRequest; @SuppressWarnings( "serial" ) public abstract class SubProcess implements Serializable @@ -131,6 +127,7 @@ public T start( P parameter, SubProcessBreakPoint... breakpoints ) } Process process; String pid; + DebugDispatch debugDispatch = null; synchronized ( debugger != null ? DebuggerConnector.class : new Object() ) { if ( debugger != null ) @@ -149,13 +146,14 @@ public T start( P parameter, SubProcessBreakPoint... breakpoints ) pipe( "[" + toString() + ":" + pid + "] ", process.getInputStream(), System.out ); if ( debugger != null ) { - debugger.connect( toString() + ":" + pid ); + debugDispatch = debugger.connect( toString() + ":" + pid ); } } Dispatcher dispatcher = callback.get( process ); if ( dispatcher == null ) throw new IllegalStateException( "failed to start sub process" ); - return t.cast( Proxy.newProxyInstance( t.getClassLoader(), new Class[] { t },// - live( new Handler( t, dispatcher, process, "<" + toString() + ":" + pid + ">" ) ) ) ); + Handler handler = new Handler( t, dispatcher, process, "<" + toString() + ":" + pid + ">" ); + if ( debugDispatch != null ) debugDispatch.handler = handler; + return t.cast( Proxy.newProxyInstance( t.getClassLoader(), new Class[] { t }, live( handler ) ) ); } private String classPath( String parentClasspath ) @@ -227,7 +225,7 @@ String listen() } } - void connect( String string ) + DebugDispatch connect( String string ) { final com.sun.jdi.VirtualMachine vm; try @@ -253,19 +251,35 @@ void connect( String string ) continue TYPES; } } - ClassPrepareRequest prepare = erm.createClassPrepareRequest(); + com.sun.jdi.request.ClassPrepareRequest prepare = erm.createClassPrepareRequest(); prepare.addClassFilter( entry.getKey() ); prepare.enable(); } - new Thread( new DebugDispatch( vm.eventQueue(), breakpoints ), "Debugger: [" + string + "]" ).start(); + if ( vm.canRequestMonitorEvents() ) + { + erm.createMonitorContendedEnterRequest().enable(); + } + DebugDispatch dispatch = new DebugDispatch( vm.eventQueue(), breakpoints ); + new Thread( dispatch, "Debugger: [" + string + "]" ).start(); + return dispatch; } } @SuppressWarnings( "restriction" ) private static class DebugDispatch implements Runnable { - private final EventQueue queue; + volatile Handler handler; + private final com.sun.jdi.event.EventQueue queue; private final Map> breakpoints; + private final Map suspended = new HashMap(); + private final DeadlockCallback defaultCallback = new DeadlockCallback() + { + @Override + public void deadlock( DebuggedThread thread ) + { + throw new DeadlockDetectedError(); + } + }; DebugDispatch( com.sun.jdi.event.EventQueue queue, Map> breakpoints ) { @@ -291,7 +305,34 @@ public void run() { for ( com.sun.jdi.event.Event event : events ) { - if ( event instanceof com.sun.jdi.event.LocatableEvent ) + if ( event instanceof com.sun.jdi.event.MonitorContendedEnterEvent ) + { + com.sun.jdi.event.MonitorContendedEnterEvent monitor = (com.sun.jdi.event.MonitorContendedEnterEvent) event; + final com.sun.jdi.ThreadReference thread; + try + { + thread = monitor.monitor().owningThread(); + } + catch ( com.sun.jdi.IncompatibleThreadStateException e ) + { + e.printStackTrace(); + continue; + } + if ( thread != null && thread.isSuspended() ) + { + DeadlockCallback callback = suspended.get( thread ); + try + { + if ( callback != null ) callback.deadlock( new DebuggedThread( this, thread ) ); + } + catch ( DeadlockDetectedError deadlock ) + { + @SuppressWarnings( "hiding" ) Handler handler = this.handler; + if ( handler != null ) handler.kill( false ); + } + } + } + else if ( event instanceof com.sun.jdi.event.LocatableEvent ) { callback( (com.sun.jdi.event.LocatableEvent) event ); } @@ -313,7 +354,7 @@ else if ( event instanceof com.sun.jdi.event.VMDisconnectEvent } } - private void setup( ReferenceType type ) + private void setup( com.sun.jdi.ReferenceType type ) { List list = breakpoints.get( type.name() ); if ( list == null ) return; @@ -323,7 +364,7 @@ private void setup( ReferenceType type ) } } - private void callback( LocatableEvent event ) + private void callback( com.sun.jdi.event.LocatableEvent event ) { List list = breakpoints.get( event.location().declaringType().name() ); if ( list == null ) return; @@ -332,10 +373,92 @@ private void callback( LocatableEvent event ) { if ( breakpoint.matches( method.name(), method.argumentTypeNames() ) ) { - breakpoint.callback(); + breakpoint.callback( new DebugInterface( this, event ) ); } } } + + private void suspended( com.sun.jdi.ThreadReference thread, DeadlockCallback callback ) + { + if ( callback == null ) callback = defaultCallback; + suspended.put( thread, callback ); + } + + private void resume( com.sun.jdi.ThreadReference thread ) + { + suspended.remove( thread ); + } + } + + @SuppressWarnings( "restriction" ) + public static class DebugInterface + { + private final com.sun.jdi.event.LocatableEvent event; + private final DebugDispatch debug; + + private DebugInterface( DebugDispatch debug, com.sun.jdi.event.LocatableEvent event ) + { + this.debug = debug; + this.event = event; + } + + public String getCallingClassName( int offset ) + { + try + { + return event.thread().frames().get( offset ).location().declaringType().name(); + } + catch ( com.sun.jdi.IncompatibleThreadStateException e ) + { + return null; + } + } + + public DebuggedThread thread() + { + return new DebuggedThread( debug, event.thread() ); + } + } + + @SuppressWarnings( "restriction" ) + public static class DebuggedThread + { + private final com.sun.jdi.ThreadReference thread; + private final DebugDispatch debug; + + public DebuggedThread( DebugDispatch debug, com.sun.jdi.ThreadReference thread ) + { + this.debug = debug; + this.thread = thread; + } + + public DebuggedThread suspend( DeadlockCallback callback ) + { + thread.suspend(); + debug.suspended( thread, callback ); + return this; + } + + public DebuggedThread resume() + { + thread.resume(); + debug.resume( thread ); + return this; + } + } + + static class DeadlockDetectedError extends Error + { + @Override + public Throwable fillInStackTrace() + { + return this; + } + } + + public interface DeadlockCallback + { + void deadlock( DebuggedThread thread ); } protected abstract void startup( P parameter ) throws Throwable; diff --git a/kernel/src/test/java/org/neo4j/test/SubProcessBreakPoint.java b/kernel/src/test/java/org/neo4j/test/SubProcessBreakPoint.java index 783ccb14c..873f3d902 100644 --- a/kernel/src/test/java/org/neo4j/test/SubProcessBreakPoint.java +++ b/kernel/src/test/java/org/neo4j/test/SubProcessBreakPoint.java @@ -22,7 +22,10 @@ import java.util.Iterator; import java.util.List; -public abstract class SubProcessBreakPoint +import org.neo4j.test.SubProcess.DebugInterface; +import org.neo4j.test.SubProcess.DebuggedThread; + +public abstract class SubProcessBreakPoint implements SubProcess.DeadlockCallback { public SubProcessBreakPoint( Class type, String method, Class... args ) { @@ -35,7 +38,13 @@ public SubProcessBreakPoint( Class type, String method, Class... args ) } } - protected abstract void callback(); + protected abstract void callback( DebugInterface debug ); + + @Override + public void deadlock( DebuggedThread thread ) + { + throw new SubProcess.DeadlockDetectedError(); + } final String type; private final String method; diff --git a/kernel/src/test/java/org/neo4j/test/SubProcessTest.java b/kernel/src/test/java/org/neo4j/test/SubProcessTest.java index 3fcda932f..a917d8fa5 100644 --- a/kernel/src/test/java/org/neo4j/test/SubProcessTest.java +++ b/kernel/src/test/java/org/neo4j/test/SubProcessTest.java @@ -32,8 +32,8 @@ import org.junit.Ignore; import org.junit.Test; +import org.neo4j.test.SubProcess.DebugInterface; -@Ignore public class SubProcessTest { private static final String MESSAGE = "message"; @@ -82,7 +82,7 @@ public void canDebugSubprocess() throws Exception new SubProcessBreakPoint( TestingProcess.class, "call" ) { @Override - protected void callback() + protected void callback( DebugInterface debug ) { called.set( true ); } diff --git a/lucene-index/CHANGES.txt b/lucene-index/CHANGES.txt index 19698cebd..357e3c517 100644 --- a/lucene-index/CHANGES.txt +++ b/lucene-index/CHANGES.txt @@ -1,3 +1,8 @@ +1.4.M03 (2011-05-26) +-------------------- +o Moved LuceneIndexProvider to public package to work better with OSGI. +o Tweaked lucene batch inserter settings for better performance. + 1.4.M02 (2011-05-12) -------------------- o Upgraded lucene version to 3.1 diff --git a/lucene-index/src/functionaltest/java/concurrency/ShutdownRaceTest.java b/lucene-index/src/functionaltest/java/concurrency/ShutdownRaceTest.java new file mode 100644 index 000000000..dba66c6c7 --- /dev/null +++ b/lucene-index/src/functionaltest/java/concurrency/ShutdownRaceTest.java @@ -0,0 +1,184 @@ +/** + * Copyright (c) 2002-2011 "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 . + */ +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.AbstractGraphDatabase; +import org.neo4j.kernel.impl.transaction.xaframework.XaContainer; +import org.neo4j.test.AbstractSubProcessTestBase; +import org.neo4j.test.SubProcess; +import org.neo4j.test.SubProcess.DebugInterface; +import org.neo4j.test.SubProcess.DebuggedThread; +import org.neo4j.test.SubProcessBreakPoint; + +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 SubProcessBreakPoint[] breakpoints( int id ) + { + final AtomicReference shutdownThread = new AtomicReference(), indexThread = new AtomicReference(); + return new SubProcessBreakPoint[] { new SubProcessBreakPoint( XaContainer.class, "close" ) + { + @Override + protected void callback( DebugInterface debug ) + { + if ( LuceneDataSource.class.getName().equals( debug.getCallingClassName( 1 ) ) ) + { + shutdownThread.set( debug.thread().suspend( this ) ); + resume( indexThread.getAndSet( null ) ); + this.disable(); + } + } + + @Override + public void deadlock( SubProcess.DebuggedThread thread ) + { + shutdownThread.set( null ); + thread.resume(); + } + }.enable(), new SubProcessBreakPoint( BreakTask.class, "breakpoint1" ) + { + @Override + protected void callback( DebugInterface debug ) + { + indexThread.set( debug.thread().suspend( this ) ); + restart.countDown(); + } + }.enable(), new SubProcessBreakPoint( 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 AbstractGraphDatabase 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 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 AbstractGraphDatabase graphdb ) + { + new Thread() + { + @Override + public void run() + { + runTask( graphdb ); + } + }.start(); + } + + void runTask( AbstractGraphDatabase 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 + } + } +} diff --git a/lucene-index/src/main/java/org/neo4j/index/impl/lucene/LuceneDataSource.java b/lucene-index/src/main/java/org/neo4j/index/impl/lucene/LuceneDataSource.java index 9f1e501de..042e0f9b5 100644 --- a/lucene-index/src/main/java/org/neo4j/index/impl/lucene/LuceneDataSource.java +++ b/lucene-index/src/main/java/org/neo4j/index/impl/lucene/LuceneDataSource.java @@ -280,7 +280,7 @@ static IndexProviderStore newIndexStore( String dbStoreDir ) } @Override - public void close() + public synchronized void close() { if ( closed ) { @@ -565,6 +565,8 @@ private static void deleteFileOrDirectory( File file ) synchronized IndexWriter getIndexWriter( IndexIdentifier identifier ) { + if ( closed ) throw new IllegalStateException( "Index has been shut down" ); + Pair writer = indexWriters.get( identifier ); if ( writer != null ) { diff --git a/shell/CHANGES.txt b/shell/CHANGES.txt index de6ced4ab..51f87848b 100644 --- a/shell/CHANGES.txt +++ b/shell/CHANGES.txt @@ -1,3 +1,7 @@ +1.4.M03 (2011-05-26) +------------------- +o No changes. + 1.3.M03 (2011-02-25) ------------------- o Updated to work with the latest changes in Neo4j: diff --git a/udc/CHANGES.txt b/udc/CHANGES.txt index a7b0e2cb1..bf9d92b96 100644 --- a/udc/CHANGES.txt +++ b/udc/CHANGES.txt @@ -1,3 +1,7 @@ +1.4.M03 (2011-05-26) +-------------------- +o No changes. + 1.3.M05 (2011-03-24) -------------------- o Removed unnecessary sys out during startup.