You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by rm...@apache.org on 2015/06/04 20:20:44 UTC

svn commit: r1683606 [1/2] - in /lucene/dev/trunk: lucene/ lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/ lucene/core/src/java/org/apache/lucene/codecs/lucene50/ lucene/core/src/java/org/apache/lucene/index/ lucene/core/src/java/org/apache...

Author: rmuir
Date: Thu Jun  4 18:20:44 2015
New Revision: 1683606

URL: http://svn.apache.org/r1683606
Log:
LUCENE-6508: Simplify directory/lock API

Added:
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/SleepingLockWrapper.java   (with props)
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockValidatingDirectoryWrapper.java   (with props)
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestSleepingLockWrapper.java   (with props)
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/store/TestNativeFSLockFactory.java   (with props)
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/store/TestSimpleFSLockFactory.java   (with props)
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/store/TestSingleInstanceLockFactory.java   (with props)
    lucene/dev/trunk/lucene/test-framework/src/java/org/apache/lucene/store/BaseLockFactoryTestCase.java   (with props)
Removed:
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/store/TestLock.java
Modified:
    lucene/dev/trunk/lucene/CHANGES.txt
    lucene/dev/trunk/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextCompoundFormat.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/codecs/lucene50/Lucene50CompoundReader.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexFileDeleter.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexWriterConfig.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/BaseDirectory.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/Directory.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FSLockFactory.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FileSwitchDirectory.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FilterDirectory.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/Lock.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockFactory.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockObtainFailedException.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/NativeFSLockFactory.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/NoLockFactory.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/SimpleFSLockFactory.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/SingleInstanceLockFactory.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/store/TestLockFactory.java
    lucene/dev/trunk/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/directory/DirectoryTaxonomyWriter.java
    lucene/dev/trunk/lucene/test-framework/src/java/org/apache/lucene/index/BaseCompoundFormatTestCase.java
    lucene/dev/trunk/lucene/test-framework/src/java/org/apache/lucene/store/MockDirectoryWrapper.java
    lucene/dev/trunk/lucene/test-framework/src/java/org/apache/lucene/util/TestUtil.java
    lucene/dev/trunk/lucene/test-framework/src/test/org/apache/lucene/store/TestMockDirectoryWrapper.java
    lucene/dev/trunk/solr/CHANGES.txt
    lucene/dev/trunk/solr/contrib/morphlines-core/src/test-files/solr/collection1/conf/solrconfig.xml
    lucene/dev/trunk/solr/contrib/morphlines-core/src/test-files/solr/minimr/conf/solrconfig.xml
    lucene/dev/trunk/solr/contrib/morphlines-core/src/test-files/solr/mrunit/conf/solrconfig.xml
    lucene/dev/trunk/solr/contrib/morphlines-core/src/test-files/solr/solrcelltest/collection1/conf/solrconfig.xml
    lucene/dev/trunk/solr/contrib/morphlines-core/src/test-files/solr/solrcloud/conf/solrconfig.xml
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/core/SolrConfig.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/core/SolrCore.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/store/hdfs/HdfsLockFactory.java
    lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/bad-solrconfig-multiple-indexconfigs.xml
    lucene/dev/trunk/solr/core/src/test/org/apache/solr/store/hdfs/HdfsLockFactoryTest.java
    lucene/dev/trunk/solr/example/example-DIH/solr/db/conf/solrconfig.xml
    lucene/dev/trunk/solr/example/example-DIH/solr/mail/conf/solrconfig.xml
    lucene/dev/trunk/solr/example/example-DIH/solr/rss/conf/solrconfig.xml
    lucene/dev/trunk/solr/example/example-DIH/solr/solr/conf/solrconfig.xml
    lucene/dev/trunk/solr/example/example-DIH/solr/tika/conf/solrconfig.xml
    lucene/dev/trunk/solr/example/files/conf/solrconfig.xml
    lucene/dev/trunk/solr/server/solr/configsets/data_driven_schema_configs/conf/solrconfig.xml
    lucene/dev/trunk/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml

Modified: lucene/dev/trunk/lucene/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/CHANGES.txt?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/CHANGES.txt (original)
+++ lucene/dev/trunk/lucene/CHANGES.txt Thu Jun  4 18:20:44 2015
@@ -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

Modified: lucene/dev/trunk/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextCompoundFormat.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextCompoundFormat.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextCompoundFormat.java (original)
+++ lucene/dev/trunk/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextCompoundFormat.java Thu Jun  4 18:20:44 2015
@@ -151,7 +151,7 @@ public class SimpleTextCompoundFormat ex
       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(); }
     };
   }
 

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/codecs/lucene50/Lucene50CompoundReader.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/codecs/lucene50/Lucene50CompoundReader.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/codecs/lucene50/Lucene50CompoundReader.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/codecs/lucene50/Lucene50CompoundReader.java Thu Jun  4 18:20:44 2015
@@ -179,7 +179,7 @@ final class Lucene50CompoundReader exten
   }
   
   @Override
-  public Lock makeLock(String name) {
+  public Lock obtainLock(String name) {
     throw new UnsupportedOperationException();
   }
 

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java Thu Jun  4 18:20:44 2015
@@ -47,7 +47,6 @@ import org.apache.lucene.store.FSDirecto
 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 Close
 
   /** 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 Close
     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() {

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/DocumentsWriter.java Thu Jun  4 18:20:44 2015
@@ -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 C
   private final Queue<Event> 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 C
     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);
     }

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java Thu Jun  4 18:20:44 2015
@@ -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;

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexFileDeleter.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexFileDeleter.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexFileDeleter.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexFileDeleter.java Thu Jun  4 18:20:44 2015
@@ -102,8 +102,9 @@ final class IndexFileDeleter implements
   private List<CommitPoint> 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
    * 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
     }
 
     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
             }
             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
               }
             }
             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
       // 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
 
     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
     Collection<String> files;
     String segmentsFileName;
     boolean deleted;
-    Directory directory;
+    Directory directoryOrig;
     Collection<CommitPoint> commitsToDelete;
     long generation;
     final Map<String,String> userData;
     private final int segmentCount;
 
-    public CommitPoint(Collection<CommitPoint> commitsToDelete, Directory directory, SegmentInfos segmentInfos) throws IOException {
-      this.directory = directory;
+    public CommitPoint(Collection<CommitPoint> 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
 
     @Override
     public Directory getDirectory() {
-      return directory;
+      return directoryOrig;
     }
 
     @Override

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java Thu Jun  4 18:20:44 2015
@@ -60,6 +60,7 @@ import org.apache.lucene.store.LockObtai
 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;
 
   <p>Opening an <code>IndexWriter</code> creates a lock file for the directory in use. Trying to open
   another <code>IndexWriter</code> 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.</p>
+  {@link LockObtainFailedException}.</p>
   
   <a name="deletionPolicy"></a>
   <p>Expert: <code>IndexWriter</code> allows an optional
@@ -254,8 +253,9 @@ public class IndexWriter implements Clos
   // 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 Clos
       // 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 Clos
   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();
+    
+    // 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);
 
-    writeLock = directory.makeLock(WRITE_LOCK_NAME);
+      // 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();
 
-    if (!writeLock.obtain(config.getWriteLockTimeout())) // obtain write lock
-      throw new LockObtainFailedException("Index locked for write: " + writeLock);
+      bufferedUpdatesStream = new BufferedUpdatesStream(infoStream);
+      poolReaders = config.getReaderPooling();
 
-    boolean success = false;
-    try {
       OpenMode mode = config.getOpenMode();
       boolean create;
       if (mode == OpenMode.CREATE) {
@@ -822,7 +830,7 @@ public class IndexWriter implements Clos
 
         // 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 Clos
           // 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 Clos
       // 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 Clos
   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 Clos
 
   /** 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 Clos
     for(int i=0;i<dirs.length;i++) {
       if (dups.contains(dirs[i]))
         throw new IllegalArgumentException("Directory " + dirs[i] + " appears more than once");
-      if (dirs[i] == directory)
+      if (dirs[i] == directoryOrig)
         throw new IllegalArgumentException("Cannot add directory to itself");
       dups.add(dirs[i]);
     }
@@ -2288,13 +2297,13 @@ public class IndexWriter implements Clos
     for(int i=0;i<dirs.length;i++) {
       boolean success = false;
       try {
-        Lock lock = dirs[i].makeLock(WRITE_LOCK_NAME);
+        Lock lock = dirs[i].obtainLock(WRITE_LOCK_NAME);
         locks.add(lock);
-        lock.obtain(config.getWriteLockTimeout());
         success = true;
       } finally {
         if (success == false) {
           // Release all previously acquired locks:
+          // TODO: addSuppressed? it could be many...
           IOUtils.closeWhileHandlingException(locks);
         }
       }
@@ -2334,8 +2343,6 @@ public class IndexWriter implements Clos
    *
    * @throws CorruptIndexException if the index is corrupt
    * @throws IOException if there is a low-level IO error
-   * @throws LockObtainFailedException if we were unable to
-   *   acquire the write lock in at least one directory
    * @throws IllegalArgumentException if addIndexes would cause
    *   the index to exceed {@link #MAX_DOCS}
    */
@@ -2496,7 +2503,7 @@ public class IndexWriter implements Clos
       // abortable so that IW.close(false) is able to stop it
       TrackingDirectoryWrapper trackingDir = new TrackingDirectoryWrapper(directory);
 
-      SegmentInfo info = new SegmentInfo(directory, Version.LATEST, mergedName, -1,
+      SegmentInfo info = new SegmentInfo(directoryOrig, Version.LATEST, mergedName, -1,
                                          false, codec, Collections.emptyMap(), StringHelper.randomId(), new HashMap<>());
 
       SegmentMerger merger = new SegmentMerger(Arrays.asList(readers), info, infoStream, trackingDir,
@@ -2600,7 +2607,7 @@ public class IndexWriter implements Clos
     
     //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 Clos
   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 Clos
         }
         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 Clos
     // 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<String,String> details = new HashMap<>();
     details.put("mergeMaxNumSegments", "" + merge.maxNumSegments);
     details.put("mergeFactor", Integer.toString(merge.segments.size()));
@@ -4365,9 +4372,17 @@ public class IndexWriter implements Clos
    * 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

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexWriterConfig.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexWriterConfig.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexWriterConfig.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/IndexWriterConfig.java Thu Jun  4 18:20:44 2015
@@ -265,7 +265,8 @@ public final class IndexWriterConfig ext
   /**
    * 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.
    *
    * <p>Only takes effect when IndexWriter is first created. */
   public IndexWriterConfig setWriteLockTimeout(long writeLockTimeout) {

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java Thu Jun  4 18:20:44 2015
@@ -441,7 +441,6 @@ public final class SegmentInfos implemen
           segnOutput.writeInt(e.getKey());
           segnOutput.writeSetOfStrings(e.getValue());
         }
-        assert si.dir == directory;
       }
       segnOutput.writeMapOfStrings(userData);
       CodecUtil.writeFooter(segnOutput);

Added: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/SleepingLockWrapper.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/SleepingLockWrapper.java?rev=1683606&view=auto
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/SleepingLockWrapper.java (added)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/index/SleepingLockWrapper.java Thu Jun  4 18:20:44 2015
@@ -0,0 +1,113 @@
+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 org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FilterDirectory;
+import org.apache.lucene.store.Lock;
+import org.apache.lucene.store.LockObtainFailedException;
+import org.apache.lucene.util.ThreadInterruptedException;
+
+/** 
+ * Directory that wraps another, and that sleeps and retries
+ * if obtaining the lock fails.
+ * <p>
+ * 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 + ")";
+  }
+}

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/BaseDirectory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/BaseDirectory.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/BaseDirectory.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/BaseDirectory.java Thu Jun  4 18:20:44 2015
@@ -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 exte
   }
 
   @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

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/Directory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/Directory.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/Directory.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/Directory.java Thu Jun  4 18:20:44 2015
@@ -50,8 +50,7 @@ public abstract class Directory implemen
   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 implemen
     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

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FSLockFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FSLockFactory.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FSLockFactory.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FSLockFactory.java Thu Jun  4 18:20:44 2015
@@ -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 exte
   }
 
   @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;
 
 }

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FileSwitchDirectory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FileSwitchDirectory.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FileSwitchDirectory.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FileSwitchDirectory.java Thu Jun  4 18:20:44 2015
@@ -71,8 +71,8 @@ public class FileSwitchDirectory extends
   }
   
   @Override
-  public Lock makeLock(String name) {
-    return getDirectory(name).makeLock(name);
+  public Lock obtainLock(String name) throws IOException {
+    return getDirectory(name).obtainLock(name);
   }
 
   @Override

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FilterDirectory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FilterDirectory.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FilterDirectory.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/FilterDirectory.java Thu Jun  4 18:20:44 2015
@@ -90,8 +90,8 @@ public class FilterDirectory extends Dir
   }
 
   @Override
-  public Lock makeLock(String name) {
-    return in.makeLock(name);
+  public Lock obtainLock(String name) throws IOException {
+    return in.obtainLock(name);
   }
 
   @Override

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/Lock.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/Lock.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/Lock.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/Lock.java Thu Jun  4 18:20:44 2015
@@ -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.
  * <p>Typical use might look like:<pre class="prettyprint">
- * new Lock.With(directory.makeLock("my.lock")) {
- *     public Object doBody() {
- *       <i>... code to execute while locked ...</i>
- *     }
- *   }.run();
+ *   try (final Lock lock = directory.obtainLock("my.lock")) {
+ *     // ... code to execute while locked ...
+ *   }
  * </pre>
  *
- * @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
-   */
-  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.
+  /** 
+   * Releases exclusive access.
+   * <p>
+   * 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.
+   * <p>
+   * {@inheritDoc} 
+   * @throws LockReleaseFailedException optional specific exception) if 
+   *         the lock could not be properly released.
    */
-  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 <i>lock</i> 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;
 }

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockFactory.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockFactory.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockFactory.java Thu Jun  4 18:20:44 2015
@@ -17,6 +17,7 @@ package org.apache.lucene.store;
  * limitations under the License.
  */
 
+import java.io.IOException;
 
 /**
  * <p>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;
 
 }

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockObtainFailedException.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockObtainFailedException.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockObtainFailedException.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockObtainFailedException.java Thu Jun  4 18:20:44 2015
@@ -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) {

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java Thu Jun  4 18:20:44 2015
@@ -38,10 +38,12 @@ import org.apache.lucene.util.SuppressFo
  */ 
 
 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.");
         }

Added: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockValidatingDirectoryWrapper.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockValidatingDirectoryWrapper.java?rev=1683606&view=auto
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockValidatingDirectoryWrapper.java (added)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/LockValidatingDirectoryWrapper.java Thu Jun  4 18:20:44 2015
@@ -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<String> names) throws IOException {
+    writeLock.ensureValid();
+    in.sync(names);
+  }
+}

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/NativeFSLockFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/NativeFSLockFactory.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/NativeFSLockFactory.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/NativeFSLockFactory.java Thu Jun  4 18:20:44 2015
@@ -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 e
    */
   public static final NativeFSLockFactory INSTANCE = new NativeFSLockFactory();
 
+  private static final Set<String> LOCK_HELD = Collections.synchronizedSet(new HashSet<String>());
+
   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);
+    }
   }
   
-  static final class NativeFSLock extends Lock {
-
-    private final Path path;
-    private final Path lockDir;
-    private static final Set<String> LOCK_HELD = Collections.synchronizedSet(new HashSet<String>());
-
-    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);
+  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);
     }
+  }
 
-    @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");
-      }
+  // TODO: kind of bogus we even pass channel:
+  // FileLock has an accessor, but mockfs doesnt yet mock the locks, too scary atm.
 
-      // 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.
-      }
-      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 
-          }
-        }
-      }
-      return obtained;
+  static final class NativeFSLock extends Lock {
+    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 void close() throws IOException {
-      if (channel != null) {
-        try {
-          IOUtils.close(channel);
-        } finally {
-          channel = null;
-          clearLockHeld(realPath); // clear LOCK_HELD last 
-        }
+    public void ensureValid() throws IOException {
+      if (closed) {
+        throw new AlreadyClosedException("Lock instance already released: " + this);
+      }
+      // 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);
+      }
+      // 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 + ")");
       }
     }
 
-    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;
-      }
-      
-      // Look if lock file is definitely not present; if not, there can definitely be no lock!
-      if (Files.notExists(path)) { 
-        return false;
+    public synchronized void close() throws IOException {
+      if (closed) {
+        return;
+      }
+      // 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);
       }
-      
-      // 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 + ")"; 
     }
   }
-
 }

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/NoLockFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/NoLockFactory.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/NoLockFactory.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/NoLockFactory.java Thu Jun  4 18:20:44 2015
@@ -37,23 +37,17 @@ public final class NoLockFactory extends
   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
       return "NoLock";
     }
   }
-
 }

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/SimpleFSLockFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/SimpleFSLockFactory.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/SimpleFSLockFactory.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/SimpleFSLockFactory.java Thu Jun  4 18:20:44 2015
@@ -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;
 
 /**
  * <p>Implements {@link LockFactory} using {@link
  * Files#createFile}.</p>
  *
- * <p><b>NOTE:</b> the {@linkplain File#createNewFile() javadocs
- * for <code>File.createNewFile()</code>} contain a vague
- * yet spooky warning about not using the API for file
- * locking.  This warning was added due to <a target="_top"
- * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4676183">this
- * bug</a>, 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.</p>
-
- * <p>When this happens, a {@link LockObtainFailedException}
- * is hit when trying to create a writer, in which case you
+ * <p>The main downside with using this API for locking is 
+ * that the Lucene write lock may not be released when 
+ * the JVM exits abnormally.</p>
+ *
+ * <p>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 e
   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 {
-
-    Path lockFile;
-    Path lockDir;
-    boolean obtained = false;
-
-    public SimpleFSLock(Path lockDir, String lockFileName) {
-      this.lockDir = lockDir;
-      lockFile = lockDir.resolve(lockFileName);
+  static final class SimpleFSLock extends Lock {
+    private final Path path;
+    private final FileTime creationTime;
+    private volatile boolean closed;
+
+    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 {
-        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;
+      // 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 LockReleaseFailedException {
-      // TODO: wierd that clearLock() throws the raw IOException...
-      if (obtained) {
+    public synchronized void close() throws IOException {
+      if (closed) {
+        return;
+      }
+      try {
+        // 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 {
+          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.deleteIfExists(lockFile);
-        } catch (Throwable cause) {
-          throw new LockReleaseFailedException("failed to delete " + lockFile, cause);
-        } finally {
-          obtained = false;
+          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 + ")";
     }
   }
-
 }

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/SingleInstanceLockFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/SingleInstanceLockFactory.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/SingleInstanceLockFactory.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/SingleInstanceLockFactory.java Thu Jun  4 18:20:44 2015
@@ -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<String> locks = new HashSet<>();
+  final HashSet<String> 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<String> locks;
-    private boolean obtained = false;
+    private volatile boolean closed;
 
-    public SingleInstanceLock(HashSet<String> 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;
       }
     }
 

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java Thu Jun  4 18:20:44 2015
@@ -43,10 +43,23 @@ public final class VerifyingLockFactory
 
   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
         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
   }
 
   @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));
   }
 }

Modified: lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java (original)
+++ lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java Thu Jun  4 18:20:44 2015
@@ -1268,7 +1268,6 @@ public class TestAddIndexes extends Luce
     Directory dest = newDirectory();
 
     IndexWriterConfig iwc = newIndexWriterConfig(new MockAnalyzer(random()));
-    iwc.setWriteLockTimeout(1);
     RandomIndexWriter w2 = new RandomIndexWriter(random(), dest, iwc);
 
     try {

Modified: lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java (original)
+++ lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java Thu Jun  4 18:20:44 2015
@@ -96,14 +96,7 @@ public class TestIndexWriter extends Luc
         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 Luc
     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

Modified: lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java?rev=1683606&r1=1683605&r2=1683606&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java (original)
+++ lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java Thu Jun  4 18:20:44 2015
@@ -2199,7 +2199,7 @@ public class TestIndexWriterExceptions e
     
     // 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 e
       
       // 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());

Added: lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestSleepingLockWrapper.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestSleepingLockWrapper.java?rev=1683606&view=auto
==============================================================================
--- lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestSleepingLockWrapper.java (added)
+++ lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/index/TestSleepingLockWrapper.java Thu Jun  4 18:20:44 2015
@@ -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
+}