package org.infinispan.query.indexmanager; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import org.hibernate.search.backend.IndexingMonitor; import org.hibernate.search.backend.LuceneWork; import org.hibernate.search.indexes.spi.IndexManager; import org.infinispan.query.logging.Log; import org.infinispan.util.logging.LogFactory; /** * Transitionally backend used when we receive indexing operation to be * applied to the (local) IndexWriter, but the index lock is not available yet. * We will try again to look for the lock to be made available at each incoming * operation, and buffer writes for later consumption if the lock is still not * available. * Such checks are synchronized, so this will cause some backpressure. * The buffer containing postponed write operations is also bounded and will * trigger more backpressure when it's filled (although filling it should not * be possible as the current implementation steals the locks aggressively). * * @author Sanne Grinovero <sanne@hibernate.org> (C) 2014 Red Hat Inc. * @since 7.0 */ public class LockAcquiringBackend implements IndexingBackend { private static final Log log = LogFactory.getLog(LockAcquiringBackend.class, Log.class); /** * Using a system property here as I don't think we'll ever hit the limit. */ private static final int MAX_QUEUE_SIZE = Integer.getInteger("org.infinispan.query.indexmanager.LockAcquiringBackend.MAX_QUEUE_SIZE", 1000); private final BlockingQueue<Work> bufferedWork = new ArrayBlockingQueue<Work>(MAX_QUEUE_SIZE); private final LazyInitializableBackend clusteredSwitchingBackend; public LockAcquiringBackend(LazyInitializableBackend clusteredSwitchingBackend) { this.clusteredSwitchingBackend = clusteredSwitchingBackend; } @Override public void applyWork(List<LuceneWork> workList, IndexingMonitor monitor, IndexManager indexManager) { log.trace("Attempting backend upgrade..."); if (clusteredSwitchingBackend.attemptUpgrade(this)) { log.trace("... backend upgrade succeeded."); clusteredSwitchingBackend.getCurrentIndexingBackend().applyWork(workList, monitor, indexManager); } else { log.trace("... backend upgrade postponed."); enqueue(new TransactionWork(workList, monitor, indexManager)); } } @Override public void applyStreamWork(LuceneWork singleOperation, IndexingMonitor monitor, IndexManager indexManager) { log.trace("Attempting backend upgrade..."); if (clusteredSwitchingBackend.attemptUpgrade(this)) { log.trace("... backend upgrade succeeded."); clusteredSwitchingBackend.getCurrentIndexingBackend().applyStreamWork(singleOperation, monitor, indexManager); } else { log.trace("... backend upgrade postponed."); enqueue(new StreamWork(singleOperation, monitor, indexManager)); } } private void enqueue(final Work work) { if (log.isDebugEnabled()) { int remainingCapacity = bufferedWork.remainingCapacity(); log.debug("Need to enqueue on blocking buffer, remaining capacity to saturation: " + remainingCapacity); } final boolean done = bufferedWork.offer(work); if (!done) { if (log.isDebugEnabled()) { log.debug("Buffer saturated: blocking"); } try { bufferedWork.put(work); log.debug("Unblocked from wait on buffer"); } catch (InterruptedException e) { log.interruptedWhileBufferingWork(e); Thread.currentThread().interrupt(); } } } @Override public void flushAndClose(final IndexingBackend replacement) { if (replacement != null) { final ArrayList<Work> all = new ArrayList<Work>(bufferedWork.size()); bufferedWork.drainTo(all); for (Work w : all) { w.applyTo(replacement); } } } @Override public boolean isMasterLocal() { // We're only master when owning the lock (not yet) return false; } private interface Work { public void applyTo(IndexingBackend target); } private static class StreamWork implements Work { private final LuceneWork singleOperation; private final IndexingMonitor monitor; private final IndexManager indexManager; public StreamWork(LuceneWork singleOperation, IndexingMonitor monitor, IndexManager indexManager) { this.singleOperation = singleOperation; this.monitor = monitor; this.indexManager = indexManager; } @Override public void applyTo(IndexingBackend target) { target.applyStreamWork(singleOperation, monitor, indexManager); } } private static class TransactionWork implements Work { private final List<LuceneWork> workList; private final IndexingMonitor monitor; private final IndexManager indexManager; public TransactionWork(List<LuceneWork> workList, IndexingMonitor monitor, IndexManager indexManager) { this.workList = workList; this.monitor = monitor; this.indexManager = indexManager; } @Override public void applyTo(IndexingBackend target) { target.applyWork(workList, monitor, indexManager); } } public String toString() { return "LockAcquiringBackend"; } }