Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package org.hl7.fhir.utilities.npm;
import lombok.Getter;
import lombok.With;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class FilesystemPackageCacheManagerLocks {
private static final ConcurrentHashMap cacheFolderLockManagers = new ConcurrentHashMap<>();
@Getter
private final CacheLock cacheLock = new CacheLock();
private final ConcurrentHashMap packageLocks = new ConcurrentHashMap<>();
private final File cacheFolder;
private static final LockParameters lockParameters = new LockParameters();
public static class LockParameters {
@Getter @With
private final long lockTimeoutTime;
@Getter @With
private final TimeUnit lockTimeoutTimeUnit;
public LockParameters() {
this(60L, TimeUnit.SECONDS);
}
public LockParameters(long lockTimeoutTime, TimeUnit lockTimeoutTimeUnit) {
this.lockTimeoutTime = lockTimeoutTime;
this.lockTimeoutTimeUnit = lockTimeoutTimeUnit;
}
}
/**
* This method is intended to be used only for testing purposes.
*
* To ensure that only one instance of the FilesystemPackageCacheManagerLocks is created for a given cacheFolder, use
* the static org.hl7.fhir.utilities.npm.FilesystemPackageCacheManagerLocks#getFilesystemPackageCacheManagerLocks(java.io.File) method.
*
* Get all the locks necessary to manage a filesystem cache.
*
* @param cacheFolder
* @throws IOException
*/
public FilesystemPackageCacheManagerLocks(File cacheFolder) throws IOException {
this.cacheFolder = cacheFolder;
}
/**
* Returns a single FilesystemPackageCacheManagerLocks instance for the given cacheFolder.
*
* If an instance already exists, it is returned. Otherwise, a new instance is created.
*
* Using this method ensures that only one instance of FilesystemPackageCacheManagerLocks is created for a given
* cacheFolder, which is useful if multiple ValidationEngine instances are running in parallel.
*
* @param cacheFolder
* @return
* @throws IOException
*/
public static FilesystemPackageCacheManagerLocks getFilesystemPackageCacheManagerLocks(File cacheFolder) throws IOException {
return cacheFolderLockManagers.computeIfAbsent(cacheFolder, k -> {
try {
return new FilesystemPackageCacheManagerLocks(k);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
public class CacheLock {
private final ReadWriteLock lock;
protected CacheLock() {
lock = new ReentrantReadWriteLock();
}
public ReadWriteLock getLock() {
return lock;
}
public T doWriteWithLock(FilesystemPackageCacheManager.CacheLockFunction f) throws IOException {
lock.writeLock().lock();
T result = null;
try {
result = f.get();
} finally {
lock.writeLock().unlock();
}
return result;
}
public boolean canLockFileBeHeldByThisProcess(File lockFile) throws IOException {
return doWriteWithLock(() -> {
try (FileChannel channel = new RandomAccessFile(lockFile, "rw").getChannel()) {
FileLock fileLock = channel.tryLock(0, Long.MAX_VALUE, false);
if (fileLock != null) {
fileLock.release();
channel.close();
return true;
}
}
return false;});
}
}
public class PackageLock {
@Getter
private final File lockFile;
private final ReadWriteLock lock;
protected PackageLock(File lockFile, ReadWriteLock lock) {
this.lockFile = lockFile;
this.lock = lock;
}
private void checkForLockFileWaitForDeleteIfExists(File lockFile, @Nonnull LockParameters lockParameters) throws IOException {
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, false);
if (fileLock != null) {
fileLock.release();
channel.close();
throw new IOException("Lock file exists, but is not locked by a process: " + lockFile.getName());
}
System.out.println("File is locked.");
}
}
try {
waitForLockFileDeletion(lockFile, lockParameters);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Thread interrupted while waiting for lock", e);
}
}
/*
Wait for the lock file to be deleted. If the lock file is not deleted within the timeout or if the thread is
interrupted, an IOException is thrown.
*/
private void waitForLockFileDeletion(File lockFile, @Nonnull LockParameters lockParameters) throws IOException, InterruptedException {
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
Path dir = lockFile.getParentFile().toPath();
dir.register(watchService, StandardWatchEventKinds.ENTRY_DELETE);
WatchKey key = watchService.poll(lockParameters.lockTimeoutTime, lockParameters.lockTimeoutTimeUnit);
if (key == null) {
// It is possible that the lock file is deleted before the watch service is registered, so if we timeout at
// this point, we should check if the lock file still exists.
if (lockFile.exists()) {
throw new TimeoutException("Timeout waiting for lock file deletion: " + lockFile.getName());
}
} else {
for (WatchEvent> event : key.pollEvents()) {
WatchEvent.Kind> kind = event.kind();
if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
Path deletedFilePath = (Path) event.context();
if (deletedFilePath.toString().equals(lockFile.getName())) {
return;
}
}
key.reset();
}
}
} catch (TimeoutException e) {
throw new IOException("Package cache timed out waiting for lock.", e);
}
}
/**
* Wraps the execution of a package read function with the appropriate cache, package, and .lock file locking and
* checks.
*
* @param function The function to execute
* @param lockParameters The parameters for the lock
* @return The return of the function
* @param The return type of the function
* @throws IOException If an error occurs while managing locking.
*/
public T doReadWithLock(FilesystemPackageCacheManager.CacheLockFunction function, @Nullable LockParameters lockParameters) throws IOException {
final LockParameters resolvedLockParameters = lockParameters != null ? lockParameters : FilesystemPackageCacheManagerLocks.lockParameters;
cacheLock.getLock().readLock().lock();
lock.readLock().lock();
checkForLockFileWaitForDeleteIfExists(lockFile, resolvedLockParameters);
T result = null;
try {
result = function.get();
} finally {
lock.readLock().unlock();
cacheLock.getLock().readLock().unlock();
}
return result;
}
/**
* Wraps the execution of a package write function with the appropriate cache, package, and .lock file locking and
* checks.
*
* @param function The function to execute
* @param lockParameters The parameters for the lock
* @return The return of the function
* @param The return type of the function
* @throws IOException If an error occurs while managing locking.
*/
public T doWriteWithLock(FilesystemPackageCacheManager.CacheLockFunction function, @Nullable LockParameters lockParameters) throws IOException {
final LockParameters resolvedLockParameters = lockParameters != null ? lockParameters : FilesystemPackageCacheManagerLocks.lockParameters;
cacheLock.getLock().writeLock().lock();
lock.writeLock().lock();
/*TODO Eventually, this logic should exist in a Lockfile class so that it isn't duplicated between the main code and
the test code.
*/
try (FileChannel channel = new RandomAccessFile(lockFile, "rw").getChannel()) {
FileLock fileLock = channel.tryLock(0, Long.MAX_VALUE, false);
if (fileLock == null) {
waitForLockFileDeletion(lockFile, resolvedLockParameters);
fileLock = channel.tryLock(0, Long.MAX_VALUE, false);
}
if (fileLock == null) {
throw new IOException("Failed to acquire lock on file: " + lockFile.getName());
}
if (!lockFile.isFile()) {
final ByteBuffer buff = ByteBuffer.wrap(String.valueOf(ProcessHandle.current().pid()).getBytes(StandardCharsets.UTF_8));
channel.write(buff);
}
T result = null;
try {
result = function.get();
} finally {
lockFile.renameTo(ManagedFileAccess.file(File.createTempFile(lockFile.getName(), ".lock-renamed").getAbsolutePath()));
fileLock.release();
channel.close();
if (!lockFile.delete()) {
lockFile.deleteOnExit();
}
lock.writeLock().unlock();
cacheLock.getLock().writeLock().unlock();
}
return result;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Thread interrupted while waiting for lock", e);
}
}
}
public synchronized PackageLock getPackageLock(String packageName) throws IOException {
File lockFile = new File(Utilities.path(cacheFolder.getAbsolutePath(), packageName + ".lock"));
return packageLocks.computeIfAbsent(lockFile, (k) -> new PackageLock(k, new ReentrantReadWriteLock()));
}
}