org.apache.lucene.store.NativeFSLockFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.lucene.store.NativeFSLockFactory.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.store;

import java.nio.channels.FileChannel;
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;
import java.util.Set;

import org.apache.lucene.util.IOUtils;

/**
 * <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 locks (not the lock file itself) 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 is fine because the OS
 * will free the locks held against these files even though the
 * files still remain. Lucene will never actively remove the lock
 * files, so although you see them, the index may not be locked.</p>
 *
 * <p>Special care needs to be taken if you change the locking
 * implementation: First be certain that no writer is in fact
 * writing to the index otherwise you can easily corrupt
 * your index. Be sure to do the LockFactory change on all Lucene
 * instances and clean up all leftover lock files before starting
 * the new configuration for the first time. Different implementations
 * can not work together!</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>
 * 
 * <p>This is a singleton, you have to use {@link #INSTANCE}.
 *
 * @see LockFactory
 */

public final class NativeFSLockFactory extends FSLockFactory {

    /**
     * Singleton instance
     */
    public static final NativeFSLockFactory INSTANCE = new NativeFSLockFactory();

    private static final Set<String> LOCK_HELD = Collections.synchronizedSet(new HashSet<String>());

    private NativeFSLockFactory() {
    }

    @Override
    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);

        IOException creationException = null;
        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.
            creationException = ignore;
        }

        // fails if the lock file does not exist
        final Path realPath;
        try {
            realPath = lockFile.toRealPath();
        } catch (IOException e) {
            // if we couldn't resolve the lock file, it might be because we couldn't create it.
            // so append any exception from createFile as a suppressed exception, in case its useful
            if (creationException != null) {
                e.addSuppressed(creationException);
            }
            throw e;
        }

        // used as a best-effort check, to see if the underlying file has changed
        final FileTime creationTime = Files.readAttributes(realPath, BasicFileAttributes.class).creationTime();

        if (LOCK_HELD.add(realPath.toString())) {
            FileChannel channel = null;
            FileLock lock = null;
            try {
                channel = FileChannel.open(realPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
                lock = channel.tryLock();
                if (lock != null) {
                    return new NativeFSLock(lock, channel, realPath, creationTime);
                } else {
                    throw new LockObtainFailedException("Lock held by another program: " + realPath);
                }
            } finally {
                if (lock == null) { // not successful - clear up and move out
                    IOUtils.closeWhileHandlingException(channel); // TODO: addSuppressed
                    clearLockHeld(realPath); // clear LOCK_HELD last 
                }
            }
        } else {
            throw new LockObtainFailedException("Lock held by this virtual machine: " + realPath);
        }
    }

    private static final void clearLockHeld(Path path) throws IOException {
        boolean remove = LOCK_HELD.remove(path.toString());
        if (remove == false) {
            throw new AlreadyClosedException("Lock path was cleared but never marked as held: " + path);
        }
    }

    // TODO: kind of bogus we even pass channel:
    // FileLock has an accessor, but mockfs doesnt yet mock the locks, too scary atm.

    static final class NativeFSLock extends Lock {
        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 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 " + ctime + ", (lock=" + this + ")");
            }
        }

        @Override
        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);
            }
        }

        @Override
        public String toString() {
            return "NativeFSLock(path=" + path + ",impl=" + lock + ",creationTime=" + creationTime + ")";
        }
    }
}