/*
* 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.backend.jgroups.impl;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import org.hibernate.search.exception.SearchException;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.backend.impl.blackhole.BlackHoleBackendQueueProcessor;
import org.hibernate.search.backend.spi.BackendQueueProcessor;
import org.hibernate.search.backend.spi.Work;
import org.hibernate.search.backend.spi.WorkType;
import org.hibernate.search.indexes.impl.DirectoryBasedIndexManager;
import org.hibernate.search.indexes.spi.IndexManager;
import org.hibernate.search.testsupport.junit.SearchFactoryHolder;
import org.hibernate.search.testsupport.setup.TransactionContextForTest;
import org.hibernate.search.testsupport.TestForIssue;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
import org.jgroups.TimeoutException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.Assert;
/**
* Verifies sync / async guarantees of the JGroups backend.
* The JGroups stack used needs to have the RSVP protocol with ack_on_delivery enabled
* (the default configuration we use does).
*
* @author Sanne Grinovero <sanne@hibernate.org> (C) 2013 Red Hat Inc.
* @since 4.3
*/
@TestForIssue(jiraKey = "HSEARCH-1296")
public class SyncJGroupsBackendTest {
private static final Log log = LoggerFactory.make();
private static final String JGROUPS_CONFIGURATION = "testing-flush-loopback.xml";
@Rule
public SearchFactoryHolder slaveNode = new SearchFactoryHolder( Dvd.class, Book.class, Drink.class, Star.class )
.withProperty( "hibernate.search.default.worker.backend", "jgroupsSlave" )
.withProperty( "hibernate.search.dvds.worker.execution", "sync" )
.withProperty( "hibernate.search.dvds.jgroups.delegate_backend", "blackhole" )
.withProperty( "hibernate.search.dvds.jgroups.messages_timeout", "200" )
.withProperty( "hibernate.search.books.worker.execution", "async" )
.withProperty( "hibernate.search.drinks.jgroups." + JGroupsBackendQueueProcessor.BLOCK_WAITING_ACK, "true" )
.withProperty( "hibernate.search.stars.jgroups." + JGroupsBackendQueueProcessor.BLOCK_WAITING_ACK, "false" )
.withProperty( DispatchMessageSender.CONFIGURATION_FILE, JGROUPS_CONFIGURATION );
@Rule
public SearchFactoryHolder masterNode = new SearchFactoryHolder( Dvd.class, Book.class, Drink.class, Star.class )
.withProperty( "hibernate.search.default.worker.backend", JGroupsReceivingMockBackend.class.getName() )
.withProperty( DispatchMessageSender.CONFIGURATION_FILE, JGROUPS_CONFIGURATION );
@Test
public void testSynchAsConfigured() {
JGroupsBackendQueueProcessor dvdsBackend = extractJGroupsBackend( "dvds" );
Assert.assertTrue( "dvds index was configured with a syncronous JGroups backend", dvdsBackend.blocksForACK() );
JGroupsBackendQueueProcessor booksBackend = extractJGroupsBackend( "books" );
Assert.assertFalse( "books index was configured with an asyncronous JGroups backend", booksBackend.blocksForACK() );
JGroupsBackendQueueProcessor drinksBackend = extractJGroupsBackend( "drinks" );
Assert.assertTrue( "drinks index was configured with a syncronous JGroups backend", drinksBackend.blocksForACK() );
JGroupsBackendQueueProcessor starsBackend = extractJGroupsBackend( "stars" );
Assert.assertFalse( "stars index was configured with an asyncronous JGroups backend", starsBackend.blocksForACK() );
JGroupsReceivingMockBackend dvdBackendMock = extractMockBackend( "dvds" );
dvdBackendMock.resetThreadTrap();
boolean timeoutTriggered = false;
try {
//DVDs are sync operations so they will timeout:
final long presendTimestamp = System.nanoTime();
log.trace( "[PRESEND] Timestamp: " + presendTimestamp );
storeDvd( 1, "Hibernate Search in Action" );
final long postsendTimestamp = System.nanoTime();
final long differenceInMilliseconds = TimeUnit.MILLISECONDS.convert( (postsendTimestamp - presendTimestamp), TimeUnit.NANOSECONDS );
log.trace( "[POSTSEND] Timestamp: " + postsendTimestamp + " Diff: " + differenceInMilliseconds + " ms." );
}
catch (SearchException se) {
//Expected: we're inducing the RPC into timeout by blocking receiver processing
Throwable cause = se.getCause();
Assert.assertTrue( "Cause was not a TimeoutException but a " + cause, cause instanceof TimeoutException );
timeoutTriggered = true;
}
finally {
//release the receiver
dvdBackendMock.releaseBlockedThreads();
}
Assert.assertTrue( "The backend didn't receive any message: something wrong with the test setup of network configuration", dvdBackendMock.wasSomethingReceived() );
Assert.assertTrue( timeoutTriggered );
JGroupsReceivingMockBackend booksBackendMock = extractMockBackend( "books" );
booksBackendMock.resetThreadTrap();
//Books are async so they should not timeout
storeBook( 1, "Hibernate Search in Action" );
//Block our own thread awaiting for the receiver.
//If we raced past it we would release the receiver, not bad either
//as it would also proof we are async.
booksBackendMock.countDownAndJoin();
dvdBackendMock.induceFailure();
boolean npeTriggered = false;
try {
storeDvd( 2, "Byteman in Action" ); //not actually needing Byteman here
}
catch (SearchException se) {
//Expected: we're inducing the RPC into NPE
Throwable cause = se.getCause().getCause().getCause();
Assert.assertTrue( "Cause was not a NullPointerException but a " + cause, cause instanceof NullPointerException );
Assert.assertEquals( "Simulated Failure", cause.getMessage() );
npeTriggered = true;
}
Assert.assertTrue( npeTriggered );
}
@Test
public void alternativeBackendConfiguration() {
BackendQueueProcessor backendQueueProcessor = extractBackendQueue( slaveNode, "dvds" );
JGroupsBackendQueueProcessor jgroupsProcessor = (JGroupsBackendQueueProcessor) backendQueueProcessor;
BackendQueueProcessor delegatedBackend = jgroupsProcessor.getDelegatedBackend();
Assert.assertTrue( "dvds backend was configured with a deleage to blackhole but it's not using it", delegatedBackend instanceof BlackHoleBackendQueueProcessor );
}
@Test
public void alternativeJGroupsTimeoutConfiguration() {
BackendQueueProcessor backendQueueProcessor = extractBackendQueue( slaveNode, "dvds" );
JGroupsBackendQueueProcessor jgroupsProcessor = (JGroupsBackendQueueProcessor) backendQueueProcessor;
long messageTimeout = jgroupsProcessor.getMessageTimeout();
Assert.assertEquals( "message timeout configuration property not applied", 200, messageTimeout );
}
private JGroupsReceivingMockBackend extractMockBackend(String indexName) {
BackendQueueProcessor backendQueueProcessor = extractBackendQueue( masterNode, indexName );
Assert.assertTrue( "Backend not using the configured Mock!", backendQueueProcessor instanceof JGroupsReceivingMockBackend );
return (JGroupsReceivingMockBackend) backendQueueProcessor;
}
private JGroupsBackendQueueProcessor extractJGroupsBackend(String indexName) {
BackendQueueProcessor backendQueueProcessor = extractBackendQueue( slaveNode, indexName );
Assert.assertTrue( "Backend not using JGroups!", backendQueueProcessor instanceof JGroupsBackendQueueProcessor );
return (JGroupsBackendQueueProcessor) backendQueueProcessor;
}
private static BackendQueueProcessor extractBackendQueue(SearchFactoryHolder node, String indexName) {
IndexManager indexManager = node.getSearchFactory().getIndexManagerHolder().getIndexManager( indexName );
Assert.assertNotNull( indexManager );
DirectoryBasedIndexManager dbi = (DirectoryBasedIndexManager) indexManager;
return dbi.getBackendQueueProcessor();
}
private void storeBook(int id, String string) {
Book book = new Book();
book.id = id;
book.title = string;
storeObject( book, id );
}
private void storeDvd(int id, String dvdTitle) {
Dvd dvd1 = new Dvd();
dvd1.id = id;
dvd1.title = dvdTitle;
storeObject( dvd1, id );
}
private void storeObject(Object entity, Serializable id) {
Work work = new Work( entity, id, WorkType.UPDATE, false );
TransactionContextForTest tc = new TransactionContextForTest();
slaveNode.getSearchFactory().getWorker().performWork( work, tc );
tc.end();
}
@Indexed(index = "dvds")
public static final class Dvd {
@DocumentId long id;
@Field String title;
}
@Indexed(index = "books")
public static final class Book {
@DocumentId long id;
@Field String title;
}
@Indexed(index = "drinks")
public static final class Drink {
@DocumentId long id;
@Field String title;
}
@Indexed(index = "stars")
public static final class Star {
@DocumentId long id;
@Field String title;
}
}