
net.sf.ehcache.transaction.local.LocalTransactionStore Maven / Gradle / Ivy
The newest version!
/**
* Copyright Terracotta, Inc.
*
* 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 net.sf.ehcache.transaction.local;
import net.sf.ehcache.CacheEntry;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.TransactionController;
import net.sf.ehcache.store.ElementValueComparator;
import net.sf.ehcache.store.Store;
import net.sf.ehcache.transaction.AbstractTransactionStore;
import net.sf.ehcache.transaction.DeadLockException;
import net.sf.ehcache.transaction.SoftLock;
import net.sf.ehcache.transaction.SoftLockID;
import net.sf.ehcache.transaction.SoftLockManager;
import net.sf.ehcache.transaction.TransactionException;
import net.sf.ehcache.transaction.TransactionID;
import net.sf.ehcache.transaction.TransactionIDFactory;
import net.sf.ehcache.transaction.TransactionInterruptedException;
import net.sf.ehcache.transaction.TransactionTimeoutException;
import net.sf.ehcache.util.LargeSet;
import net.sf.ehcache.util.SetAsList;
import net.sf.ehcache.writer.CacheWriterManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* A Store implementation with support for local transactions
*
* @author Ludovic Orban
*/
public class LocalTransactionStore extends AbstractTransactionStore {
private static final Logger LOG = LoggerFactory.getLogger(LocalTransactionStore.class.getName());
private final TransactionController transactionController;
private final TransactionIDFactory transactionIdFactory;
private final SoftLockManager softLockManager;
private final Ehcache cache;
private final String cacheName;
private final ElementValueComparator comparator;
/**
* Create a new LocalTransactionStore instance
* @param transactionController the TransactionController
* @param softLockManager the SoftLockManager
* @param cache the cache
* @param store the underlying store
* @param comparator the element value comparator
*/
public LocalTransactionStore(TransactionController transactionController, TransactionIDFactory transactionIdFactory,
SoftLockManager softLockManager, Ehcache cache, Store store, ElementValueComparator comparator) {
super(store);
this.transactionController = transactionController;
this.transactionIdFactory = transactionIdFactory;
this.softLockManager = softLockManager;
this.cache = cache;
this.comparator = comparator;
this.cacheName = cache.getName();
transactionController.getRecoveryManager().register(this);
}
@Override
public void dispose() {
super.dispose();
transactionController.getRecoveryManager().unregister(this);
}
/**
* Get the cache using this store
* @return the cache using this store
*/
Ehcache getCache() {
return cache;
}
private LocalTransactionContext getCurrentTransactionContext() {
LocalTransactionContext currentTransactionContext = transactionController.getCurrentTransactionContext();
if (currentTransactionContext == null) {
throw new TransactionException("transaction not started");
}
return currentTransactionContext;
}
private void assertNotTimedOut() {
if (getCurrentTransactionContext().timedOut()) {
throw new TransactionTimeoutException("transaction [" + getCurrentTransactionContext().getTransactionId() + "] timed out");
}
if (Thread.interrupted()) {
throw new TransactionInterruptedException("transaction [" + getCurrentTransactionContext().getTransactionId() +
"] interrupted");
}
}
private long timeBeforeTimeout() {
return getCurrentTransactionContext().timeBeforeTimeout();
}
private Element createElement(Object key, SoftLockID softLockId) {
Element element = new Element(key, softLockId);
element.setEternal(true);
return element;
}
private boolean cleanupExpiredSoftLock(Element oldElement, SoftLockID softLockId) {
SoftLock softLock = softLockManager.findSoftLockById(softLockId);
if (softLock == null || !softLock.isExpired()) {
return false;
} else {
softLock.lock();
softLock.freeze();
try {
Element frozenElement;
if (transactionIdFactory.isDecisionCommit(softLockId.getTransactionID())) {
frozenElement = softLockId.getNewElement();
} else {
frozenElement = softLockId.getOldElement();
}
if (frozenElement != null) {
underlyingStore.replace(oldElement, frozenElement, comparator);
} else {
underlyingStore.removeElement(oldElement, comparator);
}
} finally {
softLock.unfreeze();
softLock.unlock();
}
return true;
}
}
/**
* Recover and resolve all known soft locks
* @return a set of transaction IDs that were recovered
*/
public Set recover() {
Set allOurTransactionIDs = transactionIdFactory.getAllTransactionIDs();
Set recoveredIds = new HashSet(allOurTransactionIDs);
Iterator iterator = recoveredIds.iterator();
while (iterator.hasNext()) {
TransactionID transactionId = iterator.next();
if (!transactionIdFactory.isExpired(transactionId)) {
iterator.remove();
}
}
LOG.debug("recover: {} dead transactions are going to be recovered", recoveredIds.size());
for (TransactionID transactionId : recoveredIds) {
Set softLocks = new HashSet(softLockManager.collectAllSoftLocksForTransactionID(transactionId));
Iterator softLockIterator = softLocks.iterator();
while (softLockIterator.hasNext()) {
SoftLock softLock = softLockIterator.next();
Element element = underlyingStore.getQuiet(softLock.getKey());
if (element.getObjectValue() instanceof SoftLockID) {
SoftLockID softLockId = (SoftLockID)element.getObjectValue();
cleanupExpiredSoftLock(element, softLockId);
} else {
softLockIterator.remove();
}
}
LOG.debug("recover: recovered {} soft locks from dead transaction with ID [{}]", softLocks.size(), transactionId);
}
return recoveredIds;
}
/* transactional methods */
/**
* {@inheritDoc}
*/
public boolean put(Element element) throws CacheException {
if (element == null) {
return true;
}
final Object key = element.getObjectKey();
while (true) {
assertNotTimedOut();
Element oldElement = underlyingStore.getQuiet(key);
if (oldElement == null) {
SoftLockID softLockId = softLockManager.createSoftLockID(getCurrentTransactionContext().getTransactionId(), key,
element, null);
SoftLock softLock = softLockManager.findSoftLockById(softLockId);
softLock.lock();
Element newElement = createElement(key, softLockId);
oldElement = underlyingStore.putIfAbsent(newElement);
if (oldElement == null) {
// CAS succeeded, soft lock is in store, job done.
getCurrentTransactionContext().registerSoftLock(cacheName, this, softLock);
LOG.debug("put: cache [{}] key [{}] was not in, soft lock inserted", cacheName, key);
return true;
} else {
// CAS failed, something with that key may now be in store, restart.
softLock.unlock();
LOG.debug("put: cache [{}] key [{}] was not in, soft lock insertion failed, retrying...", cacheName, key);
continue;
}
} else {
Object value = oldElement.getObjectValue();
if (value instanceof SoftLockID) {
SoftLockID softLockId = (SoftLockID) value;
if (cleanupExpiredSoftLock(oldElement, softLockId)) {
LOG.debug("put: cache [{}] key [{}] guarded by expired soft lock, cleaned up {}",
new Object[] {cacheName, key, softLockId});
continue;
}
if (softLockId.getTransactionID().equals(getCurrentTransactionContext().getTransactionId())) {
SoftLockID newSoftLockId = softLockManager.createSoftLockID(getCurrentTransactionContext().getTransactionId(), softLockId
.getKey(), element, softLockId.getOldElement());
Element newElement = createElement(newSoftLockId.getKey(), newSoftLockId);
underlyingStore.put(newElement);
LOG.debug("put: cache [{}] key [{}] soft locked in current transaction, replaced old value with new one under" +
" soft lock", cacheName, key);
// replaced old value with new one under soft lock, job done.
return false;
} else {
SoftLock softLock = softLockManager.findSoftLockById(softLockId);
if (softLock != null) {
LOG.debug("put: cache [{}] key [{}] soft locked in foreign transaction, waiting {}ms for soft lock to die...",
new Object[] {cacheName, key, timeBeforeTimeout()});
try {
if (!softLock.tryLock(timeBeforeTimeout())) {
LOG.debug("put: cache [{}] key [{}] soft locked in foreign transaction and not released before" +
" current transaction timeout", cacheName, key);
if (getCurrentTransactionContext().hasLockedAnything()) {
throw new DeadLockException("deadlock detected in cache [" + cacheName + "] on key [" + key + "]" +
" between current transaction [" + getCurrentTransactionContext().getTransactionId() + "]" +
" and foreign transaction [" + softLockId.getTransactionID() + "]");
} else {
continue;
}
} else {
softLock.clearTryLock();
}
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
LOG.debug("put: cache [{}] key [{}] soft locked in foreign transaction, soft lock died, retrying...",
cacheName, key);
// once the soft lock got unlocked we don't know what's in the store anymore, restart.
continue;
}
} else {
SoftLockID softLockId = softLockManager.createSoftLockID(getCurrentTransactionContext().getTransactionId(), key,
element, oldElement);
SoftLock softLock = softLockManager.findSoftLockById(softLockId);
softLock.lock();
Element newElement = createElement(key, softLockId);
boolean replaced = underlyingStore.replace(oldElement, newElement, comparator);
if (replaced) {
// CAS succeeded, value replaced with soft lock, job done.
getCurrentTransactionContext().registerSoftLock(cacheName, this, softLock);
LOG.debug("put: cache [{}] key [{}] was in, replaced with soft lock", cacheName, key);
return false;
} else {
// CAS failed, something else with that key is now in store or the key disappeared, restart.
softLock.unlock();
LOG.debug("put: cache [{}] key [{}] was in, replacement by soft lock failed, retrying... ", cacheName, key);
continue;
}
}
}
} // while
}
/**
* {@inheritDoc}
*/
public Element getQuiet(Object key) {
if (key == null) {
return null;
}
while (true) {
assertNotTimedOut();
Element oldElement = underlyingStore.getQuiet(key);
if (oldElement == null) {
LOG.debug("getQuiet: cache [{}] key [{}] is not present", cacheName, key);
return null;
}
Object value = oldElement.getObjectValue();
if (value instanceof SoftLockID) {
SoftLockID softLockId = (SoftLockID) value;
if (cleanupExpiredSoftLock(oldElement, softLockId)) {
LOG.debug("getQuiet: cache [{}] key [{}] guarded by expired soft lock, cleaned up {}",
new Object[] {cacheName, key, softLockId});
continue;
}
LOG.debug("getQuiet: cache [{}] key [{}] soft locked, returning soft locked element", cacheName, key);
SoftLock softLock = softLockManager.findSoftLockById(softLockId);
if (softLock == null) {
LOG.debug("getQuiet: cache [{}] key [{}] soft locked in foreign transaction, soft lock died, retrying...", cacheName, key);
continue;
} else {
return softLock.getElement(getCurrentTransactionContext().getTransactionId(), softLockId);
}
} else {
LOG.debug("getQuiet: cache [{}] key [{}] not soft locked, returning underlying element", cacheName, key);
return oldElement;
}
}
}
/**
* {@inheritDoc}
*/
public Element get(Object key) {
if (key == null) {
return null;
}
while (true) {
assertNotTimedOut();
Element oldElement = underlyingStore.get(key);
if (oldElement == null) {
LOG.debug("get: cache [{}] key [{}] is not present", cacheName, key);
return null;
}
Object value = oldElement.getObjectValue();
if (value instanceof SoftLockID) {
SoftLockID softLockId = (SoftLockID) value;
if (cleanupExpiredSoftLock(oldElement, softLockId)) {
LOG.debug("get: cache [{}] key [{}] guarded by expired soft lock, cleaned up {}",
new Object[] {cacheName, key, softLockId});
continue;
}
LOG.debug("get: cache [{}] key [{}] soft locked, returning soft locked element", cacheName, key);
SoftLock softLock = softLockManager.findSoftLockById(softLockId);
if (softLock == null) {
LOG.debug("get: cache [{}] key [{}] soft locked in foreign transaction, soft lock died, retrying...", cacheName, key);
continue;
} else {
return softLock.getElement(getCurrentTransactionContext().getTransactionId(), softLockId);
}
} else {
LOG.debug("get: cache [{}] key [{}] not soft locked, returning underlying element", cacheName, key);
return oldElement;
}
}
}
/**
* {@inheritDoc}
*/
public Element remove(Object key) {
if (key == null) {
return null;
}
while (true) {
final boolean isPinned = false;
assertNotTimedOut();
Element oldElement = underlyingStore.getQuiet(key);
if (oldElement == null) {
SoftLockID softLockId = softLockManager.createSoftLockID(getCurrentTransactionContext().getTransactionId(), key,
null, null);
SoftLock softLock = softLockManager.findSoftLockById(softLockId);
softLock.lock();
Element newElement = createElement(key, softLockId);
oldElement = underlyingStore.putIfAbsent(newElement);
if (oldElement == null) {
// CAS succeeded, value is in store, job done.
getCurrentTransactionContext().registerSoftLock(cacheName, this, softLock);
LOG.debug("remove: cache [{}] key [{}] was not in, soft lock inserted", cacheName, key);
return null;
} else {
// CAS failed, something with that key may now be in store, restart.
softLock.unlock();
LOG.debug("remove: cache [{}] key [{}] was not in, soft lock insertion failed, retrying...", cacheName, key);
continue;
}
} else {
Object value = oldElement.getObjectValue();
if (value instanceof SoftLockID) {
SoftLockID softLockId = (SoftLockID) value;
if (cleanupExpiredSoftLock(oldElement, softLockId)) {
LOG.debug("remove: cache [{}] key [{}] guarded by expired soft lock, cleaned up {}",
new Object[] {cacheName, key, softLockId});
continue;
}
if (softLockId.getTransactionID().equals(getCurrentTransactionContext().getTransactionId())) {
SoftLockID newSoftLockId = softLockManager.createSoftLockID(getCurrentTransactionContext().getTransactionId(), softLockId
.getKey(), null, softLockId.getOldElement());
Element newElement = createElement(newSoftLockId.getKey(), newSoftLockId);
underlyingStore.put(newElement);
// replaced old value with new one under soft lock, job done.
LOG.debug("remove: cache [{}] key [{}] soft locked in current transaction, replaced old value with new one under" +
" soft lock", cacheName, key);
return softLockId.getNewElement();
} else {
SoftLock softLock = softLockManager.findSoftLockById(softLockId);
if (softLock != null) {
LOG.debug("remove: cache [{}] key [{}] soft locked in foreign transaction, waiting {}ms for soft lock to" +
" die...", new Object[] {cacheName, key, timeBeforeTimeout()});
try {
if (softLock.tryLock(timeBeforeTimeout())) {
softLock.clearTryLock();
} else {
LOG.debug("remove: cache [{}] key [{}] soft locked in foreign transaction and not released before" +
" current transaction timeout", cacheName, key);
if (getCurrentTransactionContext().hasLockedAnything()) {
throw new DeadLockException("deadlock detected in cache [" + cacheName + "] on key [" + key + "]" +
" between current transaction [" + getCurrentTransactionContext().getTransactionId() + "]" +
" and foreign transaction [" + softLockId.getTransactionID() + "]");
} else {
continue;
}
}
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
// once the soft lock got unlocked we don't know what's in the store anymore, restart.
LOG.debug("remove: cache [{}] key [{}] soft locked in foreign transaction, soft lock died, retrying...",
cacheName, key);
continue;
}
} else {
SoftLockID softLockId = softLockManager.createSoftLockID(getCurrentTransactionContext().getTransactionId(), key,
null, oldElement);
SoftLock softLock = softLockManager.findSoftLockById(softLockId);
softLock.lock();
Element newElement = createElement(key, softLockId);
boolean replaced = underlyingStore.replace(oldElement, newElement, comparator);
if (replaced) {
// CAS succeeded, value replaced with soft lock, job done.
getCurrentTransactionContext().registerSoftLock(cacheName, this, softLock);
LOG.debug("remove: cache [{}] key [{}] was in, replaced with soft lock", cacheName, key);
return oldElement;
} else {
// CAS failed, something else with that key is now in store or the key disappeared, restart.
softLock.unlock();
LOG.debug("remove: cache [{}] key [{}] was in, replacement by soft lock failed, retrying...", cacheName, key);
continue;
}
}
}
} // while
}
/**
* {@inheritDoc}
*/
public List getKeys() {
assertNotTimedOut();
Set
© 2015 - 2025 Weber Informatics LLC | Privacy Policy