| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.cassandra.utils.concurrent; |
| |
| import static org.apache.cassandra.utils.Throwables.maybeFail; |
| import static org.apache.cassandra.utils.Throwables.merge; |
| |
| /** |
| * An abstraction for Transactional behaviour. An object implementing this interface has a lifetime |
| * of the following pattern: |
| * |
| * Throwable failure = null; |
| * try (Transactional t1, t2 = ...) |
| * { |
| * // do work with t1 and t2 |
| * t1.prepareToCommit(); |
| * t2.prepareToCommit(); |
| * failure = t1.commit(failure); |
| * failure = t2.commit(failure); |
| * } |
| * logger.error(failure); |
| * |
| * If something goes wrong before commit() is called on any transaction, then on exiting the try block |
| * the auto close method should invoke cleanup() and then abort() to reset any state. |
| * If everything completes normally, then on exiting the try block the auto close method will invoke cleanup |
| * to release any temporary state/resources |
| * |
| * All exceptions and assertions that may be thrown should be checked and ruled out during commit preparation. |
| * Commit should generally never throw an exception unless there is a real correctness-affecting exception that |
| * cannot be moved to prepareToCommit, in which case this operation MUST be executed before any other commit |
| * methods in the object graph. |
| * |
| * If exceptions are generated by commit after this initial moment, it is not at all clear what the correct behaviour |
| * of the system should be, and so simply logging the exception is likely best (since it may have been an issue |
| * during cleanup, say), and rollback cannot now occur. As such all exceptions and assertions that may be thrown |
| * should be checked and ruled out during commit preparation. |
| * |
| * Since Transactional implementations will abort any changes they've made if calls to prepareToCommit() and commit() |
| * aren't made prior to calling close(), the semantics of its close() method differ significantly from |
| * most AutoCloseable implementations. |
| */ |
| public interface Transactional extends AutoCloseable |
| { |
| /** |
| * A simple abstract implementation of Transactional behaviour. |
| * In general this should be used as the base class for any transactional implementations. |
| * |
| * If the implementation wraps any internal Transactional objects, it must proxy every |
| * commit() and abort() call onto each internal object to ensure correct behaviour |
| */ |
| abstract class AbstractTransactional implements Transactional |
| { |
| public enum State |
| { |
| IN_PROGRESS, |
| READY_TO_COMMIT, |
| COMMITTED, |
| ABORTED; |
| } |
| |
| private boolean permitRedundantTransitions; |
| private State state = State.IN_PROGRESS; |
| |
| // the methods for actually performing the necessary behaviours, that are themselves protected against |
| // improper use by the external implementations provided by this class. empty default implementations |
| // could be provided, but we consider it safer to force implementers to consider explicitly their presence |
| |
| protected abstract Throwable doCommit(Throwable accumulate); |
| protected abstract Throwable doAbort(Throwable accumulate); |
| |
| // these only needs to perform cleanup of state unique to this instance; any internal |
| // Transactional objects will perform cleanup in the commit() or abort() calls |
| |
| /** |
| * perform an exception-safe pre-abort/commit cleanup; |
| * this will be run after prepareToCommit (so before commit), and before abort |
| */ |
| protected Throwable doPreCleanup(Throwable accumulate){ return accumulate; } |
| |
| /** |
| * perform an exception-safe post-abort cleanup |
| */ |
| protected Throwable doPostCleanup(Throwable accumulate){ return accumulate; } |
| |
| /** |
| * Do any preparatory work prior to commit. This method should throw any exceptions that can be encountered |
| * during the finalization of the behaviour. |
| */ |
| protected abstract void doPrepare(); |
| |
| /** |
| * commit any effects of this transaction object graph, then cleanup; delegates first to doCommit, then to doCleanup |
| */ |
| public final Throwable commit(Throwable accumulate) |
| { |
| if (permitRedundantTransitions && state == State.COMMITTED) |
| return accumulate; |
| if (state != State.READY_TO_COMMIT) |
| throw new IllegalStateException("Cannot commit unless READY_TO_COMMIT; state is " + state); |
| accumulate = doCommit(accumulate); |
| accumulate = doPostCleanup(accumulate); |
| state = State.COMMITTED; |
| return accumulate; |
| } |
| |
| /** |
| * rollback any effects of this transaction object graph; delegates first to doCleanup, then to doAbort |
| */ |
| public final Throwable abort(Throwable accumulate) |
| { |
| if (state == State.ABORTED) |
| return accumulate; |
| if (state == State.COMMITTED) |
| { |
| try |
| { |
| throw new IllegalStateException("Attempted to abort a committed operation"); |
| } |
| catch (Throwable t) |
| { |
| accumulate = merge(accumulate, t); |
| } |
| return accumulate; |
| } |
| state = State.ABORTED; |
| // we cleanup first so that, e.g., file handles can be released prior to deletion |
| accumulate = doPreCleanup(accumulate); |
| accumulate = doAbort(accumulate); |
| accumulate = doPostCleanup(accumulate); |
| return accumulate; |
| } |
| |
| // if we are committed or aborted, then we are done; otherwise abort |
| public final void close() |
| { |
| switch (state) |
| { |
| case COMMITTED: |
| case ABORTED: |
| break; |
| default: |
| abort(); |
| } |
| } |
| |
| /** |
| * The first phase of commit: delegates to doPrepare(), with valid state transition enforcement. |
| * This call should be propagated onto any child objects participating in the transaction |
| */ |
| public final void prepareToCommit() |
| { |
| if (permitRedundantTransitions && state == State.READY_TO_COMMIT) |
| return; |
| if (state != State.IN_PROGRESS) |
| throw new IllegalStateException("Cannot prepare to commit unless IN_PROGRESS; state is " + state); |
| |
| doPrepare(); |
| maybeFail(doPreCleanup(null)); |
| state = State.READY_TO_COMMIT; |
| } |
| |
| /** |
| * convenience method to both prepareToCommit() and commit() in one operation; |
| * only of use to outer-most transactional object of an object graph |
| */ |
| public Object finish() |
| { |
| prepareToCommit(); |
| commit(); |
| return this; |
| } |
| |
| // convenience method wrapping abort, and throwing any exception encountered |
| // only of use to (and to be used by) outer-most object in a transactional graph |
| public final void abort() |
| { |
| maybeFail(abort(null)); |
| } |
| |
| // convenience method wrapping commit, and throwing any exception encountered |
| // only of use to (and to be used by) outer-most object in a transactional graph |
| public final void commit() |
| { |
| maybeFail(commit(null)); |
| } |
| |
| public final State state() |
| { |
| return state; |
| } |
| |
| protected void permitRedundantTransitions() |
| { |
| permitRedundantTransitions = true; |
| } |
| } |
| |
| // commit should generally never throw an exception, and preferably never generate one, |
| // but if it does generate one it should accumulate it in the parameter and return the result |
| // IF a commit implementation has a real correctness affecting exception that cannot be moved to |
| // prepareToCommit, it MUST be executed before any other commit methods in the object graph |
| Throwable commit(Throwable accumulate); |
| |
| // release any resources, then rollback all state changes (unless commit() has already been invoked) |
| Throwable abort(Throwable accumulate); |
| |
| void prepareToCommit(); |
| |
| // close() does not throw |
| public void close(); |
| } |