/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.test.performance.nrt;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Norms;
import org.hibernate.search.backend.spi.Work;
import org.hibernate.search.backend.spi.WorkType;
import org.hibernate.search.backend.spi.Worker;
import org.hibernate.search.query.engine.spi.EntityInfo;
import org.hibernate.search.spi.SearchIntegrator;
import org.hibernate.search.testsupport.TestConstants;
import org.hibernate.search.testsupport.TestForIssue;
import org.hibernate.search.testsupport.junit.SearchFactoryHolder;
import org.hibernate.search.testsupport.setup.TransactionContextForTest;
import org.hibernate.search.util.impl.Executors;
import org.junit.Rule;
import org.junit.Test;
import org.junit.Assert;
/**
* @author Sanne Grinovero (C) 2013 Red Hat Inc.
*/
@TestForIssue(jiraKey = "HSEARCH-1317")
public class ReadWriteParallelismTest {
private static final Boolean PERFORMANCE_ENABLED = TestConstants.arePerformanceTestsEnabled();
private static final int THREAD_NUMBER = PERFORMANCE_ENABLED ? 30 : 1;
/**
* The values below (as committed in source code) are not right for measurement!
* We keep them reasonably low for a reasonably quick feedback from test builds.
*/
private static final int WARM_UP_SECONDS = PERFORMANCE_ENABLED ? 20 : 0;
private static final int FULL_RUN_SECONDS = PERFORMANCE_ENABLED ? 240 : 0;
private static final AtomicBoolean failures = new AtomicBoolean( false );
private static final AtomicBoolean running = new AtomicBoolean( true );
private static final AtomicInteger cyclesCompleted = new AtomicInteger( 0 );
@Rule
public SearchFactoryHolder sfHolder = new SearchFactoryHolder( Book.class )
.withProperty( "hibernate.search.default.indexmanager", "near-real-time" );
@Test
public void testPropertiesIndexing() throws InterruptedException {
SearchIntegrator integrator = sfHolder.getSearchFactory();
ThreadPoolExecutor threadPool = Executors.newFixedThreadPool( THREAD_NUMBER, "ReadWriteParallelismTest" );
for ( int i = 0; i < THREAD_NUMBER; i++ ) {
threadPool.execute( new Task( integrator, i ) );
}
threadPool.shutdown();
//Time to warmup only:
threadPool.awaitTermination( WARM_UP_SECONDS, TimeUnit.SECONDS );
System.out.println( "Warmup complete. Start measuring now.." );
//Start measuring:
cyclesCompleted.set( 0 );
long startMeasurementTime = System.nanoTime();
threadPool.awaitTermination( FULL_RUN_SECONDS, TimeUnit.SECONDS );
int doneCycles = cyclesCompleted.get();
long endMeasurementTime = System.nanoTime();
Assert.assertFalse( "Some failure happened in Task execution", failures.get() );
long totalTime = endMeasurementTime - startMeasurementTime;
long millisecondsElapsedTime = TimeUnit.MILLISECONDS.convert( totalTime, TimeUnit.NANOSECONDS );
System.out.println( "Completed " + doneCycles + " in " + millisecondsElapsedTime + " milliseconds" );
running.set( false );
}
private static void writeABook(Integer id, String bookTitle, Worker worker) {
Book book = new Book();
book.id = id;
book.title = bookTitle;
Work work = new Work( book, book.id, WorkType.ADD, false );
TransactionContextForTest tc = new TransactionContextForTest();
worker.performWork( work, tc );
tc.end();
}
private static void deleteABook(Integer id, Worker worker) {
Book book = new Book();
book.id = id;
Work work = new Work( book, id, WorkType.DELETE, false );
TransactionContextForTest tc = new TransactionContextForTest();
worker.performWork( work, tc );
tc.end();
}
private static void verifyMatches(SearchIntegrator searchIntegrator, int expectedMatches, Query query) {
List<EntityInfo> queryEntityInfos = searchIntegrator.createHSQuery( query, Book.class )
.queryEntityInfos();
Assert.assertEquals( expectedMatches, queryEntityInfos.size() );
}
private static class Task implements Runnable {
private final int threadId;
private final SearchIntegrator integrator;
public Task(SearchIntegrator integrator, int threadId) {
this.integrator = integrator;
this.threadId = threadId;
}
@Override
public void run() {
final Worker worker = integrator.getWorker();
final String title = "Volume N' " + Integer.toString( threadId );
final Integer bookId = Integer.valueOf( threadId );
final Query query = new TermQuery( new Term( "title", title ) );
try {
while ( running.get() ) {
cyclesCompleted.incrementAndGet();
verifyMatches( integrator, 0, query );
writeABook( bookId, title, worker );
verifyMatches( integrator, 1, query );
deleteABook( bookId, worker );
verifyMatches( integrator, 0, query );
}
}
catch (RuntimeException re) {
if ( running.get() ) {
re.printStackTrace();
failures.set( true );
}
}
}
}
@Indexed(index = "books")
private static class Book {
@DocumentId
Integer id;
@Field(analyze = Analyze.NO, norms = Norms.NO)
String title;
}
}