import akka.dispatch.Mapper;
import akka.dispatch.OnComplete;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.FinalizablePhantomReference;
-import com.google.common.base.FinalizableReferenceQueue;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.SettableFuture;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opendaylight.controller.cluster.datastore.compat.PreLithiumTransactionContextImpl;
import org.opendaylight.controller.cluster.datastore.exceptions.NoShardLeaderException;
import org.opendaylight.controller.cluster.datastore.identifiers.TransactionIdentifier;
-import org.opendaylight.controller.cluster.datastore.messages.CloseTransaction;
import org.opendaylight.controller.cluster.datastore.messages.CreateTransaction;
import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionReply;
import org.opendaylight.controller.cluster.datastore.shardstrategy.ShardStrategyFactory;
import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
import org.opendaylight.controller.sal.core.spi.data.AbstractDOMStoreTransaction;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
-import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
import org.opendaylight.yangtools.util.concurrent.MappingCheckedFuture;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
}
}
+ private static enum TransactionState {
+ OPEN,
+ READY,
+ CLOSED,
+ }
+
static final Mapper<Throwable, Throwable> SAME_FAILURE_TRANSFORMER =
new Mapper<Throwable, Throwable>() {
@Override
private static final FiniteDuration CREATE_TX_TRY_INTERVAL =
FiniteDuration.create(1, TimeUnit.SECONDS);
- /**
- * Used to enqueue the PhantomReferences for read-only TransactionProxy instances. The
- * FinalizableReferenceQueue is safe to use statically in an OSGi environment as it uses some
- * trickery to clean up its internal thread when the bundle is unloaded.
- */
- private static final FinalizableReferenceQueue phantomReferenceQueue =
- new FinalizableReferenceQueue();
-
- /**
- * This stores the TransactionProxyCleanupPhantomReference instances statically, This is
- * necessary because PhantomReferences need a hard reference so they're not garbage collected.
- * Once finalized, the TransactionProxyCleanupPhantomReference removes itself from this map
- * and thus becomes eligible for garbage collection.
- */
- private static final Map<TransactionProxyCleanupPhantomReference,
- TransactionProxyCleanupPhantomReference> phantomReferenceCache =
- new ConcurrentHashMap<>();
-
- /**
- * A PhantomReference that closes remote transactions for a TransactionProxy when it's
- * garbage collected. This is used for read-only transactions as they're not explicitly closed
- * by clients. So the only way to detect that a transaction is no longer in use and it's safe
- * to clean up is when it's garbage collected. It's inexact as to when an instance will be GC'ed
- * but TransactionProxy instances should generally be short-lived enough to avoid being moved
- * to the old generation space and thus should be cleaned up in a timely manner as the GC
- * runs on the young generation (eden, swap1...) space much more frequently.
- */
- private static class TransactionProxyCleanupPhantomReference
- extends FinalizablePhantomReference<TransactionProxy> {
-
- private final List<ActorSelection> remoteTransactionActors;
- private final AtomicBoolean remoteTransactionActorsMB;
- private final ActorContext actorContext;
- private final TransactionIdentifier identifier;
-
- protected TransactionProxyCleanupPhantomReference(TransactionProxy referent) {
- super(referent, phantomReferenceQueue);
-
- // Note we need to cache the relevant fields from the TransactionProxy as we can't
- // have a hard reference to the TransactionProxy instance itself.
-
- remoteTransactionActors = referent.remoteTransactionActors;
- remoteTransactionActorsMB = referent.remoteTransactionActorsMB;
- actorContext = referent.actorContext;
- identifier = referent.getIdentifier();
- }
-
- @Override
- public void finalizeReferent() {
- LOG.trace("Cleaning up {} Tx actors for TransactionProxy {}",
- remoteTransactionActors.size(), identifier);
-
- phantomReferenceCache.remove(this);
-
- // Access the memory barrier volatile to ensure all previous updates to the
- // remoteTransactionActors list are visible to this thread.
-
- if(remoteTransactionActorsMB.get()) {
- for(ActorSelection actor : remoteTransactionActors) {
- LOG.trace("Sending CloseTransaction to {}", actor);
- actorContext.sendOperationAsync(actor, CloseTransaction.INSTANCE.toSerializable());
- }
- }
- }
- }
-
/**
* Stores the remote Tx actors for each requested data store path to be used by the
* PhantomReference to close the remote Tx's. This is only used for read-only Tx's. The
* remoteTransactionActors list so they will be visible to the thread accessing the
* PhantomReference.
*/
- private List<ActorSelection> remoteTransactionActors;
- private volatile AtomicBoolean remoteTransactionActorsMB;
+ List<ActorSelection> remoteTransactionActors;
+ volatile AtomicBoolean remoteTransactionActorsMB;
/**
* Stores the create transaction results per shard.
private final Map<String, TransactionFutureCallback> txFutureCallbackMap = new HashMap<>();
private final TransactionType transactionType;
- private final ActorContext actorContext;
+ final ActorContext actorContext;
private final String transactionChainId;
private final SchemaContext schemaContext;
- private boolean inReadyState;
+ private TransactionState state = TransactionState.OPEN;
private volatile boolean initialized;
private Semaphore operationLimiter;
private void checkModificationState() {
Preconditions.checkState(transactionType != TransactionType.READ_ONLY,
"Modification operation on read-only transaction is not allowed");
- Preconditions.checkState(!inReadyState,
+ Preconditions.checkState(state == TransactionState.OPEN,
"Transaction is sealed - further modifications are not allowed");
}
}
}
-
@Override
public void write(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
});
}
- @Override
- public DOMStoreThreePhaseCommitCohort ready() {
+ private boolean seal(final TransactionState newState) {
+ if (state == TransactionState.OPEN) {
+ state = newState;
+ return true;
+ } else {
+ return false;
+ }
+ }
- checkModificationState();
+ @Override
+ public AbstractThreePhaseCommitCohort ready() {
+ Preconditions.checkState(transactionType != TransactionType.READ_ONLY,
+ "Read-only transactions cannot be readied");
- inReadyState = true;
+ final boolean success = seal(TransactionState.READY);
+ Preconditions.checkState(success, "Transaction %s is %s, it cannot be readied", getIdentifier(), state);
LOG.debug("Tx {} Readying {} transactions for commit", getIdentifier(),
txFutureCallbackMap.size());
if (txFutureCallbackMap.isEmpty()) {
- onTransactionReady(Collections.<Future<ActorSelection>>emptyList());
TransactionRateLimitingCallback.adjustRateLimitForUnusedTransaction(actorContext);
return NoOpDOMStoreThreePhaseCommitCohort.INSTANCE;
}
throttleOperation(txFutureCallbackMap.size());
- List<Future<ActorSelection>> cohortFutures = Lists.newArrayList();
-
+ List<Future<ActorSelection>> cohortFutures = new ArrayList<>(txFutureCallbackMap.size());
for(TransactionFutureCallback txFutureCallback : txFutureCallbackMap.values()) {
LOG.debug("Tx {} Readying transaction for shard {} chain {}", getIdentifier(),
cohortFutures.add(future);
}
- onTransactionReady(cohortFutures);
-
return new ThreePhaseCommitCohortProxy(actorContext, cohortFutures,
getIdentifier().toString());
}
- /**
- * Method for derived classes to be notified when the transaction has been readied.
- *
- * @param cohortFutures the cohort Futures for each shard transaction.
- */
- protected void onTransactionReady(List<Future<ActorSelection>> cohortFutures) {
- }
-
@Override
public void close() {
+ if (!seal(TransactionState.CLOSED)) {
+ if (state == TransactionState.CLOSED) {
+ // Idempotent no-op as per AutoCloseable recommendation
+ return;
+ }
+
+ throw new IllegalStateException(String.format("Transaction %s is ready, it cannot be closed",
+ getIdentifier()));
+ }
+
for (TransactionFutureCallback txFutureCallback : txFutureCallbackMap.values()) {
txFutureCallback.enqueueTransactionOperation(new TransactionOperation() {
@Override
remoteTransactionActors = Lists.newArrayList();
remoteTransactionActorsMB = new AtomicBoolean();
- TransactionProxyCleanupPhantomReference cleanup =
- new TransactionProxyCleanupPhantomReference(TransactionProxy.this);
- phantomReferenceCache.put(cleanup, cleanup);
+ TransactionProxyCleanupPhantomReference.track(TransactionProxy.this);
}
// Add the actor to the remoteTransactionActors list for access by the