package org.apache.lucene.facet.taxonomy.directory;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.FilterIndexReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.LockObtainFailedException;
import org.junit.Test;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.analysis.MockTokenizer;
import org.apache.lucene.facet.taxonomy.CategoryPath;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter;
/**
* 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.
*/
/**
* This test case attempts to catch index "leaks" in LuceneTaxonomyReader/Writer,
* i.e., cases where an index has been opened, but never closed; In that case,
* Java would eventually collect this object and close the index, but leaving
* the index open might nevertheless cause problems - e.g., on Windows it prevents
* deleting it.
*/
public class TestIndexClose extends LuceneTestCase {
@Test
public void testLeaks() throws Exception {
LeakChecker checker = new LeakChecker();
Directory dir = newDirectory();
DirectoryTaxonomyWriter tw = checker.openWriter(dir);
tw.close();
assertEquals(0, checker.nopen());
tw = checker.openWriter(dir);
tw.addCategory(new CategoryPath("animal", "dog"));
tw.close();
assertEquals(0, checker.nopen());
DirectoryTaxonomyReader tr = checker.openReader(dir);
tr.getPath(1);
tr.refresh();
tr.close();
assertEquals(0, checker.nopen());
tr = checker.openReader(dir);
tw = checker.openWriter(dir);
tw.addCategory(new CategoryPath("animal", "cat"));
tr.refresh();
tw.commit();
tw.close();
tr.refresh();
tr.close();
assertEquals(0, checker.nopen());
tw = checker.openWriter(dir);
for (int i=0; i<10000; i++) {
tw.addCategory(new CategoryPath("number", Integer.toString(i)));
}
tw.close();
assertEquals(0, checker.nopen());
tw = checker.openWriter(dir);
for (int i=0; i<10000; i++) {
tw.addCategory(new CategoryPath("number", Integer.toString(i*2)));
}
tw.close();
assertEquals(0, checker.nopen());
dir.close();
}
private static class LeakChecker {
int ireader=0;
Set<Integer> openReaders = new HashSet<Integer>();
int iwriter=0;
Set<Integer> openWriters = new HashSet<Integer>();
LeakChecker() { }
public DirectoryTaxonomyWriter openWriter(Directory dir) throws CorruptIndexException, LockObtainFailedException, IOException {
return new InstrumentedTaxonomyWriter(dir);
}
public DirectoryTaxonomyReader openReader(Directory dir) throws CorruptIndexException, LockObtainFailedException, IOException {
return new InstrumentedTaxonomyReader(dir);
}
public int nopen() {
int ret=0;
for (int i: openReaders) {
System.err.println("reader "+i+" still open");
ret++;
}
for (int i: openWriters) {
System.err.println("writer "+i+" still open");
ret++;
}
return ret;
}
private class InstrumentedTaxonomyWriter extends DirectoryTaxonomyWriter {
public InstrumentedTaxonomyWriter(Directory dir) throws CorruptIndexException, LockObtainFailedException, IOException {
super(dir);
}
@Override
protected IndexReader openReader() throws IOException {
return new InstrumentedIndexReader(super.openReader());
}
@Override
protected IndexWriter openIndexWriter (Directory directory, OpenMode openMode) throws IOException {
return new InstrumentedIndexWriter(directory,
newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.KEYWORD, false))
.setOpenMode(openMode));
}
}
private class InstrumentedTaxonomyReader extends DirectoryTaxonomyReader {
public InstrumentedTaxonomyReader(Directory dir) throws CorruptIndexException, LockObtainFailedException, IOException {
super(dir);
}
@Override
protected IndexReader openIndexReader(Directory dir) throws CorruptIndexException, IOException {
return new InstrumentedIndexReader(IndexReader.open(dir,true));
}
}
private class InstrumentedIndexReader extends FilterIndexReader {
int mynum;
public InstrumentedIndexReader(IndexReader in) {
super(in);
mynum = ireader++;
openReaders.add(mynum);
// System.err.println("opened "+mynum);
}
@Override
protected IndexReader doOpenIfChanged() throws CorruptIndexException,
IOException {
IndexReader n = IndexReader.openIfChanged(in);
if (n == null) {
return null;
}
return new InstrumentedIndexReader(n);
}
// Unfortunately, IndexReader.close() is marked final so we can't
// change it! Fortunately, close() calls (if the object wasn't
// already closed) doClose() so we can override it to do our thing -
// just like FilterIndexReader does.
@Override
public void doClose() throws IOException {
in.close();
if (!openReaders.contains(mynum)) { // probably can't happen...
fail("Reader #"+mynum+" was closed twice!");
}
openReaders.remove(mynum);
// System.err.println("closed "+mynum);
}
}
private class InstrumentedIndexWriter extends IndexWriter {
int mynum;
public InstrumentedIndexWriter(Directory d, IndexWriterConfig conf) throws CorruptIndexException, LockObtainFailedException, IOException {
super(d, conf);
mynum = iwriter++;
openWriters.add(mynum);
// System.err.println("openedw "+mynum);
}
@Override
public void close() throws IOException {
super.close();
if (!openWriters.contains(mynum)) { // probably can't happen...
fail("Writer #"+mynum+" was closed twice!");
}
openWriters.remove(mynum);
// System.err.println("closedw "+mynum);
}
}
}
}