/* * 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.impl.lucene; import org.hibernate.search.backend.IndexingMonitor; import org.hibernate.search.backend.LuceneWork; import org.hibernate.search.util.logging.impl.Log; import org.hibernate.search.util.logging.impl.LoggerFactory; import java.io.PrintWriter; import java.io.StringWriter; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; /** * Processes changesets in batches, maintaining sync guarantees. *<p> * Multiple threads produce one or more {@link org.hibernate.search.backend.LuceneWork} * by calling {@link #submit(java.util.List, org.hibernate.search.backend.IndexingMonitor)}, * and get blocked until their changes are applied to the index;</p> * The {@link org.hibernate.search.backend.impl.lucene.SyncWorkProcessor.Consumer} thread will * coalesce changes from multiple threads and apply them in the index, releasing the waiting threads * at the end. * <p> * In the absence of work to be applied, the Consumer thread is parked to avoid busy waiting.</p> * * @author gustavonalle */ final class SyncWorkProcessor implements WorkProcessor { private static final Log log = LoggerFactory.make(); private final MultiWriteDrainableLinkedList<Changeset> transferQueue = new MultiWriteDrainableLinkedList<>(); private volatile LuceneBackendResources resources; //To allow others to wait on actual shutdown of the internal threads private final CountDownLatch shutdownLatch = new CountDownLatch( 1 ); private final String indexName; private volatile boolean stop = false; final Thread consumerThread; /** * Constructor * @param resources LuceneResources to obtain the workspace * @param indexName for debugging purposes */ public SyncWorkProcessor(LuceneBackendResources resources, String indexName) { this.resources = resources; this.indexName = indexName; consumerThread = new Thread( new Consumer(), "Hibernate Search sync consumer thread for index " + indexName ); consumerThread.setDaemon( true ); } /** * Start processing */ public void start() { log.startingSyncConsumerThread( indexName ); consumerThread.start(); } /** * Submit work and wait for it to be applied to the index * @param workList list of work * @param monitor for statistics collection */ @Override public void submit(List<LuceneWork> workList, IndexingMonitor monitor) { //avoid empty work lists as workaround for HSEARCH-1769 if ( workList.isEmpty() ) { // only log this error at trace level until we properly fix HSEARCH-1769 if ( log.isTraceEnabled() ) { StringWriter stackTraceStringWriter = new StringWriter(); PrintWriter stackTracePrintWriter = new PrintWriter( stackTraceStringWriter ); new Throwable().printStackTrace( stackTracePrintWriter ); log.workListShouldNeverBeEmpty( stackTraceStringWriter.toString() ); } // skip that work return; } Changeset changeset = new Changeset( workList, Thread.currentThread(), monitor ); transferQueue.add( changeset ); wakeUpConsumer(); boolean interrupted = false; while ( ! changeset.isProcessed() && ! interrupted ) { parkCurrentThread(); if ( Thread.interrupted() ) { interrupted = true; } } if ( interrupted ) { Thread.currentThread().interrupt(); } } /** * Wakes up consumer thread if necessary */ private void wakeUpConsumer() { LockSupport.unpark( consumerThread ); } /** * Dispose resources */ @Override public void shutdown() { stop = true; LockSupport.unpark( consumerThread ); try { shutdownLatch.await( Long.MAX_VALUE, TimeUnit.SECONDS ); } catch (InterruptedException e) { log.timedOutWaitingShutdown( indexName ); } } /** * Handle on the fly rebuilds * @param resources new instance of {@link org.hibernate.search.backend.impl.lucene.LuceneBackendResources} */ @Override public void updateResources(LuceneBackendResources resources) { this.resources = resources; } /** * Consumer thread */ private class Consumer implements Runnable { @Override public void run() { Iterable<Changeset> changesets; try { while ( !stop ) { changesets = transferQueue.drainToDetachedIterable(); while ( changesets == null && !stop ) { // Avoid busy wait parkCurrentThread(); changesets = transferQueue.drainToDetachedIterable(); } if ( changesets != null ) { applyChangesets( changesets ); } } log.stoppingSyncConsumerThread( indexName ); } finally { shutdownLatch.countDown(); } } private void applyChangesets(Iterable<Changeset> changesets) { ChangesetList changesetList = new ChangesetList( changesets ); try { LuceneBackendQueueTask luceneBackendQueueTask = new LuceneBackendQueueTask( changesetList, resources, null ); luceneBackendQueueTask.run(); } finally { changesetList.markProcessed(); } } } private void parkCurrentThread() { //Always use some safety margin when parking threads: LockSupport.parkNanos( 1_000_000_000 ); } }