dd5280462c79c5f4876628725fc5cf23649ca3d6
[mdsal.git] / dom / mdsal-dom-broker / src / main / java / org / opendaylight / mdsal / dom / broker / CommitCoordinationTask.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.dom.broker;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.Throwables;
13 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 import java.util.concurrent.Callable;
15 import java.util.concurrent.ExecutionException;
16 import org.opendaylight.mdsal.common.api.CommitInfo;
17 import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
18 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
19 import org.opendaylight.mdsal.dom.spi.store.DOMStoreThreePhaseCommitCohort;
20 import org.opendaylight.yangtools.util.DurationStatisticsTracker;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23
24 /**
25  * Implementation of blocking three-phase commit-coordination tasks without support of cancellation.
26  */
27 sealed class CommitCoordinationTask implements Callable<CommitInfo> {
28     static final class WithTracker extends CommitCoordinationTask {
29         private final DurationStatisticsTracker commitStatTracker;
30
31         WithTracker(final DOMDataTreeWriteTransaction transaction, final DOMStoreThreePhaseCommitCohort cohort,
32                 final DurationStatisticsTracker commitStatTracker) {
33             super(transaction, cohort);
34             this.commitStatTracker = requireNonNull(commitStatTracker);
35         }
36
37         @Override
38         public CommitInfo call() throws TransactionCommitFailedException {
39             final long startTime = System.nanoTime();
40
41             try {
42                 return super.call();
43             } finally {
44                 commitStatTracker.addDuration(System.nanoTime() - startTime);
45             }
46         }
47     }
48
49     private enum Phase {
50         CAN_COMMIT,
51         PRE_COMMIT,
52         DO_COMMIT
53     }
54
55     private static final Logger LOG = LoggerFactory.getLogger(CommitCoordinationTask.class);
56
57     private final DOMStoreThreePhaseCommitCohort cohort;
58     private final DOMDataTreeWriteTransaction tx;
59
60     CommitCoordinationTask(final DOMDataTreeWriteTransaction transaction, final DOMStoreThreePhaseCommitCohort cohort) {
61         tx = requireNonNull(transaction, "transaction must not be null");
62         this.cohort = requireNonNull(cohort, "cohort must not be null");
63     }
64
65     @Override
66     public CommitInfo call() throws TransactionCommitFailedException {
67         var phase = Phase.CAN_COMMIT;
68         try {
69             LOG.debug("Transaction {}: canCommit Started", tx.getIdentifier());
70             canCommitBlocking();
71
72             phase = Phase.PRE_COMMIT;
73             LOG.debug("Transaction {}: preCommit Started", tx.getIdentifier());
74             preCommitBlocking();
75
76             phase = Phase.DO_COMMIT;
77             LOG.debug("Transaction {}: doCommit Started", tx.getIdentifier());
78             commitBlocking();
79
80             LOG.debug("Transaction {}: doCommit completed", tx.getIdentifier());
81             return CommitInfo.empty();
82         } catch (final TransactionCommitFailedException e) {
83             LOG.warn("Tx: {} Error during phase {}, starting Abort", tx.getIdentifier(), phase, e);
84             abortBlocking(e);
85             throw e;
86         }
87     }
88
89     /**
90      * Invokes canCommit on underlying cohort and blocks till the result is returned.
91      *
92      * <p>
93      * Valid state transition is from SUBMITTED to CAN_COMMIT, if currentPhase is not SUBMITTED throws
94      * IllegalStateException.
95      *
96      * @throws TransactionCommitFailedException If cohort fails Commit
97      */
98     @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE")
99     private void canCommitBlocking() throws TransactionCommitFailedException {
100         final var future = cohort.canCommit();
101         final Boolean result;
102         try {
103             result = future.get();
104         } catch (InterruptedException | ExecutionException e) {
105             throw TransactionCommitFailedExceptionMapper.CAN_COMMIT_ERROR_MAPPER.apply(e);
106         }
107
108         if (!Boolean.TRUE.equals(result)) {
109             throw new TransactionCommitFailedException("Can Commit failed, no detailed cause available.");
110         }
111     }
112
113     /**
114      * Invokes preCommit on underlying cohort and blocks until the result is returned.
115      *
116      * <p>
117      * Valid state transition is from CAN_COMMIT to PRE_COMMIT, if current state is not CAN_COMMIT throws
118      * IllegalStateException.
119      *
120      * @throws TransactionCommitFailedException If cohort fails preCommit
121      */
122     @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE")
123     private void preCommitBlocking() throws TransactionCommitFailedException {
124         try {
125             cohort.preCommit().get();
126         } catch (InterruptedException | ExecutionException e) {
127             throw TransactionCommitFailedExceptionMapper.PRE_COMMIT_MAPPER.apply(e);
128         }
129     }
130
131     /**
132      * Invokes commit on underlying cohort and blocks until result is returned.
133      *
134      * <p>
135      * Valid state transition is from PRE_COMMIT to COMMIT, if not throws IllegalStateException.
136      *
137      * @throws TransactionCommitFailedException If cohort fails preCommit
138      */
139     @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE")
140     private void commitBlocking() throws TransactionCommitFailedException {
141         try {
142             cohort.commit().get();
143         } catch (InterruptedException | ExecutionException e) {
144             throw TransactionCommitFailedExceptionMapper.COMMIT_ERROR_MAPPER.apply(e);
145         }
146     }
147
148     /**
149      * Aborts transaction.
150      *
151      * <p>
152      * Invokes {@link DOMStoreThreePhaseCommitCohort#abort()} on underlying cohort, blocks the results. If
153      * abort failed throws IllegalStateException, which will contains originalCause as suppressed Exception.
154      *
155      * <p>
156      * If abort was successful throws supplied exception
157      *
158      * @param originalCause Exception which should be used to fail transaction for consumers of transaction future
159      *                      and listeners of transaction failure.
160      * @throws IllegalStateException if abort failed.
161      * @throws TransactionCommitFailedException on invocation of this method.
162      */
163     private void abortBlocking(final TransactionCommitFailedException originalCause)
164             throws TransactionCommitFailedException {
165         Exception cause = originalCause;
166         try {
167             cohort.abort().get();
168         } catch (InterruptedException | ExecutionException e) {
169             LOG.error("Tx: {} Error during Abort.", tx.getIdentifier(), e);
170             cause = new IllegalStateException("Abort failed.", e);
171             cause.addSuppressed(e);
172         }
173         Throwables.propagateIfPossible(cause, TransactionCommitFailedException.class);
174     }
175 }