made LockFile reentrant (multiple locks on the same file can be acquired in the same JVM instance)
Some checks failed
CI / build (push) Has been cancelled

added argument file to JavaProcessBuilder
This commit is contained in:
2025-01-23 18:00:47 +08:00
parent d61cfc6ea7
commit cddd7889c7
8 changed files with 464 additions and 55 deletions

View File

@@ -0,0 +1,6 @@
module net.woggioni.jwo.lockfile.test {
requires net.woggioni.jwo;
requires static lombok;
exports net.woggioni.jwo.lockfile.test;
}

View File

@@ -0,0 +1,42 @@
package net.woggioni.jwo.lockfile.test;
import lombok.SneakyThrows;
import net.woggioni.jwo.LockFile;
import net.woggioni.jwo.Run;
import java.nio.file.Path;
import java.nio.file.Paths;
public class LockFileTestMain {
@SneakyThrows
private static void run(
Path lockfilePath,
boolean shared,
boolean keep
) {
Thread t = new Thread((Run)() -> {
try (AutoCloseable lockfile = LockFile.acquire(lockfilePath, shared)) {
while (keep) {
Thread.sleep(1000);
}
}
});
t.start();
try (AutoCloseable lockfile = LockFile.acquire(lockfilePath, shared)) {
while (keep) {
Thread.sleep(1000);
}
}
t.join();
}
@SneakyThrows
public static void main(String[] args) {
Path lockfilePath = Paths.get(args[0]);
boolean shared = Boolean.parseBoolean(args[1]);
boolean keep = Boolean.parseBoolean(args[2]);
run(lockfilePath, shared, keep);
}
}

View File

@@ -1,4 +1,5 @@
open module net.woggioni.jwo.unit.test {
requires org.slf4j;
requires net.woggioni.jwo;
requires net.woggioni.jwo.test.module;
requires org.junit.jupiter.api;

View File

@@ -0,0 +1,147 @@
package net.woggioni.jwo.test;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import net.woggioni.jwo.JavaProcessBuilder;
import net.woggioni.jwo.LockFile;
import net.woggioni.jwo.Run;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class LockFileTest {
private static Logger log = LoggerFactory.getLogger(LockFileTest.class);
@TempDir
public Path testDir;
private Path executablePath = Paths.get(System.getProperty("lockFileTest.executable.jar"));
@RequiredArgsConstructor
private static class LockFileTestMainArgs {
final Path lockFilePath;
final boolean shared;
final boolean keep;
public List<String> getArgs() {
return Arrays.asList(lockFilePath.toString(), Boolean.toString(shared), Boolean.toString(keep));
}
}
@SneakyThrows
private static void kill(Process p) {
if (p != null && p.isAlive()) p.destroyForcibly().waitFor();
}
@Test
@SneakyThrows
public void testExclusiveLockHeldOnFile() {
Path lockFilePath = Files.createFile(testDir.resolve("file.lock"));
// try to acquire an exclusive lock and check that the process returns immediately
JavaProcessBuilder javaProcessBuilder = new JavaProcessBuilder();
javaProcessBuilder.setExecutableJar(executablePath);
javaProcessBuilder.setCliArgs(new LockFileTestMainArgs(lockFilePath, false, false).getArgs());
Process process = javaProcessBuilder.build()
.inheritIO()
.start();
Assertions.assertTrue(process.waitFor(1, TimeUnit.SECONDS));
Assertions.assertEquals(0, process.exitValue());
Process sharedLockProcess = null;
Process anotherSharedLockProcess = null;
Process exclusiveLockProcess = null;
try {
// try to acquire and keep a shared lock on the file and check that the process does not exit
javaProcessBuilder.setCliArgs(new LockFileTestMainArgs(lockFilePath, true, true).getArgs());
sharedLockProcess = javaProcessBuilder.build()
.inheritIO()
.start();
Assertions.assertFalse(sharedLockProcess.waitFor(1000, TimeUnit.MILLISECONDS));
// try to acquire another shared lock on the file and check that the process is able to terminate
javaProcessBuilder.setCliArgs(new LockFileTestMainArgs(lockFilePath, true, false).getArgs());
anotherSharedLockProcess = javaProcessBuilder.build()
.inheritIO()
.start();
Assertions.assertTrue(anotherSharedLockProcess.waitFor(1, TimeUnit.SECONDS));
// try to acquire an exclusive lock on the file and check that process hangs
javaProcessBuilder.setCliArgs(new LockFileTestMainArgs(lockFilePath, false, false).getArgs());
exclusiveLockProcess = javaProcessBuilder.build()
.inheritIO()
.start();
Assertions.assertFalse(exclusiveLockProcess.waitFor(1, TimeUnit.SECONDS));
// kill the process holding the shared lock and check that the process holding the exclusive lock terminates
sharedLockProcess.destroyForcibly().waitFor();
Assertions.assertTrue(exclusiveLockProcess.waitFor(1, TimeUnit.SECONDS));
Assertions.assertEquals(0, exclusiveLockProcess.exitValue());
} finally {
kill(sharedLockProcess);
kill(anotherSharedLockProcess);
kill(exclusiveLockProcess);
}
}
@Test
@SneakyThrows
public void sameProcessTest(@TempDir Path testDir) {
ExecutorService executor = Executors.newThreadPerTaskExecutor(Thread::new);
Path lockfile = testDir.resolve("file.lock");
AtomicInteger readerRunning = new AtomicInteger(0);
AtomicBoolean writerRunning = new AtomicBoolean(false);
Run writerRunnable = () -> {
try(LockFile lock = LockFile.acquire(lockfile, false)) {
log.info("Writer start!!!!");
writerRunning.set(true);
FileChannel fileChannel = FileChannel.open(lockfile, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE));
Writer writer = new OutputStreamWriter(Channels.newOutputStream(fileChannel));
writer.write("asdffdgkhjdhigsdfhuifg");
Thread.sleep(100);
log.info("Writer end!!!!");
writerRunning.set(false);
Assertions.assertEquals(0, readerRunning.get());
}
};
Run readerRunnable = () -> {
try(AutoCloseable lock = LockFile.acquire(lockfile, true)) {
readerRunning.incrementAndGet();
log.info("reader start");
Thread.sleep(100);
log.info("reader end");
readerRunning.decrementAndGet();
Assertions.assertEquals(false, writerRunning.get());
}
};
CompletableFuture<?> reader1 = CompletableFuture.runAsync(readerRunnable, executor);
CompletableFuture<?> reader2 = CompletableFuture.runAsync(readerRunnable, executor);
CompletableFuture<?> writer = CompletableFuture.runAsync(writerRunnable, executor);
try {
CompletableFuture.allOf(reader1, reader2, writer).get();
} catch (ExecutionException ee) {
throw ee.getCause();
}
log.info("FINISHED");
}
}