blob: 7a93ca251c88f2279c9fd2278e031ceef96171da [file] [log] [blame]
/*
* 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.
*/
package org.apache.lucene.store;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.file.AccessDeniedException;
import java.nio.file.Path;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.Constants;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.PrintStreamInfoStream;
import org.apache.lucene.util.TestUtil;
/** Base class for per-LockFactory tests. */
public abstract class BaseLockFactoryTestCase extends LuceneTestCase {
/** Subclass returns the Directory to be tested; if it's
* an FS-based directory it should point to the specified
* path, else it can ignore it. */
protected abstract Directory getDirectory(Path path) throws IOException;
/** Test obtaining and releasing locks, checking validity */
public void testBasics() throws IOException {
Path tempPath = createTempDir();
Directory dir = getDirectory(tempPath);
Lock l = dir.obtainLock("commit");
// shouldn't be able to get the lock twice
expectThrows(LockObtainFailedException.class, () -> {
dir.obtainLock("commit");
});
l.close();
// Make sure we can obtain first one again:
l = dir.obtainLock("commit");
l.close();
dir.close();
}
/** Test closing locks twice */
public void testDoubleClose() throws IOException {
Path tempPath = createTempDir();
Directory dir = getDirectory(tempPath);
Lock l = dir.obtainLock("commit");
l.close();
l.close(); // close again, should be no exception
dir.close();
}
/** Test ensureValid returns true after acquire */
public void testValidAfterAcquire() throws IOException {
Path tempPath = createTempDir();
Directory dir = getDirectory(tempPath);
Lock l = dir.obtainLock("commit");
l.ensureValid(); // no exception
l.close();
dir.close();
}
/** Test ensureValid throws exception after close */
public void testInvalidAfterClose() throws IOException {
Path tempPath = createTempDir();
Directory dir = getDirectory(tempPath);
Lock l = dir.obtainLock("commit");
l.close();
expectThrows(AlreadyClosedException.class, () -> {
l.ensureValid();
});
dir.close();
}
public void testObtainConcurrently() throws InterruptedException, IOException {
Path tempPath = createTempDir();
final Directory directory = getDirectory(tempPath);
final AtomicBoolean running = new AtomicBoolean(true);
final AtomicInteger atomicCounter = new AtomicInteger(0);
final ReentrantLock assertingLock = new ReentrantLock();
int numThreads = 2 + random().nextInt(10);
final int runs = atLeast(1000);
CyclicBarrier barrier = new CyclicBarrier(numThreads);
Thread[] threads = new Thread[numThreads];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread() {
@Override
public void run() {
try {
barrier.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
while (running.get()) {
try (Lock lock = directory.obtainLock("foo.lock")) {
assertFalse(assertingLock.isLocked());
if (assertingLock.tryLock()) {
assertingLock.unlock();
} else {
fail();
}
assert lock != null; // stupid compiler
} catch (IOException ex) {
//
}
if (atomicCounter.incrementAndGet() > runs) {
running.set(false);
}
}
}
};
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
threads[i].join();
}
directory.close();
}
// 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 {
Path tempPath = createTempDir();
assumeFalse("cannot handle buggy Files.delete", TestUtil.hasWindowsFS(tempPath));
Directory dir = getDirectory(tempPath);
// First create a 1 doc index:
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())).setOpenMode(OpenMode.CREATE));
addDoc(w);
w.close();
int numIterations = atLeast(20);
WriterThread writer = new WriterThread(numIterations, dir);
SearcherThread searcher = new SearcherThread(numIterations, dir);
writer.start();
searcher.start();
writer.join();
searcher.join();
assertTrue("IndexWriter hit unexpected exceptions", !writer.hitException);
assertTrue("IndexSearcher hit unexpected exceptions", !searcher.hitException);
dir.close();
}
private void addDoc(IndexWriter writer) throws IOException {
Document doc = new Document();
doc.add(newTextField("content", "aaa", Field.Store.NO));
writer.addDocument(doc);
}
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;
}
private String toString(ByteArrayOutputStream baos) {
try {
return baos.toString("UTF8");
} catch (UnsupportedEncodingException uee) {
// shouldn't happen
throw new RuntimeException(uee);
}
}
@Override
public void run() {
IndexWriter writer = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for(int i=0;i<this.numIteration;i++) {
if (VERBOSE) {
System.out.println("TEST: WriterThread iter=" + i);
}
IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
// We only print the IW infoStream output on exc, below:
PrintStream printStream;
try {
printStream = new PrintStream(baos, true, "UTF8");
} catch (UnsupportedEncodingException uee) {
// shouldn't happen
throw new RuntimeException(uee);
}
iwc.setInfoStream(new PrintStreamInfoStream(printStream));
printStream.println("\nTEST: WriterThread iter=" + i);
iwc.setOpenMode(OpenMode.APPEND);
try {
writer = new IndexWriter(dir, iwc);
} catch (Throwable t) {
if (Constants.WINDOWS && t instanceof AccessDeniedException) {
// LUCENE-6684: suppress this: on Windows, a file in the curious "pending delete" state can
// cause this exc on IW init, where one thread/process deleted an old
// segments_N, but the delete hasn't finished yet because other threads/processes
// still have it open
printStream.println("TEST: AccessDeniedException on init writer");
t.printStackTrace(printStream);
} else {
hitException = true;
System.out.println("Stress Test Index Writer: creation hit unexpected exception: " + t.toString());
t.printStackTrace(System.out);
System.out.println(toString(baos));
}
break;
}
if (writer != null) {
try {
addDoc(writer);
} catch (Throwable t) {
hitException = true;
System.out.println("Stress Test Index Writer: addDoc hit unexpected exception: " + t.toString());
t.printStackTrace(System.out);
System.out.println(toString(baos));
break;
}
try {
writer.close();
} catch (Throwable t) {
hitException = true;
System.out.println("Stress Test Index Writer: close hit unexpected exception: " + t.toString());
t.printStackTrace(System.out);
System.out.println(toString(baos));
break;
}
}
}
}
}
private static 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() {
IndexReader reader = null;
IndexSearcher searcher = null;
Query query = new TermQuery(new Term("content", "aaa"));
for(int i=0;i<this.numIteration;i++) {
try{
reader = DirectoryReader.open(dir);
searcher = newSearcher(reader);
} 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, 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 {
reader.close();
} catch (IOException e) {
hitException = true;
System.out.println("Stress Test Index Searcher: close hit unexpected exception: " + e.toString());
e.printStackTrace(System.out);
break;
}
}
}
}
}