fix problem with package cache corruption when different processes install the same package at the same time
This commit is contained in:
parent
ccb6b067b3
commit
70c75fde62
|
@ -35,11 +35,15 @@ import java.io.BufferedOutputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
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.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.FileLock;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
@ -186,6 +190,42 @@ public class PackageCacheManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface CacheLockFunction<T> {
|
||||||
|
T get() throws IOException;
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!lockFile.delete()) {
|
||||||
|
lockFile.deleteOnExit();
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("unable to clean up lock file for "+lockFile.getName()+": "+t.getMessage());
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static final String PRIMARY_SERVER = "http://packages.fhir.org";
|
public static final String PRIMARY_SERVER = "http://packages.fhir.org";
|
||||||
public static final String SECONDARY_SERVER = "http://packages2.fhir.org/packages";
|
public static final String SECONDARY_SERVER = "http://packages2.fhir.org/packages";
|
||||||
// private static final String SECONDARY_SERVER = "http://local.fhir.org:960/packages";
|
// private static final String SECONDARY_SERVER = "http://local.fhir.org:960/packages";
|
||||||
|
@ -256,6 +296,7 @@ public class PackageCacheManager {
|
||||||
private void clearCache() throws IOException {
|
private void clearCache() throws IOException {
|
||||||
for (File f : new File(cacheFolder).listFiles()) {
|
for (File f : new File(cacheFolder).listFiles()) {
|
||||||
if (f.isDirectory()) {
|
if (f.isDirectory()) {
|
||||||
|
new CacheLock(f.getName()).doWithLock(() -> {
|
||||||
Utilities.clearDirectory(f.getAbsolutePath());
|
Utilities.clearDirectory(f.getAbsolutePath());
|
||||||
try {
|
try {
|
||||||
FileUtils.deleteDirectory(f);
|
FileUtils.deleteDirectory(f);
|
||||||
|
@ -266,6 +307,8 @@ public class PackageCacheManager {
|
||||||
// just give up
|
// just give up
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null; // must return something
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else if (!f.getName().equals("packages.ini"))
|
else if (!f.getName().equals("packages.ini"))
|
||||||
FileUtils.forceDelete(f);
|
FileUtils.forceDelete(f);
|
||||||
|
@ -413,6 +456,7 @@ public class PackageCacheManager {
|
||||||
* @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(() -> {
|
||||||
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()) {
|
||||||
|
@ -422,6 +466,8 @@ public class PackageCacheManager {
|
||||||
ini.save();
|
ini.save();
|
||||||
ff.delete();
|
ff.delete();
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -494,7 +540,10 @@ public class PackageCacheManager {
|
||||||
if (version == null)
|
if (version == null)
|
||||||
version = npm.version();
|
version = npm.version();
|
||||||
|
|
||||||
String packRoot = Utilities.path(cacheFolder, id+"#"+version);
|
String v = version;
|
||||||
|
return new CacheLock(id+"#"+version).doWithLock(() -> {
|
||||||
|
NpmPackage pck = null;
|
||||||
|
String packRoot = Utilities.path(cacheFolder, id+"#"+v);
|
||||||
try {
|
try {
|
||||||
Utilities.createDirectory(packRoot);
|
Utilities.createDirectory(packRoot);
|
||||||
Utilities.clearDirectory(packRoot);
|
Utilities.clearDirectory(packRoot);
|
||||||
|
@ -527,30 +576,31 @@ public class PackageCacheManager {
|
||||||
|
|
||||||
IniFile ini = new IniFile(Utilities.path(cacheFolder, "packages.ini"));
|
IniFile ini = new IniFile(Utilities.path(cacheFolder, "packages.ini"));
|
||||||
ini.setTimeStampFormat("yyyyMMddhhmmss");
|
ini.setTimeStampFormat("yyyyMMddhhmmss");
|
||||||
ini.setTimestampProperty("packages", id+"#"+version, Timestamp.from(Instant.now()), null);
|
ini.setTimestampProperty("packages", id+"#"+v, Timestamp.from(Instant.now()), null);
|
||||||
ini.setIntegerProperty("package-sizes", id+"#"+version, size, null);
|
ini.setIntegerProperty("package-sizes", id+"#"+v, size, null);
|
||||||
ini.save();
|
ini.save();
|
||||||
if (progress)
|
if (progress)
|
||||||
System.out.println(" done.");
|
System.out.println(" done.");
|
||||||
|
|
||||||
NpmPackage pck = loadPackageInfo(packRoot);
|
pck = loadPackageInfo(packRoot);
|
||||||
if (!id.equals(JSONUtil.str(npm.getNpm(), "name")) || !version.equals(JSONUtil.str(npm.getNpm(), "version"))) {
|
if (!id.equals(JSONUtil.str(npm.getNpm(), "name")) || !v.equals(JSONUtil.str(npm.getNpm(), "version"))) {
|
||||||
if (!id.equals(JSONUtil.str(npm.getNpm(), "name"))) {
|
if (!id.equals(JSONUtil.str(npm.getNpm(), "name"))) {
|
||||||
npm.getNpm().addProperty("original-name", JSONUtil.str(npm.getNpm(), "name"));
|
npm.getNpm().addProperty("original-name", JSONUtil.str(npm.getNpm(), "name"));
|
||||||
npm.getNpm().remove("name");
|
npm.getNpm().remove("name");
|
||||||
npm.getNpm().addProperty("name", id);
|
npm.getNpm().addProperty("name", id);
|
||||||
}
|
}
|
||||||
if (!version.equals(JSONUtil.str(npm.getNpm(), "version"))) {
|
if (!v.equals(JSONUtil.str(npm.getNpm(), "version"))) {
|
||||||
npm.getNpm().addProperty("original-version", JSONUtil.str(npm.getNpm(), "version"));
|
npm.getNpm().addProperty("original-version", JSONUtil.str(npm.getNpm(), "version"));
|
||||||
npm.getNpm().remove("version");
|
npm.getNpm().remove("version");
|
||||||
npm.getNpm().addProperty("version", version);
|
npm.getNpm().addProperty("version", v);
|
||||||
}
|
}
|
||||||
TextFile.stringToFile(new GsonBuilder().setPrettyPrinting().create().toJson(npm.getNpm()), Utilities.path(cacheFolder, id+"#"+version, "package", "package.json"), false);
|
TextFile.stringToFile(new GsonBuilder().setPrettyPrinting().create().toJson(npm.getNpm()), Utilities.path(cacheFolder, id+"#"+v, "package", "package.json"), false);
|
||||||
}
|
}
|
||||||
return pck;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
try {
|
try {
|
||||||
// don't leave a half extracted package behind
|
// don't leave a half extracted package behind
|
||||||
|
System.out.println("Clean up package "+packRoot+" because installation failed: "+e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
Utilities.clearDirectory(packRoot);
|
Utilities.clearDirectory(packRoot);
|
||||||
new File(packRoot).delete();
|
new File(packRoot).delete();
|
||||||
} catch (Exception ei) {
|
} catch (Exception ei) {
|
||||||
|
@ -558,6 +608,8 @@ public class PackageCacheManager {
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
return pck;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPackageId(String canonical) throws IOException {
|
public String getPackageId(String canonical) throws IOException {
|
||||||
|
|
Loading…
Reference in New Issue