blob: 101ff5e234f28a4371675a59b7a94dfb6c58c696 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.store;
import java.io.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>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
* can easily corrupt your index.</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 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 SimpleFSLockFactory extends FSLockFactory {
/**
* Singleton instance
*/
public static final SimpleFSLockFactory INSTANCE = new SimpleFSLockFactory();
private SimpleFSLockFactory() {}
@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);
// 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 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 void ensureValid() throws IOException {
if (closed) {
throw new AlreadyClosedException("Lock instance already released: " + this);
}
// try to validate the backing file name, that it still exists,
// and has the same creation time as when we obtained the lock.
// if it differs, someone deleted our lock file (and we are ineffective)
FileTime ctime = Files.readAttributes(path, BasicFileAttributes.class).creationTime();
if (!creationTime.equals(ctime)) {
throw new AlreadyClosedException("Underlying file changed by an external force at " + ctime + ", (lock=" + this + ")");
}
}
@Override
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.delete(path);
} catch (Throwable exc) {
throw new LockReleaseFailedException("Unable to remove lock file. Manual intervention is recommended", exc);
}
} finally {
closed = true;
}
}
@Override
public String toString() {
return "SimpleFSLock(path=" + path + ",creationTime=" + creationTime + ")";
}
}
}