package org.apache.lucene.store; /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.Term; import org.apache.lucene.index.IndexWriterConfig.OpenMode; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util._TestUtil; public class TestLockFactory extends LuceneTestCase { // Verify: we can provide our own LockFactory implementation, the right // methods are called at the right time, locks are created, etc. public void testCustomLockFactory() throws IOException { Directory dir = new MockDirectoryWrapper(new RAMDirectory()); MockLockFactory lf = new MockLockFactory(); dir.setLockFactory(lf); // Lock prefix should have been set: assertTrue("lock prefix was not set by the RAMDirectory", lf.lockPrefixSet); IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer())); // add 100 documents (so that commit lock is used) for (int i = 0; i < 100; i++) { addDoc(writer); } // Both write lock and commit lock should have been created: assertEquals("# of unique locks created (after instantiating IndexWriter)", 1, lf.locksCreated.size()); assertTrue("# calls to makeLock is 0 (after instantiating IndexWriter)", lf.makeLockCount >= 1); for(final String lockName : lf.locksCreated.keySet()) { MockLockFactory.MockLock lock = (MockLockFactory.MockLock) lf.locksCreated.get(lockName); assertTrue("# calls to Lock.obtain is 0 (after instantiating IndexWriter)", lock.lockAttempts > 0); } writer.close(); } // Verify: we can use the NoLockFactory with RAMDirectory w/ no // exceptions raised: // Verify: NoLockFactory allows two IndexWriters public void testRAMDirectoryNoLocking() throws IOException { Directory dir = new MockDirectoryWrapper(new RAMDirectory()); dir.setLockFactory(NoLockFactory.getNoLockFactory()); assertTrue("RAMDirectory.setLockFactory did not take", NoLockFactory.class.isInstance(dir.getLockFactory())); IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer())); writer.commit(); // required so the second open succeed // Create a 2nd IndexWriter. This is normally not allowed but it should run through since we're not // using any locks: IndexWriter writer2 = null; try { writer2 = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer()).setOpenMode(OpenMode.APPEND)); } catch (Exception e) { e.printStackTrace(System.out); fail("Should not have hit an IOException with no locking"); } writer.close(); if (writer2 != null) { writer2.close(); } } // Verify: SingleInstanceLockFactory is the default lock for RAMDirectory // Verify: RAMDirectory does basic locking correctly (can't create two IndexWriters) public void testDefaultRAMDirectory() throws IOException { Directory dir = new RAMDirectory(); assertTrue("RAMDirectory did not use correct LockFactory: got " + dir.getLockFactory(), SingleInstanceLockFactory.class.isInstance(dir.getLockFactory())); IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer())); // Create a 2nd IndexWriter. This should fail: IndexWriter writer2 = null; try { writer2 = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer()).setOpenMode(OpenMode.APPEND)); fail("Should have hit an IOException with two IndexWriters on default SingleInstanceLockFactory"); } catch (IOException e) { } writer.close(); if (writer2 != null) { writer2.close(); } } public void testSimpleFSLockFactory() throws IOException { // test string file instantiation new SimpleFSLockFactory("test"); } // Verify: do stress test, by opening IndexReaders and // IndexWriters over & over in 2 threads and making sure // no unexpected exceptions are raised: public void testStressLocks() throws Exception { _testStressLocks(null, _TestUtil.getTempDir("index.TestLockFactory6")); } // Verify: do stress test, by opening IndexReaders and // IndexWriters over & over in 2 threads and making sure // no unexpected exceptions are raised, but use // NativeFSLockFactory: public void testStressLocksNativeFSLockFactory() throws Exception { File dir = _TestUtil.getTempDir("index.TestLockFactory7"); _testStressLocks(new NativeFSLockFactory(dir), dir); } public void _testStressLocks(LockFactory lockFactory, File indexDir) throws Exception { FSDirectory fs1 = FSDirectory.open(indexDir, lockFactory); // First create a 1 doc index: IndexWriter w = new IndexWriter(fs1, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer()).setOpenMode(OpenMode.CREATE)); addDoc(w); w.close(); WriterThread writer = new WriterThread(100, fs1); SearcherThread searcher = new SearcherThread(100, fs1); writer.start(); searcher.start(); while(writer.isAlive() || searcher.isAlive()) { Thread.sleep(1000); } assertTrue("IndexWriter hit unexpected exceptions", !writer.hitException); assertTrue("IndexSearcher hit unexpected exceptions", !searcher.hitException); // Cleanup _TestUtil.rmDir(indexDir); } // Verify: NativeFSLockFactory works correctly public void testNativeFSLockFactory() throws IOException { NativeFSLockFactory f = new NativeFSLockFactory(TEMP_DIR); f.setLockPrefix("test"); Lock l = f.makeLock("commit"); Lock l2 = f.makeLock("commit"); assertTrue("failed to obtain lock", l.obtain()); assertTrue("succeeded in obtaining lock twice", !l2.obtain()); l.release(); assertTrue("failed to obtain 2nd lock after first one was freed", l2.obtain()); l2.release(); // Make sure we can obtain first one again, test isLocked(): assertTrue("failed to obtain lock", l.obtain()); assertTrue(l.isLocked()); assertTrue(l2.isLocked()); l.release(); assertFalse(l.isLocked()); assertFalse(l2.isLocked()); } // Verify: NativeFSLockFactory works correctly if the lock file exists public void testNativeFSLockFactoryLockExists() throws IOException { File lockFile = new File(TEMP_DIR, "test.lock"); lockFile.createNewFile(); Lock l = new NativeFSLockFactory(TEMP_DIR).makeLock("test.lock"); assertTrue("failed to obtain lock", l.obtain()); l.release(); assertFalse("failed to release lock", l.isLocked()); if (lockFile.exists()) { lockFile.delete(); } } public void testNativeFSLockReleaseByOtherLock() throws IOException { NativeFSLockFactory f = new NativeFSLockFactory(TEMP_DIR); f.setLockPrefix("test"); Lock l = f.makeLock("commit"); Lock l2 = f.makeLock("commit"); assertTrue("failed to obtain lock", l.obtain()); try { assertTrue(l2.isLocked()); l2.release(); fail("should not have reached here. LockReleaseFailedException should have been thrown"); } catch (LockReleaseFailedException e) { // expected } finally { l.release(); } } // Verify: NativeFSLockFactory assigns null as lockPrefix if the lockDir is inside directory public void testNativeFSLockFactoryPrefix() throws IOException { File fdir1 = _TestUtil.getTempDir("TestLockFactory.8"); File fdir2 = _TestUtil.getTempDir("TestLockFactory.8.Lockdir"); Directory dir1 = FSDirectory.open(fdir1, new NativeFSLockFactory(fdir1)); // same directory, but locks are stored somewhere else. The prefix of the lock factory should != null Directory dir2 = FSDirectory.open(fdir1, new NativeFSLockFactory(fdir2)); String prefix1 = dir1.getLockFactory().getLockPrefix(); assertNull("Lock prefix for lockDir same as directory should be null", prefix1); String prefix2 = dir2.getLockFactory().getLockPrefix(); assertNotNull("Lock prefix for lockDir outside of directory should be not null", prefix2); _TestUtil.rmDir(fdir1); _TestUtil.rmDir(fdir2); } // Verify: default LockFactory has no prefix (ie // write.lock is stored in index): public void testDefaultFSLockFactoryPrefix() throws IOException { // Make sure we get null prefix: File dirName = _TestUtil.getTempDir("TestLockFactory.10"); Directory dir = FSDirectory.open(dirName); String prefix = dir.getLockFactory().getLockPrefix(); assertTrue("Default lock prefix should be null", null == prefix); _TestUtil.rmDir(dirName); } private class WriterThread extends Thread { private Directory dir; private int numIteration; public boolean hitException = false; public WriterThread(int numIteration, Directory dir) { this.numIteration = numIteration; this.dir = dir; } @Override public void run() { IndexWriter writer = null; for(int i=0;i<this.numIteration;i++) { try { writer = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer()).setOpenMode(OpenMode.APPEND)); } catch (IOException e) { if (e.toString().indexOf(" timed out:") == -1) { hitException = true; System.out.println("Stress Test Index Writer: creation hit unexpected IOException: " + e.toString()); e.printStackTrace(System.out); } else { // lock obtain timed out // NOTE: we should at some point // consider this a failure? The lock // obtains, across IndexReader & // IndexWriters should be "fair" (ie // FIFO). } } catch (Exception e) { hitException = true; System.out.println("Stress Test Index Writer: creation hit unexpected exception: " + e.toString()); e.printStackTrace(System.out); break; } if (writer != null) { try { addDoc(writer); } catch (IOException e) { hitException = true; System.out.println("Stress Test Index Writer: addDoc hit unexpected exception: " + e.toString()); e.printStackTrace(System.out); break; } try { writer.close(); } catch (IOException e) { hitException = true; System.out.println("Stress Test Index Writer: close hit unexpected exception: " + e.toString()); e.printStackTrace(System.out); break; } writer = null; } } } } private class SearcherThread extends Thread { private Directory dir; private int numIteration; public boolean hitException = false; public SearcherThread(int numIteration, Directory dir) { this.numIteration = numIteration; this.dir = dir; } @Override public void run() { IndexSearcher searcher = null; Query query = new TermQuery(new Term("content", "aaa")); for(int i=0;i<this.numIteration;i++) { try{ searcher = new IndexSearcher(dir, false); } catch (Exception e) { hitException = true; System.out.println("Stress Test Index Searcher: create hit unexpected exception: " + e.toString()); e.printStackTrace(System.out); break; } try { searcher.search(query, null, 1000); } catch (IOException e) { hitException = true; System.out.println("Stress Test Index Searcher: search hit unexpected exception: " + e.toString()); e.printStackTrace(System.out); break; } // System.out.println(hits.length() + " total results"); try { searcher.close(); } catch (IOException e) { hitException = true; System.out.println("Stress Test Index Searcher: close hit unexpected exception: " + e.toString()); e.printStackTrace(System.out); break; } } } } public class MockLockFactory extends LockFactory { public boolean lockPrefixSet; public Map<String,Lock> locksCreated = Collections.synchronizedMap(new HashMap<String,Lock>()); public int makeLockCount = 0; @Override public void setLockPrefix(String lockPrefix) { super.setLockPrefix(lockPrefix); lockPrefixSet = true; } @Override synchronized public Lock makeLock(String lockName) { Lock lock = new MockLock(); locksCreated.put(lockName, lock); makeLockCount++; return lock; } @Override public void clearLock(String specificLockName) {} public class MockLock extends Lock { public int lockAttempts; @Override public boolean obtain() { lockAttempts++; return true; } @Override public void release() { // do nothing } @Override public boolean isLocked() { return false; } } } private void addDoc(IndexWriter writer) throws IOException { Document doc = new Document(); doc.add(new Field("content", "aaa", Field.Store.NO, Field.Index.ANALYZED)); writer.addDocument(doc); } }