org.hibernate.search.indexes.impl.PeriodicRefreshingReaderProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-search-engine Show documentation
Show all versions of hibernate-search-engine Show documentation
Core of the Object/Lucene mapper, query engine and index management
/*
* 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 .
*/
package org.hibernate.search.indexes.impl;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.store.Directory;
import org.hibernate.search.cfg.Environment;
import org.hibernate.search.exception.AssertionFailure;
import org.hibernate.search.exception.SearchException;
import org.hibernate.search.indexes.spi.DirectoryBasedIndexManager;
import org.hibernate.search.indexes.spi.DirectoryBasedReaderProvider;
import org.hibernate.search.store.DirectoryProvider;
import org.hibernate.search.util.configuration.impl.ConfigurationParseHelper;
import org.hibernate.search.util.impl.Executors;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
import java.lang.invoke.MethodHandles;
/**
* This ReaderProvider
shares IndexReader buffers among threads and
* will return "recently" refreshed ReaderProvider
, which implies
* query results might represent the state of a slightly out of date index.
*
* The definition of "recently" refreshed is defined by configuration properties,
* so the user can choose how much the index being out of date is acceptable.
*
* In exchange of allowing slightly out of date query results, the fact that we
* can cap the frequency of index reopening events can provide significant
* performance improvements in some architectures.
*
* @author Sanne Grinovero (C) 2016 Red Hat Inc.
*/
public class PeriodicRefreshingReaderProvider implements DirectoryBasedReaderProvider {
public static final int DEFAULT_ACCEPTABLE_MAX_AGE_MS = 5000;
private static final Log log = LoggerFactory.make( MethodHandles.lookup() );
/**
* contains all Readers (most current per Directory and all unclosed old readers)
*/
protected final Map allReaders = new ConcurrentHashMap();
/**
* contains last updated Reader; protected by lockOnOpenLatest (in the values)
*/
protected final Map currentReaders = new ConcurrentHashMap();
private volatile ScheduledExecutorService scheduledExecutorService;
private int delay;
private DirectoryProvider directoryProvider;
private String indexName;
@Override
public IndexReader openIndexReader() {
log.tracef( "Opening IndexReader for directoryProvider %s", indexName );
Directory directory = directoryProvider.getDirectory();
PerDirectoryLatestReader directoryLatestReader = currentReaders.get( directory );
// might eg happen for FSSlaveDirectoryProvider or for mutable SearchFactory
if ( directoryLatestReader == null ) {
directoryLatestReader = createReader( directory );
}
return directoryLatestReader.getLatestReader();
}
@Override
public void closeIndexReader(IndexReader reader) {
if ( reader == null ) {
return;
}
log.tracef( "Closing IndexReader: %s", reader );
ReaderUsagePair container = allReaders.get( reader );
container.close(); //virtual
}
//overridable method for testability:
protected DirectoryReader readerFactory(final Directory directory) throws IOException {
return DirectoryReader.open( directory );
}
@Override
public void initialize(DirectoryBasedIndexManager indexManager, Properties props) {
this.directoryProvider = indexManager.getDirectoryProvider();
this.indexName = indexManager.getIndexName();
// Initialize at least one, don't forget directoryProvider might return a different Directory later
createReader( directoryProvider.getDirectory() );
this.scheduledExecutorService = Executors.newScheduledThreadPool( "Periodic IndexReader refreshing task for index " + indexName );
this.delay = ConfigurationParseHelper.getIntValue(
props,
Environment.ASYNC_READER_REFRESH_PERIOD_MS,
DEFAULT_ACCEPTABLE_MAX_AGE_MS );
this.scheduledExecutorService.scheduleAtFixedRate(
new IndexRefreshTask(),
delay,
delay,
TimeUnit.MILLISECONDS
);
}
@Override
public void stop() {
if ( scheduledExecutorService != null ) {
try {
ScheduledExecutorService executorService = scheduledExecutorService;
scheduledExecutorService = null;
executorService.shutdown();
executorService.awaitTermination( 1, TimeUnit.SECONDS );
}
catch (InterruptedException e) {
log.timedOutWaitingShutdownOfReaderProvider( indexName );
}
}
for ( IndexReader reader : allReaders.keySet() ) {
ReaderUsagePair usage = allReaders.get( reader );
usage.close();
}
if ( allReaders.size() != 0 ) {
log.readersNotProperlyClosedInReaderProvider();
}
}
/**
* Thread safe creation of PerDirectoryLatestReader
.
*
* @param directory The Lucene directory for which to create the reader.
* @return either the cached instance for the specified Directory
or a newly created one.
* @see HSEARCH-250
*/
private synchronized PerDirectoryLatestReader createReader(Directory directory) {
PerDirectoryLatestReader reader = currentReaders.get( directory );
if ( reader != null ) {
return reader;
}
try {
reader = new PerDirectoryLatestReader( directory );
currentReaders.put( directory, reader );
return reader;
}
catch (IOException e) {
throw new SearchException( "Unable to open Lucene IndexReader for IndexManager " + this.indexName, e );
}
}
/**
* Container for the couple IndexReader,UsageCounter.
*/
protected final class ReaderUsagePair {
public final DirectoryReader reader;
/**
* When reaching 0 (always test on change) the reader should be really
* closed and then discarded.
* Starts at 2 because:
* first usage token is artificial: means "current" is not to be closed (+1)
* additionally when creating it will be used (+1)
*/
protected final AtomicInteger usageCounter = new AtomicInteger( 2 );
ReaderUsagePair(DirectoryReader r) {
reader = r;
}
/**
* Closes the IndexReader
if no other resource is using it
* in which case the reference to this container will also be removed.
*/
public void close() {
int refCount = usageCounter.decrementAndGet();
if ( refCount == 0 ) {
ReaderUsagePair removed = allReaders.remove( reader );//remove ourself
try {
reader.close();
}
catch (IOException e) {
log.unableToCloseLuceneIndexReader( e );
}
assert removed != null;
}
else if ( refCount < 0 ) {
//doesn't happen with current code, could help spotting future bugs?
throw new AssertionFailure(
"Closing an IndexReader for which you didn't own a lock-token, or somebody else which didn't own closed already."
);
}
}
@Override
public String toString() {
return "Reader:" + this.hashCode() + " ref.count=" + usageCounter.get();
}
}
/**
* An instance for each DirectoryProvider, pointing to the "current" ReaderUsagePair
* for each DirectoryProvider.
*/
protected final class PerDirectoryLatestReader {
/**
* Reference to the most current IndexReader for a DirectoryProvider
* guarded by read/write locks rl and wl.
*/
private ReaderUsagePair current;
/**
* Read/Write locks to ensure the current IndexReader isn't closed while
* it's being grabbed for usage.
*/
private final Lock rl;
private final Lock wl;
/**
* @param directory The Directory
for which we manage the IndexReader
.
*
* @throws IOException when the index initialization fails.
*/
public PerDirectoryLatestReader(Directory directory) throws IOException {
ReadWriteLock rwl = new ReentrantReadWriteLock();
rl = rwl.readLock();
wl = rwl.writeLock();
DirectoryReader reader = readerFactory( directory );
ReaderUsagePair initialPair = new ReaderUsagePair( reader );
initialPair.usageCounter.set( 1 ); //a token to mark as active (preventing real close).
wl.lock();
current = initialPair;
wl.unlock();//only to ensure safe publication of 'current' to requestors using the lock.
allReaders.put( reader, initialPair );
}
public DirectoryReader getLatestReader() {
rl.lock();
ReaderUsagePair readerUsagePair = current;
readerUsagePair.usageCounter.incrementAndGet();
rl.unlock();
return readerUsagePair.reader;
}
public synchronized void refreshIfNeeded() {
final ReaderUsagePair readerUsagePair = current;
final DirectoryReader beforeUpdateReader = readerUsagePair.reader;
final DirectoryReader updatedReader;
try {
updatedReader = DirectoryReader.openIfChanged( beforeUpdateReader );
}
catch (IOException e) {
throw new SearchException( "Unable to reopen IndexReader", e );
}
if ( updatedReader != null ) {
//In this case we need to promote the updated as "current"
//and start the lazy closing process of the previous one.
ReaderUsagePair newPair = new ReaderUsagePair( updatedReader );
allReaders.put( updatedReader, newPair );
//Acquire the write-lock, both for visibility reasons and to
//make sure a client can get it and increment the usage counter atomically.
try {
wl.lock();
current = newPair;
wl.unlock();
}
finally {
if ( readerUsagePair != null ) {
readerUsagePair.close();// release a token as it's not the current any more.
}
}
}
}
}
private final class IndexRefreshTask implements Runnable {
@Override
public void run() {
for ( PerDirectoryLatestReader lr : currentReaders.values() ) {
lr.refreshIfNeeded();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy