From 642f83ada35fca9ed7a8f242b90e41740803ddfa Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Wed, 15 Feb 2017 23:34:48 +0100 Subject: [PATCH] BUG-5280: add a transction purge step Backend needs to be informed about the client completing the transaction, so it can remove any state trakcing the result of the transaction. Since the frontend has no remaining state to transfer, there is no legal way for the frontend to ever reference it (aside the purge request itself), which always succeeds. Change-Id: Ia957f0879114eede394b76184620a38cd5967955 Signed-off-by: Robert Varga --- .../commands/TransactionPurgeRequest.java | 39 ++++++++++++++ .../TransactionPurgeRequestProxyV1.java | 38 ++++++++++++++ .../commands/TransactionPurgeResponse.java | 35 +++++++++++++ .../TransactionPurgeResponseProxyV1.java | 36 +++++++++++++ .../actors/dds/AbstractProxyTransaction.java | 40 +++++++++++++-- .../datastore/AbstractFrontendHistory.java | 51 ++++++++++++------- .../FrontendReadOnlyTransaction.java | 5 ++ .../FrontendReadWriteTransaction.java | 5 ++ .../datastore/FrontendTransaction.java | 9 ++++ 9 files changed, 237 insertions(+), 21 deletions(-) create mode 100644 opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeRequest.java create mode 100644 opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeRequestProxyV1.java create mode 100644 opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeResponse.java create mode 100644 opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeResponseProxyV1.java diff --git a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeRequest.java b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeRequest.java new file mode 100644 index 0000000000..a0fab70188 --- /dev/null +++ b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeRequest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.access.commands; + +import akka.actor.ActorRef; +import com.google.common.annotations.Beta; +import org.opendaylight.controller.cluster.access.ABIVersion; +import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier; + +/** + * A transaction request to perform the final transaction transition, which is purging it from the protocol view, + * meaning the frontend has no further knowledge of the transaction. The backend is free to purge any state related + * to the transaction and responds with a {@link TransactionPurgeResponse}. + * + * @author Robert Varga + */ +@Beta +public final class TransactionPurgeRequest extends TransactionRequest { + private static final long serialVersionUID = 1L; + + public TransactionPurgeRequest(final TransactionIdentifier target, final long sequence, final ActorRef replyTo) { + super(target, sequence, replyTo); + } + + @Override + protected TransactionPurgeRequestProxyV1 externalizableProxy(final ABIVersion version) { + return new TransactionPurgeRequestProxyV1(this); + } + + @Override + protected TransactionPurgeRequest cloneAsVersion(final ABIVersion version) { + return this; + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeRequestProxyV1.java b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeRequestProxyV1.java new file mode 100644 index 0000000000..ee56b4c81a --- /dev/null +++ b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeRequestProxyV1.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.access.commands; + +import akka.actor.ActorRef; +import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier; + +/** + * Externalizable proxy for use with {@link TransactionPurgeRequest}. It implements the initial (Boron) + * serialization format. + * + * @author Robert Varga + */ +final class TransactionPurgeRequestProxyV1 extends AbstractTransactionRequestProxy { + private static final long serialVersionUID = 1L; + + // checkstyle flags the public modifier as redundant however it is explicitly needed for Java serialization to + // be able to create instances via reflection. + @SuppressWarnings("checkstyle:RedundantModifier") + public TransactionPurgeRequestProxyV1() { + // For Externalizable + } + + TransactionPurgeRequestProxyV1(final TransactionPurgeRequest request) { + super(request); + } + + @Override + protected TransactionPurgeRequest createRequest(final TransactionIdentifier target, final long sequence, + final ActorRef replyTo) { + return new TransactionPurgeRequest(target, sequence, replyTo); + } +} diff --git a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeResponse.java b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeResponse.java new file mode 100644 index 0000000000..54710143bc --- /dev/null +++ b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeResponse.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.access.commands; + +import org.opendaylight.controller.cluster.access.ABIVersion; +import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier; + +/** + * Successful reply to a {@link TransactionPurgeRequest}. + * + * @author Robert Varga + */ +public final class TransactionPurgeResponse extends TransactionSuccess { + private static final long serialVersionUID = 1L; + + public TransactionPurgeResponse(final TransactionIdentifier identifier, final long sequence) { + super(identifier, sequence); + } + + @Override + protected AbstractTransactionSuccessProxy externalizableProxy( + final ABIVersion version) { + return new TransactionPurgeResponseProxyV1(this); + } + + @Override + protected TransactionPurgeResponse cloneAsVersion(final ABIVersion version) { + return this; + } +} diff --git a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeResponseProxyV1.java b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeResponseProxyV1.java new file mode 100644 index 0000000000..d15d7292b5 --- /dev/null +++ b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/commands/TransactionPurgeResponseProxyV1.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.access.commands; + +import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier; + +/** + * Externalizable proxy for use with {@link TransactionPurgeResponse}. It implements the initial (Boron) + * serialization format. + * + * @author Robert Varga + */ +final class TransactionPurgeResponseProxyV1 extends AbstractTransactionSuccessProxy { + private static final long serialVersionUID = 1L; + + // checkstyle flags the public modifier as redundant however it is explicitly needed for Java serialization to + // be able to create instances via reflection. + @SuppressWarnings("checkstyle:RedundantModifier") + public TransactionPurgeResponseProxyV1() { + // For Externalizable + } + + TransactionPurgeResponseProxyV1(final TransactionPurgeResponse success) { + super(success); + } + + @Override + protected TransactionPurgeResponse createSuccess(final TransactionIdentifier target, final long sequence) { + return new TransactionPurgeResponse(target, sequence); + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/databroker/actors/dds/AbstractProxyTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/databroker/actors/dds/AbstractProxyTransaction.java index e6313d3cfd..cb5a6cf229 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/databroker/actors/dds/AbstractProxyTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/databroker/actors/dds/AbstractProxyTransaction.java @@ -8,6 +8,7 @@ package org.opendaylight.controller.cluster.databroker.actors.dds; import akka.actor.ActorRef; +import com.google.common.base.MoreObjects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; @@ -34,6 +35,7 @@ import org.opendaylight.controller.cluster.access.commands.TransactionCommitSucc import org.opendaylight.controller.cluster.access.commands.TransactionDoCommitRequest; import org.opendaylight.controller.cluster.access.commands.TransactionPreCommitRequest; import org.opendaylight.controller.cluster.access.commands.TransactionPreCommitSuccess; +import org.opendaylight.controller.cluster.access.commands.TransactionPurgeRequest; import org.opendaylight.controller.cluster.access.commands.TransactionRequest; import org.opendaylight.controller.cluster.access.concepts.Request; import org.opendaylight.controller.cluster.access.concepts.RequestFailure; @@ -321,7 +323,7 @@ abstract class AbstractProxyTransaction implements Identifiable req) { + /* + * The backend has agreed that the transaction has entered PRE_COMMIT phase, meaning it will be committed + * to storage after the timeout completes. + * + * All state has been replicated to the backend, hence we do not need to keep it around. Retain only + * the precommit request, so we know which request to use for resync. + */ + LOG.debug("Transaction {} preCommit completed, clearing successfulRequests", this); + successfulRequests.clear(); + + // TODO: this works, but can contain some useless state (like batched operations). Create an empty + // equivalent of this request and store that. + recordSuccessfulRequest(req); + } + final void doCommit(final VotingFuture ret) { checkReadWrite(); checkSealed(); @@ -433,6 +450,16 @@ abstract class AbstractProxyTransaction implements Identifiable req = new TransactionPurgeRequest(getIdentifier(), nextSequence(), localActor()); + sendRequest(req, t -> { + LOG.debug("Transaction {} purge completed", this); parent.completeTransaction(this); }); } @@ -574,4 +601,9 @@ abstract class AbstractProxyTransaction implements Identifiable request, Consumer> callback); + + @Override + public final String toString() { + return MoreObjects.toStringHelper(this).add("identifier", getIdentifier()).add("state", state).toString(); + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/AbstractFrontendHistory.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/AbstractFrontendHistory.java index d7e3c6533f..dbea3a1cbb 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/AbstractFrontendHistory.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/AbstractFrontendHistory.java @@ -7,6 +7,7 @@ */ package org.opendaylight.controller.cluster.datastore; +import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.base.Ticker; import java.util.HashMap; @@ -16,6 +17,8 @@ import javax.annotation.Nullable; import org.opendaylight.controller.cluster.access.commands.AbstractReadTransactionRequest; import org.opendaylight.controller.cluster.access.commands.CommitLocalTransactionRequest; import org.opendaylight.controller.cluster.access.commands.OutOfOrderRequestException; +import org.opendaylight.controller.cluster.access.commands.TransactionPurgeRequest; +import org.opendaylight.controller.cluster.access.commands.TransactionPurgeResponse; import org.opendaylight.controller.cluster.access.commands.TransactionRequest; import org.opendaylight.controller.cluster.access.commands.TransactionSuccess; import org.opendaylight.controller.cluster.access.concepts.LocalHistoryIdentifier; @@ -56,26 +59,34 @@ abstract class AbstractFrontendHistory implements Identifiable handleTransactionRequest(final TransactionRequest request, final RequestEnvelope envelope, final long now) throws RequestException { - - // FIXME: handle purging of transactions - final TransactionIdentifier id = request.getTarget(); - FrontendTransaction tx = transactions.get(id); - if (tx == null) { - // The transaction does not exist and we are about to create it, check sequence number - if (request.getSequence() != 0) { - LOG.debug("{}: no transaction state present, unexpected request {}", persistenceId(), request); - throw UNSEQUENCED_START; - } - tx = createTransaction(request, id); - transactions.put(id, tx); + FrontendTransaction tx; + if (request instanceof TransactionPurgeRequest) { + tx = transactions.remove(id); + if (tx == null) { + // We have no record of the transaction, nothing to do + LOG.debug("{}: no state for transaction {}, purge is complete", persistenceId(), id); + return new TransactionPurgeResponse(id, request.getSequence()); + } } else { - final Optional> maybeReplay = tx.replaySequence(request.getSequence()); - if (maybeReplay.isPresent()) { - final TransactionSuccess replay = maybeReplay.get(); - LOG.debug("{}: envelope {} replaying response {}", persistenceId(), envelope, replay); - return replay; + tx = transactions.get(id); + if (tx == null) { + // The transaction does not exist and we are about to create it, check sequence number + if (request.getSequence() != 0) { + LOG.debug("{}: no transaction state present, unexpected request {}", persistenceId(), request); + throw UNSEQUENCED_START; + } + + tx = createTransaction(request, id); + transactions.put(id, tx); + } else { + final Optional> maybeReplay = tx.replaySequence(request.getSequence()); + if (maybeReplay.isPresent()) { + final TransactionSuccess replay = maybeReplay.get(); + LOG.debug("{}: envelope {} replaying response {}", persistenceId(), envelope, replay); + return replay; + } } } @@ -107,4 +118,10 @@ abstract class AbstractFrontendHistory implements Identifiable