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
Some checks failed
CI / build (push) Has been cancelled
added argument file to JavaProcessBuilder
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
alias(catalog.plugins.lombok)
|
||||
alias(catalog.plugins.envelope)
|
||||
}
|
||||
|
||||
import org.gradle.api.attributes.LibraryElements
|
||||
import static org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE
|
||||
import static org.gradle.api.attributes.LibraryElements.JAR
|
||||
import net.woggioni.gradle.envelope.EnvelopeJarTask
|
||||
import net.woggioni.gradle.envelope.EnvelopePlugin
|
||||
|
||||
|
||||
configurations {
|
||||
testImplementation {
|
||||
@@ -25,7 +29,23 @@ java {
|
||||
modularity.inferModulePath = true
|
||||
}
|
||||
|
||||
Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named(EnvelopePlugin.ENVELOPE_JAR_TASK_NAME, EnvelopeJarTask.class) {
|
||||
mainModule = "net.woggioni.jwo.lockfile.test"
|
||||
mainClass = "net.woggioni.jwo.lockfile.test.LockFileTestMain"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation rootProject
|
||||
|
||||
testImplementation catalog.slf4j.api
|
||||
testImplementation project(':jwo-test-module')
|
||||
testImplementation project(':')
|
||||
}
|
||||
|
||||
testRuntimeOnly catalog.slf4j.simple
|
||||
}
|
||||
|
||||
test {
|
||||
dependsOn(envelopeJarTaskProvider)
|
||||
systemProperty('lockFileTest.executable.jar', envelopeJarTaskProvider.flatMap {it.archiveFile}.get().getAsFile())
|
||||
systemProperty('org.slf4j.simpleLogger.showDateTime', 'true')
|
||||
}
|
||||
|
6
jwo-test/src/main/java/module-info.java
Normal file
6
jwo-test/src/main/java/module-info.java
Normal file
@@ -0,0 +1,6 @@
|
||||
module net.woggioni.jwo.lockfile.test {
|
||||
requires net.woggioni.jwo;
|
||||
requires static lombok;
|
||||
|
||||
exports net.woggioni.jwo.lockfile.test;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
147
jwo-test/src/test/java/net/woggioni/jwo/test/LockFileTest.java
Normal file
147
jwo-test/src/test/java/net/woggioni/jwo/test/LockFileTest.java
Normal 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");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user