
name.remal.io.LockingFileOperations Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of common Show documentation
Show all versions of common Show documentation
Java & Kotlin tools: common
The newest version!
package name.remal.io;
import static java.lang.Math.min;
import static name.remal.SneakyThrow.sneakyThrow;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.NonWritableChannelException;
import java.nio.file.Files;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import name.remal.lambda.Function1;
import org.jetbrains.annotations.NotNull;
public class LockingFileOperations {
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
@NotNull
public static byte[] lockAndReadBytes(@NotNull File file, @NotNull Duration lockAcquiringTimeout) {
try {
File absoluteFile = file.getAbsoluteFile();
return forLockedFileChannel(absoluteFile, "r", lockAcquiringTimeout, channel -> {
long size = channel.size();
if (MAX_ARRAY_SIZE < size) {
throw new IOException(String.format(
"%s: File can't be read as bytes array because it's length is %d bytes, that's more than manimum java array length - %d",
absoluteFile.getPath(),
size,
MAX_ARRAY_SIZE
));
}
byte[] bytes = new byte[(int) size];
channel.read(ByteBuffer.wrap(bytes));
return bytes;
});
} catch (Throwable throwable) {
throw sneakyThrow(throwable);
}
}
public static void lockAndWriteBytes(@NotNull File file, @NotNull Duration lockAcquiringTimeout, @NotNull byte[] bytes) {
try {
forLockedFileChannel(file, "rw", lockAcquiringTimeout, channel -> {
channel.write(ByteBuffer.wrap(bytes));
return null;
});
} catch (Throwable throwable) {
throw sneakyThrow(throwable);
}
}
private static final ConcurrentMap FILE_LOCKS = new ConcurrentHashMap<>();
/**
* @see java.io.RandomAccessFile#RandomAccessFile(java.io.File, String)
*/
@SuppressFBWarnings("SWL_SLEEP_WITH_LOCK_HELD")
public static R forLockedFileChannel(@NotNull File file, @NotNull String mode, @NotNull Duration lockAcquiringTimeout, @NotNull Function1 action) {
file = file.getAbsoluteFile();
Object fileLock = FILE_LOCKS.computeIfAbsent(file, it -> it);
synchronized (fileLock) {
try {
if (mode.contains("w")) {
Files.createDirectories(file.getParentFile().toPath());
}
long timeoutNanos = lockAcquiringTimeout.toNanos();
long sleepMillis = getSleepMillis(timeoutNanos);
long startNanos = System.nanoTime();
try (FileChannel channel = new RandomAccessFile(file, mode).getChannel()) {
while (true) {
FileLock lock;
try {
lock = channel.tryLock(0, Long.MAX_VALUE, !mode.contains("w"));
} catch (NonWritableChannelException e) {
if (!mode.contains("w")) {
return forLockedFileChannel(file, mode + "w", lockAcquiringTimeout, action);
} else {
throw e;
}
} catch (Exception ignored) {
lock = null;
}
if (lock == null) {
if (System.nanoTime() - startNanos < timeoutNanos) {
Thread.sleep(sleepMillis);
continue;
} else {
throw new FileLockingException(String.format("%s: File lock can't be acquired for %d nanoseconds", file.getPath(), timeoutNanos));
}
}
try {
return action.invoke(channel);
} finally {
lock.release();
}
}
}
} catch (Throwable throwable) {
throw sneakyThrow(throwable);
} finally {
FILE_LOCKS.remove(file);
}
}
}
public static void lockAndDelete(@NotNull File file, @NotNull Duration lockAcquiringTimeout) {
try {
file = file.getAbsoluteFile();
if (file.isFile()) {
long timeoutNanos = lockAcquiringTimeout.toNanos();
long sleepMillis = getSleepMillis(timeoutNanos);
long startNanos = System.nanoTime();
while (true) {
if (file.delete()) {
return;
} else {
if (System.nanoTime() - startNanos < timeoutNanos) {
Thread.sleep(sleepMillis);
} else {
throw new IOException(String.format("%s: File can't be deleted for %d nanoseconds", file.getPath(), timeoutNanos));
}
}
}
} else if (file.isDirectory()) {
throw new IllegalArgumentException("File " + file + " is a directory");
}
} catch (Throwable throwable) {
throw sneakyThrow(throwable);
}
}
private static final long MILLI_NANOS = ChronoUnit.MILLIS.getDuration().toNanos();
private static final long SECOND_MILLIS = ChronoUnit.SECONDS.getDuration().toMillis();
private static long getSleepMillis(long timeoutNanos) {
if (timeoutNanos < MILLI_NANOS) {
return 1;
} else {
return min(SECOND_MILLIS, timeoutNanos / MILLI_NANOS / 100);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy