aboutsummaryrefslogtreecommitdiffstats
path: root/vespajlib/src/main/java/com/yahoo/transaction
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /vespajlib/src/main/java/com/yahoo/transaction
Publish
Diffstat (limited to 'vespajlib/src/main/java/com/yahoo/transaction')
-rw-r--r--vespajlib/src/main/java/com/yahoo/transaction/Mutex.java13
-rw-r--r--vespajlib/src/main/java/com/yahoo/transaction/NestedTransaction.java200
-rw-r--r--vespajlib/src/main/java/com/yahoo/transaction/Transaction.java72
-rw-r--r--vespajlib/src/main/java/com/yahoo/transaction/package-info.java5
4 files changed, 290 insertions, 0 deletions
diff --git a/vespajlib/src/main/java/com/yahoo/transaction/Mutex.java b/vespajlib/src/main/java/com/yahoo/transaction/Mutex.java
new file mode 100644
index 00000000000..3c4168b77f3
--- /dev/null
+++ b/vespajlib/src/main/java/com/yahoo/transaction/Mutex.java
@@ -0,0 +1,13 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.transaction;
+
+/**
+ * An auto closeable mutex
+ *
+ * @author bratseth
+ */
+public interface Mutex extends AutoCloseable {
+
+ public void close();
+
+}
diff --git a/vespajlib/src/main/java/com/yahoo/transaction/NestedTransaction.java b/vespajlib/src/main/java/com/yahoo/transaction/NestedTransaction.java
new file mode 100644
index 00000000000..4be0a32ffe8
--- /dev/null
+++ b/vespajlib/src/main/java/com/yahoo/transaction/NestedTransaction.java
@@ -0,0 +1,200 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.transaction;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ * A transaction which may contain a list of transactions, typically to represent a distributed transaction
+ * over multiple systems.
+ *
+ * @author bratseth
+ */
+public final class NestedTransaction implements AutoCloseable {
+
+ private static final Logger log = Logger.getLogger(NestedTransaction.class.getName());
+
+ /** Nested transactions with ordering constraints, in the order they are added */
+ private final List<ConstrainedTransaction> transactions = new ArrayList<>(2);
+
+ /** Transaction ordering pairs */
+ //private final List<OrderingConstraint> transactionOrders = new ArrayList<>(2);
+
+ /** A list of (non-transactional) operations to execute after this transaction has committed successfully */
+ private final List<Runnable> onCommitted = new ArrayList<>(2);
+
+ /**
+ * Adds a transaction to this.
+ *
+ * @param transaction the transaction to add
+ * @param before transaction classes which should commit after this, if present. It is beneficial
+ * to order transaction types from the least to most reliable. If conflicting ordering constraints are
+ * given this will not be detected at add time but the transaction will fail to commit
+ * @return this for convenience
+ */
+ @SafeVarargs // don't warn on 'before' argument
+ @SuppressWarnings("varargs") // don't warn on passing 'before' to the nested class constructor
+ public final NestedTransaction add(Transaction transaction, Class<? extends Transaction> ... before) {
+ transactions.add(new ConstrainedTransaction(transaction, before));
+ return this;
+ }
+
+ /** Returns the transactions nested in this, as they will be committed. */
+ public List<Transaction> transactions() { return organizeTransactions(transactions); }
+
+ /** Perform a 2 phase commit */
+ public void commit() {
+ List<Transaction> organizedTransactions = organizeTransactions(transactions);
+
+ // First phase
+ for (Transaction transaction : organizedTransactions)
+ transaction.prepare();
+
+ // Second phase
+ for (ListIterator<Transaction> i = organizedTransactions.listIterator(); i.hasNext(); ) {
+ Transaction transaction = i.next();
+ try {
+ transaction.commit();
+ }
+ catch (Exception e) {
+ // Clean up committed part or log that we can't
+ i.previous();
+ while (i.hasPrevious())
+ i.previous().rollbackOrLog();
+ throw new IllegalStateException("Transaction failed during commit", e);
+ }
+ }
+
+ // After commit: Execute completion tasks
+ for (Runnable task : onCommitted) {
+ try {
+ task.run();
+ }
+ catch (Exception e) { // Don't throw from here as that indicates transaction didn't complete
+ log.log(Level.WARNING, "A committed task in " + this + " caused an exception", e);
+ }
+ }
+ }
+
+ public void onCommitted(Runnable runnable) {
+ onCommitted.add(runnable);
+ }
+
+ /** Free up any temporary resources held by this */
+ @Override
+ public void close() {
+ for (ConstrainedTransaction transaction : transactions)
+ transaction.transaction.close();
+ }
+
+ private List<Transaction> organizeTransactions(List<ConstrainedTransaction> transactions) {
+ return orderTransactions(combineTransactions(transactions), findOrderingConstraints(transactions));
+ }
+
+ /** Combines all transactions of the same type to one */
+ private List<Transaction> combineTransactions(List<ConstrainedTransaction> transactions) {
+ List<Transaction> combinedTransactions = new ArrayList<>(transactions.size());
+ for (List<Transaction> combinableTransactions :
+ transactions.stream().map(ConstrainedTransaction::transaction).
+ collect(Collectors.groupingBy(Transaction::getClass)).values()) {
+ Transaction combinedTransaction = combinableTransactions.get(0);
+ for (int i = 1; i < combinableTransactions.size(); i++)
+ combinedTransaction = combinedTransaction.add(combinableTransactions.get(i).operations());
+ combinedTransactions.add(combinedTransaction);
+ }
+ return combinedTransactions;
+ }
+
+ private List<OrderingConstraint> findOrderingConstraints(List<ConstrainedTransaction> transactions) {
+ List<OrderingConstraint> orderingConstraints = new ArrayList<>(1);
+ for (ConstrainedTransaction transaction : transactions) {
+ for (Class<? extends Transaction> afterThis : transaction.before())
+ orderingConstraints.add(new OrderingConstraint(transaction.transaction().getClass(), afterThis));
+ }
+ return orderingConstraints;
+ }
+
+ /** Orders combined transactions consistent with the ordering constraints */
+ private List<Transaction> orderTransactions(List<Transaction> transactions, List<OrderingConstraint> constraints) {
+ if (transactions.size() == 1) return transactions;
+
+ List<Transaction> orderedTransactions = new ArrayList<>();
+ for (Transaction transaction : transactions)
+ orderedTransactions.add(findSuitablePositionFor(transaction, orderedTransactions, constraints), transaction);
+ return orderedTransactions;
+ }
+
+ private int findSuitablePositionFor(Transaction transaction, List<Transaction> orderedTransactions,
+ List<OrderingConstraint> constraints) {
+ for (int i = 0; i < orderedTransactions.size(); i++) {
+ Transaction candidateNextTransaction = orderedTransactions.get(i);
+ if ( ! mustBeAfter(candidateNextTransaction.getClass(), transaction.getClass(), constraints)) return i;
+
+ // transaction must be after this: continue to next position
+ if (mustBeAfter(transaction.getClass(), candidateNextTransaction.getClass(), constraints)) // must be after && must be before
+ throw new IllegalStateException("Conflicting transaction ordering constraints between" +
+ transaction + " and " + candidateNextTransaction);
+ }
+ return orderedTransactions.size(); // add last as it must be after everything
+ }
+
+ /**
+ * Returns whether transaction type B must be after type A according to the ordering constraints.
+ * This is the same as asking whether there is a path between node a and b in the bi-directional
+ * graph defined by the ordering constraints.
+ */
+ private boolean mustBeAfter(Class<? extends Transaction> a, Class<? extends Transaction> b,
+ List<OrderingConstraint> constraints) {
+ for (OrderingConstraint fromA : findAllOrderingConstraintsFrom(a, constraints)) {
+ if (fromA.after().equals(b)) return true;
+ if (mustBeAfter(fromA.after(), b, constraints)) return true;
+ }
+ return false;
+ }
+
+ private List<OrderingConstraint> findAllOrderingConstraintsFrom(Class<? extends Transaction> transactionType,
+ List<OrderingConstraint> constraints) {
+ return constraints.stream().filter(c -> c.before().equals(transactionType)).collect(Collectors.toList());
+ }
+
+ private static class ConstrainedTransaction {
+
+ private final Transaction transaction;
+
+ private final Class<? extends Transaction>[] before;
+
+ public ConstrainedTransaction(Transaction transaction, Class<? extends Transaction>[] before) {
+ this.transaction = transaction;
+ this.before = before;
+ }
+
+ public Transaction transaction() { return transaction; }
+
+ /** Returns transaction types which should commit after this */
+ public Class<? extends Transaction>[] before() { return before; }
+
+ }
+
+ private static class OrderingConstraint {
+
+ private final Class<? extends Transaction> before, after;
+
+ public OrderingConstraint(Class<? extends Transaction> before, Class<? extends Transaction> after) {
+ this.before = before;
+ this.after = after;
+ }
+
+ public Class<? extends Transaction> before() { return before; }
+
+ public Class<? extends Transaction> after() { return after; }
+
+ @Override
+ public String toString() { return before + " -> " + after; }
+
+ }
+
+}
diff --git a/vespajlib/src/main/java/com/yahoo/transaction/Transaction.java b/vespajlib/src/main/java/com/yahoo/transaction/Transaction.java
new file mode 100644
index 00000000000..642438dda0a
--- /dev/null
+++ b/vespajlib/src/main/java/com/yahoo/transaction/Transaction.java
@@ -0,0 +1,72 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.transaction;
+
+import java.util.List;
+
+/**
+ * An interface for building a transaction and committing it. Implementations are required to atomically apply changes
+ * in the commit step or throw an exception if it fails.
+ *
+ * @author lulf
+ * @author bratseth
+ */
+public interface Transaction extends AutoCloseable {
+
+ /**
+ * Adds an operation to this transaction. Return self for chaining.
+ *
+ * @param operation {@link Operation} to append
+ * @return self, for chaining
+ */
+ Transaction add(Operation operation);
+
+ /**
+ * Adds multiple operations to this transaction. Return self for chaining.
+ *
+ * @param operation {@link Operation} to append
+ * @return self, for chaining
+ */
+ Transaction add(List<Operation> operation);
+
+ /**
+ * Returns the operations of this.
+ * Ownership of the returned list is transferred to the caller. The ist may be ready only.
+ */
+ List<Operation> operations();
+
+ /**
+ * Checks whether or not the transaction is able to commit in its current state and do any transient preparatory
+ * work to commit.
+ *
+ * @throws IllegalStateException if the transaction cannot be committed
+ */
+ void prepare();
+
+ /**
+ * Commit this transaction. If this method returns, all operations in this transaction was committed
+ * successfully. Implementations of this must be exception safe or log a message of type severe if they partially
+ * alter state.
+ *
+ * @throws IllegalStateException if transaction failed.
+ */
+ void commit();
+
+ /**
+ * This is called if the transaction should be rolled back after commit. If a rollback is not possible or
+ * supported. This must log a message of type severe with detailed information about the resulting state.
+ */
+ void rollbackOrLog();
+
+ /**
+ * Closes and frees any resources allocated by this transaction. The transaction instance cannot be reused once
+ * closed.
+ */
+ void close();
+
+ /**
+ * Operations that a transaction supports should implement this interface.
+ */
+ public interface Operation {
+ }
+
+}
diff --git a/vespajlib/src/main/java/com/yahoo/transaction/package-info.java b/vespajlib/src/main/java/com/yahoo/transaction/package-info.java
new file mode 100644
index 00000000000..72ac10d13d0
--- /dev/null
+++ b/vespajlib/src/main/java/com/yahoo/transaction/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.transaction;
+
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file