blob: 28074e249a0355aa8c89d6db4593c36be27dff60 [file] [log] [blame]
Madan Jampanicadd70b2016-02-08 13:45:43 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Madan Jampanicadd70b2016-02-08 13:45:43 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.store.primitives.impl;
17
18import java.util.List;
Jordan Halterman948d6592017-04-20 17:18:24 -070019import java.util.concurrent.CompletableFuture;
20import java.util.concurrent.atomic.AtomicBoolean;
Madan Jampanicadd70b2016-02-08 13:45:43 -080021
22import org.onosproject.store.primitives.TransactionId;
Jordan Halterman948d6592017-04-20 17:18:24 -070023import org.onosproject.store.service.TransactionLog;
24import org.onosproject.store.service.Transactional;
25import org.onosproject.store.service.Version;
26import org.onosproject.store.service.TransactionContext;
27import org.onosproject.store.service.TransactionException;
Jordan Halterman03b83182017-05-09 12:11:22 -070028import org.slf4j.Logger;
29import org.slf4j.LoggerFactory;
Madan Jampanicadd70b2016-02-08 13:45:43 -080030
Jordan Halterman948d6592017-04-20 17:18:24 -070031import static com.google.common.base.MoreObjects.toStringHelper;
32import static com.google.common.base.Preconditions.checkState;
Madan Jampanicadd70b2016-02-08 13:45:43 -080033
34/**
Jordan Halterman948d6592017-04-20 17:18:24 -070035 * Manages a transaction within the context of a single primitive.
36 * <p>
37 * The {@code Transaction} object is used to manage the transaction for a single partition primitive that implements
38 * the {@link Transactional} interface. It's used as a proxy for {@link TransactionContext}s to manage the transaction
39 * as it relates to a single piece of atomic state.
Madan Jampanicadd70b2016-02-08 13:45:43 -080040 */
Jordan Halterman948d6592017-04-20 17:18:24 -070041public class Transaction<T> {
Madan Jampanicadd70b2016-02-08 13:45:43 -080042
Jordan Halterman948d6592017-04-20 17:18:24 -070043 /**
44 * Transaction state.
45 * <p>
46 * The transaction state is used to indicate the phase within which the transaction is currently running.
47 */
Madan Jampanicadd70b2016-02-08 13:45:43 -080048 enum State {
Jordan Halterman948d6592017-04-20 17:18:24 -070049
Madan Jampanicadd70b2016-02-08 13:45:43 -080050 /**
Jordan Halterman948d6592017-04-20 17:18:24 -070051 * Active transaction state.
52 * <p>
53 * The {@code ACTIVE} state represents a transaction in progress. Active transactions may or may not affect
54 * concurrently running transactions depending on the transaction's isolation level.
55 */
56 ACTIVE,
57
58 /**
59 * Preparing transaction state.
60 * <p>
61 * Once a transaction commitment begins, it enters the {@code PREPARING} phase of the two-phase commit protocol.
Madan Jampanicadd70b2016-02-08 13:45:43 -080062 */
63 PREPARING,
64
65 /**
Jordan Halterman948d6592017-04-20 17:18:24 -070066 * Prepared transaction state.
67 * <p>
68 * Once the first phase of the two-phase commit protocol is complete, the transaction's state is set to
69 * {@code PREPARED}.
Madan Jampanicadd70b2016-02-08 13:45:43 -080070 */
71 PREPARED,
72
73 /**
Jordan Halterman948d6592017-04-20 17:18:24 -070074 * Committing transaction state.
75 * <p>
76 * The {@code COMMITTING} state represents a transaction within the second phase of the two-phase commit
77 * protocol.
Madan Jampanicadd70b2016-02-08 13:45:43 -080078 */
79 COMMITTING,
80
81 /**
Jordan Halterman948d6592017-04-20 17:18:24 -070082 * Committed transaction state.
83 * <p>
84 * Once the second phase of the two-phase commit protocol is complete, the transaction's state is set to
85 * {@code COMMITTED}.
Madan Jampanicadd70b2016-02-08 13:45:43 -080086 */
87 COMMITTED,
88
89 /**
Jordan Halterman948d6592017-04-20 17:18:24 -070090 * Rolling back transaction state.
91 * <p>
92 * In the event of a two-phase lock failure, when the transaction is rolled back it will enter the
93 * {@code ROLLING_BACK} state while the rollback is in progress.
Madan Jampanicadd70b2016-02-08 13:45:43 -080094 */
Jordan Halterman948d6592017-04-20 17:18:24 -070095 ROLLING_BACK,
Madan Jampanicadd70b2016-02-08 13:45:43 -080096
97 /**
Jordan Halterman948d6592017-04-20 17:18:24 -070098 * Rolled back transaction state.
99 * <p>
100 * Once a transaction has been rolled back, it will enter the {@code ROLLED_BACK} state.
Madan Jampanicadd70b2016-02-08 13:45:43 -0800101 */
Jordan Halterman948d6592017-04-20 17:18:24 -0700102 ROLLED_BACK,
Madan Jampanicadd70b2016-02-08 13:45:43 -0800103 }
104
Jordan Halterman948d6592017-04-20 17:18:24 -0700105 private static final String TX_OPEN_ERROR = "transaction already open";
106 private static final String TX_CLOSED_ERROR = "transaction not open";
107 private static final String TX_INACTIVE_ERROR = "transaction is not active";
108 private static final String TX_UNPREPARED_ERROR = "transaction has not been prepared";
Madan Jampanicadd70b2016-02-08 13:45:43 -0800109
Jordan Halterman03b83182017-05-09 12:11:22 -0700110 protected final Logger log = LoggerFactory.getLogger(getClass());
Jordan Halterman948d6592017-04-20 17:18:24 -0700111 protected final TransactionId transactionId;
112 protected final Transactional<T> transactionalObject;
113 private final AtomicBoolean open = new AtomicBoolean();
114 private volatile State state = State.ACTIVE;
Jordan Halterman5f97a302017-04-26 23:41:31 -0700115 private volatile Version lock;
Madan Jampanicadd70b2016-02-08 13:45:43 -0800116
Jordan Halterman948d6592017-04-20 17:18:24 -0700117 public Transaction(TransactionId transactionId, Transactional<T> transactionalObject) {
Madan Jampanicadd70b2016-02-08 13:45:43 -0800118 this.transactionId = transactionId;
Jordan Halterman948d6592017-04-20 17:18:24 -0700119 this.transactionalObject = transactionalObject;
Madan Jampanicadd70b2016-02-08 13:45:43 -0800120 }
121
Jordan Halterman948d6592017-04-20 17:18:24 -0700122 /**
123 * Returns the transaction identifier.
124 *
125 * @return the transaction identifier
126 */
127 public TransactionId transactionId() {
Madan Jampanicadd70b2016-02-08 13:45:43 -0800128 return transactionId;
129 }
130
Jordan Halterman948d6592017-04-20 17:18:24 -0700131 /**
132 * Returns the current transaction state.
133 *
134 * @return the current transaction state
135 */
Madan Jampanicadd70b2016-02-08 13:45:43 -0800136 public State state() {
137 return state;
138 }
139
Jordan Halterman948d6592017-04-20 17:18:24 -0700140 /**
141 * Returns a boolean indicating whether the transaction is open.
142 *
143 * @return indicates whether the transaction is open
144 */
145 public boolean isOpen() {
146 return open.get();
147 }
148
149 /**
150 * Opens the transaction, throwing an {@link IllegalStateException} if it's already open.
151 */
152 protected void open() {
153 if (!open.compareAndSet(false, true)) {
154 throw new IllegalStateException(TX_OPEN_ERROR);
155 }
156 }
157
158 /**
159 * Checks that the transaction is open and throws an {@link IllegalStateException} if not.
160 */
161 protected void checkOpen() {
162 checkState(isOpen(), TX_CLOSED_ERROR);
163 }
164
165 /**
166 * Checks that the transaction state is {@code ACTIVE} and throws an {@link IllegalStateException} if not.
167 */
168 protected void checkActive() {
169 checkState(state == State.ACTIVE, TX_INACTIVE_ERROR);
170 }
171
172 /**
173 * Checks that the transaction state is {@code PREPARED} and throws an {@link IllegalStateException} if not.
174 */
175 protected void checkPrepared() {
176 checkState(state == State.PREPARED, TX_UNPREPARED_ERROR);
177 }
178
179 /**
180 * Updates the transaction state.
181 *
182 * @param state the updated transaction state
183 */
184 protected void setState(State state) {
185 this.state = state;
186 }
187
188 /**
189 * Begins the transaction.
190 * <p>
191 * Locks are acquired when the transaction is begun to prevent concurrent transactions from operating on the shared
192 * resource to which this transaction relates.
193 *
194 * @return a completable future to be completed once the transaction has been started
195 */
196 public CompletableFuture<Version> begin() {
Jordan Halterman03b83182017-05-09 12:11:22 -0700197 log.debug("Beginning transaction {} for {}", transactionId, transactionalObject);
Jordan Halterman948d6592017-04-20 17:18:24 -0700198 open();
Jordan Halterman5f97a302017-04-26 23:41:31 -0700199 return transactionalObject.begin(transactionId).thenApply(lock -> {
200 this.lock = lock;
Jordan Halterman03b83182017-05-09 12:11:22 -0700201 log.trace("Transaction lock acquired: {}", lock);
Jordan Halterman5f97a302017-04-26 23:41:31 -0700202 return lock;
203 });
Jordan Halterman948d6592017-04-20 17:18:24 -0700204 }
205
206 /**
207 * Prepares the transaction.
208 * <p>
209 * When preparing the transaction, the given list of updates for the shared resource will be prepared, and
210 * concurrent modification checks will be performed. The returned future may be completed with a
211 * {@link TransactionException} if a concurrent modification is detected for an isolation level that does
212 * not allow such modifications.
213 *
214 * @param updates the transaction updates
215 * @return a completable future to be completed once the transaction has been prepared
216 */
217 public CompletableFuture<Boolean> prepare(List<T> updates) {
218 checkOpen();
219 checkActive();
Jordan Halterman03b83182017-05-09 12:11:22 -0700220 log.debug("Preparing transaction {} for {}", transactionId, transactionalObject);
Jordan Halterman5f97a302017-04-26 23:41:31 -0700221 Version lock = this.lock;
222 checkState(lock != null, TX_INACTIVE_ERROR);
Jordan Halterman948d6592017-04-20 17:18:24 -0700223 setState(State.PREPARING);
Jordan Halterman5f97a302017-04-26 23:41:31 -0700224 return transactionalObject.prepare(new TransactionLog<T>(transactionId, lock.value(), updates))
Jordan Halterman948d6592017-04-20 17:18:24 -0700225 .thenApply(succeeded -> {
226 setState(State.PREPARED);
227 return succeeded;
228 });
229 }
230
231 /**
232 * Prepares and commits the transaction in a single atomic operation.
233 * <p>
234 * Both the prepare and commit phases of the protocol must be executed within a single atomic operation. This method
235 * is used to optimize committing transactions that operate only on a single partition within a single primitive.
236 *
237 * @param updates the transaction updates
238 * @return a completable future to be completed once the transaction has been prepared
239 */
240 public CompletableFuture<Boolean> prepareAndCommit(List<T> updates) {
241 checkOpen();
242 checkActive();
Jordan Halterman03b83182017-05-09 12:11:22 -0700243 log.debug("Preparing and committing transaction {} for {}", transactionId, transactionalObject);
Jordan Halterman5f97a302017-04-26 23:41:31 -0700244 Version lock = this.lock;
245 checkState(lock != null, TX_INACTIVE_ERROR);
Jordan Halterman948d6592017-04-20 17:18:24 -0700246 setState(State.PREPARING);
Jordan Halterman5f97a302017-04-26 23:41:31 -0700247 return transactionalObject.prepareAndCommit(new TransactionLog<T>(transactionId, lock.value(), updates))
Jordan Halterman948d6592017-04-20 17:18:24 -0700248 .thenApply(succeeded -> {
249 setState(State.COMMITTED);
250 return succeeded;
251 });
252 }
253
254 /**
255 * Commits the transaction.
256 * <p>
257 * Performs the second phase of the two-phase commit protocol, committing the previously
258 * {@link #prepare(List) prepared} updates.
259 *
260 * @return a completable future to be completed once the transaction has been committed
261 */
262 public CompletableFuture<Void> commit() {
263 checkOpen();
264 checkPrepared();
Jordan Halterman03b83182017-05-09 12:11:22 -0700265 log.debug("Committing transaction {} for {}", transactionId, transactionalObject);
Jordan Halterman948d6592017-04-20 17:18:24 -0700266 setState(State.COMMITTING);
267 return transactionalObject.commit(transactionId).thenRun(() -> {
268 setState(State.COMMITTED);
269 });
270 }
271
272 /**
273 * Rolls back the transaction.
274 * <p>
275 * Rolls back the first phase of the two-phase commit protocol, cancelling prepared updates.
276 *
277 * @return a completable future to be completed once the transaction has been rolled back
278 */
279 public CompletableFuture<Void> rollback() {
280 checkOpen();
281 checkPrepared();
Jordan Halterman03b83182017-05-09 12:11:22 -0700282 log.debug("Rolling back transaction {} for {}", transactionId, transactionalObject);
Jordan Halterman948d6592017-04-20 17:18:24 -0700283 setState(State.ROLLING_BACK);
284 return transactionalObject.rollback(transactionId).thenRun(() -> {
285 setState(State.ROLLED_BACK);
286 });
Madan Jampanicadd70b2016-02-08 13:45:43 -0800287 }
288
289 @Override
290 public String toString() {
Jordan Halterman948d6592017-04-20 17:18:24 -0700291 return toStringHelper(this)
Madan Jampanicadd70b2016-02-08 13:45:43 -0800292 .add("transactionId", transactionId)
Madan Jampanicadd70b2016-02-08 13:45:43 -0800293 .add("state", state)
294 .toString();
295 }
296}