content: 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.nio.channels.FileChannel;
  import java.nio.channels.FileLock;
  import java.io.File;
  import java.io.RandomAccessFile;
  import java.io.IOException;
  import java.util.HashSet;
  
  /**
   * <p>Implements {@link LockFactory} using native OS file
   * locks.  Note that because this LockFactory relies on
   * java.nio.* APIs for locking, any problems with those APIs
   * will cause locking to fail.  Specifically, on certain NFS
   * environments the java.nio.* locks will fail (the lock can
   * incorrectly be double acquired) whereas {@link
   * SimpleFSLockFactory} worked perfectly in those same
   * environments.  For NFS based access to an index, it's
   * recommended that you try {@link SimpleFSLockFactory}
   * first and work around the one limitation that a lock file
   * could be left when the JVM exits abnormally.</p>
   *
   * <p>The primary benefit of {@link NativeFSLockFactory} is
   * that lock files will be properly removed (by the OS) if
   * the JVM has an abnormal exit.</p>
   * 
   * <p>Note that, unlike {@link SimpleFSLockFactory}, the existence of
   * leftover lock files in the filesystem on exiting the JVM
   * is fine because the OS will free the locks held against
   * these files even though the files still remain.</p>
   *
   * <p>If you suspect that this or any other LockFactory is
   * not working properly in your environment, you can easily
   * test it by using {@link VerifyingLockFactory}, {@link
   * LockVerifyServer} and {@link LockStressTest}.</p>
   *
   * @see LockFactory
   */
  
  public class NativeFSLockFactory extends FSLockFactory {
  
    /**
     * Create a NativeFSLockFactory instance, with null (unset)
     * lock directory. When you pass this factory to a {@link FSDirectory}
     * subclass, the lock directory is automatically set to the
     * directory itself. Be sure to create one instance for each directory
     * your create!
     */
    public NativeFSLockFactory() throws IOException {
      this((File) null);
    }
  
    /**
     * Create a NativeFSLockFactory instance, storing lock
     * files into the specified lockDirName:
     *
     * @param lockDirName where lock files are created.
     */
    public NativeFSLockFactory(String lockDirName) throws IOException {
      this(new File(lockDirName));
    }
  
    /**
     * Create a NativeFSLockFactory instance, storing lock
     * files into the specified lockDir:
     * 
     * @param lockDir where lock files are created.
     */
    public NativeFSLockFactory(File lockDir) throws IOException {
      setLockDir(lockDir);
    }
  
    @Override
    public synchronized Lock makeLock(String lockName) {
      if (lockPrefix != null)
        lockName = lockPrefix + "-" + lockName;
      return new NativeFSLock(lockDir, lockName);
    }
  
    @Override
    public void clearLock(String lockName) throws IOException {
      // Note that this isn't strictly required anymore
      // because the existence of these files does not mean
      // they are locked, but, still do this in case people
      // really want to see the files go away:
      if (lockDir.exists()) {
        
        // Try to release the lock first - if it's held by another process, this
        // method should not silently fail.
        // NOTE: makeLock fixes the lock name by prefixing it w/ lockPrefix.
        // Therefore it should be called before the code block next which prefixes
        // the given name.
        makeLock(lockName).release();
  
        if (lockPrefix != null) {
          lockName = lockPrefix + "-" + lockName;
        }
        
        // As mentioned above, we don't care if the deletion of the file failed.
        new File(lockDir, lockName).delete();
      }
    }
  }
  
  class NativeFSLock extends Lock {
  
    private RandomAccessFile f;
    private FileChannel channel;
    private FileLock lock;
    private File path;
    private File lockDir;
  
    /*
     * The javadocs for FileChannel state that you should have
     * a single instance of a FileChannel (per JVM) for all
     * locking against a given file (locks are tracked per 
     * FileChannel instance in Java 1.4/1.5). Even using the same 
     * FileChannel instance is not completely thread-safe with Java 
     * 1.4/1.5 though. To work around this, we have a single (static) 
     * HashSet that contains the file paths of all currently 
     * locked locks.  This protects against possible cases 
     * where different Directory instances in one JVM (each 
     * with their own NativeFSLockFactory instance) have set 
     * the same lock dir and lock prefix. However, this will not 
     * work when LockFactorys are created by different 
     * classloaders (eg multiple webapps). 
     * 
     * TODO: Java 1.6 tracks system wide locks in a thread safe manner 
     * (same FileChannel instance or not), so we may want to 
     * change this when Lucene moves to Java 1.6.
     */
    private static HashSet<String> LOCK_HELD = new HashSet<String>();
  
    public NativeFSLock(File lockDir, String lockFileName) {
      this.lockDir = lockDir;
      path = new File(lockDir, lockFileName);
    }
  
    private synchronized boolean lockExists() {
      return lock != null;
    }
  
    @Override
    public synchronized boolean obtain() throws IOException {
  
      if (lockExists()) {
        // Our instance is already locked:
        return false;
      }
  
      // Ensure that lockDir exists and is a directory.
      if (!lockDir.exists()) {
        if (!lockDir.mkdirs())
          throw new IOException("Cannot create directory: " +
                                lockDir.getAbsolutePath());
      } else if (!lockDir.isDirectory()) {
        throw new IOException("Found regular file where directory expected: " + 
                              lockDir.getAbsolutePath());
      }
  
      String canonicalPath = path.getCanonicalPath();
  
      boolean markedHeld = false;
  
      try {
  
        // Make sure nobody else in-process has this lock held
        // already, and, mark it held if not:
  
        synchronized(LOCK_HELD) {
          if (LOCK_HELD.contains(canonicalPath)) {
            // Someone else in this JVM already has the lock:
            return false;
          } else {
            // This "reserves" the fact that we are the one
            // thread trying to obtain this lock, so we own
            // the only instance of a channel against this
            // file:
            LOCK_HELD.add(canonicalPath);
            markedHeld = true;
          }
        }
  
        try {
          f = new RandomAccessFile(path, "rw");
        } catch (IOException e) {
          // On Windows, we can get intermittent "Access
          // Denied" here.  So, we treat this as failure to
          // acquire the lock, but, store the reason in case
          // there is in fact a real error case.
          failureReason = e;
          f = null;
        }
  
        if (f != null) {
          try {
            channel = f.getChannel();
            try {
              lock = channel.tryLock();
            } catch (IOException 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 (lock == null) {
                try {
                  channel.close();
                } finally {
                  channel = null;
                }
              }
            }
          } finally {
            if (channel == null) {
              try {
                f.close();
              } finally {
                f = null;
              }
            }
          }
        }
  
      } finally {
        if (markedHeld && !lockExists()) {
          synchronized(LOCK_HELD) {
            if (LOCK_HELD.contains(canonicalPath)) {
              LOCK_HELD.remove(canonicalPath);
            }
          }
        }
      }
      return lockExists();
    }
  
    @Override
    public synchronized void release() throws IOException {
      if (lockExists()) {
        try {
          lock.release();
        } finally {
          lock = null;
          try {
            channel.close();
          } finally {
            channel = null;
            try {
              f.close();
            } finally {
              f = null;
              synchronized(LOCK_HELD) {
                LOCK_HELD.remove(path.getCanonicalPath());
              }
            }
          }
        }
        // LUCENE-2421: we don't care anymore if the file cannot be deleted
        // because it's held up by another process (e.g. AntiVirus). NativeFSLock
        // does not depend on the existence/absence of the lock file
        path.delete();
      } else {
        // if we don't hold the lock, and somebody still called release(), for
        // example as a result of calling IndexWriter.unlock(), we should attempt
        // to obtain the lock and release it. If the obtain fails, it means the
        // lock cannot be released, and we should throw a proper exception rather
        // than silently failing/not doing anything.
        boolean obtained = false;
        try {
          if (!(obtained = obtain())) {
            throw new LockReleaseFailedException(
                "Cannot forcefully unlock a NativeFSLock which is held by another indexer component: "
                    + path);
          }
        } finally {
          if (obtained) {
            release();
          }
        }
      }
    }
  
    @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 (lockExists()) return true;
      
      // Look if lock file is present; if not, there can definitely be no lock!
      if (!path.exists()) return false;
      
      // Try to obtain and release (if was locked) the lock
      try {
        boolean obtained = obtain();
        if (obtained) release();
        return !obtained;
      } catch (IOException ioe) {
        return false;
      }    
    }
  
    @Override
    public String toString() {
      return "NativeFSLock@" + path;
    }
  }
  