WIP tests for lock file cleanup
This commit is contained in:
parent
4cbf1860d7
commit
e001d781b9
|
@ -215,12 +215,31 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
|
|||
createIniFile();
|
||||
} else {
|
||||
deleteOldTempDirectories();
|
||||
cleanUpCorruptPackages();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Look for .lock files that are not actively held by a process. If found, delete the lock file, and the package
|
||||
referenced.
|
||||
*/
|
||||
protected void cleanUpCorruptPackages() throws IOException {
|
||||
for (File file : Objects.requireNonNull(cacheFolder.listFiles())) {
|
||||
if (file.getName().endsWith(".lock")) {
|
||||
String packageDirectoryName = file.getName().substring(0, file.getName().length() - 5);
|
||||
File packageDirectory = ManagedFileAccess.file(Utilities.path(cacheFolder, packageDirectoryName));
|
||||
if (packageDirectory.exists()) {
|
||||
Utilities.clearDirectory(packageDirectory.getAbsolutePath());
|
||||
packageDirectory.delete();
|
||||
}
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCacheFolderValid() throws IOException {
|
||||
String iniPath = getPackagesIniPath();
|
||||
File iniFile = ManagedFileAccess.file(iniPath);
|
||||
|
|
|
@ -118,6 +118,20 @@ public class FilesystemPackageCacheManagerLocks {
|
|||
if (!lockFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the file is locked by a process. If it is not, it is likely an incomplete package cache install, and
|
||||
// we should throw an exception.
|
||||
if (lockFile.isFile()) {
|
||||
try (FileChannel channel = new RandomAccessFile(lockFile, "rw").getChannel()) {
|
||||
FileLock fileLock = channel.tryLock(0, Long.MAX_VALUE, true);
|
||||
if (fileLock != null) {
|
||||
fileLock.release();
|
||||
channel.close();
|
||||
throw new IOException("Lock file exists, but is not locked by a process: " + lockFile.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
|
||||
Path dir = lockFile.getParentFile().toPath();
|
||||
dir.register(watchService, StandardWatchEventKinds.ENTRY_DELETE);
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
package org.hl7.fhir.utilities.npm;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
|
||||
import org.junit.jupiter.api.RepeatedTest;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
|
@ -111,6 +119,67 @@ public class FilesystemPackageManagerTests {
|
|||
assertEquals( System.getenv("ProgramData") + "\\.fhir\\packages", folder.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailureForUnlockedLockFiles() throws IOException, InterruptedException {
|
||||
String pcmPath = ManagedFileAccess.fromPath(Files.createTempDirectory("fpcm-multithreadingTest")).getAbsolutePath();
|
||||
|
||||
final FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager.Builder().withCacheFolder(pcmPath).build();
|
||||
|
||||
Assertions.assertTrue(pcm.listPackages().isEmpty());
|
||||
|
||||
//Now sneak in a new lock file and directory:
|
||||
File lockFile = ManagedFileAccess.file(pcmPath, "example.fhir.uv.myig#1.2.3.lock");
|
||||
lockFile.createNewFile();
|
||||
File directory = ManagedFileAccess.file(pcmPath, "example.fhir.uv.myig#1.2.3" );
|
||||
directory.mkdir();
|
||||
|
||||
IOException exception = assertThrows(IOException.class, () -> pcm.loadPackageFromCacheOnly("example.fhir.uv.myig", "1.2.3"));
|
||||
assertThat(exception.getMessage()).contains("Lock file exists, but is not locked by a process");
|
||||
}
|
||||
|
||||
@Test
|
||||
//@EnabledOnOs(OS.LINUX)
|
||||
public void testCacheCleanupForUnlockedLockFiles() throws IOException, InterruptedException {
|
||||
String pcmPath = ManagedFileAccess.fromPath(Files.createTempDirectory("fpcm-multithreadingTest")).getAbsolutePath();
|
||||
|
||||
final FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager.Builder().withCacheFolder(pcmPath).build();
|
||||
|
||||
Assertions.assertTrue(pcm.listPackages().isEmpty());
|
||||
|
||||
String packageAndVersion = "example.fhir.uv.myig#1.2.3";
|
||||
String lockFileName = packageAndVersion + ".lock";
|
||||
//Now sneak in a new lock file and directory:
|
||||
File lockFile = ManagedFileAccess.file(pcmPath, lockFileName);
|
||||
lockFile.createNewFile();
|
||||
File directory = ManagedFileAccess.file(pcmPath, packageAndVersion);
|
||||
directory.mkdir();
|
||||
|
||||
// We can't create a lock file from within the same JVM, so we have to use the flock utility, which is OS dependent.
|
||||
// The following works for Linux only.
|
||||
ProcessBuilder processBuilder = new ProcessBuilder("flock", lockFileName, "--command", "sleep 10");
|
||||
processBuilder.directory(new File(pcmPath));
|
||||
processBuilder.start();
|
||||
|
||||
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
executorService.schedule(()->{
|
||||
try {
|
||||
Utilities.clearDirectory(directory.getAbsolutePath());
|
||||
directory.delete();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
lockFile.delete();
|
||||
}, 15, TimeUnit.SECONDS);
|
||||
|
||||
IOException ioException = assertThrows(IOException.class, () -> { pcm.loadPackageFromCacheOnly("example.fhir.uv.myig", "1.2.3"); });
|
||||
|
||||
|
||||
ioException.printStackTrace();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
We repeat the same tests multiple times here, in order to catch very rare edge cases.
|
||||
*/
|
||||
|
@ -133,7 +202,7 @@ public class FilesystemPackageManagerTests {
|
|||
Random rand = new Random();
|
||||
|
||||
final AtomicInteger totalSuccessful = new AtomicInteger();
|
||||
final ConcurrentHashMap successfulThreads = new ConcurrentHashMap();
|
||||
final ConcurrentHashMap<Long, Integer> successfulThreads = new ConcurrentHashMap<>();
|
||||
List<Thread> threads = new ArrayList<>();
|
||||
for (int i = 0; i < threadTotal; i++) {
|
||||
final int index = i;
|
||||
|
@ -142,22 +211,27 @@ public class FilesystemPackageManagerTests {
|
|||
System.out.println("Thread #" + index + ": " + Thread.currentThread().getId() + " started");
|
||||
final int randomPCM = rand.nextInt(packageCacheManagerTotal);
|
||||
final int randomOperation = rand.nextInt(4);
|
||||
final String operationName;
|
||||
if (packageCacheManagers[randomPCM] == null) {
|
||||
packageCacheManagers[randomPCM] = new FilesystemPackageCacheManager.Builder().withCacheFolder(pcmPath).build();
|
||||
}
|
||||
FilesystemPackageCacheManager pcm = packageCacheManagers[randomPCM];
|
||||
if (randomOperation == 0) {
|
||||
operationName = "addPackageToCache";
|
||||
pcm.addPackageToCache("example.fhir.uv.myig", "1.2.3", this.getClass().getResourceAsStream("/npm/dummy-package.tgz"), "https://packages.fhir.org/example.fhir.uv.myig/1.2.3");
|
||||
} else if (randomOperation == 1) {
|
||||
operationName = "clear";
|
||||
pcm.clear();
|
||||
} else if (randomOperation == 2) {
|
||||
operationName = "loadPackageFromCacheOnly";
|
||||
pcm.loadPackageFromCacheOnly("example.fhir.uv.myig", "1.2.3");
|
||||
} else {
|
||||
operationName = "removePackage";
|
||||
pcm.removePackage("example.fhir.uv.myig", "1.2.3");
|
||||
}
|
||||
totalSuccessful.incrementAndGet();
|
||||
successfulThreads.put(Thread.currentThread().getId(), index);
|
||||
System.out.println("Thread #" + index + ": " + Thread.currentThread().getId() + " completed");
|
||||
System.out.println("Thread #" + index + ": " + Thread.currentThread().getId() + " completed. Ran: " + operationName);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.err.println("Thread #" + index + ": " + Thread.currentThread().getId() + " failed");
|
||||
|
|
Loading…
Reference in New Issue