Skip to content

Standardize transaction retries to attempts #2764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Void> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@

/** TransactionRunner encapsulates the logic needed to run and retry transactions with backoff. */
public class TransactionRunner<TResult> {
private static final int RETRY_COUNT = 5;
public static final int DEFAULT_MAX_ATTEMPTS_COUNT = 5;
private AsyncQueue asyncQueue;
private RemoteStore remoteStore;
private Function<Transaction, Task<TResult>> updateFunction;
private int retriesLeft;
private int attemptsRemaining;

private ExponentialBackoff backoff;
private TaskCompletionSource<TResult> taskSource = new TaskCompletionSource<>();
Expand All @@ -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);
}
Expand All @@ -56,6 +56,7 @@ public Task<TResult> run() {
}

private void runWithBackoff() {
attemptsRemaining -= 1;
backoff.backoffAndRun(
() -> {
final Transaction transaction = remoteStore.createTransaction();
Expand Down Expand Up @@ -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());
Expand Down