/** * Amanuensis, a distributed Lucene Index Writer for Infinispan * * Copyright (c) 2011, Tristan Tarrant * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package net.dataforte.infinispan.amanuensis; import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import net.dataforte.commons.slf4j.LoggerFactory; import net.dataforte.infinispan.amanuensis.backend.lucene.LuceneOperationExecutorFactory; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.index.CheckIndex; import org.apache.lucene.index.CheckIndex.Status; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.store.Directory; import org.slf4j.Logger; /** * The ExecutorContext contains all of the state associated with an executor. * * @author Tristan Tarrant * */ public class ExecutorContext { private static final Logger log = LoggerFactory.make(); private static final String THREAD_GROUP_PREFIX = "Amanuensis: "; private static final int QUEUE_MAX_LENGTH = 10000; private static final IndexWriter.MaxFieldLength MAX_FIELD_LENGTH = new IndexWriter.MaxFieldLength(IndexWriter.DEFAULT_MAX_FIELD_LENGTH); private final ExecutorService executor; private LuceneOperationExecutorFactory operationExecutorFactory; private final Directory directory; private IndexWriter writer; private Analyzer analyzer; private AmanuensisManager manager; public ExecutorContext(AmanuensisManager manager, Directory directory, Analyzer analyzer) { this.manager = manager; this.executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(QUEUE_MAX_LENGTH), new ExecutorThreadFactory("IndexWriter"), new BlockingPolicy()); this.directory = directory; this.analyzer = analyzer; this.operationExecutorFactory = new LuceneOperationExecutorFactory(); } public ExecutorService getExecutor() { return executor; } public LuceneOperationExecutorFactory getOperationExecutorFactory() { return operationExecutorFactory; } public void setOperationExecutorFactory(LuceneOperationExecutorFactory operationExecutorFactory) { this.operationExecutorFactory = operationExecutorFactory; } public Directory getDirectory() { return directory; } public synchronized IndexWriter getWriter() throws IndexerException { if (writer != null) return writer; try { if (IndexWriter.isLocked(directory)) { log.warn("Unlocking index " + AmanuensisManager.getUniqueDirectoryIdentifier(directory) + " probable, crash of coordinator"); forceUnlock(); } writer = new IndexWriter(directory, analyzer, MAX_FIELD_LENGTH); manager.getWriterConfigurator().configure(writer); } catch (IOException e) { writer = null; throw new IndexerException("Error while creating writer for index " + AmanuensisManager.getUniqueDirectoryIdentifier(directory), e); } return writer; } public synchronized void commit() throws IndexerException { if (writer != null) { try { writer.commit(); if (log.isTraceEnabled()) { log.trace("Committed writer for index " + AmanuensisManager.getUniqueDirectoryIdentifier(directory)); } } catch (IOException e) { throw new IndexerException("Error while committing writer for index " + AmanuensisManager.getUniqueDirectoryIdentifier(directory), e); } } } public synchronized void close() { IndexWriter w = writer; writer = null; if (w != null) { try { w.close(); if (log.isTraceEnabled()) { log.debug("Closed writer for index " + AmanuensisManager.getUniqueDirectoryIdentifier(directory)); } } catch (IOException e) { log.error("Error while closing writer for index " + AmanuensisManager.getUniqueDirectoryIdentifier(directory), e); } } } public synchronized Status check(boolean fix) { try { forceUnlock(); CheckIndex checker = new CheckIndex(directory); Status check = checker.checkIndex(); if (check.clean) { log.info("Index " + AmanuensisManager.getUniqueDirectoryIdentifier(directory) + " is clean"); } else { log.warn("Index " + AmanuensisManager.getUniqueDirectoryIdentifier(directory) + " is NOT clean, fixing..."); checker.fixIndex(check); } return check; } catch (IOException e) { log.error("", e); return null; } } public synchronized void rollback() { if(writer!=null) { try { writer.rollback(); writer.close(false); } catch (Exception e) { log.error("Error during rollback/close", e); } finally { writer = null; } } forceUnlock(); } public synchronized void forceUnlock() { try { if(IndexWriter.isLocked(directory)) { IndexWriter.unlock(this.directory); // Remove locks } } catch (Exception e) { log.warn("Error while unlocking writer for index " + AmanuensisManager.getUniqueDirectoryIdentifier(directory), e); } } /** * Provide an implementation of {@link ThreadFactory} which gives sensible * names to threads * * @author Tristan Tarrant * @author Sanne Grinovero */ private static class ExecutorThreadFactory implements ThreadFactory { final ThreadGroup group; final AtomicInteger threadNumber = new AtomicInteger(1); final String namePrefix; ExecutorThreadFactory(String groupname) { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = THREAD_GROUP_PREFIX + groupname + "-"; } public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); return t; } } /** * Provide an implementation of {@link RejectedExecutionHandler} which * queues up threads * * @author Tristan Tarrant * @author Sanne Grinovero */ public static class BlockingPolicy implements RejectedExecutionHandler { public BlockingPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor exec) { try { exec.getQueue().put(r); } catch (InterruptedException e) { log.error("Work discarded, thread was interrupted while waiting for space to schedule: {}", r); } } } }