/** * 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 org.neo4j.index.impl.lucene; import static java.util.concurrent.Executors.newCachedThreadPool; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.neo4j.kernel.impl.transaction.xaframework.LogExtractor.newLogReaderBuffer; import static org.neo4j.kernel.impl.transaction.xaframework.LogIoUtils.readLogHeader; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.index.Index; import org.neo4j.kernel.GraphDatabaseAPI; import org.neo4j.kernel.impl.transaction.xaframework.LogEntry; import org.neo4j.kernel.impl.transaction.xaframework.LogIoUtils; import org.neo4j.kernel.impl.transaction.xaframework.XaCommand; import org.neo4j.kernel.impl.transaction.xaframework.XaCommandFactory; import org.neo4j.kernel.impl.transaction.xaframework.XaDataSource; import org.neo4j.test.ImpermanentGraphDatabase; /** * Test for a problem where multiple threads getting an index for the first time * and adding to or removing from it right there after. There was a race condition * where the transaction which created the index came after the first one using it. * * @author Mattias Persson */ public class TestIndexCreation { private GraphDatabaseAPI db; @Before public void before() throws Exception { db = new ImpermanentGraphDatabase(); } @After public void after() throws Exception { db.shutdown(); } @Test public void indexCreationConfigRaceCondition() throws Exception { // Since this is a probability test and not a precise test run do the run // a couple of times to be sure. for ( int run = 0; run < 10; run++ ) { final int r = run; final CountDownLatch latch = new CountDownLatch( 1 ); ExecutorService executor = newCachedThreadPool(); for ( int thread = 0; thread < 10; thread++ ) { executor.submit( new Runnable() { @Override public void run() { Transaction tx = db.beginTx(); try { latch.await(); Index<Node> index = db.index().forNodes( "index" + r ); Node node = db.createNode(); index.add( node, "name", "Name" ); tx.success(); } catch ( InterruptedException e ) { Thread.interrupted(); } finally { tx.finish(); } } } ); } latch.countDown(); executor.shutdown(); executor.awaitTermination( 10, TimeUnit.SECONDS ); verifyThatIndexCreationTransactionIsTheFirstOne(); } } private void verifyThatIndexCreationTransactionIsTheFirstOne() throws Exception { XaDataSource ds = db.getXaDataSourceManager().getXaDataSource( LuceneDataSource.DEFAULT_NAME ); long version = ds.getCurrentLogVersion(); ds.rotateLogicalLog(); ReadableByteChannel log = ds.getLogicalLog( version ); ByteBuffer buffer = newLogReaderBuffer(); readLogHeader( buffer, log, true ); XaCommandFactory cf = new XaCommandFactory() { @Override public XaCommand readCommand( ReadableByteChannel byteChannel, ByteBuffer buffer ) throws IOException { return LuceneCommand.readCommand( byteChannel, buffer, null ); } }; LogEntry entry = null; int creationIdentifier = -1; while ( (entry = LogIoUtils.readEntry( buffer, log, cf ) ) != null ) { if ( entry instanceof LogEntry.Command && ((LogEntry.Command) entry).getXaCommand() instanceof LuceneCommand.CreateIndexCommand ) { if ( creationIdentifier != -1 ) throw new IllegalArgumentException( "More than one creation command" ); creationIdentifier = entry.getIdentifier(); } if ( entry instanceof LogEntry.Commit ) { // The first COMMIT assertTrue( "Index creation transaction wasn't the first one", creationIdentifier != -1 ); assertEquals( "Index creation transaction wasn't the first one", creationIdentifier, entry.getIdentifier() ); return; } } fail( "Didn't find any commit record in log " + version ); } }