FileDocCategorySizeDatePackage
TestLockFactory.javaAPI DocApache Lucene 2.2.022245Sat Jun 16 22:20:24 BST 2007org.apache.lucene.store

TestLockFactory.java

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 junit.framework.TestCase;

import java.util.Hashtable;
import java.util.Enumeration;

import java.io.IOException;
import java.io.File;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.Hits;
import org.apache.lucene.analysis.WhitespaceAnalyzer;

public class TestLockFactory extends TestCase {

    // 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 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 WhitespaceAnalyzer(), true);

        // 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(Enumeration e = lf.locksCreated.keys(); e.hasMoreElements();) {
            String lockName = (String) e.nextElement();
            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 RAMDirectory();
        dir.setLockFactory(NoLockFactory.getNoLockFactory());

        assertTrue("RAMDirectory.setLockFactory did not take",
                   NoLockFactory.class.isInstance(dir.getLockFactory()));

        IndexWriter writer = new IndexWriter(dir, new WhitespaceAnalyzer(), true);

        // 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 WhitespaceAnalyzer(), false);
        } 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 WhitespaceAnalyzer(), true);

        // Create a 2nd IndexWriter.  This should fail:
        IndexWriter writer2 = null;
        try {
            writer2 = new IndexWriter(dir, new WhitespaceAnalyzer(), false);
            fail("Should have hit an IOException with two IndexWriters on default SingleInstanceLockFactory");
        } catch (IOException e) {
        }

        writer.close();
        if (writer2 != null) {
            writer2.close();
        }
    }

    // Verify: SimpleFSLockFactory is the default for FSDirectory
    // Verify: FSDirectory does basic locking correctly
    public void testDefaultFSDirectory() throws IOException {
        String indexDirName = "index.TestLockFactory1";

        IndexWriter writer = new IndexWriter(indexDirName, new WhitespaceAnalyzer(), true);

        assertTrue("FSDirectory did not use correct LockFactory: got " + writer.getDirectory().getLockFactory(),
                   SimpleFSLockFactory.class.isInstance(writer.getDirectory().getLockFactory()) ||
                   NativeFSLockFactory.class.isInstance(writer.getDirectory().getLockFactory()));

        IndexWriter writer2 = null;

        // Create a 2nd IndexWriter.  This should fail:
        try {
            writer2 = new IndexWriter(indexDirName, new WhitespaceAnalyzer(), false);
            fail("Should have hit an IOException with two IndexWriters on default SimpleFSLockFactory");
        } catch (IOException e) {
        }

        writer.close();
        if (writer2 != null) {
            writer2.close();
        }

        // Cleanup
        rmDir(indexDirName);
    }

    // Verify: FSDirectory's default lockFactory clears all locks correctly
    public void testFSDirectoryTwoCreates() throws IOException {
        String indexDirName = "index.TestLockFactory2";

        IndexWriter writer = new IndexWriter(indexDirName, new WhitespaceAnalyzer(), true);

        assertTrue("FSDirectory did not use correct LockFactory: got " + writer.getDirectory().getLockFactory(),
                   SimpleFSLockFactory.class.isInstance(writer.getDirectory().getLockFactory()) ||
                   NativeFSLockFactory.class.isInstance(writer.getDirectory().getLockFactory()));

        // Intentionally do not close the first writer here.
        // The goal is to "simulate" a crashed writer and
        // ensure the second writer, with create=true, is
        // able to remove the lock files.  This works OK
        // with SimpleFSLockFactory as the locking
        // implementation.  Note, however, that this test
        // will not work on WIN32 when we switch to
        // NativeFSLockFactory as the default locking for
        // FSDirectory because the second IndexWriter cannot
        // remove those lock files since they are held open
        // by the first writer.  This is because leaving the
        // first IndexWriter open is not really a good way
        // to simulate a crashed writer.
        
        // Create a 2nd IndexWriter.  This should not fail:
        IndexWriter writer2 = null;
        try {
            writer2 = new IndexWriter(indexDirName, new WhitespaceAnalyzer(), true);
        } catch (IOException e) {
            e.printStackTrace(System.out);
            fail("Should not have hit an IOException with two IndexWriters with create=true, on default SimpleFSLockFactory");
        }

        writer.close();
        if (writer2 != null) {
            writer2.close();
        }

        // Cleanup
        rmDir(indexDirName);
    }
    

    // Verify: setting custom lock factory class (as system property) works:
    // Verify: all 4 builtin LockFactory implementations are
    //         settable this way 
    // Verify: FSDirectory does basic locking correctly
    public void testLockClassProperty() throws IOException {
        String indexDirName = "index.TestLockFactory3";
        String prpName = "org.apache.lucene.store.FSDirectoryLockFactoryClass";

        try {

          // NoLockFactory:
          System.setProperty(prpName, "org.apache.lucene.store.NoLockFactory");
          IndexWriter writer = new IndexWriter(indexDirName, new WhitespaceAnalyzer(), true);
          assertTrue("FSDirectory did not use correct LockFactory: got " + writer.getDirectory().getLockFactory(),
                     NoLockFactory.class.isInstance(writer.getDirectory().getLockFactory()));
          writer.close();

          // SingleInstanceLockFactory:
          System.setProperty(prpName, "org.apache.lucene.store.SingleInstanceLockFactory");
          writer = new IndexWriter(indexDirName, new WhitespaceAnalyzer(), true);
          assertTrue("FSDirectory did not use correct LockFactory: got " + writer.getDirectory().getLockFactory(),
                     SingleInstanceLockFactory.class.isInstance(writer.getDirectory().getLockFactory()));
          writer.close();

          // NativeFSLockFactory:
          System.setProperty(prpName, "org.apache.lucene.store.NativeFSLockFactory");
          writer = new IndexWriter(indexDirName, new WhitespaceAnalyzer(), true);
          assertTrue("FSDirectory did not use correct LockFactory: got " + writer.getDirectory().getLockFactory(),
                     NativeFSLockFactory.class.isInstance(writer.getDirectory().getLockFactory()));
          writer.close();

          // SimpleFSLockFactory:
          System.setProperty(prpName, "org.apache.lucene.store.SimpleFSLockFactory");
          writer = new IndexWriter(indexDirName, new WhitespaceAnalyzer(), true);
          assertTrue("FSDirectory did not use correct LockFactory: got " + writer.getDirectory().getLockFactory(),
                     SimpleFSLockFactory.class.isInstance(writer.getDirectory().getLockFactory()));
          writer.close();
        } finally {
          // Put back to the correct default for subsequent tests:
          System.setProperty("org.apache.lucene.store.FSDirectoryLockFactoryClass", "");
        }

        // Cleanup
        rmDir(indexDirName);
    }

    // Verify: setDisableLocks works
    public void testDisableLocks() throws IOException {
        String indexDirName = "index.TestLockFactory4";
        
        assertTrue("Locks are already disabled", !FSDirectory.getDisableLocks());
        FSDirectory.setDisableLocks(true);

        IndexWriter writer = new IndexWriter(indexDirName, new WhitespaceAnalyzer(), true);

        assertTrue("FSDirectory did not use correct default LockFactory: got " + writer.getDirectory().getLockFactory(),
                   NoLockFactory.class.isInstance(writer.getDirectory().getLockFactory()));

        // Should be no error since locking is disabled:
        IndexWriter writer2 = null;
        try {
            writer2 = new IndexWriter(indexDirName, new WhitespaceAnalyzer(), false);
        } catch (IOException e) {
            e.printStackTrace(System.out);
            fail("Should not have hit an IOException with locking disabled");
        }

        FSDirectory.setDisableLocks(false);
        writer.close();
        if (writer2 != null) {
            writer2.close();
        }
        // Cleanup
        rmDir(indexDirName);
    }

    // Verify: if I try to getDirectory() with two different locking implementations, I get an IOException
    public void testFSDirectoryDifferentLockFactory() throws IOException {
        String indexDirName = "index.TestLockFactory5";

        LockFactory lf = new SingleInstanceLockFactory();
        FSDirectory fs1 = FSDirectory.getDirectory(indexDirName, lf);

        // Different lock factory instance should hit IOException:
        try {
            FSDirectory fs2 = FSDirectory.getDirectory(indexDirName, new SingleInstanceLockFactory());
            fail("Should have hit an IOException because LockFactory instances differ");
        } catch (IOException e) {
        }

        FSDirectory fs2 = null;

        // Same lock factory instance should not:
        try {
            fs2 = FSDirectory.getDirectory(indexDirName, lf);
        } catch (IOException e) {
            e.printStackTrace(System.out);
            fail("Should not have hit an IOException because LockFactory instances are the same");
        }

        fs1.close();
        if (fs2 != null) {
            fs2.close();
        }
        // Cleanup
        rmDir(indexDirName);
    }

    // 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 IOException {
      _testStressLocks(null, "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 IOException {
      _testStressLocks(new NativeFSLockFactory("index.TestLockFactory7"), "index.TestLockFactory7");
    }

    public void _testStressLocks(LockFactory lockFactory, String indexDirName) throws IOException {
        FSDirectory fs1 = FSDirectory.getDirectory(indexDirName, lockFactory);

        // First create a 1 doc index:
        IndexWriter w = new IndexWriter(fs1, new WhitespaceAnalyzer(), true);
        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()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }

        assertTrue("IndexWriter hit unexpected exceptions", !writer.hitException);
        assertTrue("IndexSearcher hit unexpected exceptions", !searcher.hitException);

        // Cleanup
        rmDir(indexDirName);
    }

    // Verify: NativeFSLockFactory works correctly
    public void testNativeFSLockFactory() throws IOException {

      NativeFSLockFactory f = new NativeFSLockFactory(System.getProperty("tempDir"));

      NativeFSLockFactory f2 = new NativeFSLockFactory(System.getProperty("tempDir"));

      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:
      assertTrue("failed to obtain lock", l.obtain());
      l.release();
    }

    // Verify: NativeFSLockFactory assigns different lock
    // prefixes to different directories:
    public void testNativeFSLockFactoryPrefix() throws IOException {

      // Make sure we get identical instances:
      Directory dir1 = FSDirectory.getDirectory("TestLockFactory.8", new NativeFSLockFactory("TestLockFactory.8"));
      Directory dir2 = FSDirectory.getDirectory("TestLockFactory.9", new NativeFSLockFactory("TestLockFactory.9"));

      String prefix1 = dir1.getLockFactory().getLockPrefix();
      String prefix2 = dir2.getLockFactory().getLockPrefix();

      assertTrue("Native Lock Factories are incorrectly shared: dir1 and dir2 have same lock prefix '" + prefix1 + "'; they should be different",
                 !prefix1.equals(prefix2));
      rmDir("TestLockFactory.8");
      rmDir("TestLockFactory.9");
    }

    // Verify: default LockFactory has no prefix (ie
    // write.lock is stored in index):
    public void testDefaultFSLockFactoryPrefix() throws IOException {

      // Make sure we get null prefix:
      Directory dir = FSDirectory.getDirectory("TestLockFactory.10");

      String prefix = dir.getLockFactory().getLockPrefix();

      assertTrue("Default lock prefix should be null", null == prefix);

      rmDir("TestLockFactory.10");
    }

    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;
        }
        public void run() {
            WhitespaceAnalyzer analyzer = new WhitespaceAnalyzer();
            IndexWriter writer = null;
            for(int i=0;i<this.numIteration;i++) {
                try {
                    writer = new IndexWriter(dir, analyzer, false);
                } catch (IOException e) {
                    if (e.toString().indexOf(" timed out:") == -1) {
                        hitException = true;
                    } 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;
        }
        public void run() {
            IndexSearcher searcher = null;
            WhitespaceAnalyzer analyzer = new WhitespaceAnalyzer();
            Query query = new TermQuery(new Term("content", "aaa"));
            for(int i=0;i<this.numIteration;i++) {
                try{
                    searcher = new IndexSearcher(dir);
                } catch (Exception e) {
                    hitException = true;
                    System.out.println("Stress Test Index Searcher: create hit unexpected exception: " + e.toString());
                    e.printStackTrace(System.out);
                    break;
                }
                if (searcher != null) {
                    Hits hits = null;
                    try {
                        hits = searcher.search(query);
                    } 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;
                    }
                    searcher = null;
                }
            }
        }
    }

    public class MockLockFactory extends LockFactory {

        public boolean lockPrefixSet;
        public Hashtable locksCreated = new Hashtable();
        public int makeLockCount = 0;

        public void setLockPrefix(String lockPrefix) {    
            super.setLockPrefix(lockPrefix);
            lockPrefixSet = true;
        }

        synchronized public Lock makeLock(String lockName) {
            Lock lock = new MockLock();
            locksCreated.put(lockName, lock);
            makeLockCount++;
            return lock;
        }

        public void clearLock(String specificLockName) {}

        public class MockLock extends Lock {
            public int lockAttempts;

            public boolean obtain() {
                lockAttempts++;
                return true;
            }
            public void release() {
                // do nothing
            }
            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.TOKENIZED));
        writer.addDocument(doc);
    }

    private void rmDir(String dirName) {
        File dir = new java.io.File(dirName);
        String[] files = dir.list();            // clear old files
        for (int i = 0; i < files.length; i++) {
            File file = new File(dir, files[i]);
            file.delete();
        }
        dir.delete();
    }
}