diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 34e78ac16da..4439484148a 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -54,6 +54,14 @@ New Features
and queries, for fast "bbox/polygon contains lat/lon points" (Mike
McCandless)
+API Changes
+
+* LUCENE-6508: Simplify Lock api, there is now just
+ Directory.obtainLock() which returns a Lock that can be
+ released (or fails with exception). Add lock verification
+ to IndexWriter. Improve exception messages when locking fails.
+ (Uwe Schindler, Mike McCandless, Robert Muir)
+
Bug fixes
* LUCENE-6500: ParallelCompositeReader did not always call
diff --git a/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextCompoundFormat.java b/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextCompoundFormat.java
index bcc42b9ed4a..96e7b599bb8 100644
--- a/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextCompoundFormat.java
+++ b/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextCompoundFormat.java
@@ -151,7 +151,7 @@ public class SimpleTextCompoundFormat extends CompoundFormat {
public void renameFile(String source, String dest) { throw new UnsupportedOperationException(); }
@Override
- public Lock makeLock(String name) { throw new UnsupportedOperationException(); }
+ public Lock obtainLock(String name) { throw new UnsupportedOperationException(); }
};
}
diff --git a/lucene/core/src/java/org/apache/lucene/codecs/lucene50/Lucene50CompoundReader.java b/lucene/core/src/java/org/apache/lucene/codecs/lucene50/Lucene50CompoundReader.java
index 7d7fdc2502f..5c14b86ef55 100644
--- a/lucene/core/src/java/org/apache/lucene/codecs/lucene50/Lucene50CompoundReader.java
+++ b/lucene/core/src/java/org/apache/lucene/codecs/lucene50/Lucene50CompoundReader.java
@@ -179,7 +179,7 @@ final class Lucene50CompoundReader extends Directory {
}
@Override
- public Lock makeLock(String name) {
+ public Lock obtainLock(String name) {
throw new UnsupportedOperationException();
}
diff --git a/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java b/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
index 41c97fc4c8a..47532203fbc 100644
--- a/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
+++ b/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
@@ -47,7 +47,6 @@ import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.store.Lock;
-import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.util.Accountables;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.BytesRef;
@@ -356,7 +355,7 @@ public class CheckIndex implements Closeable {
/** Create a new CheckIndex on the directory. */
public CheckIndex(Directory dir) throws IOException {
- this(dir, dir.makeLock(IndexWriter.WRITE_LOCK_NAME));
+ this(dir, dir.obtainLock(IndexWriter.WRITE_LOCK_NAME));
}
/**
@@ -370,9 +369,6 @@ public class CheckIndex implements Closeable {
this.dir = dir;
this.writeLock = writeLock;
this.infoStream = null;
- if (!writeLock.obtain(IndexWriterConfig.WRITE_LOCK_TIMEOUT)) { // obtain write lock
- throw new LockObtainFailedException("Index locked for write: " + writeLock);
- }
}
private void ensureOpen() {
diff --git a/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java b/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java
index 8a6232c3ca7..0edd72ada52 100644
--- a/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java
+++ b/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java
@@ -95,6 +95,7 @@ import org.apache.lucene.util.InfoStream;
*/
final class DocumentsWriter implements Closeable, Accountable {
+ private final Directory directoryOrig; // no wrapping, for infos
private final Directory directory;
private volatile boolean closed;
@@ -123,7 +124,8 @@ final class DocumentsWriter implements Closeable, Accountable {
private final Queue events;
- DocumentsWriter(IndexWriter writer, LiveIndexWriterConfig config, Directory directory) {
+ DocumentsWriter(IndexWriter writer, LiveIndexWriterConfig config, Directory directoryOrig, Directory directory) {
+ this.directoryOrig = directoryOrig;
this.directory = directory;
this.config = config;
this.infoStream = config.getInfoStream();
@@ -393,7 +395,7 @@ final class DocumentsWriter implements Closeable, Accountable {
if (state.isActive() && state.dwpt == null) {
final FieldInfos.Builder infos = new FieldInfos.Builder(
writer.globalFieldNumberMap);
- state.dwpt = new DocumentsWriterPerThread(writer.newSegmentName(),
+ state.dwpt = new DocumentsWriterPerThread(writer.newSegmentName(), directoryOrig,
directory, config, infoStream, deleteQueue, infos,
writer.pendingNumDocs, writer.enableTestPoints);
}
diff --git a/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java b/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java
index 8ad99343bfd..321ab9b3249 100644
--- a/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java
+++ b/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java
@@ -158,9 +158,9 @@ class DocumentsWriterPerThread {
private final LiveIndexWriterConfig indexWriterConfig;
private final boolean enableTestPoints;
- public DocumentsWriterPerThread(String segmentName, Directory directory, LiveIndexWriterConfig indexWriterConfig, InfoStream infoStream, DocumentsWriterDeleteQueue deleteQueue,
+ public DocumentsWriterPerThread(String segmentName, Directory directoryOrig, Directory directory, LiveIndexWriterConfig indexWriterConfig, InfoStream infoStream, DocumentsWriterDeleteQueue deleteQueue,
FieldInfos.Builder fieldInfos, AtomicLong pendingNumDocs, boolean enableTestPoints) throws IOException {
- this.directoryOrig = directory;
+ this.directoryOrig = directoryOrig;
this.directory = new TrackingDirectoryWrapper(directory);
this.fieldInfos = fieldInfos;
this.indexWriterConfig = indexWriterConfig;
diff --git a/lucene/core/src/java/org/apache/lucene/index/IndexFileDeleter.java b/lucene/core/src/java/org/apache/lucene/index/IndexFileDeleter.java
index 2500c03cd3c..d4fd100b544 100644
--- a/lucene/core/src/java/org/apache/lucene/index/IndexFileDeleter.java
+++ b/lucene/core/src/java/org/apache/lucene/index/IndexFileDeleter.java
@@ -102,8 +102,9 @@ final class IndexFileDeleter implements Closeable {
private List commitsToDelete = new ArrayList<>();
private final InfoStream infoStream;
- private Directory directory;
- private IndexDeletionPolicy policy;
+ private final Directory directoryOrig; // for commit point metadata
+ private final Directory directory;
+ private final IndexDeletionPolicy policy;
final boolean startingCommitDeleted;
private SegmentInfos lastSegmentInfos;
@@ -126,7 +127,7 @@ final class IndexFileDeleter implements Closeable {
* any files not referenced by any of the commits.
* @throws IOException if there is a low-level IO error
*/
- public IndexFileDeleter(Directory directory, IndexDeletionPolicy policy, SegmentInfos segmentInfos,
+ public IndexFileDeleter(Directory directoryOrig, Directory directory, IndexDeletionPolicy policy, SegmentInfos segmentInfos,
InfoStream infoStream, IndexWriter writer, boolean initialIndexExists) throws IOException {
Objects.requireNonNull(writer);
this.infoStream = infoStream;
@@ -139,6 +140,7 @@ final class IndexFileDeleter implements Closeable {
}
this.policy = policy;
+ this.directoryOrig = directoryOrig;
this.directory = directory;
// First pass: walk the files and initialize our ref
@@ -165,7 +167,7 @@ final class IndexFileDeleter implements Closeable {
}
SegmentInfos sis = null;
try {
- sis = SegmentInfos.readCommit(directory, fileName);
+ sis = SegmentInfos.readCommit(directoryOrig, fileName);
} catch (FileNotFoundException | NoSuchFileException e) {
// LUCENE-948: on NFS (and maybe others), if
// you have writers switching back and forth
@@ -179,7 +181,7 @@ final class IndexFileDeleter implements Closeable {
}
}
if (sis != null) {
- final CommitPoint commitPoint = new CommitPoint(commitsToDelete, directory, sis);
+ final CommitPoint commitPoint = new CommitPoint(commitsToDelete, directoryOrig, sis);
if (sis.getGeneration() == segmentInfos.getGeneration()) {
currentCommitPoint = commitPoint;
}
@@ -205,14 +207,14 @@ final class IndexFileDeleter implements Closeable {
// try now to explicitly open this commit point:
SegmentInfos sis = null;
try {
- sis = SegmentInfos.readCommit(directory, currentSegmentsFile);
+ sis = SegmentInfos.readCommit(directoryOrig, currentSegmentsFile);
} catch (IOException e) {
throw new CorruptIndexException("unable to read current segments_N file", currentSegmentsFile, e);
}
if (infoStream.isEnabled("IFD")) {
infoStream.message("IFD", "forced open of current segments file " + segmentInfos.getSegmentsFileName());
}
- currentCommitPoint = new CommitPoint(commitsToDelete, directory, sis);
+ currentCommitPoint = new CommitPoint(commitsToDelete, directoryOrig, sis);
commits.add(currentCommitPoint);
incRef(sis, true);
}
@@ -557,7 +559,7 @@ final class IndexFileDeleter implements Closeable {
if (isCommit) {
// Append to our commits list:
- commits.add(new CommitPoint(commitsToDelete, directory, segmentInfos));
+ commits.add(new CommitPoint(commitsToDelete, directoryOrig, segmentInfos));
// Tell policy so it can remove commits:
policy.onCommit(commits);
@@ -780,14 +782,14 @@ final class IndexFileDeleter implements Closeable {
Collection files;
String segmentsFileName;
boolean deleted;
- Directory directory;
+ Directory directoryOrig;
Collection commitsToDelete;
long generation;
final Map userData;
private final int segmentCount;
- public CommitPoint(Collection commitsToDelete, Directory directory, SegmentInfos segmentInfos) throws IOException {
- this.directory = directory;
+ public CommitPoint(Collection commitsToDelete, Directory directoryOrig, SegmentInfos segmentInfos) throws IOException {
+ this.directoryOrig = directoryOrig;
this.commitsToDelete = commitsToDelete;
userData = segmentInfos.getUserData();
segmentsFileName = segmentInfos.getSegmentsFileName();
@@ -818,7 +820,7 @@ final class IndexFileDeleter implements Closeable {
@Override
public Directory getDirectory() {
- return directory;
+ return directoryOrig;
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java b/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
index dbf16866109..d37cd35f560 100644
--- a/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
+++ b/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
@@ -60,6 +60,7 @@ import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.MergeInfo;
import org.apache.lucene.store.RateLimitedIndexOutput;
import org.apache.lucene.store.TrackingDirectoryWrapper;
+import org.apache.lucene.store.LockValidatingDirectoryWrapper;
import org.apache.lucene.util.Accountable;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.BytesRef;
@@ -118,9 +119,7 @@ import org.apache.lucene.util.Version;
Opening an IndexWriter
creates a lock file for the directory in use. Trying to open
another IndexWriter
on the same directory will lead to a
- {@link LockObtainFailedException}. The {@link LockObtainFailedException}
- is also thrown if an IndexReader on the same directory is used to delete documents
- from the index.
+ {@link LockObtainFailedException}.
Expert: IndexWriter
allows an optional
@@ -254,8 +253,9 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
// when unrecoverable disaster strikes, we populate this with the reason that we had to close IndexWriter
volatile Throwable tragedy;
- private final Directory directory; // where this index resides
- private final Directory mergeDirectory; // used for merging
+ private final Directory directoryOrig; // original user directory
+ private final Directory directory; // wrapped with additional checks
+ private final Directory mergeDirectory; // wrapped with throttling: used for merging
private final Analyzer analyzer; // how to analyze text
private final AtomicLong changeCount = new AtomicLong(); // increments every time a change is completed
@@ -645,7 +645,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
// Make sure no new readers can be opened if another thread just closed us:
ensureOpen(false);
- assert info.info.dir == directory: "info.dir=" + info.info.dir + " vs " + directory;
+ assert info.info.dir == directoryOrig: "info.dir=" + info.info.dir + " vs " + directoryOrig;
ReadersAndUpdates rld = readerMap.get(info);
if (rld == null) {
@@ -754,29 +754,37 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
public IndexWriter(Directory d, IndexWriterConfig conf) throws IOException {
conf.setIndexWriter(this); // prevent reuse by other instances
config = conf;
-
- directory = d;
-
- // Directory we use for merging, so we can abort running merges, and so
- // merge schedulers can optionally rate-limit per-merge IO:
- mergeDirectory = addMergeRateLimiters(d);
-
- analyzer = config.getAnalyzer();
infoStream = config.getInfoStream();
- mergeScheduler = config.getMergeScheduler();
- mergeScheduler.setInfoStream(infoStream);
- codec = config.getCodec();
-
- bufferedUpdatesStream = new BufferedUpdatesStream(infoStream);
- poolReaders = config.getReaderPooling();
-
- writeLock = directory.makeLock(WRITE_LOCK_NAME);
-
- if (!writeLock.obtain(config.getWriteLockTimeout())) // obtain write lock
- throw new LockObtainFailedException("Index locked for write: " + writeLock);
-
+
+ // obtain the write.lock. If the user configured a timeout,
+ // we wrap with a sleeper and this might take some time.
+ long timeout = config.getWriteLockTimeout();
+ final Directory lockDir;
+ if (timeout == 0) {
+ // user doesn't want sleep/retries
+ lockDir = d;
+ } else {
+ lockDir = new SleepingLockWrapper(d, timeout);
+ }
+ writeLock = lockDir.obtainLock(WRITE_LOCK_NAME);
+
boolean success = false;
try {
+ directoryOrig = d;
+ directory = new LockValidatingDirectoryWrapper(d, writeLock);
+
+ // Directory we use for merging, so we can abort running merges, and so
+ // merge schedulers can optionally rate-limit per-merge IO:
+ mergeDirectory = addMergeRateLimiters(directory);
+
+ analyzer = config.getAnalyzer();
+ mergeScheduler = config.getMergeScheduler();
+ mergeScheduler.setInfoStream(infoStream);
+ codec = config.getCodec();
+
+ bufferedUpdatesStream = new BufferedUpdatesStream(infoStream);
+ poolReaders = config.getReaderPooling();
+
OpenMode mode = config.getOpenMode();
boolean create;
if (mode == OpenMode.CREATE) {
@@ -822,7 +830,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
// Do not use SegmentInfos.read(Directory) since the spooky
// retrying it does is not necessary here (we hold the write lock):
- segmentInfos = SegmentInfos.readCommit(directory, lastSegmentsFile);
+ segmentInfos = SegmentInfos.readCommit(directoryOrig, lastSegmentsFile);
IndexCommit commit = config.getIndexCommit();
if (commit != null) {
@@ -831,9 +839,9 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
// preserve write-once. This is important if
// readers are open against the future commit
// points.
- if (commit.getDirectory() != directory)
- throw new IllegalArgumentException("IndexCommit's directory doesn't match my directory");
- SegmentInfos oldInfos = SegmentInfos.readCommit(directory, commit.getSegmentsFileName());
+ if (commit.getDirectory() != directoryOrig)
+ throw new IllegalArgumentException("IndexCommit's directory doesn't match my directory, expected=" + directoryOrig + ", got=" + commit.getDirectory());
+ SegmentInfos oldInfos = SegmentInfos.readCommit(directoryOrig, commit.getSegmentsFileName());
segmentInfos.replace(oldInfos);
changed();
if (infoStream.isEnabled("IW")) {
@@ -848,13 +856,13 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
// start with previous field numbers, but new FieldInfos
globalFieldNumberMap = getFieldNumberMap();
config.getFlushPolicy().init(config);
- docWriter = new DocumentsWriter(this, config, directory);
+ docWriter = new DocumentsWriter(this, config, directoryOrig, directory);
eventQueue = docWriter.eventQueue();
// Default deleter (for backwards compatibility) is
// KeepOnlyLastCommitDeleter:
synchronized(this) {
- deleter = new IndexFileDeleter(directory,
+ deleter = new IndexFileDeleter(directoryOrig, directory,
config.getIndexDeletionPolicy(),
segmentInfos, infoStream, this,
initialIndexExists);
@@ -937,7 +945,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
private void messageState() {
if (infoStream.isEnabled("IW") && didMessageState == false) {
didMessageState = true;
- infoStream.message("IW", "\ndir=" + directory + "\n" +
+ infoStream.message("IW", "\ndir=" + directoryOrig + "\n" +
"index=" + segString() + "\n" +
"version=" + Version.LATEST.toString() + "\n" +
config.toString());
@@ -1036,7 +1044,8 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
/** Returns the Directory used by this index. */
public Directory getDirectory() {
- return directory;
+ // return the original directory the user supplied, unwrapped.
+ return directoryOrig;
}
/** Returns the analyzer used by this index. */
@@ -2274,7 +2283,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
for(int i=0;i());
SegmentMerger merger = new SegmentMerger(Arrays.asList(readers), info, infoStream, trackingDir,
@@ -2600,7 +2607,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
//System.out.println("copy seg=" + info.info.name + " version=" + info.info.getVersion());
// Same SI as before but we change directory and name
- SegmentInfo newInfo = new SegmentInfo(directory, info.info.getVersion(), segName, info.info.maxDoc(),
+ SegmentInfo newInfo = new SegmentInfo(directoryOrig, info.info.getVersion(), segName, info.info.maxDoc(),
info.info.getUseCompoundFile(), info.info.getCodec(),
info.info.getDiagnostics(), info.info.getId(), info.info.getAttributes());
SegmentCommitInfo newInfoPerCommit = new SegmentCommitInfo(newInfo, info.getDelCount(), info.getDelGen(),
@@ -3075,7 +3082,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
private synchronized void ensureValidMerge(MergePolicy.OneMerge merge) {
for(SegmentCommitInfo info : merge.segments) {
if (!segmentInfos.contains(info)) {
- throw new MergePolicy.MergeException("MergePolicy selected a segment (" + info.info.name + ") that is not in the current index " + segString(), directory);
+ throw new MergePolicy.MergeException("MergePolicy selected a segment (" + info.info.name + ") that is not in the current index " + segString(), directoryOrig);
}
}
}
@@ -3599,7 +3606,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
}
return false;
}
- if (info.info.dir != directory) {
+ if (info.info.dir != directoryOrig) {
isExternal = true;
}
if (segmentsToMerge.containsKey(info)) {
@@ -3732,7 +3739,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
// ConcurrentMergePolicy we keep deterministic segment
// names.
final String mergeSegmentName = newSegmentName();
- SegmentInfo si = new SegmentInfo(directory, Version.LATEST, mergeSegmentName, -1, false, codec, Collections.emptyMap(), StringHelper.randomId(), new HashMap<>());
+ SegmentInfo si = new SegmentInfo(directoryOrig, Version.LATEST, mergeSegmentName, -1, false, codec, Collections.emptyMap(), StringHelper.randomId(), new HashMap<>());
Map details = new HashMap<>();
details.put("mergeMaxNumSegments", "" + merge.maxNumSegments);
details.put("mergeFactor", Integer.toString(merge.segments.size()));
@@ -4365,9 +4372,17 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
* currently locked.
* @param directory the directory to check for a lock
* @throws IOException if there is a low-level IO error
+ * @deprecated Use of this method can only lead to race conditions. Try
+ * to actually obtain a lock instead.
*/
+ @Deprecated
public static boolean isLocked(Directory directory) throws IOException {
- return directory.makeLock(WRITE_LOCK_NAME).isLocked();
+ try {
+ directory.obtainLock(WRITE_LOCK_NAME).close();
+ return false;
+ } catch (LockObtainFailedException failed) {
+ return true;
+ }
}
/** If {@link DirectoryReader#open(IndexWriter,boolean)} has
diff --git a/lucene/core/src/java/org/apache/lucene/index/IndexWriterConfig.java b/lucene/core/src/java/org/apache/lucene/index/IndexWriterConfig.java
index 89ace398b8c..623a342d91d 100644
--- a/lucene/core/src/java/org/apache/lucene/index/IndexWriterConfig.java
+++ b/lucene/core/src/java/org/apache/lucene/index/IndexWriterConfig.java
@@ -265,7 +265,8 @@ public final class IndexWriterConfig extends LiveIndexWriterConfig {
/**
* Sets the maximum time to wait for a write lock (in milliseconds) for this
* instance. You can change the default value for all instances by calling
- * {@link #setDefaultWriteLockTimeout(long)}.
+ * {@link #setDefaultWriteLockTimeout(long)}. Note that the value can be zero,
+ * for no sleep/retry behavior.
*
* Only takes effect when IndexWriter is first created. */
public IndexWriterConfig setWriteLockTimeout(long writeLockTimeout) {
diff --git a/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java b/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java
index 4ef5529154b..f9f5db8e1f4 100644
--- a/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java
+++ b/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java
@@ -441,7 +441,6 @@ public final class SegmentInfos implements Cloneable, Iterable
+ * This is not a good idea.
+ */
+final class SleepingLockWrapper extends FilterDirectory {
+
+ /**
+ * Pass this lockWaitTimeout to try forever to obtain the lock.
+ */
+ public static final long LOCK_OBTAIN_WAIT_FOREVER = -1;
+
+ /**
+ * How long {@link #obtainLock} waits, in milliseconds,
+ * in between attempts to acquire the lock.
+ */
+ public static long DEFAULT_POLL_INTERVAL = 1000;
+
+ private final long lockWaitTimeout;
+ private final long pollInterval;
+
+ /**
+ * Create a new SleepingLockFactory
+ * @param delegate underlying directory to wrap
+ * @param lockWaitTimeout length of time to wait in milliseconds
+ * or {@link #LOCK_OBTAIN_WAIT_FOREVER} to retry forever.
+ */
+ public SleepingLockWrapper(Directory delegate, long lockWaitTimeout) {
+ this(delegate, lockWaitTimeout, DEFAULT_POLL_INTERVAL);
+ }
+
+ /**
+ * Create a new SleepingLockFactory
+ * @param delegate underlying directory to wrap
+ * @param lockWaitTimeout length of time to wait in milliseconds
+ * or {@link #LOCK_OBTAIN_WAIT_FOREVER} to retry forever.
+ * @param pollInterval poll once per this interval in milliseconds until
+ * {@code lockWaitTimeout} is exceeded.
+ */
+ public SleepingLockWrapper(Directory delegate, long lockWaitTimeout, long pollInterval) {
+ super(delegate);
+ this.lockWaitTimeout = lockWaitTimeout;
+ this.pollInterval = pollInterval;
+ if (lockWaitTimeout < 0 && lockWaitTimeout != LOCK_OBTAIN_WAIT_FOREVER) {
+ throw new IllegalArgumentException("lockWaitTimeout should be LOCK_OBTAIN_WAIT_FOREVER or a non-negative number (got " + lockWaitTimeout + ")");
+ }
+ if (pollInterval < 0) {
+ throw new IllegalArgumentException("pollInterval must be a non-negative number (got " + pollInterval + ")");
+ }
+ }
+
+ @Override
+ public Lock obtainLock(String lockName) throws IOException {
+ LockObtainFailedException failureReason = null;
+ long maxSleepCount = lockWaitTimeout / pollInterval;
+ long sleepCount = 0;
+
+ do {
+ try {
+ return in.obtainLock(lockName);
+ } catch (LockObtainFailedException failed) {
+ if (failureReason == null) {
+ failureReason = failed;
+ }
+ }
+ try {
+ Thread.sleep(pollInterval);
+ } catch (InterruptedException ie) {
+ throw new ThreadInterruptedException(ie);
+ }
+ } while (sleepCount++ < maxSleepCount || lockWaitTimeout == LOCK_OBTAIN_WAIT_FOREVER);
+
+ // we failed to obtain the lock in the required time
+ String reason = "Lock obtain timed out: " + this.toString();
+ if (failureReason != null) {
+ reason += ": " + failureReason;
+ }
+ throw new LockObtainFailedException(reason, failureReason);
+ }
+
+ @Override
+ public String toString() {
+ return "SleepingLockWrapper(" + in + ")";
+ }
+}
diff --git a/lucene/core/src/java/org/apache/lucene/store/BaseDirectory.java b/lucene/core/src/java/org/apache/lucene/store/BaseDirectory.java
index 616c90e6a82..9950465a733 100644
--- a/lucene/core/src/java/org/apache/lucene/store/BaseDirectory.java
+++ b/lucene/core/src/java/org/apache/lucene/store/BaseDirectory.java
@@ -17,6 +17,7 @@ package org.apache.lucene.store;
* limitations under the License.
*/
+import java.io.IOException;
/**
* Base implementation for a concrete {@link Directory} that uses a {@link LockFactory} for locking.
@@ -40,8 +41,8 @@ public abstract class BaseDirectory extends Directory {
}
@Override
- public final Lock makeLock(String name) {
- return lockFactory.makeLock(this, name);
+ public final Lock obtainLock(String name) throws IOException {
+ return lockFactory.obtainLock(this, name);
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/store/Directory.java b/lucene/core/src/java/org/apache/lucene/store/Directory.java
index 3ab6dbfda49..ac2f5fc892b 100644
--- a/lucene/core/src/java/org/apache/lucene/store/Directory.java
+++ b/lucene/core/src/java/org/apache/lucene/store/Directory.java
@@ -50,8 +50,7 @@ public abstract class Directory implements Closeable {
public abstract String[] listAll() throws IOException;
/** Removes an existing file in the directory. */
- public abstract void deleteFile(String name)
- throws IOException;
+ public abstract void deleteFile(String name) throws IOException;
/**
* Returns the length of a file in the directory. This method follows the
@@ -110,10 +109,14 @@ public abstract class Directory implements Closeable {
return new BufferedChecksumIndexInput(openInput(name, context));
}
- /** Construct a {@link Lock}.
+ /**
+ * Returns an obtained {@link Lock}.
* @param name the name of the lock file
+ * @throws LockObtainFailedException (optional specific exception) if the lock could
+ * not be obtained because it is currently held elsewhere.
+ * @throws IOException if any i/o error occurs attempting to gain the lock
*/
- public abstract Lock makeLock(String name);
+ public abstract Lock obtainLock(String name) throws IOException;
/** Closes the store. */
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/store/FSLockFactory.java b/lucene/core/src/java/org/apache/lucene/store/FSLockFactory.java
index e49ef188772..d666075b142 100644
--- a/lucene/core/src/java/org/apache/lucene/store/FSLockFactory.java
+++ b/lucene/core/src/java/org/apache/lucene/store/FSLockFactory.java
@@ -17,6 +17,8 @@ package org.apache.lucene.store;
* limitations under the License.
*/
+import java.io.IOException;
+
/**
* Base class for file system based locking implementation.
* This class is explicitly checking that the passed {@link Directory}
@@ -32,14 +34,17 @@ public abstract class FSLockFactory extends LockFactory {
}
@Override
- public final Lock makeLock(Directory dir, String lockName) {
+ public final Lock obtainLock(Directory dir, String lockName) throws IOException {
if (!(dir instanceof FSDirectory)) {
throw new UnsupportedOperationException(getClass().getSimpleName() + " can only be used with FSDirectory subclasses, got: " + dir);
}
- return makeFSLock((FSDirectory) dir, lockName);
+ return obtainFSLock((FSDirectory) dir, lockName);
}
- /** Implement this method to create a lock for a FSDirectory instance. */
- protected abstract Lock makeFSLock(FSDirectory dir, String lockName);
+ /**
+ * Implement this method to obtain a lock for a FSDirectory instance.
+ * @throws IOException if the lock could not be obtained.
+ */
+ protected abstract Lock obtainFSLock(FSDirectory dir, String lockName) throws IOException;
}
diff --git a/lucene/core/src/java/org/apache/lucene/store/FileSwitchDirectory.java b/lucene/core/src/java/org/apache/lucene/store/FileSwitchDirectory.java
index 7a420870d00..24750fa9e7b 100644
--- a/lucene/core/src/java/org/apache/lucene/store/FileSwitchDirectory.java
+++ b/lucene/core/src/java/org/apache/lucene/store/FileSwitchDirectory.java
@@ -71,8 +71,8 @@ public class FileSwitchDirectory extends Directory {
}
@Override
- public Lock makeLock(String name) {
- return getDirectory(name).makeLock(name);
+ public Lock obtainLock(String name) throws IOException {
+ return getDirectory(name).obtainLock(name);
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/store/FilterDirectory.java b/lucene/core/src/java/org/apache/lucene/store/FilterDirectory.java
index 765b5c24c96..93642424a3e 100644
--- a/lucene/core/src/java/org/apache/lucene/store/FilterDirectory.java
+++ b/lucene/core/src/java/org/apache/lucene/store/FilterDirectory.java
@@ -90,8 +90,8 @@ public class FilterDirectory extends Directory {
}
@Override
- public Lock makeLock(String name) {
- return in.makeLock(name);
+ public Lock obtainLock(String name) throws IOException {
+ return in.obtainLock(name);
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/store/Lock.java b/lucene/core/src/java/org/apache/lucene/store/Lock.java
index a59c59b1fa8..70b855bbd54 100644
--- a/lucene/core/src/java/org/apache/lucene/store/Lock.java
+++ b/lucene/core/src/java/org/apache/lucene/store/Lock.java
@@ -20,126 +20,39 @@ package org.apache.lucene.store;
import java.io.Closeable;
import java.io.IOException;
-import org.apache.lucene.util.ThreadInterruptedException;
-
/** An interprocess mutex lock.
* Typical use might look like:
- * new Lock.With(directory.makeLock("my.lock")) {
- * public Object doBody() {
- * ... code to execute while locked ...
- * }
- * }.run();
+ * try (final Lock lock = directory.obtainLock("my.lock")) {
+ * // ... code to execute while locked ...
+ * }
*
*
- * @see Directory#makeLock(String)
+ * @see Directory#obtainLock(String)
*
* @lucene.internal
*/
public abstract class Lock implements Closeable {
- /** How long {@link #obtain(long)} waits, in milliseconds,
- * in between attempts to acquire the lock. */
- public static long LOCK_POLL_INTERVAL = 1000;
-
- /** Pass this value to {@link #obtain(long)} to try
- * forever to obtain the lock. */
- public static final long LOCK_OBTAIN_WAIT_FOREVER = -1;
-
- /** Attempts to obtain exclusive access and immediately return
- * upon success or failure. Use {@link #close} to
- * release the lock.
- * @return true iff exclusive access is obtained
+ /**
+ * Releases exclusive access.
+ *
+ * Note that exceptions thrown from close may require
+ * human intervention, as it may mean the lock was no
+ * longer valid, or that fs permissions prevent removal
+ * of the lock file, or other reasons.
+ *
+ * {@inheritDoc}
+ * @throws LockReleaseFailedException optional specific exception) if
+ * the lock could not be properly released.
*/
- public abstract boolean obtain() throws IOException;
-
- /**
- * If a lock obtain called, this failureReason may be set
- * with the "root cause" Exception as to why the lock was
- * not obtained.
- */
- protected Throwable failureReason;
-
- /** Attempts to obtain an exclusive lock within amount of
- * time given. Polls once per {@link #LOCK_POLL_INTERVAL}
- * (currently 1000) milliseconds until lockWaitTimeout is
- * passed.
- * @param lockWaitTimeout length of time to wait in
- * milliseconds or {@link
- * #LOCK_OBTAIN_WAIT_FOREVER} to retry forever
- * @return true if lock was obtained
- * @throws LockObtainFailedException if lock wait times out
- * @throws IllegalArgumentException if lockWaitTimeout is
- * out of bounds
- * @throws IOException if obtain() throws IOException
- */
- public final boolean obtain(long lockWaitTimeout) throws IOException {
- failureReason = null;
- boolean locked = obtain();
- if (lockWaitTimeout < 0 && lockWaitTimeout != LOCK_OBTAIN_WAIT_FOREVER)
- throw new IllegalArgumentException("lockWaitTimeout should be LOCK_OBTAIN_WAIT_FOREVER or a non-negative number (got " + lockWaitTimeout + ")");
-
- long maxSleepCount = lockWaitTimeout / LOCK_POLL_INTERVAL;
- long sleepCount = 0;
- while (!locked) {
- if (lockWaitTimeout != LOCK_OBTAIN_WAIT_FOREVER && sleepCount++ >= maxSleepCount) {
- String reason = "Lock obtain timed out: " + this.toString();
- if (failureReason != null) {
- reason += ": " + failureReason;
- }
- throw new LockObtainFailedException(reason, failureReason);
- }
- try {
- Thread.sleep(LOCK_POLL_INTERVAL);
- } catch (InterruptedException ie) {
- throw new ThreadInterruptedException(ie);
- }
- locked = obtain();
- }
- return locked;
- }
-
- /** Releases exclusive access. */
public abstract void close() throws IOException;
-
- /** Returns true if the resource is currently locked. Note that one must
- * still call {@link #obtain()} before using the resource. */
- public abstract boolean isLocked() throws IOException;
-
-
- /** Utility class for executing code with exclusive access. */
- public abstract static class With {
- private Lock lock;
- private long lockWaitTimeout;
-
-
- /** Constructs an executor that will grab the named lock. */
- public With(Lock lock, long lockWaitTimeout) {
- this.lock = lock;
- this.lockWaitTimeout = lockWaitTimeout;
- }
-
- /** Code to execute with exclusive access. */
- protected abstract Object doBody() throws IOException;
-
- /** Calls {@link #doBody} while lock is obtained. Blocks if lock
- * cannot be obtained immediately. Retries to obtain lock once per second
- * until it is obtained, or until it has tried ten times. Lock is released when
- * {@link #doBody} exits.
- * @throws LockObtainFailedException if lock could not
- * be obtained
- * @throws IOException if {@link Lock#obtain} throws IOException
- */
- public Object run() throws IOException {
- boolean locked = false;
- try {
- locked = lock.obtain(lockWaitTimeout);
- return doBody();
- } finally {
- if (locked) {
- lock.close();
- }
- }
- }
- }
-
+
+ /**
+ * Best effort check that this lock is still valid. Locks
+ * could become invalidated externally for a number of reasons,
+ * for example if a user deletes the lock file manually or
+ * when a network filesystem is in use.
+ * @throws IOException if the lock is no longer valid.
+ */
+ public abstract void ensureValid() throws IOException;
}
diff --git a/lucene/core/src/java/org/apache/lucene/store/LockFactory.java b/lucene/core/src/java/org/apache/lucene/store/LockFactory.java
index 3a6632fd321..ca92590bd17 100644
--- a/lucene/core/src/java/org/apache/lucene/store/LockFactory.java
+++ b/lucene/core/src/java/org/apache/lucene/store/LockFactory.java
@@ -17,6 +17,7 @@ package org.apache.lucene.store;
* limitations under the License.
*/
+import java.io.IOException;
/**
*
Base class for Locking implementation. {@link Directory} uses
@@ -46,9 +47,12 @@ package org.apache.lucene.store;
public abstract class LockFactory {
/**
- * Return a new Lock instance identified by lockName.
+ * Return a new obtained Lock instance identified by lockName.
* @param lockName name of the lock to be created.
+ * @throws LockObtainFailedException (optional specific exception) if the lock could
+ * not be obtained because it is currently held elsewhere.
+ * @throws IOException if any i/o error occurs attempting to gain the lock
*/
- public abstract Lock makeLock(Directory dir, String lockName);
+ public abstract Lock obtainLock(Directory dir, String lockName) throws IOException;
}
diff --git a/lucene/core/src/java/org/apache/lucene/store/LockObtainFailedException.java b/lucene/core/src/java/org/apache/lucene/store/LockObtainFailedException.java
index 14b0b5403f3..dedfea14a91 100644
--- a/lucene/core/src/java/org/apache/lucene/store/LockObtainFailedException.java
+++ b/lucene/core/src/java/org/apache/lucene/store/LockObtainFailedException.java
@@ -24,7 +24,7 @@ import java.io.IOException;
* could not be acquired. This
* happens when a writer tries to open an index
* that another writer already has open.
- * @see Lock#obtain(long)
+ * @see LockFactory#obtainLock(Directory, String)
*/
public class LockObtainFailedException extends IOException {
public LockObtainFailedException(String message) {
diff --git a/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java b/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java
index 7726734b675..8c2d8a86feb 100644
--- a/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java
+++ b/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java
@@ -38,10 +38,12 @@ import org.apache.lucene.util.SuppressForbidden;
*/
public class LockStressTest {
+
+ static final String LOCK_FILE_NAME = "test.lock";
@SuppressForbidden(reason = "System.out required: command line tool")
+ @SuppressWarnings("try")
public static void main(String[] args) throws Exception {
-
if (args.length != 7) {
System.out.println("Usage: java org.apache.lucene.store.LockStressTest myID verifierHost verifierPort lockFactoryClassName lockDirName sleepTimeMS count\n" +
"\n" +
@@ -91,7 +93,6 @@ public class LockStressTest {
out.write(myID);
out.flush();
LockFactory verifyLF = new VerifyingLockFactory(lockFactory, in, out);
- Lock l = verifyLF.makeLock(lockDir, "test.lock");
final Random rnd = new Random();
// wait for starting gun
@@ -100,25 +101,22 @@ public class LockStressTest {
}
for (int i = 0; i < count; i++) {
- boolean obtained = false;
- try {
- obtained = l.obtain(rnd.nextInt(100) + 10);
- } catch (LockObtainFailedException e) {}
-
- if (obtained) {
+ try (final Lock l = verifyLF.obtainLock(lockDir, LOCK_FILE_NAME)) {
if (rnd.nextInt(10) == 0) {
if (rnd.nextBoolean()) {
verifyLF = new VerifyingLockFactory(getNewLockFactory(lockFactoryClassName), in, out);
}
- final Lock secondLock = verifyLF.makeLock(lockDir, "test.lock");
- if (secondLock.obtain()) {
- throw new IOException("Double Obtain");
+ try (final Lock secondLock = verifyLF.obtainLock(lockDir, LOCK_FILE_NAME)) {
+ throw new IOException("Double obtain");
+ } catch (LockObtainFailedException loe) {
+ // pass
}
}
Thread.sleep(sleepTimeMS);
- l.close();
+ } catch (LockObtainFailedException loe) {
+ // obtain failed
}
-
+
if (i % 500 == 0) {
System.out.println((i * 100. / count) + "% done.");
}
diff --git a/lucene/core/src/java/org/apache/lucene/store/LockValidatingDirectoryWrapper.java b/lucene/core/src/java/org/apache/lucene/store/LockValidatingDirectoryWrapper.java
new file mode 100644
index 00000000000..389c56ddfe4
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/store/LockValidatingDirectoryWrapper.java
@@ -0,0 +1,64 @@
+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.IOException;
+import java.util.Collection;
+
+/**
+ * This class makes a best-effort check that a provided {@link Lock}
+ * is valid before any destructive filesystem operation.
+ */
+public final class LockValidatingDirectoryWrapper extends FilterDirectory {
+ private final Lock writeLock;
+
+ public LockValidatingDirectoryWrapper(Directory in, Lock writeLock) {
+ super(in);
+ this.writeLock = writeLock;
+ }
+
+ @Override
+ public void deleteFile(String name) throws IOException {
+ writeLock.ensureValid();
+ in.deleteFile(name);
+ }
+
+ @Override
+ public IndexOutput createOutput(String name, IOContext context) throws IOException {
+ writeLock.ensureValid();
+ return in.createOutput(name, context);
+ }
+
+ @Override
+ public void copyFrom(Directory from, String src, String dest, IOContext context) throws IOException {
+ writeLock.ensureValid();
+ in.copyFrom(from, src, dest, context);
+ }
+
+ @Override
+ public void renameFile(String source, String dest) throws IOException {
+ writeLock.ensureValid();
+ in.renameFile(source, dest);
+ }
+
+ @Override
+ public void sync(Collection names) throws IOException {
+ writeLock.ensureValid();
+ in.sync(names);
+ }
+}
diff --git a/lucene/core/src/java/org/apache/lucene/store/NativeFSLockFactory.java b/lucene/core/src/java/org/apache/lucene/store/NativeFSLockFactory.java
index d59888639dd..7ab06ae119f 100644
--- a/lucene/core/src/java/org/apache/lucene/store/NativeFSLockFactory.java
+++ b/lucene/core/src/java/org/apache/lucene/store/NativeFSLockFactory.java
@@ -18,10 +18,12 @@ package org.apache.lucene.store;
*/
import java.nio.channels.FileChannel;
-import java.nio.channels.OverlappingFileLockException;
+import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
@@ -77,136 +79,127 @@ public final class NativeFSLockFactory extends FSLockFactory {
*/
public static final NativeFSLockFactory INSTANCE = new NativeFSLockFactory();
+ private static final Set LOCK_HELD = Collections.synchronizedSet(new HashSet());
+
private NativeFSLockFactory() {}
@Override
- protected Lock makeFSLock(FSDirectory dir, String lockName) {
- return new NativeFSLock(dir.getDirectory(), lockName);
+ protected Lock obtainFSLock(FSDirectory dir, String lockName) throws IOException {
+ Path lockDir = dir.getDirectory();
+
+ // Ensure that lockDir exists and is a directory.
+ // note: this will fail if lockDir is a symlink
+ Files.createDirectories(lockDir);
+
+ Path lockFile = lockDir.resolve(lockName);
+
+ try {
+ Files.createFile(lockFile);
+ } catch (IOException ignore) {
+ // we must create the file to have a truly canonical path.
+ // if it's already created, we don't care. if it cant be created, it will fail below.
+ }
+
+ // fails if the lock file does not exist
+ final Path realPath = lockFile.toRealPath();
+
+ // used as a best-effort check, to see if the underlying file has changed
+ final FileTime creationTime = Files.readAttributes(realPath, BasicFileAttributes.class).creationTime();
+
+ if (LOCK_HELD.add(realPath.toString())) {
+ FileChannel channel = null;
+ FileLock lock = null;
+ try {
+ channel = FileChannel.open(realPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
+ lock = channel.tryLock();
+ if (lock != null) {
+ return new NativeFSLock(lock, channel, realPath, creationTime);
+ } else {
+ throw new LockObtainFailedException("Lock held by another program: " + realPath);
+ }
+ } finally {
+ if (lock == null) { // not successful - clear up and move out
+ IOUtils.closeWhileHandlingException(channel); // TODO: addSuppressed
+ clearLockHeld(realPath); // clear LOCK_HELD last
+ }
+ }
+ } else {
+ throw new LockObtainFailedException("Lock held by this virtual machine: " + realPath);
+ }
}
+ private static final void clearLockHeld(Path path) throws IOException {
+ boolean remove = LOCK_HELD.remove(path.toString());
+ if (remove == false) {
+ throw new AlreadyClosedException("Lock path was cleared but never marked as held: " + path);
+ }
+ }
+
+ // TODO: kind of bogus we even pass channel:
+ // FileLock has an accessor, but mockfs doesnt yet mock the locks, too scary atm.
+
static final class NativeFSLock extends Lock {
-
- private final Path path;
- private final Path lockDir;
- private static final Set LOCK_HELD = Collections.synchronizedSet(new HashSet());
-
- private FileChannel channel; // set when we have the lock
- private Path realPath; // unconditionally set in obtain(), for use in close()
-
- public NativeFSLock(Path lockDir, String lockFileName) {
- this.lockDir = lockDir;
- path = lockDir.resolve(lockFileName);
+ final FileLock lock;
+ final FileChannel channel;
+ final Path path;
+ final FileTime creationTime;
+ volatile boolean closed;
+
+ NativeFSLock(FileLock lock, FileChannel channel, Path path, FileTime creationTime) {
+ this.lock = lock;
+ this.channel = channel;
+ this.path = path;
+ this.creationTime = creationTime;
}
@Override
- public synchronized boolean obtain() throws IOException {
-
- if (channel != null) {
- // Our instance is already locked:
- assert channel.isOpen();
- assert realPath != null;
- throw new LockObtainFailedException("this lock instance was already obtained");
+ public void ensureValid() throws IOException {
+ if (closed) {
+ throw new AlreadyClosedException("Lock instance already released: " + this);
}
-
- // Ensure that lockDir exists and is a directory.
- Files.createDirectories(lockDir);
- try {
- Files.createFile(path);
- } catch (IOException ignore) {
- // we must create the file to have a truly canonical path.
- // if it's already created, we don't care. if it cant be created, it will fail below.
+ // check we are still in the locks map (some debugger or something crazy didn't remove us)
+ if (!LOCK_HELD.contains(path.toString())) {
+ throw new AlreadyClosedException("Lock path unexpectedly cleared from map: " + this);
}
- realPath = path.toRealPath();
- // Make sure nobody else in-process has this lock held
- // already, and, mark it held if not:
- // This is a pretty crazy workaround for some documented
- // but yet awkward JVM behavior:
- //
- // On some systems, closing a channel releases all locks held by the Java virtual machine on the underlying file
- // regardless of whether the locks were acquired via that channel or via another channel open on the same file.
- // It is strongly recommended that, within a program, a unique channel be used to acquire all locks on any given
- // file.
- //
- // This essentially means if we close "A" channel for a given file all locks might be released... the odd part
- // is that we can't re-obtain the lock in the same JVM but from a different process if that happens. Nevertheless
- // this is super trappy. See LUCENE-5738
- boolean obtained = false;
- if (LOCK_HELD.add(realPath.toString())) {
- FileChannel ch = null;
- try {
- ch = FileChannel.open(realPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
- try {
- if (ch.tryLock() != null) {
- channel = ch;
- obtained = true;
- }
- } catch (IOException | OverlappingFileLockException e) {
- // At least on OS X, we will sometimes get an
- // intermittent "Permission Denied" IOException,
- // which seems to simply mean "you failed to get
- // the lock". But other IOExceptions could be
- // "permanent" (eg, locking is not supported via
- // the filesystem). So, we record the failure
- // reason here; the timeout obtain (usually the
- // one calling us) will use this as "root cause"
- // if it fails to get the lock.
- failureReason = e;
- }
- } finally {
- if (obtained == false) { // not successful - clear up and move out
- IOUtils.closeWhileHandlingException(ch);
- clearLockHeld(realPath); // clear LOCK_HELD last
- }
- }
+ // check our lock wasn't invalidated.
+ if (!lock.isValid()) {
+ throw new AlreadyClosedException("FileLock invalidated by an external force: " + this);
+ }
+ // try to validate the underlying file descriptor.
+ // this will throw IOException if something is wrong.
+ long size = channel.size();
+ if (size != 0) {
+ throw new AlreadyClosedException("Unexpected lock file size: " + size + ", (lock=" + this + ")");
+ }
+ // try to validate the backing file name, that it still exists,
+ // and has the same creation time as when we obtained the lock.
+ // if it differs, someone deleted our lock file (and we are ineffective)
+ FileTime ctime = Files.readAttributes(path, BasicFileAttributes.class).creationTime();
+ if (!creationTime.equals(ctime)) {
+ throw new AlreadyClosedException("Underlying file changed by an external force at " + creationTime + ", (lock=" + this + ")");
}
- return obtained;
}
@Override
public synchronized void close() throws IOException {
- if (channel != null) {
- try {
- IOUtils.close(channel);
- } finally {
- channel = null;
- clearLockHeld(realPath); // clear LOCK_HELD last
- }
+ if (closed) {
+ return;
}
- }
-
- private static final void clearLockHeld(Path path) {
- boolean remove = LOCK_HELD.remove(path.toString());
- assert remove : "Lock was cleared but never marked as held";
- }
-
- @Override
- public synchronized boolean isLocked() {
- // The test for is isLocked is not directly possible with native file locks:
-
- // First a shortcut, if a lock reference in this instance is available
- if (channel != null) {
- return true;
+ // NOTE: we don't validate, as unlike SimpleFSLockFactory, we can't break others locks
+ // first release the lock, then the channel
+ try (FileChannel channel = this.channel;
+ FileLock lock = this.lock) {
+ assert lock != null;
+ assert channel != null;
+ } finally {
+ closed = true;
+ clearLockHeld(path);
}
-
- // Look if lock file is definitely not present; if not, there can definitely be no lock!
- if (Files.notExists(path)) {
- return false;
- }
-
- // Try to obtain and release (if was locked) the lock
- try {
- boolean obtained = obtain();
- if (obtained) close();
- return !obtained;
- } catch (IOException ioe) {
- return false;
- }
}
@Override
public String toString() {
- return "NativeFSLock@" + path;
+ return "NativeFSLock(path=" + path + ",impl=" + lock + ",ctime=" + creationTime + ")";
}
}
-
}
diff --git a/lucene/core/src/java/org/apache/lucene/store/NoLockFactory.java b/lucene/core/src/java/org/apache/lucene/store/NoLockFactory.java
index a5417df2c7d..7a209c51c79 100644
--- a/lucene/core/src/java/org/apache/lucene/store/NoLockFactory.java
+++ b/lucene/core/src/java/org/apache/lucene/store/NoLockFactory.java
@@ -37,23 +37,17 @@ public final class NoLockFactory extends LockFactory {
private NoLockFactory() {}
@Override
- public Lock makeLock(Directory dir, String lockName) {
+ public Lock obtainLock(Directory dir, String lockName) {
return SINGLETON_LOCK;
}
private static class NoLock extends Lock {
- @Override
- public boolean obtain() throws IOException {
- return true;
- }
-
@Override
public void close() {
}
@Override
- public boolean isLocked() {
- return false;
+ public void ensureValid() throws IOException {
}
@Override
@@ -61,5 +55,4 @@ public final class NoLockFactory extends LockFactory {
return "NoLock";
}
}
-
}
diff --git a/lucene/core/src/java/org/apache/lucene/store/SimpleFSLockFactory.java b/lucene/core/src/java/org/apache/lucene/store/SimpleFSLockFactory.java
index a35945177cc..0985ef67afe 100644
--- a/lucene/core/src/java/org/apache/lucene/store/SimpleFSLockFactory.java
+++ b/lucene/core/src/java/org/apache/lucene/store/SimpleFSLockFactory.java
@@ -17,26 +17,24 @@ package org.apache.lucene.store;
* limitations under the License.
*/
-import java.io.File;
import java.io.IOException;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
/**
* Implements {@link LockFactory} using {@link
* Files#createFile}.
*
- * NOTE: the {@linkplain File#createNewFile() javadocs
- * for File.createNewFile()
} contain a vague
- * yet spooky warning about not using the API for file
- * locking. This warning was added due to this
- * bug, and in fact the only known problem with using
- * this API for locking is that the Lucene write lock may
- * not be released when the JVM exits abnormally.
-
- * When this happens, a {@link LockObtainFailedException}
- * is hit when trying to create a writer, in which case you
+ *
The main downside with using this API for locking is
+ * that the Lucene write lock may not be released when
+ * the JVM exits abnormally.
+ *
+ * When this happens, an {@link LockObtainFailedException}
+ * is hit when trying to create a writer, in which case you may
* need to explicitly clear the lock file first by
* manually removing the file. But, first be certain that
* no writer is in fact writing to the index otherwise you
@@ -70,66 +68,83 @@ public final class SimpleFSLockFactory extends FSLockFactory {
private SimpleFSLockFactory() {}
@Override
- protected Lock makeFSLock(FSDirectory dir, String lockName) {
- return new SimpleFSLock(dir.getDirectory(), lockName);
+ protected Lock obtainFSLock(FSDirectory dir, String lockName) throws IOException {
+ Path lockDir = dir.getDirectory();
+
+ // Ensure that lockDir exists and is a directory.
+ // note: this will fail if lockDir is a symlink
+ Files.createDirectories(lockDir);
+
+ Path lockFile = lockDir.resolve(lockName);
+
+ // create the file: this will fail if it already exists
+ try {
+ Files.createFile(lockFile);
+ } catch (FileAlreadyExistsException | AccessDeniedException e) {
+ // convert optional specific exception to our optional specific exception
+ throw new LockObtainFailedException("Lock held elsewhere: " + lockFile, e);
+ }
+
+ // used as a best-effort check, to see if the underlying file has changed
+ final FileTime creationTime = Files.readAttributes(lockFile, BasicFileAttributes.class).creationTime();
+
+ return new SimpleFSLock(lockFile, creationTime);
}
- static class SimpleFSLock extends Lock {
+ static final class SimpleFSLock extends Lock {
+ private final Path path;
+ private final FileTime creationTime;
+ private volatile boolean closed;
- Path lockFile;
- Path lockDir;
- boolean obtained = false;
-
- public SimpleFSLock(Path lockDir, String lockFileName) {
- this.lockDir = lockDir;
- lockFile = lockDir.resolve(lockFileName);
+ SimpleFSLock(Path path, FileTime creationTime) throws IOException {
+ this.path = path;
+ this.creationTime = creationTime;
}
@Override
- public synchronized boolean obtain() throws IOException {
- if (obtained) {
- // Our instance is already locked:
- throw new LockObtainFailedException("this lock instance was already obtained");
+ public void ensureValid() throws IOException {
+ if (closed) {
+ throw new AlreadyClosedException("Lock instance already released: " + this);
+ }
+ // try to validate the backing file name, that it still exists,
+ // and has the same creation time as when we obtained the lock.
+ // if it differs, someone deleted our lock file (and we are ineffective)
+ FileTime ctime = Files.readAttributes(path, BasicFileAttributes.class).creationTime();
+ if (!creationTime.equals(ctime)) {
+ throw new AlreadyClosedException("Underlying file changed by an external force at " + creationTime + ", (lock=" + this + ")");
+ }
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ if (closed) {
+ return;
}
-
try {
- Files.createDirectories(lockDir);
- Files.createFile(lockFile);
- obtained = true;
- } catch (IOException ioe) {
- // On Windows, on concurrent createNewFile, the 2nd process gets "access denied".
- // In that case, the lock was not aquired successfully, so return false.
- // We record the failure reason here; the obtain with timeout (usually the
- // one calling us) will use this as "root cause" if it fails to get the lock.
- failureReason = ioe;
- }
-
- return obtained;
- }
-
- @Override
- public synchronized void close() throws LockReleaseFailedException {
- // TODO: wierd that clearLock() throws the raw IOException...
- if (obtained) {
+ // NOTE: unlike NativeFSLockFactory, we can potentially delete someone else's
+ // lock if things have gone wrong. we do best-effort check (ensureValid) to
+ // avoid doing this.
try {
- Files.deleteIfExists(lockFile);
- } catch (Throwable cause) {
- throw new LockReleaseFailedException("failed to delete " + lockFile, cause);
- } finally {
- obtained = false;
+ ensureValid();
+ } catch (Throwable exc) {
+ // notify the user they may need to intervene.
+ throw new LockReleaseFailedException("Lock file cannot be safely removed. Manual intervention is recommended.", exc);
}
+ // we did a best effort check, now try to remove the file. if something goes wrong,
+ // we need to make it clear to the user that the directory may still remain locked.
+ try {
+ Files.delete(path);
+ } catch (Throwable exc) {
+ throw new LockReleaseFailedException("Unable to remove lock file. Manual intervention is recommended", exc);
+ }
+ } finally {
+ closed = true;
}
}
- @Override
- public boolean isLocked() {
- return Files.exists(lockFile);
- }
-
@Override
public String toString() {
- return "SimpleFSLock@" + lockFile;
+ return "SimpleFSLock(path=" + path + ",ctime=" + creationTime + ")";
}
}
-
}
diff --git a/lucene/core/src/java/org/apache/lucene/store/SingleInstanceLockFactory.java b/lucene/core/src/java/org/apache/lucene/store/SingleInstanceLockFactory.java
index 52f1b0bad07..68d3f34e91a 100644
--- a/lucene/core/src/java/org/apache/lucene/store/SingleInstanceLockFactory.java
+++ b/lucene/core/src/java/org/apache/lucene/store/SingleInstanceLockFactory.java
@@ -24,7 +24,7 @@ import java.util.HashSet;
* Implements {@link LockFactory} for a single in-process instance,
* meaning all locking will take place through this one instance.
* Only use this {@link LockFactory} when you are certain all
- * IndexReaders and IndexWriters for a given index are running
+ * IndexWriters for a given index are running
* against a single shared in-process Directory instance. This is
* currently the default locking for RAMDirectory.
*
@@ -33,51 +33,53 @@ import java.util.HashSet;
public final class SingleInstanceLockFactory extends LockFactory {
- private final HashSet locks = new HashSet<>();
+ final HashSet locks = new HashSet<>();
@Override
- public Lock makeLock(Directory dir, String lockName) {
- return new SingleInstanceLock(locks, lockName);
+ public Lock obtainLock(Directory dir, String lockName) throws IOException {
+ synchronized (locks) {
+ if (locks.add(lockName)) {
+ return new SingleInstanceLock(lockName);
+ } else {
+ throw new LockObtainFailedException("lock instance already obtained: (dir=" + dir + ", lockName=" + lockName + ")");
+ }
+ }
}
- private static class SingleInstanceLock extends Lock {
-
+ private class SingleInstanceLock extends Lock {
private final String lockName;
- private final HashSet locks;
- private boolean obtained = false;
+ private volatile boolean closed;
- public SingleInstanceLock(HashSet locks, String lockName) {
- this.locks = locks;
+ public SingleInstanceLock(String lockName) {
this.lockName = lockName;
}
@Override
- public boolean obtain() throws IOException {
- synchronized(locks) {
- if (obtained) {
- // Our instance is already locked:
- throw new LockObtainFailedException("this lock instance was already obtained");
- }
- obtained = locks.add(lockName);
-
- return obtained;
+ public void ensureValid() throws IOException {
+ if (closed) {
+ throw new AlreadyClosedException("Lock instance already released: " + this);
}
- }
-
- @Override
- public void close() {
- synchronized(locks) {
- if (obtained) {
- locks.remove(lockName);
- obtained = false;
+ // check we are still in the locks map (some debugger or something crazy didn't remove us)
+ synchronized (locks) {
+ if (!locks.contains(lockName)) {
+ throw new AlreadyClosedException("Lock instance was invalidated from map: " + this);
}
}
}
@Override
- public boolean isLocked() {
- synchronized(locks) {
- return locks.contains(lockName);
+ public synchronized void close() throws IOException {
+ if (closed) {
+ return;
+ }
+ try {
+ synchronized (locks) {
+ if (!locks.remove(lockName)) {
+ throw new AlreadyClosedException("Lock was already released: " + this);
+ }
+ }
+ } finally {
+ closed = true;
}
}
diff --git a/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java b/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java
index 5c877481a5c..37906735ae0 100644
--- a/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java
+++ b/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java
@@ -43,10 +43,23 @@ public final class VerifyingLockFactory extends LockFactory {
private class CheckedLock extends Lock {
private final Lock lock;
- private boolean obtained = false;
- public CheckedLock(Lock lock) {
+ public CheckedLock(Lock lock) throws IOException {
this.lock = lock;
+ verify((byte) 1);
+ }
+
+ @Override
+ public void ensureValid() throws IOException {
+ lock.ensureValid();
+ }
+
+ @Override
+ public void close() throws IOException {
+ try (Lock l = lock) {
+ l.ensureValid();
+ verify((byte) 0);
+ }
}
private void verify(byte message) throws IOException {
@@ -60,29 +73,6 @@ public final class VerifyingLockFactory extends LockFactory {
throw new IOException("Protocol violation.");
}
}
-
- @Override
- public synchronized boolean obtain() throws IOException {
- obtained = lock.obtain();
- if (obtained) {
- verify((byte) 1);
- }
- return obtained;
- }
-
- @Override
- public synchronized boolean isLocked() throws IOException {
- return lock.isLocked();
- }
-
- @Override
- public synchronized void close() throws IOException {
- if (obtained) {
- assert isLocked();
- verify((byte) 0);
- }
- lock.close();
- }
}
/**
@@ -97,7 +87,7 @@ public final class VerifyingLockFactory extends LockFactory {
}
@Override
- public Lock makeLock(Directory dir, String lockName) {
- return new CheckedLock(lf.makeLock(dir, lockName));
+ public Lock obtainLock(Directory dir, String lockName) throws IOException {
+ return new CheckedLock(lf.obtainLock(dir, lockName));
}
}
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java b/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java
index c98f1a6eaa4..55197e72f86 100644
--- a/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java
+++ b/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java
@@ -1268,7 +1268,6 @@ public class TestAddIndexes extends LuceneTestCase {
Directory dest = newDirectory();
IndexWriterConfig iwc = newIndexWriterConfig(new MockAnalyzer(random()));
- iwc.setWriteLockTimeout(1);
RandomIndexWriter w2 = new RandomIndexWriter(random(), dest, iwc);
try {
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java b/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java
index 5909d0489a4..59f9e1d45c3 100644
--- a/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java
+++ b/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java
@@ -96,14 +96,7 @@ public class TestIndexWriter extends LuceneTestCase {
IndexReader reader = null;
int i;
- long savedWriteLockTimeout = IndexWriterConfig.getDefaultWriteLockTimeout();
- try {
- IndexWriterConfig.setDefaultWriteLockTimeout(2000);
- assertEquals(2000, IndexWriterConfig.getDefaultWriteLockTimeout());
- writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random())));
- } finally {
- IndexWriterConfig.setDefaultWriteLockTimeout(savedWriteLockTimeout);
- }
+ writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random())));
// add 100 documents
for (i = 0; i < 100; i++) {
@@ -1725,8 +1718,7 @@ public class TestIndexWriter extends LuceneTestCase {
RandomIndexWriter w1 = new RandomIndexWriter(random(), d);
w1.deleteAll();
try {
- new RandomIndexWriter(random(), d, newIndexWriterConfig(null)
- .setWriteLockTimeout(100));
+ new RandomIndexWriter(random(), d, newIndexWriterConfig(null));
fail("should not be able to create another writer");
} catch (LockObtainFailedException lofe) {
// expected
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java b/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java
index 7c9e35eb941..f71f6d8655e 100644
--- a/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java
+++ b/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java
@@ -2199,7 +2199,7 @@ public class TestIndexWriterExceptions extends LuceneTestCase {
// even though we hit exception: we are closed, no locks or files held, index in good state
assertTrue(iw.isClosed());
- assertFalse(IndexWriter.isLocked(dir));
+ dir.obtainLock(IndexWriter.WRITE_LOCK_NAME).close();
r = DirectoryReader.open(dir);
assertEquals(10, r.maxDoc());
@@ -2268,7 +2268,7 @@ public class TestIndexWriterExceptions extends LuceneTestCase {
// even though we hit exception: we are closed, no locks or files held, index in good state
assertTrue(iw.isClosed());
- assertFalse(IndexWriter.isLocked(dir));
+ dir.obtainLock(IndexWriter.WRITE_LOCK_NAME).close();
r = DirectoryReader.open(dir);
assertEquals(10, r.maxDoc());
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestSleepingLockWrapper.java b/lucene/core/src/test/org/apache/lucene/index/TestSleepingLockWrapper.java
new file mode 100644
index 00000000000..daa3952ac77
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/index/TestSleepingLockWrapper.java
@@ -0,0 +1,49 @@
+package org.apache.lucene.index;
+
+/*
+ * 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.IOException;
+import java.nio.file.Path;
+
+import org.apache.lucene.index.SleepingLockWrapper;
+import org.apache.lucene.store.BaseLockFactoryTestCase;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.SingleInstanceLockFactory;
+import org.apache.lucene.util.TestUtil;
+
+/** Simple tests for SleepingLockWrapper */
+public class TestSleepingLockWrapper extends BaseLockFactoryTestCase {
+
+ @Override
+ protected Directory getDirectory(Path path) throws IOException {
+ long lockWaitTimeout = TestUtil.nextLong(random(), 20, 100);
+ long pollInterval = TestUtil.nextLong(random(), 2, 10);
+
+ int which = random().nextInt(3);
+ switch (which) {
+ case 0:
+ return new SleepingLockWrapper(newDirectory(random(), new SingleInstanceLockFactory()), lockWaitTimeout, pollInterval);
+ case 1:
+ return new SleepingLockWrapper(newFSDirectory(path), lockWaitTimeout, pollInterval);
+ default:
+ return new SleepingLockWrapper(newFSDirectory(path), lockWaitTimeout, pollInterval);
+ }
+ }
+
+ // TODO: specific tests to this impl
+}
diff --git a/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java b/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java
index fdbdb11a696..8534fbffa50 100644
--- a/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java
+++ b/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java
@@ -86,14 +86,12 @@ public class TestDirectory extends LuceneTestCase {
assertFalse(slowFileExists(d2, fname));
}
- Lock lock = dir.makeLock(lockname);
- assertTrue(lock.obtain());
+ Lock lock = dir.obtainLock(lockname);
- for (int j=0; j runs) {
- running.set(false);
- }
- }
- }
- };
- threads[i].start();
- }
-
- for (int i = 0; i < threads.length; i++) {
- threads[i].join();
- }
- directory.close();
- }
-
- public void testSingleInstanceLockFactoryDoubleObtain() throws Exception {
- LockFactory lf = new SingleInstanceLockFactory();
- Directory dir = newFSDirectory(createTempDir(), lf);
- Lock lock = dir.makeLock("foo");
- assertTrue(lock.obtain());
- try {
- lock.obtain();
- fail("did not hit double-obtain failure");
- } catch (LockObtainFailedException lofe) {
- // expected
- }
- lock.close();
-
- lock = dir.makeLock("foo");
- assertTrue(lock.obtain());
- lock.close();
- dir.close();
- }
-
- public void testSimpleFSLockFactoryDoubleObtain() throws Exception {
- Directory dir = newFSDirectory(createTempDir(), SimpleFSLockFactory.INSTANCE);
- Lock lock = dir.makeLock("foo");
- assertTrue(lock.obtain());
- try {
- lock.obtain();
- fail("did not hit double-obtain failure");
- } catch (LockObtainFailedException lofe) {
- // expected
- }
- lock.close();
-
- lock = dir.makeLock("foo");
- assertTrue(lock.obtain());
- lock.close();
- dir.close();
- }
-
- public void testNativeFSLockFactoryDoubleObtain() throws Exception {
- Directory dir = newFSDirectory(createTempDir(), NativeFSLockFactory.INSTANCE);
- Lock lock = dir.makeLock("foo");
- assertTrue(lock.obtain());
- try {
- lock.obtain();
- fail("did not hit double-obtain failure");
- } catch (LockObtainFailedException lofe) {
- // expected
- }
- lock.close();
-
- lock = dir.makeLock("foo");
- assertTrue(lock.obtain());
- lock.close();
- dir.close();
- }
-}
diff --git a/lucene/core/src/test/org/apache/lucene/store/TestLockFactory.java b/lucene/core/src/test/org/apache/lucene/store/TestLockFactory.java
index 8b582d3ac7d..d3eca511afe 100644
--- a/lucene/core/src/test/org/apache/lucene/store/TestLockFactory.java
+++ b/lucene/core/src/test/org/apache/lucene/store/TestLockFactory.java
@@ -18,8 +18,6 @@ package org.apache.lucene.store;
*/
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -27,16 +25,9 @@ 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.DirectoryReader;
-import org.apache.lucene.index.IndexReader;
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.IOUtils;
import org.apache.lucene.util.LuceneTestCase;
public class TestLockFactory extends LuceneTestCase {
@@ -58,15 +49,6 @@ public class TestLockFactory extends LuceneTestCase {
// 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();
}
@@ -75,7 +57,6 @@ public class TestLockFactory extends LuceneTestCase {
// Verify: NoLockFactory allows two IndexWriters
public void testRAMDirectoryNoLocking() throws IOException {
MockDirectoryWrapper dir = new MockDirectoryWrapper(random(), new RAMDirectory(NoLockFactory.INSTANCE));
- dir.setAssertLocks(false); // we are gonna explicitly test we get this back
IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())));
writer.commit(); // required so the second open succeed
@@ -95,240 +76,29 @@ public class TestLockFactory extends LuceneTestCase {
}
}
- // Verify: SingleInstanceLockFactory is the default lock for RAMDirectory
- // Verify: RAMDirectory does basic locking correctly (can't create two IndexWriters)
- public void testDefaultRAMDirectory() throws IOException {
- RAMDirectory dir = new RAMDirectory();
-
- assertTrue("RAMDirectory did not use correct LockFactory: got " + dir.lockFactory,
- dir.lockFactory instanceof SingleInstanceLockFactory);
-
- IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())));
-
- // Create a 2nd IndexWriter. This should fail:
- IndexWriter writer2 = null;
- try {
- writer2 = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())).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();
- }
- }
-
- // Verify: do stress test, by opening IndexReaders and
- // IndexWriters over & over in 2 threads and making sure
- // no unexpected exceptions are raised:
- @Nightly
- public void testStressLocksSimpleFSLockFactory() throws Exception {
- _testStressLocks(SimpleFSLockFactory.INSTANCE, createTempDir("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:
- @Nightly
- public void testStressLocksNativeFSLockFactory() throws Exception {
- Path dir = createTempDir("index.TestLockFactory7");
- _testStressLocks(NativeFSLockFactory.INSTANCE, dir);
- }
-
- public void _testStressLocks(LockFactory lockFactory, Path indexDir) throws Exception {
- Directory dir = newFSDirectory(indexDir, lockFactory);
-
- // First create a 1 doc index:
- IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())).setOpenMode(OpenMode.CREATE));
- addDoc(w);
- w.close();
-
- WriterThread writer = new WriterThread(100, dir);
- SearcherThread searcher = new SearcherThread(100, dir);
- 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);
-
- dir.close();
- // Cleanup
- IOUtils.rm(indexDir);
- }
-
- // Verify: NativeFSLockFactory works correctly
- public void testNativeFSLockFactory() throws IOException {
- Directory dir = FSDirectory.open(createTempDir(LuceneTestCase.getTestClass().getSimpleName()), NativeFSLockFactory.INSTANCE);
-
- Lock l = dir.makeLock("commit");
- Lock l2 = dir.makeLock("commit");
-
- assertTrue("failed to obtain lock", l.obtain());
- assertTrue("succeeded in obtaining lock twice", !l2.obtain());
- l.close();
-
- assertTrue("failed to obtain 2nd lock after first one was freed", l2.obtain());
- l2.close();
-
- // Make sure we can obtain first one again, test isLocked():
- assertTrue("failed to obtain lock", l.obtain());
- assertTrue(l.isLocked());
- assertTrue(l2.isLocked());
- l.close();
- assertFalse(l.isLocked());
- assertFalse(l2.isLocked());
- }
-
-
- // Verify: NativeFSLockFactory works correctly if the lock file exists
- public void testNativeFSLockFactoryLockExists() throws IOException {
- Path tempDir = createTempDir(LuceneTestCase.getTestClass().getSimpleName());
- Path lockFile = tempDir.resolve("test.lock");
- Files.createFile(lockFile);
-
- Directory dir = FSDirectory.open(tempDir, NativeFSLockFactory.INSTANCE);
- Lock l = dir.makeLock("test.lock");
- assertTrue("failed to obtain lock", l.obtain());
- l.close();
- assertFalse("failed to release lock", l.isLocked());
- Files.deleteIfExists(lockFile);
- }
-
- 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 locksCreated = Collections.synchronizedMap(new HashMap());
- public int makeLockCount = 0;
@Override
- public synchronized Lock makeLock(Directory dir, String lockName) {
+ public synchronized Lock obtainLock(Directory dir, String lockName) {
Lock lock = new MockLock();
locksCreated.put(lockName, lock);
- makeLockCount++;
return lock;
}
public class MockLock extends Lock {
- public int lockAttempts;
- @Override
- public boolean obtain() {
- lockAttempts++;
- return true;
- }
@Override
public void close() {
// do nothing
}
+
@Override
- public boolean isLocked() {
- return false;
+ public void ensureValid() throws IOException {
+ // do nothing
}
+
}
}
diff --git a/lucene/core/src/test/org/apache/lucene/store/TestNativeFSLockFactory.java b/lucene/core/src/test/org/apache/lucene/store/TestNativeFSLockFactory.java
new file mode 100644
index 00000000000..b53707ee9c6
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/store/TestNativeFSLockFactory.java
@@ -0,0 +1,108 @@
+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.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.lucene.util.IOUtils;
+
+/** Simple tests for NativeFSLockFactory */
+public class TestNativeFSLockFactory extends BaseLockFactoryTestCase {
+
+ @Override
+ protected Directory getDirectory(Path path) throws IOException {
+ return newFSDirectory(path, NativeFSLockFactory.INSTANCE);
+ }
+
+ /** Verify NativeFSLockFactory works correctly if the lock file exists */
+ public void testLockFileExists() throws IOException {
+ Path tempDir = createTempDir();
+ Path lockFile = tempDir.resolve("test.lock");
+ Files.createFile(lockFile);
+
+ Directory dir = getDirectory(tempDir);
+ Lock l = dir.obtainLock("test.lock");
+ l.close();
+ dir.close();
+ }
+
+ /** release the lock and test ensureValid fails */
+ public void testInvalidateLock() throws IOException {
+ Directory dir = getDirectory(createTempDir());
+ NativeFSLockFactory.NativeFSLock lock = (NativeFSLockFactory.NativeFSLock) dir.obtainLock("test.lock");
+ lock.ensureValid();
+ lock.lock.release();
+ try {
+ lock.ensureValid();
+ fail("no exception");
+ } catch (AlreadyClosedException expected) {
+ // ok
+ } finally {
+ IOUtils.closeWhileHandlingException(lock);
+ }
+ dir.close();
+ }
+
+ /** close the channel and test ensureValid fails */
+ public void testInvalidateChannel() throws IOException {
+ Directory dir = getDirectory(createTempDir());
+ NativeFSLockFactory.NativeFSLock lock = (NativeFSLockFactory.NativeFSLock) dir.obtainLock("test.lock");
+ lock.ensureValid();
+ lock.channel.close();
+ try {
+ lock.ensureValid();
+ fail("no exception");
+ } catch (AlreadyClosedException expected) {
+ // ok
+ } finally {
+ IOUtils.closeWhileHandlingException(lock);
+ }
+ dir.close();
+ }
+
+ /** delete the lockfile and test ensureValid fails */
+ public void testDeleteLockFile() throws IOException {
+ Directory dir = getDirectory(createTempDir());
+ try {
+ Lock lock = dir.obtainLock("test.lock");
+ lock.ensureValid();
+
+ try {
+ dir.deleteFile("test.lock");
+ } catch (Exception e) {
+ // we can't delete a file for some reason, just clean up and assume the test.
+ IOUtils.closeWhileHandlingException(lock);
+ assumeNoException("test requires the ability to delete a locked file", e);
+ }
+
+ try {
+ lock.ensureValid();
+ fail("no exception");
+ } catch (IOException expected) {
+ // ok
+ } finally {
+ IOUtils.closeWhileHandlingException(lock);
+ }
+ } finally {
+ // Do this in finally clause in case the assumeNoException is false:
+ dir.close();
+ }
+ }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/store/TestSimpleFSLockFactory.java b/lucene/core/src/test/org/apache/lucene/store/TestSimpleFSLockFactory.java
new file mode 100644
index 00000000000..f05297e5778
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/store/TestSimpleFSLockFactory.java
@@ -0,0 +1,61 @@
+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.IOException;
+import java.nio.file.Path;
+
+import org.apache.lucene.util.IOUtils;
+
+/** Simple tests for SimpleFSLockFactory */
+public class TestSimpleFSLockFactory extends BaseLockFactoryTestCase {
+
+ @Override
+ protected Directory getDirectory(Path path) throws IOException {
+ return newFSDirectory(path, SimpleFSLockFactory.INSTANCE);
+ }
+
+ /** delete the lockfile and test ensureValid fails */
+ public void testDeleteLockFile() throws IOException {
+ Directory dir = getDirectory(createTempDir());
+ try {
+ Lock lock = dir.obtainLock("test.lock");
+ lock.ensureValid();
+
+ try {
+ dir.deleteFile("test.lock");
+ } catch (Exception e) {
+ // we can't delete a file for some reason, just clean up and assume the test.
+ IOUtils.closeWhileHandlingException(lock);
+ assumeNoException("test requires the ability to delete a locked file", e);
+ }
+
+ try {
+ lock.ensureValid();
+ fail("no exception");
+ } catch (IOException expected) {
+ // ok
+ } finally {
+ IOUtils.closeWhileHandlingException(lock);
+ }
+ } finally {
+ // Do this in finally clause in case the assumeNoException is false:
+ dir.close();
+ }
+ }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/store/TestSingleInstanceLockFactory.java b/lucene/core/src/test/org/apache/lucene/store/TestSingleInstanceLockFactory.java
new file mode 100644
index 00000000000..c9f4668862c
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/store/TestSingleInstanceLockFactory.java
@@ -0,0 +1,59 @@
+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.IOException;
+import java.nio.file.Path;
+
+import org.apache.lucene.analysis.MockAnalyzer;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.IndexWriterConfig.OpenMode;
+
+/** Simple tests for SingleInstanceLockFactory */
+public class TestSingleInstanceLockFactory extends BaseLockFactoryTestCase {
+
+ @Override
+ protected Directory getDirectory(Path path) throws IOException {
+ return newDirectory(random(), new SingleInstanceLockFactory());
+ }
+
+ // Verify: SingleInstanceLockFactory is the default lock for RAMDirectory
+ // Verify: RAMDirectory does basic locking correctly (can't create two IndexWriters)
+ public void testDefaultRAMDirectory() throws IOException {
+ RAMDirectory dir = new RAMDirectory();
+
+ assertTrue("RAMDirectory did not use correct LockFactory: got " + dir.lockFactory,
+ dir.lockFactory instanceof SingleInstanceLockFactory);
+
+ IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())));
+
+ // Create a 2nd IndexWriter. This should fail:
+ IndexWriter writer2 = null;
+ try {
+ writer2 = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())).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();
+ }
+ }
+}
diff --git a/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/directory/DirectoryTaxonomyWriter.java b/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/directory/DirectoryTaxonomyWriter.java
index e98e565ca58..77b2389ef35 100644
--- a/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/directory/DirectoryTaxonomyWriter.java
+++ b/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/directory/DirectoryTaxonomyWriter.java
@@ -43,7 +43,7 @@ import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.index.TieredMergePolicy;
import org.apache.lucene.store.AlreadyClosedException;
import org.apache.lucene.store.Directory;
-import org.apache.lucene.store.LockObtainFailedException; // javadocs
+import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.util.BytesRef;
/*
diff --git a/lucene/test-framework/src/java/org/apache/lucene/index/BaseCompoundFormatTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/index/BaseCompoundFormatTestCase.java
index 34e5adca798..34d14dc4680 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/index/BaseCompoundFormatTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/index/BaseCompoundFormatTestCase.java
@@ -320,7 +320,7 @@ public abstract class BaseCompoundFormatTestCase extends BaseIndexFileFormatTest
si.getCodec().compoundFormat().write(dir, si, IOContext.DEFAULT);
Directory cfs = si.getCodec().compoundFormat().getCompoundReader(dir, si, IOContext.DEFAULT);
try {
- cfs.makeLock("foobar");
+ cfs.obtainLock("foobar");
fail("didn't get expected exception");
} catch (UnsupportedOperationException expected) {
// expected UOE
diff --git a/lucene/test-framework/src/java/org/apache/lucene/store/BaseLockFactoryTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/store/BaseLockFactoryTestCase.java
new file mode 100644
index 00000000000..edda9d5c75f
--- /dev/null
+++ b/lucene/test-framework/src/java/org/apache/lucene/store/BaseLockFactoryTestCase.java
@@ -0,0 +1,275 @@
+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.IOException;
+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;
+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;
+
+/** 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 {
+ Directory dir = getDirectory(createTempDir());
+
+ Lock l = dir.obtainLock("commit");
+ try {
+ dir.obtainLock("commit");
+ fail("succeeded in obtaining lock twice, didn't get exception");
+ } catch (LockObtainFailedException expected) {}
+ 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 {
+ Directory dir = getDirectory(createTempDir());
+
+ 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 {
+ Directory dir = getDirectory(createTempDir());
+
+ Lock l = dir.obtainLock("commit");
+ l.ensureValid(); // no exception
+ l.close();
+
+ dir.close();
+ }
+
+ /** Test ensureValid throws exception after close */
+ public void testInvalidAfterClose() throws IOException {
+ Directory dir = getDirectory(createTempDir());
+
+ Lock l = dir.obtainLock("commit");
+ l.close();
+
+ try {
+ l.ensureValid();
+ fail("didn't get exception");
+ } catch (AlreadyClosedException expected) {}
+
+ dir.close();
+ }
+
+ public void testObtainConcurrently() throws InterruptedException, IOException {
+ final Directory directory = getDirectory(createTempDir());
+ 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(10000);
+ 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 {
+ Directory dir = getDirectory(createTempDir());
+
+ // First create a 1 doc index:
+ IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())).setOpenMode(OpenMode.CREATE));
+ addDoc(w);
+ w.close();
+
+ WriterThread writer = new WriterThread(100, dir);
+ SearcherThread searcher = new SearcherThread(100, dir);
+ 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);
+
+ 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;
+ }
+ @Override
+ public void run() {
+ IndexWriter writer = null;
+ for(int i=0;i
- * Be careful if you turn this off: {@code MockDirectoryWrapper} might
- * no longer be able to detect if you forget to close an {@link IndexWriter},
- * and spit out horribly scary confusing exceptions instead of
- * simply telling you that.
- */
- public void setAssertLocks(boolean v) {
- this.wrapLocking = v;
- }
@Override
public synchronized void close() throws IOException {
@@ -994,62 +980,12 @@ public class MockDirectoryWrapper extends BaseDirectoryWrapper {
}
@Override
- public synchronized Lock makeLock(String name) {
+ public synchronized Lock obtainLock(String name) throws IOException {
maybeYield();
- if (wrapLocking) {
- return new AssertingLock(super.makeLock(name), name);
- } else {
- return super.makeLock(name);
- }
+ return super.obtainLock(name);
+ // TODO: consider mocking locks, but not all the time, can hide bugs
}
- private final class AssertingLock extends Lock {
- private final Lock delegateLock;
- private final String name;
- private boolean obtained = false;
-
- AssertingLock(Lock delegate, String name) {
- this.delegateLock = delegate;
- this.name = name;
- }
-
- @Override
- public boolean obtain() throws IOException {
- if (delegateLock.obtain()) {
- final RuntimeException exception = openLocks.putIfAbsent(name, new RuntimeException("lock \"" + name + "\" was not released: " + delegateLock));
- if (exception != null && delegateLock != NoLockFactory.SINGLETON_LOCK) {
- throw exception;
- }
- obtained = true;
- } else {
- obtained = false;
- }
-
- return obtained;
- }
-
- @Override
- public void close() throws IOException {
- if (obtained) {
- RuntimeException remove = openLocks.remove(name);
- // TODO: fix stupid tests like TestIndexWriter.testNoSegmentFile to not do this!
- assert remove != null || delegateLock == NoLockFactory.SINGLETON_LOCK;
- obtained = false;
- }
- delegateLock.close();
- }
-
- @Override
- public boolean isLocked() throws IOException {
- return delegateLock.isLocked();
- }
-
- @Override
- public String toString() {
- return "AssertingLock(" + delegateLock + ")";
- }
- }
-
/** Use this when throwing fake {@code IOException},
* e.g. from {@link MockDirectoryWrapper.Failure}. */
public static class FakeIOException extends IOException {
diff --git a/lucene/test-framework/src/java/org/apache/lucene/util/TestUtil.java b/lucene/test-framework/src/java/org/apache/lucene/util/TestUtil.java
index c789bd3783b..a66c6beb411 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/util/TestUtil.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/util/TestUtil.java
@@ -271,7 +271,7 @@ public final class TestUtil {
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
// TODO: actually use the dir's locking, unless test uses a special method?
// some tests e.g. exception tests become much more complicated if they have to close the writer
- try (CheckIndex checker = new CheckIndex(dir, NoLockFactory.INSTANCE.makeLock(dir, "bogus"))) {
+ try (CheckIndex checker = new CheckIndex(dir, NoLockFactory.INSTANCE.obtainLock(dir, "bogus"))) {
checker.setCrossCheckTermVectors(crossCheckTermVectors);
checker.setFailFast(failFast);
checker.setInfoStream(new PrintStream(bos, false, IOUtils.UTF_8), false);
diff --git a/lucene/test-framework/src/test/org/apache/lucene/store/TestMockDirectoryWrapper.java b/lucene/test-framework/src/test/org/apache/lucene/store/TestMockDirectoryWrapper.java
index 3602e01c829..fd30582fa5a 100644
--- a/lucene/test-framework/src/test/org/apache/lucene/store/TestMockDirectoryWrapper.java
+++ b/lucene/test-framework/src/test/org/apache/lucene/store/TestMockDirectoryWrapper.java
@@ -47,32 +47,6 @@ public class TestMockDirectoryWrapper extends BaseDirectoryTestCase {
super.testThreadSafety();
}
- public void testFailIfIndexWriterNotClosed() throws IOException {
- MockDirectoryWrapper dir = newMockDirectory();
- IndexWriter iw = new IndexWriter(dir, new IndexWriterConfig(null));
- try {
- dir.close();
- fail();
- } catch (Exception expected) {
- assertTrue(expected.getMessage().contains("there are still open locks"));
- } finally {
- IOUtils.closeWhileHandlingException(iw);
- }
- }
-
- public void testFailIfIndexWriterNotClosedChangeLockFactory() throws IOException {
- MockDirectoryWrapper dir = newMockDirectory(random(), new SingleInstanceLockFactory());
- IndexWriter iw = new IndexWriter(dir, new IndexWriterConfig(null));
- try {
- dir.close();
- fail();
- } catch (Exception expected) {
- assertTrue(expected.getMessage().contains("there are still open locks"));
- } finally {
- IOUtils.closeWhileHandlingException(iw);
- }
- }
-
public void testDiskFull() throws IOException {
// test writeBytes
MockDirectoryWrapper dir = newMockDirectory();
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 5ea39f9e832..986ab0b1257 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -72,6 +72,16 @@ Upgrading from Solr 5.2
* SolrJ's CollectionAdminRequest class is now marked as abstract. Use one of its concrete
sub-classes instead.
+* Solr no longer supports forcefully unlocking an index.
+ This is no longer supported by the underlying Lucene locking
+ framework. The setting in solrconfig.xml has no effect anymore.
+ Background: If you use native lock factory, unlocking should
+ not be needed, because the locks are cleared after process
+ shutdown automatically by the operating system. If you are
+ using simple lock factory (not recommended) or hdfs lock
+ factory, you may need to manually unlock by deleting the lock
+ file from filesystem / HDFS.
+
Detailed Change List
----------------------
@@ -127,6 +137,9 @@ Other Changes
* SOLR-7636: CLUSTERSTATUS API is executed at CollectionsHandler (noble)
+* LUCENE-6508: Remove ability to forcefully unlock an index.
+ This is no longer supported by the underlying Lucene locking
+ framework. (Uwe Schindler, Mike McCandless, Robert Muir)
================== 5.2.0 ==================
diff --git a/solr/contrib/morphlines-core/src/test-files/solr/collection1/conf/solrconfig.xml b/solr/contrib/morphlines-core/src/test-files/solr/collection1/conf/solrconfig.xml
index f1b03ebc92f..794e3d23cc4 100644
--- a/solr/contrib/morphlines-core/src/test-files/solr/collection1/conf/solrconfig.xml
+++ b/solr/contrib/morphlines-core/src/test-files/solr/collection1/conf/solrconfig.xml
@@ -220,19 +220,6 @@
-->
-
-
-
diff --git a/solr/contrib/morphlines-core/src/test-files/solr/minimr/conf/solrconfig.xml b/solr/contrib/morphlines-core/src/test-files/solr/minimr/conf/solrconfig.xml
index 9393c9c2cb6..22d97f73122 100644
--- a/solr/contrib/morphlines-core/src/test-files/solr/minimr/conf/solrconfig.xml
+++ b/solr/contrib/morphlines-core/src/test-files/solr/minimr/conf/solrconfig.xml
@@ -236,19 +236,6 @@
-->
${solr.lock.type:hdfs}
-
-
-
diff --git a/solr/contrib/morphlines-core/src/test-files/solr/mrunit/conf/solrconfig.xml b/solr/contrib/morphlines-core/src/test-files/solr/mrunit/conf/solrconfig.xml
index ed7d8a279c6..d08d47494b1 100644
--- a/solr/contrib/morphlines-core/src/test-files/solr/mrunit/conf/solrconfig.xml
+++ b/solr/contrib/morphlines-core/src/test-files/solr/mrunit/conf/solrconfig.xml
@@ -238,19 +238,6 @@
-->
${solr.lock.type:hdfs}
-
-
-
diff --git a/solr/contrib/morphlines-core/src/test-files/solr/solrcelltest/collection1/conf/solrconfig.xml b/solr/contrib/morphlines-core/src/test-files/solr/solrcelltest/collection1/conf/solrconfig.xml
index 43ce4337792..004bafe24e2 100644
--- a/solr/contrib/morphlines-core/src/test-files/solr/solrcelltest/collection1/conf/solrconfig.xml
+++ b/solr/contrib/morphlines-core/src/test-files/solr/solrcelltest/collection1/conf/solrconfig.xml
@@ -220,19 +220,6 @@
-->
-
-
-
diff --git a/solr/contrib/morphlines-core/src/test-files/solr/solrcloud/conf/solrconfig.xml b/solr/contrib/morphlines-core/src/test-files/solr/solrcloud/conf/solrconfig.xml
index f84e2d6d03f..bb3575373eb 100644
--- a/solr/contrib/morphlines-core/src/test-files/solr/solrcloud/conf/solrconfig.xml
+++ b/solr/contrib/morphlines-core/src/test-files/solr/solrcloud/conf/solrconfig.xml
@@ -239,19 +239,6 @@
-->
${solr.lock.type:hdfs}
-
-
-
diff --git a/solr/core/src/java/org/apache/solr/core/SolrConfig.java b/solr/core/src/java/org/apache/solr/core/SolrConfig.java
index 16d94ea0260..21d60ad810c 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrConfig.java
@@ -255,7 +255,6 @@ public class SolrConfig extends Config implements MapSerializable {
conf = new CacheConfig(FastLRUCache.class, args, null);
}
fieldValueCacheConfig = conf;
- unlockOnStartup = getBool(indexConfigPrefix + "/unlockOnStartup", false);
useColdSearcher = getBool("query/useColdSearcher", false);
dataDir = get("dataDir", null);
if (dataDir != null && dataDir.length() == 0) dataDir = null;
@@ -485,7 +484,6 @@ public class SolrConfig extends Config implements MapSerializable {
private Map> pluginStore = new LinkedHashMap<>();
public final int maxWarmingSearchers;
- public final boolean unlockOnStartup;
public final boolean useColdSearcher;
public final Version luceneMatchVersion;
protected String dataDir;
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index 5251a02707e..8f0cb70dc33 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -506,7 +506,6 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
synchronized (SolrCore.class) {
firstTime = dirs.add(getDirectoryFactory().normalize(indexDir));
}
- boolean removeLocks = solrConfig.unlockOnStartup;
initIndexReaderFactory();
@@ -516,20 +515,12 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
getSolrConfig().indexConfig.lockType);
try {
if (IndexWriter.isLocked(dir)) {
- if (removeLocks) {
- log.warn(
- logid
- + "WARNING: Solr index directory '{}' is locked. Unlocking...",
- indexDir);
- dir.makeLock(IndexWriter.WRITE_LOCK_NAME).close();
- } else {
- log.error(logid
- + "Solr index directory '{}' is locked. Throwing exception",
- indexDir);
- throw new LockObtainFailedException(
- "Index locked for write for core " + name);
- }
-
+ log.error(logid
+ + "Solr index directory '{}' is locked. Throwing exception.",
+ indexDir);
+ throw new LockObtainFailedException(
+ "Index locked for write for core '" + name +
+ "'. Solr now longer supports forceful unlocking via 'unlockOnStartup'. Please verify locks manually!");
}
} finally {
directoryFactory.release(dir);
diff --git a/solr/core/src/java/org/apache/solr/store/hdfs/HdfsLockFactory.java b/solr/core/src/java/org/apache/solr/store/hdfs/HdfsLockFactory.java
index cee924fdbe4..940c78cd116 100644
--- a/solr/core/src/java/org/apache/solr/store/hdfs/HdfsLockFactory.java
+++ b/solr/core/src/java/org/apache/solr/store/hdfs/HdfsLockFactory.java
@@ -42,105 +42,83 @@ public class HdfsLockFactory extends LockFactory {
private HdfsLockFactory() {}
@Override
- public Lock makeLock(Directory dir, String lockName) {
+ public Lock obtainLock(Directory dir, String lockName) throws IOException {
if (!(dir instanceof HdfsDirectory)) {
throw new UnsupportedOperationException("HdfsLockFactory can only be used with HdfsDirectory subclasses, got: " + dir);
}
final HdfsDirectory hdfsDir = (HdfsDirectory) dir;
- return new HdfsLock(hdfsDir.getHdfsDirPath(), lockName, hdfsDir.getConfiguration());
+ final Configuration conf = hdfsDir.getConfiguration();
+ final Path lockPath = hdfsDir.getHdfsDirPath();
+ final Path lockFile = new Path(lockPath, lockName);
+
+ FSDataOutputStream file = null;
+ final FileSystem fs = FileSystem.get(lockPath.toUri(), conf);
+ while (true) {
+ try {
+ if (!fs.exists(lockPath)) {
+ boolean success = fs.mkdirs(lockPath);
+ if (!success) {
+ throw new RuntimeException("Could not create directory: " + lockPath);
+ }
+ } else {
+ // just to check for safe mode
+ fs.mkdirs(lockPath);
+ }
+
+ file = fs.create(lockFile, false);
+ break;
+ } catch (FileAlreadyExistsException e) {
+ throw new LockObtainFailedException("Cannot obtain lock file: " + lockFile, e);
+ } catch (RemoteException e) {
+ if (e.getClassName().equals(
+ "org.apache.hadoop.hdfs.server.namenode.SafeModeException")) {
+ log.warn("The NameNode is in SafeMode - Solr will wait 5 seconds and try again.");
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e1) {
+ Thread.interrupted();
+ }
+ continue;
+ }
+ throw new LockObtainFailedException("Cannot obtain lock file: " + lockFile, e);
+ } catch (IOException e) {
+ throw new LockObtainFailedException("Cannot obtain lock file: " + lockFile, e);
+ } finally {
+ IOUtils.closeQuietly(file);
+ }
+ }
+
+ return new HdfsLock(fs, lockFile);
}
- static class HdfsLock extends Lock {
+ private static final class HdfsLock extends Lock {
- private final Path lockPath;
- private final String lockName;
- private final Configuration conf;
- private boolean obtained;
+ private final FileSystem fs;
+ private final Path lockFile;
+ private volatile boolean closed;
- public HdfsLock(Path lockPath, String lockName, Configuration conf) {
- this.lockPath = lockPath;
- this.lockName = lockName;
- this.conf = conf;
- }
-
- @Override
- public boolean obtain() throws IOException {
-
- if (obtained) {
- // Our instance is already locked:
- throw new LockObtainFailedException("this lock instance was already obtained");
- }
-
- FSDataOutputStream file = null;
- FileSystem fs = FileSystem.get(lockPath.toUri(), conf);
- try {
- while (true) {
- try {
- if (!fs.exists(lockPath)) {
- boolean success = fs.mkdirs(lockPath);
- if (!success) {
- throw new RuntimeException("Could not create directory: " + lockPath);
- }
- } else {
- // just to check for safe mode
- fs.mkdirs(lockPath);
- }
-
- file = fs.create(new Path(lockPath, lockName), false);
- break;
- } catch (FileAlreadyExistsException e) {
- return obtained = false;
- } catch (RemoteException e) {
- if (e.getClassName().equals(
- "org.apache.hadoop.hdfs.server.namenode.SafeModeException")) {
- log.warn("The NameNode is in SafeMode - Solr will wait 5 seconds and try again.");
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e1) {
- Thread.interrupted();
- }
- continue;
- }
- log.error("Error creating lock file", e);
- return obtained = false;
- } catch (IOException e) {
- log.error("Error creating lock file", e);
- return obtained = false;
- } finally {
- IOUtils.closeQuietly(file);
- }
- }
- } finally {
- IOUtils.closeQuietly(fs);
- }
- return obtained = true;
+ HdfsLock(FileSystem fs, Path lockFile) {
+ this.fs = fs;
+ this.lockFile = lockFile;
}
@Override
public void close() throws IOException {
- if (obtained) {
- FileSystem fs = FileSystem.get(lockPath.toUri(), conf);
- try {
- if (fs.exists(new Path(lockPath, lockName))
- && !fs.delete(new Path(lockPath, lockName), false)) throw new LockReleaseFailedException(
- "failed to delete " + new Path(lockPath, lockName));
- } finally {
- obtained = false;
- IOUtils.closeQuietly(fs);
- }
+ if (closed) {
+ return;
}
- }
-
- @Override
- public boolean isLocked() throws IOException {
- boolean isLocked = false;
- FileSystem fs = FileSystem.get(lockPath.toUri(), conf);
try {
- isLocked = fs.exists(new Path(lockPath, lockName));
+ if (fs.exists(lockFile) && !fs.delete(lockFile, false)) {
+ throw new LockReleaseFailedException("failed to delete: " + lockFile);
+ }
} finally {
IOUtils.closeQuietly(fs);
}
- return isLocked;
+ }
+
+ @Override
+ public void ensureValid() throws IOException {
+ // no idea how to implement this on HDFS
}
}
}
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-solrconfig-multiple-indexconfigs.xml b/solr/core/src/test-files/solr/collection1/conf/bad-solrconfig-multiple-indexconfigs.xml
index 61d507cc0a3..6c3f90c51b4 100644
--- a/solr/core/src/test-files/solr/collection1/conf/bad-solrconfig-multiple-indexconfigs.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-solrconfig-multiple-indexconfigs.xml
@@ -23,12 +23,10 @@
true
- false
${useCompoundFile:false}
- true
diff --git a/solr/core/src/test/org/apache/solr/store/hdfs/HdfsLockFactoryTest.java b/solr/core/src/test/org/apache/solr/store/hdfs/HdfsLockFactoryTest.java
index 096a7d90520..6e3a1b6f4ed 100644
--- a/solr/core/src/test/org/apache/solr/store/hdfs/HdfsLockFactoryTest.java
+++ b/solr/core/src/test/org/apache/solr/store/hdfs/HdfsLockFactoryTest.java
@@ -18,20 +18,16 @@ package org.apache.solr.store.hdfs;
*/
import java.io.IOException;
-import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.lucene.store.Lock;
import org.apache.lucene.store.LockObtainFailedException;
-import org.apache.lucene.util.IOUtils;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.cloud.hdfs.HdfsTestUtil;
import org.apache.solr.util.BadHdfsThreadsFilter;
-import org.junit.After;
import org.junit.AfterClass;
-import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -55,58 +51,32 @@ public class HdfsLockFactoryTest extends SolrTestCaseJ4 {
dfsCluster = null;
}
- @Before
- public void setUp() throws Exception {
- super.setUp();
- }
-
- @After
- public void tearDown() throws Exception {
- super.tearDown();
- }
-
@Test
public void testBasic() throws IOException {
String uri = HdfsTestUtil.getURI(dfsCluster);
Path lockPath = new Path(uri, "/basedir/lock");
Configuration conf = HdfsTestUtil.getClientConfiguration(dfsCluster);
HdfsDirectory dir = new HdfsDirectory(lockPath, conf);
- Lock lock = dir.makeLock("testlock");
- boolean success = lock.obtain();
- assertTrue("We could not get the lock when it should be available", success);
- Lock lock2 = dir.makeLock("testlock");
- success = lock2.obtain();
- assertFalse("We got the lock but it should be unavailble", success);
- IOUtils.close(lock, lock2);
- // now repeat after close()
- lock = dir.makeLock("testlock");
- success = lock.obtain();
- assertTrue("We could not get the lock when it should be available", success);
- lock2 = dir.makeLock("testlock");
- success = lock2.obtain();
- assertFalse("We got the lock but it should be unavailble", success);
- IOUtils.close(lock, lock2);
- dir.close();
- }
-
- public void testDoubleObtain() throws Exception {
- String uri = HdfsTestUtil.getURI(dfsCluster);
- Path lockPath = new Path(uri, "/basedir/lock");
- Configuration conf = HdfsTestUtil.getClientConfiguration(dfsCluster);
- HdfsDirectory dir = new HdfsDirectory(lockPath, conf);
- Lock lock = dir.makeLock("foo");
- assertTrue(lock.obtain());
- try {
- lock.obtain();
- fail("did not hit double-obtain failure");
- } catch (LockObtainFailedException lofe) {
- // expected
- }
- lock.close();
- lock = dir.makeLock("foo");
- assertTrue(lock.obtain());
- lock.close();
+ try (Lock lock = dir.obtainLock("testlock")) {
+ assert lock != null;
+ try (Lock lock2 = dir.obtainLock("testlock")) {
+ assert lock2 != null;
+ fail("Locking should fail");
+ } catch (LockObtainFailedException lofe) {
+ // pass
+ }
+ }
+ // now repeat after close()
+ try (Lock lock = dir.obtainLock("testlock")) {
+ assert lock != null;
+ try (Lock lock2 = dir.obtainLock("testlock")) {
+ assert lock2 != null;
+ fail("Locking should fail");
+ } catch (LockObtainFailedException lofe) {
+ // pass
+ }
+ }
dir.close();
}
}
diff --git a/solr/example/example-DIH/solr/db/conf/solrconfig.xml b/solr/example/example-DIH/solr/db/conf/solrconfig.xml
index 2b323756eca..0663dcfdf6e 100644
--- a/solr/example/example-DIH/solr/db/conf/solrconfig.xml
+++ b/solr/example/example-DIH/solr/db/conf/solrconfig.xml
@@ -263,19 +263,6 @@
-->
${solr.lock.type:native}
-
-
-
${solr.lock.type:native}
-
-
-
${solr.lock.type:native}
-
-
-
${solr.lock.type:native}
-
-
-
${solr.lock.type:native}
-
-
-
${solr.lock.type:native}
-
-
-
${solr.lock.type:native}
-
-
-
${solr.lock.type:native}
-
-
-