blob: 8ad8cb8dfe18ac68be8869ab4b813b3d0e6270a5 [file] [log] [blame]
Madan Jampanicadd70b2016-02-08 13:45:43 -08001/*
Jordan Halterman948d6592017-04-20 17:18:24 -07002 * Copyright 2017-present Open Networking Laboratory
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;
Madan Jampanicadd70b2016-02-08 13:45:43 -080028
Jordan Halterman948d6592017-04-20 17:18:24 -070029import static com.google.common.base.MoreObjects.toStringHelper;
30import static com.google.common.base.Preconditions.checkState;
Madan Jampanicadd70b2016-02-08 13:45:43 -080031
32/**
Jordan Halterman948d6592017-04-20 17:18:24 -070033 * Manages a transaction within the context of a single primitive.
34 * <p>
35 * The {@code Transaction} object is used to manage the transaction for a single partition primitive that implements
36 * the {@link Transactional} interface. It's used as a proxy for {@link TransactionContext}s to manage the transaction
37 * as it relates to a single piece of atomic state.
Madan Jampanicadd70b2016-02-08 13:45:43 -080038 */
Jordan Halterman948d6592017-04-20 17:18:24 -070039public class Transaction<T> {
Madan Jampanicadd70b2016-02-08 13:45:43 -080040
Jordan Halterman948d6592017-04-20 17:18:24 -070041 /**
42 * Transaction state.
43 * <p>
44 * The transaction state is used to indicate the phase within which the transaction is currently running.
45 */
Madan Jampanicadd70b2016-02-08 13:45:43 -080046 enum State {
Jordan Halterman948d6592017-04-20 17:18:24 -070047
Madan Jampanicadd70b2016-02-08 13:45:43 -080048 /**
Jordan Halterman948d6592017-04-20 17:18:24 -070049 * Active transaction state.
50 * <p>
51 * The {@code ACTIVE} state represents a transaction in progress. Active transactions may or may not affect
52 * concurrently running transactions depending on the transaction's isolation level.
53 */
54 ACTIVE,
55
56 /**
57 * Preparing transaction state.
58 * <p>
59 * Once a transaction commitment begins, it enters the {@code PREPARING} phase of the two-phase commit protocol.
Madan Jampanicadd70b2016-02-08 13:45:43 -080060 */
61 PREPARING,
62
63 /**
Jordan Halterman948d6592017-04-20 17:18:24 -070064 * Prepared transaction state.
65 * <p>
66 * Once the first phase of the two-phase commit protocol is complete, the transaction's state is set to
67 * {@code PREPARED}.
Madan Jampanicadd70b2016-02-08 13:45:43 -080068 */
69 PREPARED,
70
71 /**
Jordan Halterman948d6592017-04-20 17:18:24 -070072 * Committing transaction state.
73 * <p>
74 * The {@code COMMITTING} state represents a transaction within the second phase of the two-phase commit
75 * protocol.
Madan Jampanicadd70b2016-02-08 13:45:43 -080076 */
77 COMMITTING,
78
79 /**
Jordan Halterman948d6592017-04-20 17:18:24 -070080 * Committed transaction state.
81 * <p>
82 * Once the second phase of the two-phase commit protocol is complete, the transaction's state is set to
83 * {@code COMMITTED}.
Madan Jampanicadd70b2016-02-08 13:45:43 -080084 */
85 COMMITTED,
86
87 /**
Jordan Halterman948d6592017-04-20 17:18:24 -070088 * Rolling back transaction state.
89 * <p>
90 * In the event of a two-phase lock failure, when the transaction is rolled back it will enter the
91 * {@code ROLLING_BACK} state while the rollback is in progress.
Madan Jampanicadd70b2016-02-08 13:45:43 -080092 */
Jordan Halterman948d6592017-04-20 17:18:24 -070093 ROLLING_BACK,
Madan Jampanicadd70b2016-02-08 13:45:43 -080094
95 /**
Jordan Halterman948d6592017-04-20 17:18:24 -070096 * Rolled back transaction state.
97 * <p>
98 * Once a transaction has been rolled back, it will enter the {@code ROLLED_BACK} state.
Madan Jampanicadd70b2016-02-08 13:45:43 -080099 */
Jordan Halterman948d6592017-04-20 17:18:24 -0700100 ROLLED_BACK,
Madan Jampanicadd70b2016-02-08 13:45:43 -0800101 }
102
Jordan Halterman948d6592017-04-20 17:18:24 -0700103 private static final String TX_OPEN_ERROR = "transaction already open";
104 private static final String TX_CLOSED_ERROR = "transaction not open";
105 private static final String TX_INACTIVE_ERROR = "transaction is not active";
106 private static final String TX_UNPREPARED_ERROR = "transaction has not been prepared";
Madan Jampanicadd70b2016-02-08 13:45:43 -0800107
Jordan Halterman948d6592017-04-20 17:18:24 -0700108 protected final TransactionId transactionId;
109 protected final Transactional<T> transactionalObject;
110 private final AtomicBoolean open = new AtomicBoolean();
111 private volatile State state = State.ACTIVE;
Jordan Halterman5f97a302017-04-26 23:41:31 -0700112 private volatile Version lock;
Madan Jampanicadd70b2016-02-08 13:45:43 -0800113
Jordan Halterman948d6592017-04-20 17:18:24 -0700114 public Transaction(TransactionId transactionId, Transactional<T> transactionalObject) {
Madan Jampanicadd70b2016-02-08 13:45:43 -0800115 this.transactionId = transactionId;
Jordan Halterman948d6592017-04-20 17:18:24 -0700116 this.transactionalObject = transactionalObject;
Madan Jampanicadd70b2016-02-08 13:45:43 -0800117 }
118
Jordan Halterman948d6592017-04-20 17:18:24 -0700119 /**
120 * Returns the transaction identifier.
121 *
122 * @return the transaction identifier
123 */
124 public TransactionId transactionId() {
Madan Jampanicadd70b2016-02-08 13:45:43 -0800125 return transactionId;
126 }
127
Jordan Halterman948d6592017-04-20 17:18:24 -0700128 /**
129 * Returns the current transaction state.
130 *
131 * @return the current transaction state
132 */
Madan Jampanicadd70b2016-02-08 13:45:43 -0800133 public State state() {
134 return state;
135 }
136
Jordan Halterman948d6592017-04-20 17:18:24 -0700137 /**
138 * Returns a boolean indicating whether the transaction is open.
139 *
140 * @return indicates whether the transaction is open
141 */
142 public boolean isOpen() {
143 return open.get();
144 }
145
146 /**
147 * Opens the transaction, throwing an {@link IllegalStateException} if it's already open.
148 */
149 protected void open() {
150 if (!open.compareAndSet(false, true)) {
151 throw new IllegalStateException(TX_OPEN_ERROR);
152 }
153 }
154
155 /**
156 * Checks that the transaction is open and throws an {@link IllegalStateException} if not.
157 */
158 protected void checkOpen() {
159 checkState(isOpen(), TX_CLOSED_ERROR);
160 }
161
162 /**
163 * Checks that the transaction state is {@code ACTIVE} and throws an {@link IllegalStateException} if not.
164 */
165 protected void checkActive() {
166 checkState(state == State.ACTIVE, TX_INACTIVE_ERROR);
167 }
168
169 /**
170 * Checks that the transaction state is {@code PREPARED} and throws an {@link IllegalStateException} if not.
171 */
172 protected void checkPrepared() {
173 checkState(state == State.PREPARED, TX_UNPREPARED_ERROR);
174 }
175
176 /**
177 * Updates the transaction state.
178 *
179 * @param state the updated transaction state
180 */
181 protected void setState(State state) {
182 this.state = state;
183 }
184
185 /**
186 * Begins the transaction.
187 * <p>
188 * Locks are acquired when the transaction is begun to prevent concurrent transactions from operating on the shared
189 * resource to which this transaction relates.
190 *
191 * @return a completable future to be completed once the transaction has been started
192 */
193 public CompletableFuture<Version> begin() {
194 open();
Jordan Halterman5f97a302017-04-26 23:41:31 -0700195 return transactionalObject.begin(transactionId).thenApply(lock -> {
196 this.lock = lock;
197 return lock;
198 });
Jordan Halterman948d6592017-04-20 17:18:24 -0700199 }
200
201 /**
202 * Prepares the transaction.
203 * <p>
204 * When preparing the transaction, the given list of updates for the shared resource will be prepared, and
205 * concurrent modification checks will be performed. The returned future may be completed with a
206 * {@link TransactionException} if a concurrent modification is detected for an isolation level that does
207 * not allow such modifications.
208 *
209 * @param updates the transaction updates
210 * @return a completable future to be completed once the transaction has been prepared
211 */
212 public CompletableFuture<Boolean> prepare(List<T> updates) {
213 checkOpen();
214 checkActive();
Jordan Halterman5f97a302017-04-26 23:41:31 -0700215 Version lock = this.lock;
216 checkState(lock != null, TX_INACTIVE_ERROR);
Jordan Halterman948d6592017-04-20 17:18:24 -0700217 setState(State.PREPARING);
Jordan Halterman5f97a302017-04-26 23:41:31 -0700218 return transactionalObject.prepare(new TransactionLog<T>(transactionId, lock.value(), updates))
Jordan Halterman948d6592017-04-20 17:18:24 -0700219 .thenApply(succeeded -> {
220 setState(State.PREPARED);
221 return succeeded;
222 });
223 }
224
225 /**
226 * Prepares and commits the transaction in a single atomic operation.
227 * <p>
228 * Both the prepare and commit phases of the protocol must be executed within a single atomic operation. This method
229 * is used to optimize committing transactions that operate only on a single partition within a single primitive.
230 *
231 * @param updates the transaction updates
232 * @return a completable future to be completed once the transaction has been prepared
233 */
234 public CompletableFuture<Boolean> prepareAndCommit(List<T> updates) {
235 checkOpen();
236 checkActive();
Jordan Halterman5f97a302017-04-26 23:41:31 -0700237 Version lock = this.lock;
238 checkState(lock != null, TX_INACTIVE_ERROR);
Jordan Halterman948d6592017-04-20 17:18:24 -0700239 setState(State.PREPARING);
Jordan Halterman5f97a302017-04-26 23:41:31 -0700240 return transactionalObject.prepareAndCommit(new TransactionLog<T>(transactionId, lock.value(), updates))
Jordan Halterman948d6592017-04-20 17:18:24 -0700241 .thenApply(succeeded -> {
242 setState(State.COMMITTED);
243 return succeeded;
244 });
245 }
246
247 /**
248 * Commits the transaction.
249 * <p>
250 * Performs the second phase of the two-phase commit protocol, committing the previously
251 * {@link #prepare(List) prepared} updates.
252 *
253 * @return a completable future to be completed once the transaction has been committed
254 */
255 public CompletableFuture<Void> commit() {
256 checkOpen();
257 checkPrepared();
258 setState(State.COMMITTING);
259 return transactionalObject.commit(transactionId).thenRun(() -> {
260 setState(State.COMMITTED);
261 });
262 }
263
264 /**
265 * Rolls back the transaction.
266 * <p>
267 * Rolls back the first phase of the two-phase commit protocol, cancelling prepared updates.
268 *
269 * @return a completable future to be completed once the transaction has been rolled back
270 */
271 public CompletableFuture<Void> rollback() {
272 checkOpen();
273 checkPrepared();
274 setState(State.ROLLING_BACK);
275 return transactionalObject.rollback(transactionId).thenRun(() -> {
276 setState(State.ROLLED_BACK);
277 });
Madan Jampanicadd70b2016-02-08 13:45:43 -0800278 }
279
280 @Override
281 public String toString() {
Jordan Halterman948d6592017-04-20 17:18:24 -0700282 return toStringHelper(this)
Madan Jampanicadd70b2016-02-08 13:45:43 -0800283 .add("transactionId", transactionId)
Madan Jampanicadd70b2016-02-08 13:45:43 -0800284 .add("state", state)
285 .toString();
286 }
287}