mirror of
https://github.com/OpenHFT/Chronicle-Map.git
synced 2021-09-13 01:26:12 +00:00
Protect about failure to reopen map immediately after it is closed
Remove RandomAccessFiles closed by interrupt Merge file locks into canonical files store to avoid desync Fixes ChronicleEnterprise/Chronicle-Map-Enterprise#68
This commit is contained in:
committed by
Ilya Kaznacheev
parent
73ae264124
commit
62e9c6f159
+266
-14
@@ -18,38 +18,71 @@ package net.openhft.chronicle.hash.impl.util;
|
||||
|
||||
import net.openhft.chronicle.core.CleaningRandomAccessFile;
|
||||
import net.openhft.chronicle.core.Jvm;
|
||||
import net.openhft.chronicle.core.OS;
|
||||
import net.openhft.chronicle.hash.ChronicleFileLockException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class CanonicalRandomAccessFiles {
|
||||
|
||||
/**
|
||||
* Java file locks are maintained on a per JVM basis. So we need to manage them.
|
||||
*/
|
||||
private static final String DISABLE_LOCKING = "chronicle.map.disable.locking";
|
||||
private static final boolean USE_EXCLUSIVE_LOCKING = !Jvm.getBoolean(DISABLE_LOCKING);
|
||||
private static final boolean USE_SHARED_LOCKING = !OS.isWindows() && !Jvm.getBoolean(DISABLE_LOCKING) &&
|
||||
!"shared".equalsIgnoreCase(System.getProperty(DISABLE_LOCKING));
|
||||
private static final AtomicBoolean LOCK_WARNING_PRINTED = new AtomicBoolean();
|
||||
private static final ConcurrentHashMap<File, RafReference> CANONICAL_RAFS = new ConcurrentHashMap<>();
|
||||
|
||||
private CanonicalRandomAccessFiles() {
|
||||
}
|
||||
|
||||
public static RandomAccessFile acquire(@NotNull final File file) throws FileNotFoundException {
|
||||
return CANONICAL_RAFS.compute(file, (f, ref) -> {
|
||||
if (ref == null) {
|
||||
try {
|
||||
return new RafReference(new CleaningRandomAccessFile(f, "rw"));
|
||||
} catch (FileNotFoundException e) {
|
||||
throw Jvm.rethrow(e);
|
||||
}
|
||||
} else {
|
||||
ref.refCount++;
|
||||
return ref;
|
||||
}
|
||||
}).raf;
|
||||
return acquire0(file, (ref) -> {}).raf;
|
||||
}
|
||||
|
||||
public static void release(@NotNull final File file) throws IOException {
|
||||
CANONICAL_RAFS.computeIfPresent(file, (f, ref) -> {
|
||||
private static RafReference acquire0(@NotNull final File file, Consumer<RafReference> action) {
|
||||
return CANONICAL_RAFS.compute(file, (f, ref) -> {
|
||||
while (ref != null) {
|
||||
try {
|
||||
ref.raf.length();
|
||||
} catch (IOException e) {
|
||||
// File is closed by interrupt;
|
||||
break;
|
||||
}
|
||||
|
||||
ref.refCount++;
|
||||
action.accept(ref);
|
||||
return ref;
|
||||
}
|
||||
|
||||
try {
|
||||
return new RafReference(new CleaningRandomAccessFile(f, "rw"));
|
||||
} catch (FileNotFoundException e) {
|
||||
throw Jvm.rethrow(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void release(@NotNull final File file) {
|
||||
release0(file, (ref) -> {});
|
||||
}
|
||||
|
||||
private static RafReference release0(@NotNull final File file, Consumer<RafReference> action) {
|
||||
return CANONICAL_RAFS.computeIfPresent(file, (f, ref) -> {
|
||||
action.accept(ref);
|
||||
if (--ref.refCount == 0) {
|
||||
try {
|
||||
ref.raf.close();
|
||||
@@ -63,15 +96,234 @@ public final class CanonicalRandomAccessFiles {
|
||||
});
|
||||
}
|
||||
|
||||
public static void acquireSharedFileLock(@NotNull final File canonicalFile, @NotNull final FileChannel channel) {
|
||||
if (USE_SHARED_LOCKING)
|
||||
acquire0(canonicalFile, (rafReference) -> {
|
||||
try {
|
||||
if (rafReference.lockRef == null)
|
||||
rafReference.lockRef = new FileLockReference(channel.lock(0, Long.MAX_VALUE, true));
|
||||
else {
|
||||
if (!rafReference.lockRef.fileLock.isShared()) {
|
||||
throw newUnableToAcquireSharedFileLockException(canonicalFile, null);
|
||||
}
|
||||
rafReference.lockRef.reserve();
|
||||
}
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
throw newUnableToAcquireSharedFileLockException(canonicalFile, e);
|
||||
}
|
||||
});
|
||||
else
|
||||
printWarningTheFirstTime();
|
||||
}
|
||||
|
||||
public static void acquireExclusiveFileLock(@NotNull final File canonicalFile, @NotNull final FileChannel channel) {
|
||||
if (USE_EXCLUSIVE_LOCKING)
|
||||
acquire0(canonicalFile, (rafReference) -> {
|
||||
if (rafReference.lockRef == null) {
|
||||
try {
|
||||
final FileLock fileLock = channel.lock(0, Long.MAX_VALUE, false);
|
||||
rafReference.lockRef = new FileLockReference(fileLock);
|
||||
} catch (IOException e) {
|
||||
throw newUnableToAcquireExclusiveFileLockException(canonicalFile, e);
|
||||
}
|
||||
} else {
|
||||
throw newUnableToAcquireExclusiveFileLockException(canonicalFile, null);
|
||||
}
|
||||
});
|
||||
else
|
||||
printWarningTheFirstTime();
|
||||
}
|
||||
|
||||
public static void releaseSharedFileLock(@NotNull final File canonicalFile) {
|
||||
if (USE_SHARED_LOCKING)
|
||||
releaseFileLock0(canonicalFile);
|
||||
else
|
||||
printWarningTheFirstTime();
|
||||
}
|
||||
|
||||
public static void releaseExclusiveFileLock(@NotNull final File canonicalFile) {
|
||||
if (USE_EXCLUSIVE_LOCKING)
|
||||
releaseFileLock0(canonicalFile);
|
||||
else
|
||||
printWarningTheFirstTime();
|
||||
}
|
||||
|
||||
private static void releaseFileLock0(@NotNull File canonicalFile) {
|
||||
release0(canonicalFile, (rafReference) -> {
|
||||
if (rafReference.lockRef == null)
|
||||
throw new ChronicleFileLockException("Trying to release lock on file " + canonicalFile + " that did not exist");
|
||||
else {
|
||||
final int cnt = rafReference.lockRef.release();
|
||||
if (cnt == 0)
|
||||
rafReference.lockRef = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to execute a closure under exclusive file lock.
|
||||
* If USE_LOCKING is false, provides synchronization only within local JVM.
|
||||
*
|
||||
* @param fileIOAction Closure to run, can throw {@link IOException}s.
|
||||
* @return <code>true</code> if the lock was successfully acquired and IO action was executed, <code>false</code> otherwise.
|
||||
*/
|
||||
public static boolean tryRunExclusively(@NotNull final File canonicalFile,
|
||||
@NotNull final FileChannel fileChannel,
|
||||
@NotNull final FileIOAction fileIOAction) {
|
||||
AtomicBoolean locked = new AtomicBoolean(false);
|
||||
|
||||
acquire0(canonicalFile, (rafReference) -> {
|
||||
if (rafReference.lockRef != null)
|
||||
return;
|
||||
|
||||
try {
|
||||
if (USE_EXCLUSIVE_LOCKING) {
|
||||
try (FileLock ignored = fileChannel.tryLock()) {
|
||||
if (ignored == null) {
|
||||
rafReference.lockRef = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
fileIOAction.fileIOAction();
|
||||
|
||||
locked.set(true);
|
||||
}
|
||||
catch (OverlappingFileLockException ignored) {
|
||||
// File lock is being held by this JVM, unsuccessful attempt.
|
||||
}
|
||||
} else {
|
||||
fileIOAction.fileIOAction();
|
||||
|
||||
locked.set(true);
|
||||
}
|
||||
|
||||
rafReference.lockRef = null;
|
||||
} catch (Exception e) {
|
||||
throw Jvm.rethrow(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
release(canonicalFile);
|
||||
|
||||
return locked.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a closure under exclusive file lock.
|
||||
* If USE_LOCKING is false, provides synchronization only within local JVM.
|
||||
*
|
||||
* @param fileIOAction Closure to run, can throw {@link IOException}s.
|
||||
*/
|
||||
public static void runExclusively(@NotNull final File canonicalFile,
|
||||
@NotNull final FileChannel fileChannel,
|
||||
@NotNull final FileIOAction fileIOAction) {
|
||||
acquire0(canonicalFile, (rafReference) -> {
|
||||
if (rafReference.lockRef != null)
|
||||
throw new ChronicleFileLockException("A file lock instance already exists for the file " + canonicalFile);
|
||||
|
||||
try {
|
||||
if (USE_EXCLUSIVE_LOCKING) {
|
||||
try (FileLock ignored = fileChannel.lock()) {
|
||||
fileIOAction.fileIOAction();
|
||||
}
|
||||
} else {
|
||||
fileIOAction.fileIOAction();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw Jvm.rethrow(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void dump() {
|
||||
System.out.println(CANONICAL_RAFS);
|
||||
}
|
||||
|
||||
private static ChronicleFileLockException newUnableToAcquireSharedFileLockException(@NotNull final File canonicalFile, @Nullable final Exception e) {
|
||||
return new ChronicleFileLockException("Unable to acquire a shared file lock for " + canonicalFile + ". " +
|
||||
"Make sure another process is not recovering the map.", e);
|
||||
}
|
||||
|
||||
private static ChronicleFileLockException newUnableToAcquireExclusiveFileLockException(@NotNull final File canonicalFile, @Nullable final Exception e) {
|
||||
return new ChronicleFileLockException("Unable to acquire an exclusive file lock for " + canonicalFile + ". " +
|
||||
"Make sure no other process is using the map.", e);
|
||||
}
|
||||
|
||||
private static void printWarningTheFirstTime() {
|
||||
if (LOCK_WARNING_PRINTED.compareAndSet(false, true)) {
|
||||
Jvm.warn().on(CanonicalRandomAccessFiles.class, "File locking is disabled or not supported on this platform (" + System.getProperty("os.name") + "). " +
|
||||
"Make sure you are not running ChronicleMapBuilder::*recover* methods when other processes or threads have the mapped file open!");
|
||||
}
|
||||
}
|
||||
|
||||
// This class is not thread-safe but instances
|
||||
// are protected by means of the FILE_LOCKS map
|
||||
public static final class FileLockReference {
|
||||
private final FileLock fileLock;
|
||||
private int refCount;
|
||||
|
||||
FileLockReference(@NotNull final FileLock fileLock) {
|
||||
this.fileLock = fileLock;
|
||||
refCount = 1;
|
||||
}
|
||||
|
||||
int reserve() {
|
||||
if (refCount == 0)
|
||||
throw new IllegalStateException("Ref counter previously released");
|
||||
return ++refCount;
|
||||
}
|
||||
|
||||
int release() {
|
||||
final int cnt = --refCount;
|
||||
if (cnt == 0) {
|
||||
try {
|
||||
fileLock.release();
|
||||
} catch (IOException e) {
|
||||
throw new ChronicleFileLockException(e);
|
||||
}
|
||||
}
|
||||
if (cnt < 0)
|
||||
throw new IllegalStateException("Ref counter was " + cnt);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FileLockReference{" +
|
||||
"fileLock=" + fileLock +
|
||||
", refCount=" + refCount +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface FileIOAction {
|
||||
void fileIOAction() throws IOException;
|
||||
}
|
||||
|
||||
// This class is not thread-safe but instances
|
||||
// are protected by means of the CANONICAL_RAFS map
|
||||
private static final class RafReference {
|
||||
private final RandomAccessFile raf;
|
||||
private FileLockReference lockRef;
|
||||
private int refCount;
|
||||
|
||||
RafReference(@NotNull final RandomAccessFile raf) {
|
||||
this.raf = raf;
|
||||
this.lockRef = null;
|
||||
refCount = 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RafReference{" +
|
||||
"raf=" + raf +
|
||||
", lockRef=" + lockRef +
|
||||
", refCount=" + refCount +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1713,7 +1713,7 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
TimingPauser pauser = Pauser.balanced();
|
||||
|
||||
while (raf.length() == 0) {
|
||||
final boolean locked = FileLockUtil.tryRunExclusively(canonicalFile, fileChannel, () -> {
|
||||
final boolean locked = CanonicalRandomAccessFiles.tryRunExclusively(canonicalFile, fileChannel, () -> {
|
||||
// Double-checked locking
|
||||
if (raf.length() == 0) {
|
||||
map.set(newMap());
|
||||
@@ -1840,8 +1840,8 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
|
||||
map.initBeforeMapping(canonicalFile, raf, headerBuffer.limit(), false);
|
||||
map.createMappedStoreAndSegments(resources);
|
||||
FileLockUtil.acquireSharedFileLock(canonicalFile, raf.getChannel());
|
||||
map.addCloseable(() -> FileLockUtil.releaseSharedFileLock(canonicalFile));
|
||||
CanonicalRandomAccessFiles.acquireSharedFileLock(canonicalFile, raf.getChannel());
|
||||
map.addCloseable(() -> CanonicalRandomAccessFiles.releaseSharedFileLock(canonicalFile));
|
||||
commitChronicleMapReady(map, raf, headerBuffer, headerSize);
|
||||
return map;
|
||||
}
|
||||
@@ -1907,22 +1907,22 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
map.initTransientsFromBuilder(this);
|
||||
if (!recover) {
|
||||
map.createMappedStoreAndSegments(resources);
|
||||
FileLockUtil.acquireSharedFileLock(file, raf.getChannel());
|
||||
CanonicalRandomAccessFiles.acquireSharedFileLock(file, raf.getChannel());
|
||||
} else {
|
||||
if (!headerWritten)
|
||||
writeNotComplete(fileChannel, headerBuffer, headerSize);
|
||||
try {
|
||||
FileLockUtil.acquireExclusiveFileLock(file, raf.getChannel());
|
||||
CanonicalRandomAccessFiles.acquireExclusiveFileLock(file, raf.getChannel());
|
||||
map.recover(resources, corruptionListener, corruption);
|
||||
} finally {
|
||||
FileLockUtil.releaseExclusiveFileLock(file);
|
||||
CanonicalRandomAccessFiles.releaseExclusiveFileLock(file);
|
||||
}
|
||||
// We are ready with exclusive access.
|
||||
// Demote the lock from exclusive to shared
|
||||
FileLockUtil.acquireSharedFileLock(file, fileChannel);
|
||||
CanonicalRandomAccessFiles.acquireSharedFileLock(file, fileChannel);
|
||||
commitChronicleMapReady(map, raf, headerBuffer, headerSize);
|
||||
}
|
||||
map.addCloseable(() -> FileLockUtil.releaseSharedFileLock(file));
|
||||
map.addCloseable(() -> CanonicalRandomAccessFiles.releaseSharedFileLock(file));
|
||||
if (MAP_CREATION_DEBUG) {
|
||||
Jvm.warn().on(getClass(), "<map creation debug> Created map [name=" + map.name() +
|
||||
", size=" + map.longSize() + "] from file [canonizedMapDataFile=" +
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
package net.openhft.chronicle.map;
|
||||
|
||||
import net.openhft.chronicle.core.Jvm;
|
||||
import net.openhft.chronicle.core.OS;
|
||||
import net.openhft.chronicle.hash.ChronicleFileLockException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public final class FileLockUtil {
|
||||
|
||||
/**
|
||||
* Java file locks are maintained on a per JVM basis. So we need to manage them.
|
||||
*/
|
||||
private static final String DISABLE_LOCKING = "chronicle.map.disable.locking";
|
||||
private static final ConcurrentHashMap<File, FileLockReference> FILE_LOCKS = new ConcurrentHashMap<>();
|
||||
private static final boolean USE_EXCLUSIVE_LOCKING = !Jvm.getBoolean(DISABLE_LOCKING);
|
||||
private static final boolean USE_SHARED_LOCKING = !OS.isWindows() && !Jvm.getBoolean(DISABLE_LOCKING) &&
|
||||
!"shared".equalsIgnoreCase(System.getProperty(DISABLE_LOCKING));
|
||||
private static final AtomicBoolean LOCK_WARNING_PRINTED = new AtomicBoolean();
|
||||
|
||||
private FileLockUtil() {
|
||||
}
|
||||
|
||||
public static void acquireSharedFileLock(@NotNull final File canonicalFile, @NotNull final FileChannel channel) {
|
||||
if (USE_SHARED_LOCKING)
|
||||
FILE_LOCKS.compute(canonicalFile, (f, flr) ->
|
||||
{
|
||||
try {
|
||||
if (flr == null)
|
||||
return new FileLockReference(channel.lock(0, Long.MAX_VALUE, true));
|
||||
else {
|
||||
if (!flr.fileLock.isShared()) {
|
||||
throw newUnableToAcquireSharedFileLockException(canonicalFile, null);
|
||||
}
|
||||
flr.reserve();
|
||||
return flr; // keep the old one
|
||||
}
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
throw newUnableToAcquireSharedFileLockException(canonicalFile, e);
|
||||
}
|
||||
}
|
||||
);
|
||||
else
|
||||
printWarningTheFirstTime();
|
||||
|
||||
}
|
||||
|
||||
public static void acquireExclusiveFileLock(@NotNull final File canonicalFile, @NotNull final FileChannel channel) {
|
||||
if (USE_EXCLUSIVE_LOCKING)
|
||||
FILE_LOCKS.compute(canonicalFile, (f, flr) ->
|
||||
{
|
||||
if (flr == null) {
|
||||
try {
|
||||
final FileLock fileLock = channel.lock(0, Long.MAX_VALUE, false);
|
||||
return new FileLockReference(fileLock);
|
||||
} catch (IOException e) {
|
||||
throw newUnableToAcquireExclusiveFileLockException(canonicalFile, e);
|
||||
}
|
||||
} else {
|
||||
throw newUnableToAcquireExclusiveFileLockException(canonicalFile, null);
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
else
|
||||
printWarningTheFirstTime();
|
||||
}
|
||||
|
||||
public static void releaseSharedFileLock(@NotNull final File canonicalFile) {
|
||||
if (USE_SHARED_LOCKING)
|
||||
releaseFileLock0(canonicalFile);
|
||||
else
|
||||
printWarningTheFirstTime();
|
||||
}
|
||||
|
||||
public static void releaseExclusiveFileLock(@NotNull final File canonicalFile) {
|
||||
if (USE_EXCLUSIVE_LOCKING)
|
||||
releaseFileLock0(canonicalFile);
|
||||
else
|
||||
printWarningTheFirstTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #releaseExclusiveFileLock(File)} or {@link #releaseSharedFileLock(File)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void releaseFileLock(@NotNull final File canonicalFile) {
|
||||
releaseExclusiveFileLock(canonicalFile);
|
||||
}
|
||||
|
||||
private static void releaseFileLock0(@NotNull File canonicalFile) {
|
||||
FILE_LOCKS.compute(canonicalFile, (f, flr) ->
|
||||
{
|
||||
if (flr == null)
|
||||
throw new ChronicleFileLockException("Trying to release lock on file " + canonicalFile + " that did not exist");
|
||||
else {
|
||||
final int cnt = flr.release();
|
||||
if (cnt == 0)
|
||||
return null; // Remove the old one
|
||||
else
|
||||
return flr;
|
||||
}
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to execute a closure under exclusive file lock.
|
||||
* If USE_LOCKING is false, provides synchronization only within local JVM.
|
||||
*
|
||||
* @param fileIOAction Closure to run, can throw {@link IOException}s.
|
||||
* @return <code>true</code> if the lock was successfully acquired and IO action was executed, <code>false</code> otherwise.
|
||||
*/
|
||||
public static boolean tryRunExclusively(@NotNull final File canonicalFile,
|
||||
@NotNull final FileChannel fileChannel,
|
||||
@NotNull final FileIOAction fileIOAction) {
|
||||
AtomicBoolean locked = new AtomicBoolean(false);
|
||||
|
||||
FILE_LOCKS.compute(canonicalFile, (f, flr) -> {
|
||||
if (flr != null)
|
||||
return flr;
|
||||
|
||||
try {
|
||||
if (USE_EXCLUSIVE_LOCKING) {
|
||||
try (FileLock ignored = fileChannel.tryLock()) {
|
||||
if (ignored == null)
|
||||
return null;
|
||||
|
||||
fileIOAction.fileIOAction();
|
||||
|
||||
locked.set(true);
|
||||
}
|
||||
catch (OverlappingFileLockException ignored) {
|
||||
// File lock is being held by this JVM, unsuccessful attempt.
|
||||
}
|
||||
} else {
|
||||
fileIOAction.fileIOAction();
|
||||
|
||||
locked.set(true);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
throw Jvm.rethrow(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return locked.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a closure under exclusive file lock.
|
||||
* If USE_LOCKING is false, provides synchronization only within local JVM.
|
||||
*
|
||||
* @param fileIOAction Closure to run, can throw {@link IOException}s.
|
||||
*/
|
||||
public static void runExclusively(@NotNull final File canonicalFile,
|
||||
@NotNull final FileChannel fileChannel,
|
||||
@NotNull final FileIOAction fileIOAction) {
|
||||
FILE_LOCKS.compute(canonicalFile, (f, flr) -> {
|
||||
if (flr != null)
|
||||
throw new ChronicleFileLockException("A file lock instance already exists for the file " + canonicalFile);
|
||||
|
||||
try {
|
||||
if (USE_EXCLUSIVE_LOCKING) {
|
||||
try (FileLock ignored = fileChannel.lock()) {
|
||||
fileIOAction.fileIOAction();
|
||||
}
|
||||
} else {
|
||||
fileIOAction.fileIOAction();
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
throw Jvm.rethrow(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static void dump() {
|
||||
System.out.println(FILE_LOCKS);
|
||||
}
|
||||
|
||||
private static ChronicleFileLockException newUnableToAcquireSharedFileLockException(@NotNull final File canonicalFile, @Nullable final Exception e) {
|
||||
return new ChronicleFileLockException("Unable to acquire a shared file lock for " + canonicalFile + ". " +
|
||||
"Make sure another process is not recovering the map.", e);
|
||||
}
|
||||
|
||||
private static ChronicleFileLockException newUnableToAcquireExclusiveFileLockException(@NotNull final File canonicalFile, @Nullable final Exception e) {
|
||||
return new ChronicleFileLockException("Unable to acquire an exclusive file lock for " + canonicalFile + ". " +
|
||||
"Make sure no other process is using the map.", e);
|
||||
}
|
||||
|
||||
private static void printWarningTheFirstTime() {
|
||||
if (LOCK_WARNING_PRINTED.compareAndSet(false, true)) {
|
||||
Jvm.warn().on(FileLockUtil.class, "File locking is disabled or not supported on this platform (" + System.getProperty("os.name") + "). " +
|
||||
"Make sure you are not running ChronicleMapBuilder::*recover* methods when other processes or threads have the mapped file open!");
|
||||
}
|
||||
}
|
||||
|
||||
// This class is not thread-safe but instances
|
||||
// are protected by means of the FILE_LOCKS map
|
||||
private static final class FileLockReference {
|
||||
private final FileLock fileLock;
|
||||
private int refCount;
|
||||
|
||||
FileLockReference(@NotNull final FileLock fileLock) {
|
||||
this.fileLock = fileLock;
|
||||
refCount = 1;
|
||||
}
|
||||
|
||||
int reserve() {
|
||||
if (refCount == 0)
|
||||
throw new IllegalStateException("Ref counter previously released");
|
||||
return ++refCount;
|
||||
}
|
||||
|
||||
int release() {
|
||||
final int cnt = --refCount;
|
||||
if (cnt == 0) {
|
||||
try {
|
||||
fileLock.release();
|
||||
} catch (IOException e) {
|
||||
throw new ChronicleFileLockException(e);
|
||||
}
|
||||
}
|
||||
if (cnt < 0)
|
||||
throw new IllegalStateException("Ref counter was " + cnt);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FileLockReference{" +
|
||||
"fileLock=" + fileLock +
|
||||
", refCount=" + refCount +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface FileIOAction {
|
||||
void fileIOAction() throws IOException;
|
||||
}
|
||||
}
|
||||
@@ -39,78 +39,78 @@ public class FileLockUtilTest {
|
||||
@Test
|
||||
public void testShared() {
|
||||
if (!OS.isWindows()) {
|
||||
FileLockUtil.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
FileLockUtil.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
FileLockUtil.releaseSharedFileLock(canonicalFile);
|
||||
FileLockUtil.releaseSharedFileLock(canonicalFile);
|
||||
CanonicalRandomAccessFiles.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.releaseSharedFileLock(canonicalFile);
|
||||
CanonicalRandomAccessFiles.releaseSharedFileLock(canonicalFile);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExclusiveNormalCase() {
|
||||
FileLockUtil.acquireExclusiveFileLock(canonicalFile, fileChannel);
|
||||
FileLockUtil.releaseExclusiveFileLock(canonicalFile);
|
||||
FileLockUtil.acquireExclusiveFileLock(canonicalFile, fileChannel);
|
||||
FileLockUtil.releaseExclusiveFileLock(canonicalFile);
|
||||
CanonicalRandomAccessFiles.acquireExclusiveFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.releaseExclusiveFileLock(canonicalFile);
|
||||
CanonicalRandomAccessFiles.acquireExclusiveFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.releaseExclusiveFileLock(canonicalFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTryExclusiveButWasShared() {
|
||||
if (!OS.isWindows()) {
|
||||
FileLockUtil.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
try {
|
||||
FileLockUtil.acquireExclusiveFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.acquireExclusiveFileLock(canonicalFile, fileChannel);
|
||||
fail();
|
||||
} catch (ChronicleFileLockException ignore) {
|
||||
}
|
||||
FileLockUtil.releaseSharedFileLock(canonicalFile);
|
||||
CanonicalRandomAccessFiles.releaseSharedFileLock(canonicalFile);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrySharedButWasExclusive() {
|
||||
if (!OS.isWindows()) {
|
||||
FileLockUtil.acquireExclusiveFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.acquireExclusiveFileLock(canonicalFile, fileChannel);
|
||||
try {
|
||||
FileLockUtil.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
fail();
|
||||
} catch (ChronicleFileLockException ignore) {
|
||||
}
|
||||
FileLockUtil.releaseExclusiveFileLock(canonicalFile);
|
||||
CanonicalRandomAccessFiles.releaseExclusiveFileLock(canonicalFile);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComplicated() {
|
||||
if (!OS.isWindows()) {
|
||||
FileLockUtil.acquireExclusiveFileLock(canonicalFile, fileChannel);
|
||||
FileLockUtil.releaseExclusiveFileLock(canonicalFile);
|
||||
FileLockUtil.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
FileLockUtil.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
FileLockUtil.releaseSharedFileLock(canonicalFile);
|
||||
FileLockUtil.releaseSharedFileLock(canonicalFile);
|
||||
FileLockUtil.acquireExclusiveFileLock(canonicalFile, fileChannel);
|
||||
FileLockUtil.releaseExclusiveFileLock(canonicalFile);
|
||||
CanonicalRandomAccessFiles.acquireExclusiveFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.releaseExclusiveFileLock(canonicalFile);
|
||||
CanonicalRandomAccessFiles.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.releaseSharedFileLock(canonicalFile);
|
||||
CanonicalRandomAccessFiles.releaseSharedFileLock(canonicalFile);
|
||||
CanonicalRandomAccessFiles.acquireExclusiveFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.releaseExclusiveFileLock(canonicalFile);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunExclusively() {
|
||||
final AtomicInteger cnt = new AtomicInteger();
|
||||
FileLockUtil.runExclusively(canonicalFile, fileChannel, cnt::incrementAndGet);
|
||||
CanonicalRandomAccessFiles.runExclusively(canonicalFile, fileChannel, cnt::incrementAndGet);
|
||||
assertEquals(1, cnt.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunExclusivelyButUsed() {
|
||||
if (!OS.isWindows()) {
|
||||
FileLockUtil.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
try {
|
||||
FileLockUtil.runExclusively(canonicalFile, fileChannel, () -> {
|
||||
CanonicalRandomAccessFiles.runExclusively(canonicalFile, fileChannel, () -> {
|
||||
});
|
||||
fail();
|
||||
} catch (ChronicleFileLockException e) {
|
||||
FileLockUtil.releaseSharedFileLock(canonicalFile);
|
||||
CanonicalRandomAccessFiles.releaseSharedFileLock(canonicalFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,16 +118,16 @@ public class FileLockUtilTest {
|
||||
@Test
|
||||
public void testTryRunExclusively() {
|
||||
if (!OS.isWindows()) {
|
||||
FileLockUtil.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
CanonicalRandomAccessFiles.acquireSharedFileLock(canonicalFile, fileChannel);
|
||||
|
||||
boolean lockedAndRun = FileLockUtil.tryRunExclusively(canonicalFile, fileChannel, () -> {
|
||||
boolean lockedAndRun = CanonicalRandomAccessFiles.tryRunExclusively(canonicalFile, fileChannel, () -> {
|
||||
});
|
||||
|
||||
assertFalse(lockedAndRun);
|
||||
|
||||
FileLockUtil.releaseSharedFileLock(canonicalFile);
|
||||
CanonicalRandomAccessFiles.releaseSharedFileLock(canonicalFile);
|
||||
|
||||
lockedAndRun = FileLockUtil.tryRunExclusively(canonicalFile, fileChannel, () -> {
|
||||
lockedAndRun = CanonicalRandomAccessFiles.tryRunExclusively(canonicalFile, fileChannel, () -> {
|
||||
});
|
||||
|
||||
assertTrue(lockedAndRun);
|
||||
|
||||
Reference in New Issue
Block a user