diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/TransactionTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/TransactionTest.java index 0f084f60bb9..23c74406068 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/TransactionTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/TransactionTest.java @@ -29,6 +29,7 @@ import com.google.android.gms.tasks.TaskCompletionSource; import com.google.android.gms.tasks.Tasks; import com.google.firebase.firestore.FirebaseFirestoreException.Code; +import com.google.firebase.firestore.core.TransactionRunner; import com.google.firebase.firestore.testutil.IntegrationTestUtil; import com.google.firebase.firestore.util.AsyncQueue.TimerId; import java.util.ArrayList; @@ -632,6 +633,27 @@ public void testDoesNotRetryOnPermanentError() { assertEquals(1, count.get()); } + @Test + public void testMakesDefaultMaxAttempts() { + FirebaseFirestore firestore = testFirestore(); + DocumentReference doc1 = firestore.collection("counters").document(); + AtomicInteger count = new AtomicInteger(0); + waitFor(doc1.set(map("count", 15))); + Task transactionTask = + firestore.runTransaction( + transaction -> { + // Get the first doc. + transaction.get(doc1); + // Do a write outside of the transaction to cause the transaction to fail. + waitFor(doc1.set(map("count", 1234 + count.incrementAndGet()))); + return null; + }); + + Exception e = waitForException(transactionTask); + assertEquals(Code.FAILED_PRECONDITION, ((FirebaseFirestoreException) e).getCode()); + assertEquals(TransactionRunner.DEFAULT_MAX_ATTEMPTS_COUNT, count.get()); + } + @Test public void testSuccessWithNoTransactionOperations() { FirebaseFirestore firestore = testFirestore(); diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/TransactionRunner.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/TransactionRunner.java index 2adf95b5213..7dd50b36ca4 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/TransactionRunner.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/TransactionRunner.java @@ -27,11 +27,11 @@ /** TransactionRunner encapsulates the logic needed to run and retry transactions with backoff. */ public class TransactionRunner { - private static final int RETRY_COUNT = 5; + public static final int DEFAULT_MAX_ATTEMPTS_COUNT = 5; private AsyncQueue asyncQueue; private RemoteStore remoteStore; private Function> updateFunction; - private int retriesLeft; + private int attemptsRemaining; private ExponentialBackoff backoff; private TaskCompletionSource taskSource = new TaskCompletionSource<>(); @@ -44,7 +44,7 @@ public TransactionRunner( this.asyncQueue = asyncQueue; this.remoteStore = remoteStore; this.updateFunction = updateFunction; - this.retriesLeft = RETRY_COUNT; + this.attemptsRemaining = DEFAULT_MAX_ATTEMPTS_COUNT; backoff = new ExponentialBackoff(asyncQueue, TimerId.RETRY_TRANSACTION); } @@ -56,6 +56,7 @@ public Task run() { } private void runWithBackoff() { + attemptsRemaining -= 1; backoff.backoffAndRun( () -> { final Transaction transaction = remoteStore.createTransaction(); @@ -84,8 +85,7 @@ private void runWithBackoff() { } private void handleTransactionError(Task task) { - if (retriesLeft > 0 && isRetryableTransactionError(task.getException())) { - retriesLeft -= 1; + if (attemptsRemaining > 0 && isRetryableTransactionError(task.getException())) { runWithBackoff(); } else { taskSource.setException(task.getException());