blob: 82b39195e77d5e50fc89a3cf04ea3b30aadd56a5 [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;
Madan Jampanicadd70b2016-02-08 13:45:43 -0800112
Jordan Halterman948d6592017-04-20 17:18:24 -0700113 public Transaction(TransactionId transactionId, Transactional<T> transactionalObject) {
Madan Jampanicadd70b2016-02-08 13:45:43 -0800114 this.transactionId = transactionId;
Jordan Halterman948d6592017-04-20 17:18:24 -0700115 this.transactionalObject = transactionalObject;
Madan Jampanicadd70b2016-02-08 13:45:43 -0800116 }
117
Jordan Halterman948d6592017-04-20 17:18:24 -0700118 /**
119 * Returns the transaction identifier.
120 *
121 * @return the transaction identifier
122 */
123 public TransactionId transactionId() {
Madan Jampanicadd70b2016-02-08 13:45:43 -0800124 return transactionId;
125 }
126
Jordan Halterman948d6592017-04-20 17:18:24 -0700127 /**
128 * Returns the current transaction state.
129 *
130 * @return the current transaction state
131 */
Madan Jampanicadd70b2016-02-08 13:45:43 -0800132 public State state() {
133 return state;
134 }
135
Jordan Halterman948d6592017-04-20 17:18:24 -0700136 /**
137 * Returns a boolean indicating whether the transaction is open.
138 *
139 * @return indicates whether the transaction is open
140 */
141 public boolean isOpen() {
142 return open.get();
143 }
144
145 /**
146 * Opens the transaction, throwing an {@link IllegalStateException} if it's already open.
147 */
148 protected void open() {
149 if (!open.compareAndSet(false, true)) {
150 throw new IllegalStateException(TX_OPEN_ERROR);
151 }
152 }
153
154 /**
155 * Checks that the transaction is open and throws an {@link IllegalStateException} if not.
156 */
157 protected void checkOpen() {
158 checkState(isOpen(), TX_CLOSED_ERROR);
159 }
160
161 /**
162 * Checks that the transaction state is {@code ACTIVE} and throws an {@link IllegalStateException} if not.
163 */
164 protected void checkActive() {
165 checkState(state == State.ACTIVE, TX_INACTIVE_ERROR);
166 }
167
168 /**
169 * Checks that the transaction state is {@code PREPARED} and throws an {@link IllegalStateException} if not.
170 */
171 protected void checkPrepared() {
172 checkState(state == State.PREPARED, TX_UNPREPARED_ERROR);
173 }
174
175 /**
176 * Updates the transaction state.
177 *
178 * @param state the updated transaction state
179 */
180 protected void setState(State state) {
181 this.state = state;
182 }
183
184 /**
185 * Begins the transaction.
186 * <p>
187 * Locks are acquired when the transaction is begun to prevent concurrent transactions from operating on the shared
188 * resource to which this transaction relates.
189 *
190 * @return a completable future to be completed once the transaction has been started
191 */
192 public CompletableFuture<Version> begin() {
193 open();
194 return transactionalObject.begin(transactionId);
195 }
196
197 /**
198 * Prepares the transaction.
199 * <p>
200 * When preparing the transaction, the given list of updates for the shared resource will be prepared, and
201 * concurrent modification checks will be performed. The returned future may be completed with a
202 * {@link TransactionException} if a concurrent modification is detected for an isolation level that does
203 * not allow such modifications.
204 *
205 * @param updates the transaction updates
206 * @return a completable future to be completed once the transaction has been prepared
207 */
208 public CompletableFuture<Boolean> prepare(List<T> updates) {
209 checkOpen();
210 checkActive();
211 setState(State.PREPARING);
212 return transactionalObject.prepare(new TransactionLog<T>(transactionId, updates))
213 .thenApply(succeeded -> {
214 setState(State.PREPARED);
215 return succeeded;
216 });
217 }
218
219 /**
220 * Prepares and commits the transaction in a single atomic operation.
221 * <p>
222 * Both the prepare and commit phases of the protocol must be executed within a single atomic operation. This method
223 * is used to optimize committing transactions that operate only on a single partition within a single primitive.
224 *
225 * @param updates the transaction updates
226 * @return a completable future to be completed once the transaction has been prepared
227 */
228 public CompletableFuture<Boolean> prepareAndCommit(List<T> updates) {
229 checkOpen();
230 checkActive();
231 setState(State.PREPARING);
232 return transactionalObject.prepareAndCommit(new TransactionLog<T>(transactionId, updates))
233 .thenApply(succeeded -> {
234 setState(State.COMMITTED);
235 return succeeded;
236 });
237 }
238
239 /**
240 * Commits the transaction.
241 * <p>
242 * Performs the second phase of the two-phase commit protocol, committing the previously
243 * {@link #prepare(List) prepared} updates.
244 *
245 * @return a completable future to be completed once the transaction has been committed
246 */
247 public CompletableFuture<Void> commit() {
248 checkOpen();
249 checkPrepared();
250 setState(State.COMMITTING);
251 return transactionalObject.commit(transactionId).thenRun(() -> {
252 setState(State.COMMITTED);
253 });
254 }
255
256 /**
257 * Rolls back the transaction.
258 * <p>
259 * Rolls back the first phase of the two-phase commit protocol, cancelling prepared updates.
260 *
261 * @return a completable future to be completed once the transaction has been rolled back
262 */
263 public CompletableFuture<Void> rollback() {
264 checkOpen();
265 checkPrepared();
266 setState(State.ROLLING_BACK);
267 return transactionalObject.rollback(transactionId).thenRun(() -> {
268 setState(State.ROLLED_BACK);
269 });
Madan Jampanicadd70b2016-02-08 13:45:43 -0800270 }
271
272 @Override
273 public String toString() {
Jordan Halterman948d6592017-04-20 17:18:24 -0700274 return toStringHelper(this)
Madan Jampanicadd70b2016-02-08 13:45:43 -0800275 .add("transactionId", transactionId)
Madan Jampanicadd70b2016-02-08 13:45:43 -0800276 .add("state", state)
277 .toString();
278 }
279}