
org.ehcache.transactions.xa.internal.XATransactionContext Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache single jar, containing all modules
/*
* 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 org.ehcache.transactions.xa.internal;
import org.ehcache.core.spi.store.StoreAccessException;
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import org.ehcache.core.spi.time.TimeSource;
import org.ehcache.core.spi.store.Store;
import org.ehcache.core.spi.store.Store.RemoveStatus;
import org.ehcache.core.spi.store.Store.ReplaceStatus;
import org.ehcache.transactions.xa.internal.commands.Command;
import org.ehcache.transactions.xa.internal.commands.StoreEvictCommand;
import org.ehcache.transactions.xa.internal.commands.StorePutCommand;
import org.ehcache.transactions.xa.internal.commands.StoreRemoveCommand;
import org.ehcache.transactions.xa.internal.journal.Journal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* Context holder of an in-flight XA transaction. Modifications to the {@link XAStore} are registered in an instance
* of this class in the form of {@link Command}s and can then be applied to the {@link Store} backing the {@link XAStore}
* in the form of {@link SoftLock}s.
*
* @author Ludovic Orban
*/
public class XATransactionContext {
private static final Logger LOGGER = LoggerFactory.getLogger(XATransactionContext.class);
private final ConcurrentHashMap> commands = new ConcurrentHashMap>();
private final TransactionId transactionId;
private final Store> underlyingStore;
private final Journal journal;
private final TimeSource timeSource;
private final long timeoutTimestamp;
XATransactionContext(TransactionId transactionId, Store> underlyingStore, Journal journal, TimeSource timeSource, long timeoutTimestamp) {
this.transactionId = transactionId;
this.underlyingStore = underlyingStore;
this.journal = journal;
this.timeSource = timeSource;
this.timeoutTimestamp = timeoutTimestamp;
}
public boolean hasTimedOut() {
return timeSource.getTimeMillis() >= timeoutTimestamp;
}
public TransactionId getTransactionId() {
return transactionId;
}
public boolean addCommand(K key, Command command) {
if (commands.get(key) instanceof StoreEvictCommand) {
// once a mapping is marked as evict, that's the only thing that can happen
return false;
}
commands.put(key, command);
return true;
}
public void removeCommand(K key) {
commands.remove(key);
}
public Map> newValueHolders() {
Map> puts = new HashMap>();
for (Map.Entry> entry : commands.entrySet()) {
Command command = entry.getValue();
if (command instanceof StorePutCommand) {
puts.put(entry.getKey(), entry.getValue().getNewValueHolder());
}
}
return puts;
}
public boolean touched(K key) {
return commands.containsKey(key);
}
public boolean removed(K key) {
return commands.get(key) instanceof StoreRemoveCommand;
}
public boolean updated(K key) {
return commands.get(key) instanceof StorePutCommand;
}
public boolean evicted(K key) {
return commands.get(key) instanceof StoreEvictCommand;
}
public V oldValueOf(K key) {
Command command = commands.get(key);
return command != null ? command.getOldValue() : null;
}
public XAValueHolder newValueHolderOf(K key) {
Command command = commands.get(key);
return command != null ? command.getNewValueHolder() : null;
}
public V newValueOf(K key) {
Command command = commands.get(key);
XAValueHolder valueHolder = command == null ? null : command.getNewValueHolder();
return valueHolder == null ? null : valueHolder.value();
}
public int prepare() throws StoreAccessException, IllegalStateException, TransactionTimeoutException {
try {
if (hasTimedOut()) {
throw new TransactionTimeoutException();
}
if (journal.isInDoubt(transactionId)) {
throw new IllegalStateException("Cannot prepare transaction that is not in-flight : " + transactionId);
}
journal.saveInDoubt(transactionId, commands.keySet());
for (Map.Entry> entry : commands.entrySet()) {
if (entry.getValue() instanceof StoreEvictCommand) {
evictFromUnderlyingStore(entry.getKey());
continue;
}
V oldValue = entry.getValue().getOldValue();
SoftLock oldSoftLock = oldValue == null ? null : new SoftLock(null, oldValue, null);
SoftLock newSoftLock = new SoftLock(transactionId, oldValue, entry.getValue().getNewValueHolder());
if (oldSoftLock != null) {
boolean replaced = replaceInUnderlyingStore(entry.getKey(), oldSoftLock, newSoftLock);
if (!replaced) {
LOGGER.debug("prepare failed replace of softlock (concurrent modification?)");
evictFromUnderlyingStore(entry.getKey());
}
} else {
Store.ValueHolder> existing = putIfAbsentInUnderlyingStore(entry, newSoftLock);
if (existing != null) {
LOGGER.debug("prepare failed putIfAbsent of softlock (concurrent modification?)");
evictFromUnderlyingStore(entry.getKey());
}
}
}
if (commands.isEmpty()) {
journal.saveRolledBack(transactionId, false);
}
return commands.size();
} finally {
commands.clear();
}
}
/**
* @throws IllegalStateException if the transaction ID is unknown
* @throws IllegalArgumentException if the transaction ID has not been prepared
*/
public void commit(boolean recovering) throws StoreAccessException, IllegalStateException, IllegalArgumentException {
if (!journal.isInDoubt(transactionId)) {
if (recovering) {
throw new IllegalStateException("Cannot commit unknown transaction : " + transactionId);
} else {
throw new IllegalArgumentException("Cannot commit transaction that has not been prepared : " + transactionId);
}
}
Collection keys = journal.getInDoubtKeys(transactionId);
for (K key : keys) {
SoftLock preparedSoftLock = getFromUnderlyingStore(key);
XAValueHolder newValueHolder = preparedSoftLock == null ? null : preparedSoftLock.getNewValueHolder();
SoftLock definitiveSoftLock = newValueHolder == null ? null : new SoftLock(null, newValueHolder.value(), null);
if (preparedSoftLock != null) {
if (preparedSoftLock.getTransactionId() != null && !preparedSoftLock.getTransactionId().equals(transactionId)) {
LOGGER.debug("commit skipping prepared softlock with non-matching TX ID (concurrent modification?)");
evictFromUnderlyingStore(key);
continue;
}
if (definitiveSoftLock != null) {
boolean replaced = replaceInUnderlyingStore(key, preparedSoftLock, definitiveSoftLock);
if (!replaced) {
LOGGER.debug("commit failed replace of softlock (concurrent modification?)");
evictFromUnderlyingStore(key);
}
} else {
boolean removed = removeFromUnderlyingStore(key, preparedSoftLock);
if (!removed) {
LOGGER.debug("commit failed remove of softlock (concurrent modification?)");
evictFromUnderlyingStore(key);
}
}
} else {
LOGGER.debug("commit skipping evicted prepared softlock");
}
}
journal.saveCommitted(transactionId, false);
}
public void commitInOnePhase() throws StoreAccessException, IllegalStateException, TransactionTimeoutException {
if (journal.isInDoubt(transactionId)) {
throw new IllegalStateException("Cannot commit-one-phase transaction that has been prepared : " + transactionId);
}
int prepared = prepare();
if (prepared > 0) {
commit(false);
}
}
/**
* @throws IllegalStateException if the transaction ID is unknown
*/
public void rollback(boolean recovering) throws StoreAccessException, IllegalStateException {
boolean inDoubt = journal.isInDoubt(transactionId);
if (inDoubt) {
// phase 2 rollback
Collection keys = journal.getInDoubtKeys(transactionId);
for (K key : keys) {
SoftLock preparedSoftLock = getFromUnderlyingStore(key);
V oldValue = preparedSoftLock == null ? null : preparedSoftLock.getOldValue();
SoftLock definitiveSoftLock = oldValue == null ? null : new SoftLock(null, oldValue, null);
if (preparedSoftLock != null) {
if (preparedSoftLock.getTransactionId() != null && !preparedSoftLock.getTransactionId().equals(transactionId)) {
LOGGER.debug("rollback skipping prepared softlock with non-matching TX ID (concurrent modification?)");
evictFromUnderlyingStore(key);
continue;
}
if (definitiveSoftLock != null) {
boolean replaced = replaceInUnderlyingStore(key, preparedSoftLock, definitiveSoftLock);
if (!replaced) {
LOGGER.debug("rollback failed replace of softlock (concurrent modification?)");
evictFromUnderlyingStore(key);
}
} else {
boolean removed = removeFromUnderlyingStore(key, preparedSoftLock);
if (!removed) {
LOGGER.debug("rollback failed remove of softlock (concurrent modification?)");
evictFromUnderlyingStore(key);
}
}
} else {
LOGGER.debug("rollback skipping evicted prepared softlock");
}
}
journal.saveRolledBack(transactionId, false);
} else if (recovering) {
throw new IllegalStateException("Cannot rollback unknown transaction : " + transactionId);
} else {
// phase 1 rollback
commands.clear();
}
}
private boolean removeFromUnderlyingStore(K key, SoftLock preparedSoftLock) throws StoreAccessException {
if (underlyingStore.remove(key, preparedSoftLock).equals(RemoveStatus.REMOVED)) {
return true;
}
return false;
}
private boolean replaceInUnderlyingStore(K key, SoftLock preparedSoftLock, SoftLock definitiveSoftLock) throws StoreAccessException {
if (underlyingStore.replace(key, preparedSoftLock, definitiveSoftLock).equals(ReplaceStatus.HIT)) {
return true;
}
return false;
}
private Store.ValueHolder> putIfAbsentInUnderlyingStore(Map.Entry> entry, SoftLock newSoftLock) throws StoreAccessException {
return underlyingStore.putIfAbsent(entry.getKey(), newSoftLock);
}
private SoftLock getFromUnderlyingStore(K key) throws StoreAccessException {
Store.ValueHolder> softLockValueHolder = underlyingStore.get(key);
return softLockValueHolder == null ? null : softLockValueHolder.value();
}
private void evictFromUnderlyingStore(K key) throws StoreAccessException {
underlyingStore.remove(key);
}
static class TransactionTimeoutException extends RuntimeException {
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy