diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheLock.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheLock.java new file mode 100644 index 000000000..babdb03b7 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheLock.java @@ -0,0 +1,49 @@ +package org.hl7.fhir.utilities.npm; + +import org.hl7.fhir.utilities.TextFile; + +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.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class FilesystemPackageCacheLock { + + private static final ConcurrentHashMap locks = new ConcurrentHashMap<>(); + + private final File lockFile; + + public FilesystemPackageCacheLock(File cacheFolder, String name) throws IOException { + this.lockFile = new File(cacheFolder, name + ".lock"); + if (!lockFile.isFile()) { + TextFile.stringToFile("", lockFile); + } + } + + public T doWriteWithLock(FilesystemPackageCacheManager.CacheLockFunction f) throws IOException { + + try (FileChannel channel = new RandomAccessFile(lockFile, "rw").getChannel()) { + locks.putIfAbsent(lockFile, new ReentrantReadWriteLock()); + ReadWriteLock lock = locks.get(lockFile); + lock.writeLock().lock(); + final FileLock fileLock = channel.lock(); + T result = null; + try { + result = f.get(); + } finally { + fileLock.release(); + lock.writeLock().unlock(); + } + if (!lockFile.delete()) { + lockFile.deleteOnExit(); + } + + return result; + } + } +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java index 2876d7a69..561058aec 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java @@ -3,12 +3,8 @@ package org.hl7.fhir.utilities.npm; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -118,8 +114,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple private JsonArray buildInfo; private boolean suppressErrors; private boolean minimalMemory; - - + public FilesystemPackageCacheManager(boolean userMode) throws IOException { init(userMode ? FilesystemPackageCacheMode.USER : FilesystemPackageCacheMode.SYSTEM); } @@ -139,36 +134,35 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple init(FilesystemPackageCacheMode.CUSTOM); } - - protected void init(FilesystemPackageCacheMode mode) throws IOException { initPackageServers(); - switch (mode) { - case SYSTEM: - if (Utilities.isWindows()) { - cacheFolder = new File(Utilities.path(System.getenv("ProgramData"), ".fhir", "packages")); - } else { - cacheFolder = new File(Utilities.path("/var", "lib", ".fhir", "packages")); - } - break; - case USER: - cacheFolder = new File(Utilities.path(System.getProperty("user.home"), ".fhir", "packages")); - break; - case TESTING: - cacheFolder = new File(Utilities.path("[tmp]", ".fhir", "packages")); - break; - case CUSTOM: - if (!cacheFolder.exists()) { + if (mode == FilesystemPackageCacheMode.CUSTOM) { + if (!this.cacheFolder.exists()) { throw new FHIRException("The folder ''"+cacheFolder+"' could not be found"); } - default: - break; + } else { + this.cacheFolder = getCacheFolder(mode); } - initCacheFolder(); } + public static File getCacheFolder(FilesystemPackageCacheMode mode) throws IOException { + switch (mode) { + case SYSTEM: + if (Utilities.isWindows()) { + return new File(Utilities.path(System.getenv("ProgramData"), ".fhir", "packages")); + } else { + return new File(Utilities.path("/var", "lib", ".fhir", "packages")); + } + case USER: + return new File(Utilities.path(System.getProperty("user.home"), ".fhir", "packages")); + case TESTING: + return new File(Utilities.path("[tmp]", ".fhir", "packages")); + } + return null; + } + protected void initCacheFolder() throws IOException { if (!(cacheFolder.exists())) Utilities.createDirectory(cacheFolder.getAbsolutePath()); @@ -254,7 +248,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple private void clearCache() throws IOException { for (File f : cacheFolder.listFiles()) { if (f.isDirectory()) { - new CacheLock(f.getName()).doWithLock(() -> { + new FilesystemPackageCacheLock(cacheFolder, f.getName()).doWriteWithLock(() -> { Utilities.clearDirectory(f.getAbsolutePath()); try { FileUtils.deleteDirectory(f); @@ -399,7 +393,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple * @throws IOException */ public void removePackage(String id, String ver) throws IOException { - new CacheLock(id + "#" + ver).doWithLock(() -> { + new FilesystemPackageCacheLock(cacheFolder, id + "#" + ver).doWriteWithLock(() -> { String f = Utilities.path(cacheFolder, id + "#" + ver); File ff = new File(f); if (ff.exists()) { @@ -490,7 +484,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple } String v = version; - return new CacheLock(id + "#" + version).doWithLock(() -> { + return new FilesystemPackageCacheLock(cacheFolder, id + "#" + version).doWriteWithLock(() -> { NpmPackage pck = null; String packRoot = Utilities.path(cacheFolder, id + "#" + v); try { @@ -1015,34 +1009,6 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple } } - public class CacheLock { - - private final File lockFile; - - public CacheLock(String name) throws IOException { - this.lockFile = new File(cacheFolder, name + ".lock"); - if (!lockFile.isFile()) { - TextFile.stringToFile("", lockFile); - } - } - - public T doWithLock(CacheLockFunction f) throws FileNotFoundException, IOException { - try (FileChannel channel = new RandomAccessFile(lockFile, "rw").getChannel()) { - final FileLock fileLock = channel.lock(); - T result = null; - try { - result = f.get(); - } finally { - fileLock.release(); - } - if (!lockFile.delete()) { - lockFile.deleteOnExit(); - } - return result; - } - } - } - public boolean packageExists(String id, String ver) throws IOException { if (packageInstalled(id, ver)) { return true; diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/FilesystemPackageManagerTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/FilesystemPackageManagerTests.java index 21bea74a6..4f6e49cc5 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/FilesystemPackageManagerTests.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/FilesystemPackageManagerTests.java @@ -4,7 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nonnull; @@ -100,4 +103,37 @@ public class FilesystemPackageManagerTests { }; assertEquals( System.getenv("ProgramData") + "\\.fhir\\packages", filesystemPackageCacheManager.getFolder()); } + + @Test + public void multithreadingTest() throws IOException { + String pcmPath = Files.createTempDirectory("fpcm-multithreadingTest").toFile().getAbsolutePath(); + FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager(pcmPath); + + final AtomicInteger totalSuccessful = new AtomicInteger(); + + List threads = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + final int index = i; + Thread t = new Thread(() -> { + try { + pcm.loadPackage("hl7.fhir.xver-extensions#0.0.12"); + totalSuccessful.incrementAndGet(); + System.out.println("Thread " + index + " completed"); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Thread " + index + " failed"); + } + }); + t.start(); + threads.add(t); + } + threads.forEach(t -> { + try { + t.join(); + } catch (InterruptedException e) { + + } + }); + assertEquals(3, totalSuccessful.get()); + } }