
org.gradle.cache.internal.DefaultCacheAccess Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.cache.internal;
import net.jcip.annotations.ThreadSafe;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.cache.CacheOpenException;
import org.gradle.cache.PersistentIndexedCacheParameters;
import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
import org.gradle.cache.internal.cacheops.CacheAccessOperationsStack;
import org.gradle.cache.internal.filelock.LockOptions;
import org.gradle.internal.Factories;
import org.gradle.internal.Factory;
import org.gradle.internal.UncheckedException;
import org.gradle.internal.concurrent.CompositeStoppable;
import org.gradle.internal.serialize.Serializer;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static org.gradle.cache.internal.FileLockManager.LockMode.Exclusive;
import static org.gradle.cache.internal.FileLockManager.LockMode.Shared;
@ThreadSafe
public class DefaultCacheAccess implements CacheCoordinator {
private final static Logger LOG = Logging.getLogger(DefaultCacheAccess.class);
private final String cacheDisplayName;
private final File lockTarget;
private final File baseDir;
private final FileLockManager lockManager;
private final CacheInitializationAction initializationAction;
private final FileAccess fileAccess = new UnitOfWorkFileAccess();
private final Set caches = new HashSet();
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private Thread owner;
private LockOptions lockOptions;
private FileLock fileLock;
private FileLock.State stateAtOpen;
private boolean contended;
private final CacheAccessOperationsStack operations;
private int cacheClosedCount;
public DefaultCacheAccess(String cacheDisplayName, File lockTarget, File baseDir, FileLockManager lockManager, CacheInitializationAction initializationAction) {
this.cacheDisplayName = cacheDisplayName;
this.lockTarget = lockTarget;
this.baseDir = baseDir;
this.lockManager = lockManager;
this.initializationAction = initializationAction;
this.operations = new CacheAccessOperationsStack();
}
public void open(LockOptions lockOptions) {
lock.lock();
try {
if (this.lockOptions != null) {
throw new IllegalStateException(String.format("Cannot open the %s, as it has already been opened.", cacheDisplayName));
}
this.lockOptions = lockOptions;
if (lockOptions.getMode() == FileLockManager.LockMode.None) {
return;
}
if (fileLock != null) {
throw new IllegalStateException("File lock " + lockTarget + " is already open.");
}
fileLock = lockManager.lock(lockTarget, lockOptions, cacheDisplayName);
boolean rebuild = initializationAction.requiresInitialization(fileLock);
if (rebuild) {
if (lockOptions.getMode() == Exclusive) {
fileLock.writeFile(new Runnable() {
public void run() {
initializationAction.initialize(fileLock);
}
});
} else {
for (int tries = 0; rebuild && tries < 3; tries++) {
fileLock.close();
fileLock = lockManager.lock(lockTarget, lockOptions.withMode(Exclusive), cacheDisplayName, "Initialize cache");
rebuild = initializationAction.requiresInitialization(fileLock);
if (rebuild) {
fileLock.writeFile(new Runnable() {
public void run() {
initializationAction.initialize(fileLock);
}
});
}
fileLock.close();
fileLock = lockManager.lock(lockTarget, lockOptions, cacheDisplayName);
rebuild = initializationAction.requiresInitialization(fileLock);
}
if (rebuild) {
throw new CacheOpenException(String.format("Failed to initialize %s", cacheDisplayName));
}
}
}
stateAtOpen = fileLock.getState();
takeOwnership(String.format("Access %s", cacheDisplayName));
} catch (Throwable throwable) {
if (fileLock != null) {
fileLock.close();
fileLock = null;
}
throw UncheckedException.throwAsUncheckedException(throwable);
} finally {
lock.unlock();
}
}
private void closeFileLock() {
try {
cacheClosedCount++;
try {
// Close the caches and then notify them of the final state, in case the caches do work on close
new CompositeStoppable().add(caches).stop();
FileLock.State state = fileLock.getState();
for (MultiProcessSafePersistentIndexedCache cache : caches) {
cache.onEndWork(state);
}
} finally {
fileLock.close();
}
} finally {
fileLock = null;
stateAtOpen = null;
contended = false;
}
}
public void close() {
lock.lock();
try {
// Take ownership
if (owner == null) {
owner = Thread.currentThread();
} else if (lockOptions.getMode() != Shared && owner != Thread.currentThread()) {
// TODO:ADAM - The check for shared mode is a work around. Owner should release the lock
throw new IllegalStateException(String.format("Cannot close %s as it is currently being used by another thread.", cacheDisplayName));
}
if (fileLock != null) {
closeFileLock();
}
if (cacheClosedCount != 1) {
LOG.debug("Cache {} was closed {} times.", cacheDisplayName, cacheClosedCount);
}
} finally {
lockOptions = null;
owner = null;
lock.unlock();
}
}
public void useCache(String operationDisplayName, Runnable action) {
useCache(operationDisplayName, Factories.toFactory(action));
}
public T useCache(String operationDisplayName, Factory extends T> factory) {
if (lockOptions != null && lockOptions.getMode() == FileLockManager.LockMode.Shared) {
throw new UnsupportedOperationException("Not implemented yet.");
}
boolean wasStarted = false;
lock.lock();
try {
takeOwnership(operationDisplayName);
wasStarted = onStartWork();
} finally {
lock.unlock();
}
try {
return factory.create();
} finally {
lock.lock();
try {
try {
if (wasStarted) {
onEndWork();
}
} finally {
releaseOwnership();
}
} finally {
lock.unlock();
}
}
}
private void takeOwnership(String operationDisplayName) {
lock.lock();
try {
while (owner != null && owner != Thread.currentThread()) {
try {
condition.await();
} catch (InterruptedException e) {
throw UncheckedException.throwAsUncheckedException(e);
}
}
owner = Thread.currentThread();
operations.pushCacheAction(operationDisplayName);
} finally {
lock.unlock();
}
}
private void releaseOwnership() {
lock.lock();
try {
operations.popCacheAction();
if (!operations.isInCacheAction()) {
owner = null;
condition.signalAll();
}
} finally {
lock.unlock();
}
}
public T longRunningOperation(String operationDisplayName, Factory extends T> action) {
boolean wasEnded = startLongRunningOperation(operationDisplayName);
try {
return action.create();
} finally {
finishLongRunningOperation(wasEnded);
}
}
private boolean startLongRunningOperation(String operationDisplayName) {
boolean wasEnded;
lock.lock();
try {
if (lockOptions == null || lockOptions.getMode() == Shared) {
throw new UnsupportedOperationException("Not supported for this lock mode.");
}
if (operations.isInCacheAction()) {
checkThreadIsOwner();
wasEnded = onEndWork();
owner = null;
condition.signalAll();
} else {
wasEnded = false;
}
operations.pushLongRunningOperation(operationDisplayName);
} finally {
lock.unlock();
}
return wasEnded;
}
private void finishLongRunningOperation(boolean wasEnded) {
lock.lock();
try {
operations.popLongRunningOperation();
if (operations.isInCacheAction()) {
restoreOwner();
if (wasEnded) {
onStartWork();
}
}
} finally {
lock.unlock();
}
}
private void checkThreadIsOwner() {
lock.lock();
try {
if (owner != Thread.currentThread()) {
throw new IllegalStateException(String.format("Cannot start long running operation, as the %s has not been locked.", cacheDisplayName));
}
} finally {
lock.unlock();
}
}
private void restoreOwner() {
lock.lock();
try {
while (owner != null) {
try {
condition.await();
} catch (InterruptedException e) {
throw UncheckedException.throwAsUncheckedException(e);
}
}
owner = Thread.currentThread();
} finally {
lock.unlock();
}
}
public void longRunningOperation(String operationDisplayName, Runnable action) {
longRunningOperation(operationDisplayName, Factories.toFactory(action));
}
public MultiProcessSafePersistentIndexedCache newCache(final PersistentIndexedCacheParameters parameters) {
final File cacheFile = new File(baseDir, parameters.getCacheName() + ".bin");
Factory> indexedCacheFactory = new Factory>() {
public BTreePersistentIndexedCache create() {
return doCreateCache(cacheFile, parameters.getKeySerializer(), parameters.getValueSerializer());
}
};
MultiProcessSafePersistentIndexedCache indexedCache = new DefaultMultiProcessSafePersistentIndexedCache(indexedCacheFactory, fileAccess);
CacheDecorator decorator = parameters.getCacheDecorator();
indexedCache = decorator == null ? indexedCache : decorator.decorate(cacheFile.getAbsolutePath(), parameters.getCacheName(), indexedCache);
lock.lock();
try {
caches.add(indexedCache);
if (fileLock != null) {
String description = operations.isInCacheAction() ? operations.getDescription() : "cache creation";
indexedCache.onStartWork(description, stateAtOpen);
}
} finally {
lock.unlock();
}
return indexedCache;
}
BTreePersistentIndexedCache doCreateCache(File cacheFile, Serializer keySerializer, Serializer valueSerializer) {
return new BTreePersistentIndexedCache(cacheFile, keySerializer, valueSerializer);
}
private boolean onStartWork() {
if (fileLock != null) {
return false;
}
fileLock = lockManager.lock(lockTarget, lockOptions.withMode(Exclusive), cacheDisplayName, operations.getDescription());
if (initializationAction.requiresInitialization(fileLock)) {
fileLock.writeFile(new Runnable() {
public void run() {
initializationAction.initialize(fileLock);
}
});
}
stateAtOpen = fileLock.getState();
for (UnitOfWorkParticipant cache : caches) {
cache.onStartWork(operations.getDescription(), stateAtOpen);
}
lockManager.allowContention(fileLock, whenContended());
return true;
}
private boolean onEndWork() {
if (fileLock == null) {
return false;
}
if (contended || fileLock.getMode() == Shared) {
closeFileLock();
}
return true;
}
private FileLock getLock() {
lock.lock();
try {
if (Thread.currentThread() != owner) {
throw new IllegalStateException(String.format("The %s has not been locked for this thread. File lock: %s, owner: %s", cacheDisplayName, fileLock != null, owner));
}
} finally {
lock.unlock();
}
return fileLock;
}
private class UnitOfWorkFileAccess extends AbstractFileAccess {
@Override
public String toString() {
return cacheDisplayName;
}
public T readFile(Factory extends T> action) throws LockTimeoutException {
return getLock().readFile(action);
}
public void updateFile(Runnable action) throws LockTimeoutException {
getLock().updateFile(action);
}
public void writeFile(Runnable action) throws LockTimeoutException {
getLock().writeFile(action);
}
}
Runnable whenContended() {
return new Runnable() {
public void run() {
lock.lock();
try {
LOG.debug("Detected file lock contention of {} (fileLock={}, contended={}, owner={})", cacheDisplayName, fileLock != null, contended, owner);
if (fileLock == null) {
//the lock may have been closed
return;
}
if (owner != null) {
contended = true;
return;
}
takeOwnership("Other process requested access to " + cacheDisplayName);
try {
closeFileLock();
} finally {
releaseOwnership();
}
} finally {
lock.unlock();
}
}
};
}
Thread getOwner() {
return owner;
}
FileAccess getFileAccess() {
return fileAccess;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy