Index: CHANGES.txt =================================================================== --- CHANGES.txt (revision 687039) +++ CHANGES.txt (working copy) @@ -108,6 +108,14 @@ 16. LUCENE-1334: Add new constructor for Term: Term(String fieldName) which defaults term text to "". (DM Smith via Mike McCandless) +17. LUCENE-1329: Add optional readOnly boolean when opening an + IndexReader. A readOnly reader is not allowed to make changes + (deletions, norms) to the index; in exchanged, the isDeleted + method, often a bottleneck when searching with many threads, is + not synchronized. The default for readOnly is still false, but in + 3.0 the default will become true. (Jason Rutherglen via Mike + McCandless) + Bug fixes 1. LUCENE-1134: Fixed BooleanQuery.rewrite to only optimize a single Index: src/test/org/apache/lucene/index/TestIndexReader.java =================================================================== --- src/test/org/apache/lucene/index/TestIndexReader.java (revision 687039) +++ src/test/org/apache/lucene/index/TestIndexReader.java (working copy) @@ -1329,4 +1329,61 @@ r2.close(); d.close(); } + + public void testReadOnly() throws Throwable { + RAMDirectory d = new MockRAMDirectory(); + IndexWriter writer = new IndexWriter(d, new StandardAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED); + addDocumentWithFields(writer); + writer.commit(); + addDocumentWithFields(writer); + writer.close(); + + IndexReader r = IndexReader.open(d, true); + try { + r.deleteDocument(0); + fail(); + } catch (UnsupportedOperationException uoe) { + // expected + } + + writer = new IndexWriter(d, new StandardAnalyzer(), false, IndexWriter.MaxFieldLength.LIMITED); + addDocumentWithFields(writer); + writer.close(); + + // Make sure reopen is still readonly: + IndexReader r2 = r.reopen(); + r.close(); + + assertFalse(r == r2); + + try { + r2.deleteDocument(0); + fail(); + } catch (UnsupportedOperationException uoe) { + // expected + } + + writer = new IndexWriter(d, new StandardAnalyzer(), false, IndexWriter.MaxFieldLength.LIMITED); + writer.optimize(); + writer.close(); + + // Make sure reopen to a single segment is still readonly: + IndexReader r3 = r2.reopen(); + r2.close(); + + assertFalse(r == r2); + + try { + r3.deleteDocument(0); + fail(); + } catch (UnsupportedOperationException uoe) { + // expected + } + + // Make sure write lock isn't held + writer = new IndexWriter(d, new StandardAnalyzer(), false, IndexWriter.MaxFieldLength.LIMITED); + writer.close(); + + r3.close(); + } } Index: src/java/org/apache/lucene/index/DirectoryIndexReader.java =================================================================== --- src/java/org/apache/lucene/index/DirectoryIndexReader.java (revision 687039) +++ src/java/org/apache/lucene/index/DirectoryIndexReader.java (working copy) @@ -43,21 +43,24 @@ private SegmentInfos segmentInfos; private Lock writeLock; private boolean stale; - private HashSet synced = new HashSet(); + private final HashSet synced = new HashSet(); /** Used by commit() to record pre-commit state in case * rollback is necessary */ private boolean rollbackHasChanges; private SegmentInfos rollbackSegmentInfos; + protected boolean readOnly; + - void init(Directory directory, SegmentInfos segmentInfos, boolean closeDirectory) + void init(Directory directory, SegmentInfos segmentInfos, boolean closeDirectory, boolean readOnly) throws IOException { this.directory = directory; this.segmentInfos = segmentInfos; this.closeDirectory = closeDirectory; + this.readOnly = readOnly; - if (segmentInfos != null) { + if (!readOnly && segmentInfos != null) { // We assume that this segments_N was previously // properly sync'd: for(int i=0;i - NOTE: for backwards API compatibility, several methods are not listed + NOTE: for backwards API compatibility, several methods are not listed as abstract, but have no useful implementations in this base class and instead always throw UnsupportedOperationException. Subclasses are strongly encouraged to override these methods, but in many cases may not need to.

+

+ + NOTE: as of 2.4, it's possible to open a read-only + IndexReader using one of the static open methods that + accepts the boolean readOnly parameter. Such a reader has + better concurrency as it's not necessary to synchronize on + the isDeleted method. Currently the default for readOnly + is false, meaning if not specified you will get a + read/write IndexReader. But in 3.0 this default will + change to true, meaning you must explicitly specify false + if you want to make changes with the resulting IndexReader. +

+ @version $Id$ */ public abstract class IndexReader { + // NOTE: in 3.0 this will change to true + final static boolean READ_ONLY_DEFAULT = false; + /** * Constants describing field properties, for example used for * {@link IndexReader#getFieldNames(FieldOption)}. @@ -181,46 +197,61 @@ } } - /** Returns an IndexReader reading the index in an FSDirectory in the named - path. + /** Returns a read/write IndexReader reading the index in an FSDirectory in the named + path. NOTE: starting in 3.0 this will return a readOnly IndexReader. * @throws CorruptIndexException if the index is corrupt * @throws IOException if there is a low-level IO error * @param path the path to the index directory */ public static IndexReader open(String path) throws CorruptIndexException, IOException { - return open(FSDirectory.getDirectory(path), true, null, null); + return open(FSDirectory.getDirectory(path), true, null, null, READ_ONLY_DEFAULT); } - /** Returns an IndexReader reading the index in an FSDirectory in the named - * path. + /** Returns a read/write IndexReader reading the index in an FSDirectory in the named + * path. NOTE: starting in 3.0 this will return a readOnly IndexReader. * @param path the path to the index directory * @throws CorruptIndexException if the index is corrupt * @throws IOException if there is a low-level IO error */ public static IndexReader open(File path) throws CorruptIndexException, IOException { - return open(FSDirectory.getDirectory(path), true, null, null); + return open(FSDirectory.getDirectory(path), true, null, null, READ_ONLY_DEFAULT); } - /** Returns an IndexReader reading the index in the given Directory. + /** Returns a read/write IndexReader reading the index in + * the given Directory. NOTE: starting in 3.0 this + * will return a readOnly IndexReader. * @param directory the index directory * @throws CorruptIndexException if the index is corrupt * @throws IOException if there is a low-level IO error */ public static IndexReader open(final Directory directory) throws CorruptIndexException, IOException { - return open(directory, false, null, null); + return open(directory, false, null, null, READ_ONLY_DEFAULT); } - /** Expert: returns an IndexReader reading the index in the given - * {@link IndexCommit}. + /** Returns a read/write or read only IndexReader reading the index in the given Directory. + * @param directory the index directory + * @param readOnly true if no changes (deletions, norms) will be made with this IndexReader + * @throws CorruptIndexException if the index is corrupt + * @throws IOException if there is a low-level IO error + */ + public static IndexReader open(final Directory directory, boolean readOnly) throws CorruptIndexException, IOException { + return open(directory, false, null, null, readOnly); + } + + /** Expert: returns a read/write IndexReader reading the index in the given + * {@link IndexCommit}. NOTE: starting in 3.0 this + * will return a readOnly IndexReader. * @param commit the commit point to open * @throws CorruptIndexException if the index is corrupt * @throws IOException if there is a low-level IO error */ public static IndexReader open(final IndexCommit commit) throws CorruptIndexException, IOException { - return open(commit.getDirectory(), false, null, commit); + return open(commit.getDirectory(), false, null, commit, READ_ONLY_DEFAULT); } - /** Expert: returns an IndexReader reading the index in the given + /** Expert: returns a read/write IndexReader reading the index in the given * Directory, with a custom {@link IndexDeletionPolicy}. + * NOTE: starting in 3.0 this will return a + * readOnly IndexReader. * @param directory the index directory * @param deletionPolicy a custom deletion policy (only used * if you use this reader to perform deletes or to set @@ -229,11 +260,29 @@ * @throws IOException if there is a low-level IO error */ public static IndexReader open(final Directory directory, IndexDeletionPolicy deletionPolicy) throws CorruptIndexException, IOException { - return open(directory, false, deletionPolicy, null); + return open(directory, false, deletionPolicy, null, READ_ONLY_DEFAULT); } - /** Expert: returns an IndexReader reading the index in the given - * Directory, using a specific commit and with a custom {@link IndexDeletionPolicy}. + /** Expert: returns a read/write or read only IndexReader reading the index in the given + * Directory, with a custom {@link IndexDeletionPolicy}. + * NOTE: starting in 3.0 this will return a + * readOnly IndexReader. + * @param directory the index directory + * @param deletionPolicy a custom deletion policy (only used + * if you use this reader to perform deletes or to set + * norms); see {@link IndexWriter} for details. + * @param readOnly true if no changes (deletions, norms) will be made with this IndexReader + * @throws CorruptIndexException if the index is corrupt + * @throws IOException if there is a low-level IO error + */ + public static IndexReader open(final Directory directory, IndexDeletionPolicy deletionPolicy, boolean readOnly) throws CorruptIndexException, IOException { + return open(directory, false, deletionPolicy, null, readOnly); + } + + /** Expert: returns a read/write IndexReader reading the index in the given + * Directory, using a specific commit and with a custom + * {@link IndexDeletionPolicy}. NOTE: starting in + * 3.0 this will return a readOnly IndexReader. * @param commit the specific {@link IndexCommit} to open; * see {@link IndexReader#listCommits} to list all commits * in a directory @@ -244,13 +293,29 @@ * @throws IOException if there is a low-level IO error */ public static IndexReader open(final IndexCommit commit, IndexDeletionPolicy deletionPolicy) throws CorruptIndexException, IOException { - return open(commit.getDirectory(), false, deletionPolicy, commit); + return open(commit.getDirectory(), false, deletionPolicy, commit, READ_ONLY_DEFAULT); } - private static IndexReader open(final Directory directory, final boolean closeDirectory, final IndexDeletionPolicy deletionPolicy, final IndexCommit commit) throws CorruptIndexException, IOException { - return DirectoryIndexReader.open(directory, closeDirectory, deletionPolicy, commit); + /** Expert: returns a read/write or read only IndexReader reading the index in the given + * Directory, using a specific commit and with a custom {@link IndexDeletionPolicy}. + * @param commit the specific {@link IndexCommit} to open; + * see {@link IndexReader#listCommits} to list all commits + * in a directory + * @param deletionPolicy a custom deletion policy (only used + * if you use this reader to perform deletes or to set + * norms); see {@link IndexWriter} for details. + * @param readOnly true if no changes (deletions, norms) will be made with this IndexReader + * @throws CorruptIndexException if the index is corrupt + * @throws IOException if there is a low-level IO error + */ + public static IndexReader open(final IndexCommit commit, IndexDeletionPolicy deletionPolicy, boolean readOnly) throws CorruptIndexException, IOException { + return open(commit.getDirectory(), false, deletionPolicy, commit, readOnly); } + private static IndexReader open(final Directory directory, final boolean closeDirectory, final IndexDeletionPolicy deletionPolicy, final IndexCommit commit, final boolean readOnly) throws CorruptIndexException, IOException { + return DirectoryIndexReader.open(directory, closeDirectory, deletionPolicy, commit, readOnly); + } + /** * Refreshes an IndexReader if the index has changed since this instance * was (re)opened. @@ -637,7 +702,7 @@ * be obtained) * @throws IOException if there is a low-level IO error */ - public final synchronized void setNorm(int doc, String field, byte value) + public synchronized void setNorm(int doc, String field, byte value) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { ensureOpen(); acquireWriteLock(); @@ -762,7 +827,7 @@ * be obtained) * @throws IOException if there is a low-level IO error */ - public final synchronized void deleteDocument(int docNum) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { + public synchronized void deleteDocument(int docNum) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { ensureOpen(); acquireWriteLock(); hasChanges = true; @@ -793,7 +858,7 @@ * be obtained) * @throws IOException if there is a low-level IO error */ - public final int deleteDocuments(Term term) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { + public int deleteDocuments(Term term) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { ensureOpen(); TermDocs docs = termDocs(term); if (docs == null) return 0; @@ -819,7 +884,7 @@ * @throws CorruptIndexException if the index is corrupt * @throws IOException if there is a low-level IO error */ - public final synchronized void undeleteAll() throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { + public synchronized void undeleteAll() throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { ensureOpen(); acquireWriteLock(); hasChanges = true; Index: src/java/org/apache/lucene/index/MultiSegmentReader.java =================================================================== --- src/java/org/apache/lucene/index/MultiSegmentReader.java (revision 687039) +++ src/java/org/apache/lucene/index/MultiSegmentReader.java (working copy) @@ -42,8 +42,9 @@ private boolean hasDeletions = false; /** Construct reading the named set of readers. */ - MultiSegmentReader(Directory directory, SegmentInfos sis, boolean closeDirectory) throws IOException { - super(directory, sis, closeDirectory); + MultiSegmentReader(Directory directory, SegmentInfos sis, boolean closeDirectory, boolean readOnly) throws IOException { + super(directory, sis, closeDirectory, readOnly); + // To reduce the chance of hitting FileNotFound // (and having to retry), we open segments in // reverse because IndexWriter merges & deletes @@ -52,7 +53,7 @@ SegmentReader[] readers = new SegmentReader[sis.size()]; for (int i = sis.size()-1; i >= 0; i--) { try { - readers[i] = SegmentReader.get(sis.info(i)); + readers[i] = SegmentReader.get(readOnly, sis.info(i)); } catch (IOException e) { // Close all readers we had opened: for(i++;i