Add per-file ReadWriteLock to CacheLock

This commit is contained in:
dotasek 2023-11-29 10:59:01 -05:00
parent 84e456c5dc
commit 09ab60405d
2 changed files with 73 additions and 58 deletions

View File

@ -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<File, ReadWriteLock> 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> T doWriteWithLock(FilesystemPackageCacheManager.CacheLockFunction<T> 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;
}
}
}

View File

@ -3,12 +3,8 @@ package org.hl7.fhir.utilities.npm;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; 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.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -118,8 +114,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
private JsonArray buildInfo; private JsonArray buildInfo;
private boolean suppressErrors; private boolean suppressErrors;
private boolean minimalMemory; private boolean minimalMemory;
public FilesystemPackageCacheManager(boolean userMode) throws IOException { public FilesystemPackageCacheManager(boolean userMode) throws IOException {
init(userMode ? FilesystemPackageCacheMode.USER : FilesystemPackageCacheMode.SYSTEM); init(userMode ? FilesystemPackageCacheMode.USER : FilesystemPackageCacheMode.SYSTEM);
} }
@ -139,36 +134,35 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
init(FilesystemPackageCacheMode.CUSTOM); init(FilesystemPackageCacheMode.CUSTOM);
} }
protected void init(FilesystemPackageCacheMode mode) throws IOException { protected void init(FilesystemPackageCacheMode mode) throws IOException {
initPackageServers(); initPackageServers();
switch (mode) { if (mode == FilesystemPackageCacheMode.CUSTOM) {
case SYSTEM: if (!this.cacheFolder.exists()) {
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()) {
throw new FHIRException("The folder ''"+cacheFolder+"' could not be found"); throw new FHIRException("The folder ''"+cacheFolder+"' could not be found");
} }
default: } else {
break; this.cacheFolder = getCacheFolder(mode);
} }
initCacheFolder(); 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 { protected void initCacheFolder() throws IOException {
if (!(cacheFolder.exists())) if (!(cacheFolder.exists()))
Utilities.createDirectory(cacheFolder.getAbsolutePath()); Utilities.createDirectory(cacheFolder.getAbsolutePath());
@ -254,7 +248,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
private void clearCache() throws IOException { private void clearCache() throws IOException {
for (File f : cacheFolder.listFiles()) { for (File f : cacheFolder.listFiles()) {
if (f.isDirectory()) { if (f.isDirectory()) {
new CacheLock(f.getName()).doWithLock(() -> { new FilesystemPackageCacheLock(cacheFolder, f.getName()).doWriteWithLock(() -> {
Utilities.clearDirectory(f.getAbsolutePath()); Utilities.clearDirectory(f.getAbsolutePath());
try { try {
FileUtils.deleteDirectory(f); FileUtils.deleteDirectory(f);
@ -399,7 +393,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
* @throws IOException * @throws IOException
*/ */
public void removePackage(String id, String ver) 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); String f = Utilities.path(cacheFolder, id + "#" + ver);
File ff = new File(f); File ff = new File(f);
if (ff.exists()) { if (ff.exists()) {
@ -490,7 +484,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
} }
String v = version; String v = version;
return new CacheLock(id + "#" + version).doWithLock(() -> { return new FilesystemPackageCacheLock(cacheFolder, id + "#" + version).doWriteWithLock(() -> {
NpmPackage pck = null; NpmPackage pck = null;
String packRoot = Utilities.path(cacheFolder, id + "#" + v); String packRoot = Utilities.path(cacheFolder, id + "#" + v);
try { 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> T doWithLock(CacheLockFunction<T> 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 { public boolean packageExists(String id, String ver) throws IOException {
if (packageInstalled(id, ver)) { if (packageInstalled(id, ver)) {
return true; return true;