LUCENE-7959: Improve NativeFSLockFactory's NoSuchFileException

This commit is contained in:
Robert Muir 2017-09-09 02:00:47 -04:00
parent 8452edb130
commit 0283d3e28a
3 changed files with 59 additions and 1 deletions

View File

@ -57,6 +57,10 @@ Bug Fixes
* LUCENE-7891: Lucene's taxonomy facets now uses a non-buggy LRU cache * LUCENE-7891: Lucene's taxonomy facets now uses a non-buggy LRU cache
by default. (Jan-Willem van den Broek via Mike McCandless) by default. (Jan-Willem van den Broek via Mike McCandless)
* LUCENE-7959: Improve NativeFSLockFactory's exception message if it cannot create
write.lock for an empty index due to bad permissions/read-only filesystem/etc.
(Erick Erickson, Shawn Heisey, Robert Muir)
Build Build
* SOLR-11181: Switch order of maven artifact publishing procedure: deploy first * SOLR-11181: Switch order of maven artifact publishing procedure: deploy first

View File

@ -93,15 +93,27 @@ public final class NativeFSLockFactory extends FSLockFactory {
Path lockFile = lockDir.resolve(lockName); Path lockFile = lockDir.resolve(lockName);
IOException creationException = null;
try { try {
Files.createFile(lockFile); Files.createFile(lockFile);
} catch (IOException ignore) { } catch (IOException ignore) {
// we must create the file to have a truly canonical path. // 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. // 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 // fails if the lock file does not exist
final Path realPath = lockFile.toRealPath(); 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 // used as a best-effort check, to see if the underlying file has changed
final FileTime creationTime = Files.readAttributes(realPath, BasicFileAttributes.class).creationTime(); final FileTime creationTime = Files.readAttributes(realPath, BasicFileAttributes.class).creationTime();

View File

@ -18,9 +18,17 @@ package org.apache.lucene.store;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessDeniedException;
import java.nio.file.FileSystem;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import java.util.Set;
import org.apache.lucene.mockfile.FilterFileSystemProvider;
import org.apache.lucene.mockfile.FilterPath;
import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.TestUtil; import org.apache.lucene.util.TestUtil;
@ -89,4 +97,38 @@ public class TestNativeFSLockFactory extends BaseLockFactoryTestCase {
IOUtils.closeWhileHandlingException(lock); IOUtils.closeWhileHandlingException(lock);
} }
} }
/** MockFileSystem that throws AccessDeniedException on creating test.lock */
static class MockBadPermissionsFileSystem extends FilterFileSystemProvider {
public MockBadPermissionsFileSystem(FileSystem delegateInstance) {
super("mockbadpermissions://", delegateInstance);
}
@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
if (path.getFileName().toString().equals("test.lock")) {
throw new AccessDeniedException(path.toString(), null, "fake access denied");
}
return super.newByteChannel(path, options, attrs);
}
}
public void testBadPermissions() throws IOException {
// create a mock filesystem that will throw exc on creating test.lock
Path tmpDir = createTempDir();
tmpDir = FilterPath.unwrap(tmpDir).toRealPath();
FileSystem mock = new MockBadPermissionsFileSystem(tmpDir.getFileSystem()).getFileSystem(null);
Path mockPath = mock.getPath(tmpDir.toString());
// we should get an IOException (typically NoSuchFileException but no guarantee) with
// our fake AccessDenied added as suppressed.
Directory dir = getDirectory(mockPath.resolve("indexDir"));
IOException expected = expectThrows(IOException.class, () -> {
dir.obtainLock("test.lock");
});
AccessDeniedException suppressed = (AccessDeniedException) expected.getSuppressed()[0];
assertTrue(suppressed.getMessage().contains("fake access denied"));
dir.close();
}
} }