org.hibernate.search.indexes.impl.SharingBufferReaderProvider 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.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.store.Directory;
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.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
import java.lang.invoke.MethodHandles;
/**
* This ReaderProvider
shares IndexReaders as long as they are "current";
* It uses IndexReader.reopen() which should improve performance on larger indexes
* as it shares buffers with previous IndexReader generation for the segments which didn't change.
*
* @author Sanne Grinovero (C) 2011 Red Hat Inc.
*/
public class SharingBufferReaderProvider implements DirectoryBasedReaderProvider {
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();
/**
* Each actual IndexReader refresh will change this value. To what value exactly doesn't matter,
* as long as it's not a value recently used, so we don't care for overflow conditions.
* When a client needs to check for index freshness a lock is acquired to protect from
* too many checks (which result in IO operations); when it is actually able to acquire
* this lock it should check if the refreshOperationId changed: if so, a refresh can
* be skipped and we release the lock quickly as the IndexReader was then for
* sure updated in the time interval between this client arriving to request a reader
* and actually being able to get one.
*/
private volatile int refreshOperationId = 0;
private DirectoryProvider directoryProvider;
private String indexName;
@Override
public DirectoryReader 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.refreshAndGet();
}
@Override
public void closeIndexReader(IndexReader reader) {
if ( reader == null ) {
return;
}
log.tracef( "Closing IndexReader: %s", reader );
ReaderUsagePair container = allReaders.get( reader );
container.close(); //virtual
}
@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 different Directory later
createReader( directoryProvider.getDirectory() );
}
/**
* 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 );
}
}
@Override
public void stop() {
for ( IndexReader reader : allReaders.keySet() ) {
ReaderUsagePair usage = allReaders.get( reader );
usage.close();
}
if ( allReaders.size() != 0 ) {
log.readersNotProperlyClosedInReaderProvider();
}
}
//overridable method for testability:
protected DirectoryReader readerFactory(final Directory directory) throws IOException {
return DirectoryReader.open( directory );
}
/**
* 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 ) {
//TODO I've been experimenting with the idea of an async-close: didn't appear to have an interesting benefit,
//so discarded the code. should try with bigger indexes to see if the effect gets more impressive.
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,
* establishing the association between "current" ReaderUsagePair
* for a DirectoryProvider and it's lock.
*/
protected final class PerDirectoryLatestReader {
/**
* Reference to the most current IndexReader for a DirectoryProvider;
* guarded by lockOnReplaceCurrent;
*/
public ReaderUsagePair current; //guarded by lockOnReplaceCurrent
private final Lock lockOnReplaceCurrent = new ReentrantLock();
/**
* @param directory The Directory
for which we manage the IndexReader
.
*
* @throws IOException when the index initialization fails.
*/
public PerDirectoryLatestReader(Directory directory) throws IOException {
DirectoryReader reader = readerFactory( directory );
ReaderUsagePair initialPair = new ReaderUsagePair( reader );
initialPair.usageCounter.set( 1 ); //a token to mark as active (preventing real close).
lockOnReplaceCurrent.lock(); //no harm, just ensuring safe publishing.
current = initialPair;
lockOnReplaceCurrent.unlock();
allReaders.put( reader, initialPair );
}
/**
* Gets an updated IndexReader for the current Directory;
* the index status will be checked.
*
* @return the current IndexReader if it's in sync with underlying index, a new one otherwise.
*/
public DirectoryReader refreshAndGet() {
final DirectoryReader updatedReader;
//it's important that we read this volatile before acquiring the lock:
final int preAcquireVersionId = refreshOperationId;
ReaderUsagePair toCloseReaderPair = null;
lockOnReplaceCurrent.lock();
final DirectoryReader beforeUpdateReader = current.reader;
try {
if ( refreshOperationId != preAcquireVersionId ) {
// We can take a good shortcut
current.usageCounter.incrementAndGet();
return beforeUpdateReader;
}
else {
try {
//Guarded by the lockOnReplaceCurrent of current IndexReader
//technically the final value doesn't even matter, as long as we change it
refreshOperationId++;
updatedReader = DirectoryReader.openIfChanged( beforeUpdateReader );
}
catch (IOException e) {
throw new SearchException( "Unable to reopen IndexReader", e );
}
}
if ( updatedReader == null ) {
current.usageCounter.incrementAndGet();
return beforeUpdateReader;
}
else {
ReaderUsagePair newPair = new ReaderUsagePair( updatedReader );
//no need to increment usageCounter in newPair, as it is constructed with correct number 2.
assert newPair.usageCounter.get() == 2;
toCloseReaderPair = current;
current = newPair;
allReaders.put( updatedReader, newPair );//unfortunately still needs lock
}
}
finally {
lockOnReplaceCurrent.unlock();
}
// doesn't need lock:
if ( toCloseReaderPair != null ) {
toCloseReaderPair.close();// release a token as it's not the current any more.
}
return updatedReader;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy