From 246a0676af95e255fffb421521f3c9c952e04734 Mon Sep 17 00:00:00 2001 From: Eldhose Mathokkil Babu Date: Wed, 23 Feb 2022 16:21:35 -0800 Subject: [PATCH 01/13] - Changes to protoc version to make it working for m1 macs. --- build.gradle | 2 +- .../protoc-gen-firebase-encoders.gradle | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f408825f930..863a0ea780a 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ ext { googleTruthVersion = '1.1.2' grpcVersion = '1.44.1' robolectricVersion = '4.3.1' - protocVersion = '3.14.0' + protocVersion = '3.17.3' javaliteVersion = '3.14.0' } diff --git a/encoders/protoc-gen-firebase-encoders/protoc-gen-firebase-encoders.gradle b/encoders/protoc-gen-firebase-encoders/protoc-gen-firebase-encoders.gradle index d3638b435e5..f939164d797 100644 --- a/encoders/protoc-gen-firebase-encoders/protoc-gen-firebase-encoders.gradle +++ b/encoders/protoc-gen-firebase-encoders/protoc-gen-firebase-encoders.gradle @@ -30,6 +30,10 @@ jar { manifest.attributes "Main-Class": "com.google.firebase.encoders.proto.codegen.MainKt" } +kapt { + correctErrorTypes true +} + dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72" From f7873ee4fe0eec122622f209c7bf1b9e681491c7 Mon Sep 17 00:00:00 2001 From: Eldhose Mathokkil Babu Date: Thu, 24 Feb 2022 10:53:34 -0800 Subject: [PATCH 02/13] - Changes to protoc version to make it working for m1 macs. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 863a0ea780a..a7702166e08 100644 --- a/build.gradle +++ b/build.gradle @@ -49,7 +49,7 @@ ext { grpcVersion = '1.44.1' robolectricVersion = '4.3.1' protocVersion = '3.17.3' - javaliteVersion = '3.14.0' + javaliteVersion = '3.17.3' } apply plugin: com.google.firebase.gradle.plugins.publish.PublishingPlugin From 5a31b19c9b7746ebdd6e0e3cefb23220fdfe139f Mon Sep 17 00:00:00 2001 From: Eldhose Mathokkil Babu Date: Fri, 25 Feb 2022 16:59:05 -0800 Subject: [PATCH 03/13] - Changes to dependency versions to make it working for m1 macs. --- build.gradle | 2 +- firebase-database/firebase-database.gradle | 2 ++ firebase-firestore/firebase-firestore.gradle | 2 ++ firebase-installations/firebase-installations.gradle | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a7702166e08..97d9de95d7f 100644 --- a/build.gradle +++ b/build.gradle @@ -47,7 +47,7 @@ ext { supportAnnotationsVersion = '28.0.0' googleTruthVersion = '1.1.2' grpcVersion = '1.44.1' - robolectricVersion = '4.3.1' + robolectricVersion = '4.7.3' protocVersion = '3.17.3' javaliteVersion = '3.17.3' } diff --git a/firebase-database/firebase-database.gradle b/firebase-database/firebase-database.gradle index 4a4898d39a1..b7249d6e389 100644 --- a/firebase-database/firebase-database.gradle +++ b/firebase-database/firebase-database.gradle @@ -97,6 +97,7 @@ dependencies { androidTestImplementation 'org.hamcrest:hamcrest:2.2' androidTestImplementation 'org.hamcrest:hamcrest-library:2.2' androidTestImplementation 'net.java:quickcheck:0.6' + androidTestImplementation 'org.codehaus.plexus:plexus-utils:1.5.7' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.25.0' @@ -108,6 +109,7 @@ dependencies { testImplementation "com.google.truth:truth:$googleTruthVersion" testImplementation 'androidx.test:core:1.2.0' testImplementation 'androidx.test:rules:1.2.0' + testImplementation 'org.codehaus.plexus:plexus-utils:1.5.7' } gradle.projectsEvaluated { diff --git a/firebase-firestore/firebase-firestore.gradle b/firebase-firestore/firebase-firestore.gradle index 6be15dbad61..834dc48f6df 100644 --- a/firebase-firestore/firebase-firestore.gradle +++ b/firebase-firestore/firebase-firestore.gradle @@ -162,6 +162,7 @@ dependencies { testImplementation "com.google.truth:truth:$googleTruthVersion" testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' testImplementation 'com.google.guava:guava-testlib:12.0-rc2' + testImplementation 'org.hamcrest:hamcrest:2.2' androidTestImplementation 'junit:junit:4.12' androidTestImplementation("com.google.truth:truth:$googleTruthVersion"){ @@ -174,6 +175,7 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'org.hamcrest:hamcrest:2.2' } gradle.projectsEvaluated { diff --git a/firebase-installations/firebase-installations.gradle b/firebase-installations/firebase-installations.gradle index 331bb783032..aee64054a18 100644 --- a/firebase-installations/firebase-installations.gradle +++ b/firebase-installations/firebase-installations.gradle @@ -56,6 +56,7 @@ dependencies { testImplementation "com.google.truth:truth:$googleTruthVersion" testImplementation 'org.mockito:mockito-core:2.25.0' testImplementation 'org.mockito:mockito-inline:2.25.0' + testImplementation 'org.hamcrest:hamcrest:2.2' androidTestImplementation project(':integ-testing') androidTestImplementation 'androidx.test.ext:junit:1.1.1' @@ -65,4 +66,5 @@ dependencies { androidTestImplementation "androidx.annotation:annotation:1.0.0" androidTestImplementation 'org.mockito:mockito-core:2.25.0' androidTestImplementation 'org.mockito:mockito-inline:2.25.0' + androidTestImplementation 'org.hamcrest:hamcrest:2.2' } From b8a85ef43bb31cfcb9c88bfd8b1d11d4fb98ae5d Mon Sep 17 00:00:00 2001 From: Eldhose Mathokkil Babu Date: Mon, 28 Feb 2022 12:15:09 -0800 Subject: [PATCH 04/13] - Reverting the robolectric version change --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 97d9de95d7f..a7702166e08 100644 --- a/build.gradle +++ b/build.gradle @@ -47,7 +47,7 @@ ext { supportAnnotationsVersion = '28.0.0' googleTruthVersion = '1.1.2' grpcVersion = '1.44.1' - robolectricVersion = '4.7.3' + robolectricVersion = '4.3.1' protocVersion = '3.17.3' javaliteVersion = '3.17.3' } From 80da16efa919b10cdfaae8c61b775a529d7575f1 Mon Sep 17 00:00:00 2001 From: Eldhose Mathokkil Babu Date: Mon, 28 Feb 2022 12:19:24 -0800 Subject: [PATCH 05/13] Revert "- Changes to dependency versions to make it working for m1 macs." This reverts commit 5a31b19c --- firebase-database/firebase-database.gradle | 2 -- firebase-firestore/firebase-firestore.gradle | 2 -- firebase-installations/firebase-installations.gradle | 2 -- 3 files changed, 6 deletions(-) diff --git a/firebase-database/firebase-database.gradle b/firebase-database/firebase-database.gradle index b7249d6e389..4a4898d39a1 100644 --- a/firebase-database/firebase-database.gradle +++ b/firebase-database/firebase-database.gradle @@ -97,7 +97,6 @@ dependencies { androidTestImplementation 'org.hamcrest:hamcrest:2.2' androidTestImplementation 'org.hamcrest:hamcrest-library:2.2' androidTestImplementation 'net.java:quickcheck:0.6' - androidTestImplementation 'org.codehaus.plexus:plexus-utils:1.5.7' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.25.0' @@ -109,7 +108,6 @@ dependencies { testImplementation "com.google.truth:truth:$googleTruthVersion" testImplementation 'androidx.test:core:1.2.0' testImplementation 'androidx.test:rules:1.2.0' - testImplementation 'org.codehaus.plexus:plexus-utils:1.5.7' } gradle.projectsEvaluated { diff --git a/firebase-firestore/firebase-firestore.gradle b/firebase-firestore/firebase-firestore.gradle index 834dc48f6df..6be15dbad61 100644 --- a/firebase-firestore/firebase-firestore.gradle +++ b/firebase-firestore/firebase-firestore.gradle @@ -162,7 +162,6 @@ dependencies { testImplementation "com.google.truth:truth:$googleTruthVersion" testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' testImplementation 'com.google.guava:guava-testlib:12.0-rc2' - testImplementation 'org.hamcrest:hamcrest:2.2' androidTestImplementation 'junit:junit:4.12' androidTestImplementation("com.google.truth:truth:$googleTruthVersion"){ @@ -175,7 +174,6 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'org.hamcrest:hamcrest:2.2' } gradle.projectsEvaluated { diff --git a/firebase-installations/firebase-installations.gradle b/firebase-installations/firebase-installations.gradle index aee64054a18..331bb783032 100644 --- a/firebase-installations/firebase-installations.gradle +++ b/firebase-installations/firebase-installations.gradle @@ -56,7 +56,6 @@ dependencies { testImplementation "com.google.truth:truth:$googleTruthVersion" testImplementation 'org.mockito:mockito-core:2.25.0' testImplementation 'org.mockito:mockito-inline:2.25.0' - testImplementation 'org.hamcrest:hamcrest:2.2' androidTestImplementation project(':integ-testing') androidTestImplementation 'androidx.test.ext:junit:1.1.1' @@ -66,5 +65,4 @@ dependencies { androidTestImplementation "androidx.annotation:annotation:1.0.0" androidTestImplementation 'org.mockito:mockito-core:2.25.0' androidTestImplementation 'org.mockito:mockito-inline:2.25.0' - androidTestImplementation 'org.hamcrest:hamcrest:2.2' } From cca600ee462648410471a65ed7db0b96dda94ad4 Mon Sep 17 00:00:00 2001 From: Eldhose Mathokkil Babu Date: Mon, 28 Feb 2022 14:18:09 -0800 Subject: [PATCH 06/13] - Updating robolectric versions to 4.7+ to get support for m1 macs. See: https://github.com/robolectric/robolectric/issues/6311 --- firebase-firestore/firebase-firestore.gradle | 4 +++- transport/transport-runtime/transport-runtime.gradle | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/firebase-firestore/firebase-firestore.gradle b/firebase-firestore/firebase-firestore.gradle index 6be15dbad61..77d8a10a8f2 100644 --- a/firebase-firestore/firebase-firestore.gradle +++ b/firebase-firestore/firebase-firestore.gradle @@ -156,12 +156,13 @@ dependencies { testImplementation 'junit:junit:4.12' testImplementation 'androidx.test:core:1.2.0' testImplementation 'org.mockito:mockito-core:2.25.0' - testImplementation ("org.robolectric:robolectric:$robolectricVersion") { + testImplementation ("org.robolectric:robolectric:4.7.3") { exclude group: 'com.google.protobuf', module: 'protobuf-java' } testImplementation "com.google.truth:truth:$googleTruthVersion" testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' testImplementation 'com.google.guava:guava-testlib:12.0-rc2' + testImplementation 'org.hamcrest:hamcrest:2.2' androidTestImplementation 'junit:junit:4.12' androidTestImplementation("com.google.truth:truth:$googleTruthVersion"){ @@ -174,6 +175,7 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test.ext:junit:1.1.1' + testImplementation 'org.hamcrest:hamcrest:2.2' } gradle.projectsEvaluated { diff --git a/transport/transport-runtime/transport-runtime.gradle b/transport/transport-runtime/transport-runtime.gradle index 617895a7058..99c37338407 100644 --- a/transport/transport-runtime/transport-runtime.gradle +++ b/transport/transport-runtime/transport-runtime.gradle @@ -120,7 +120,7 @@ dependencies { testImplementation "com.google.truth:truth:$googleTruthVersion" testImplementation 'androidx.test:core:1.2.0' testImplementation 'androidx.test.ext:junit:1.1.1' - testImplementation "org.robolectric:robolectric:$robolectricVersion" + testImplementation "org.robolectric:robolectric:4.7.3" testImplementation 'org.mockito:mockito-core:2.25.0' androidTestImplementation 'junit:junit:4.13-beta-3' From 090773ac039f659be31040d1ffe63b3933427a31 Mon Sep 17 00:00:00 2001 From: Eldhose Mathokkil Babu Date: Thu, 3 Mar 2022 13:46:20 -0800 Subject: [PATCH 07/13] Reverting some changes to check --- .../protoc-gen-firebase-encoders.gradle | 4 ---- firebase-firestore/firebase-firestore.gradle | 4 +--- transport/transport-runtime/transport-runtime.gradle | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/encoders/protoc-gen-firebase-encoders/protoc-gen-firebase-encoders.gradle b/encoders/protoc-gen-firebase-encoders/protoc-gen-firebase-encoders.gradle index f939164d797..d3638b435e5 100644 --- a/encoders/protoc-gen-firebase-encoders/protoc-gen-firebase-encoders.gradle +++ b/encoders/protoc-gen-firebase-encoders/protoc-gen-firebase-encoders.gradle @@ -30,10 +30,6 @@ jar { manifest.attributes "Main-Class": "com.google.firebase.encoders.proto.codegen.MainKt" } -kapt { - correctErrorTypes true -} - dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72" diff --git a/firebase-firestore/firebase-firestore.gradle b/firebase-firestore/firebase-firestore.gradle index 77d8a10a8f2..6be15dbad61 100644 --- a/firebase-firestore/firebase-firestore.gradle +++ b/firebase-firestore/firebase-firestore.gradle @@ -156,13 +156,12 @@ dependencies { testImplementation 'junit:junit:4.12' testImplementation 'androidx.test:core:1.2.0' testImplementation 'org.mockito:mockito-core:2.25.0' - testImplementation ("org.robolectric:robolectric:4.7.3") { + testImplementation ("org.robolectric:robolectric:$robolectricVersion") { exclude group: 'com.google.protobuf', module: 'protobuf-java' } testImplementation "com.google.truth:truth:$googleTruthVersion" testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' testImplementation 'com.google.guava:guava-testlib:12.0-rc2' - testImplementation 'org.hamcrest:hamcrest:2.2' androidTestImplementation 'junit:junit:4.12' androidTestImplementation("com.google.truth:truth:$googleTruthVersion"){ @@ -175,7 +174,6 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test.ext:junit:1.1.1' - testImplementation 'org.hamcrest:hamcrest:2.2' } gradle.projectsEvaluated { diff --git a/transport/transport-runtime/transport-runtime.gradle b/transport/transport-runtime/transport-runtime.gradle index 99c37338407..617895a7058 100644 --- a/transport/transport-runtime/transport-runtime.gradle +++ b/transport/transport-runtime/transport-runtime.gradle @@ -120,7 +120,7 @@ dependencies { testImplementation "com.google.truth:truth:$googleTruthVersion" testImplementation 'androidx.test:core:1.2.0' testImplementation 'androidx.test.ext:junit:1.1.1' - testImplementation "org.robolectric:robolectric:4.7.3" + testImplementation "org.robolectric:robolectric:$robolectricVersion" testImplementation 'org.mockito:mockito-core:2.25.0' androidTestImplementation 'junit:junit:4.13-beta-3' From a93bfce4c42570b3a44b55037a53692b7df1385f Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Tue, 19 Apr 2022 18:32:56 -0400 Subject: [PATCH 08/13] Add TransactionOptions to allow control over number of attempts to commit, before transaction fails. --- .../firebase/firestore/TransactionTest.java | 35 ++++++- .../firebase/firestore/FirebaseFirestore.java | 23 ++++- .../firestore/TransactionOptions.java | 96 +++++++++++++++++++ .../firestore/core/FirestoreClient.java | 7 +- .../firebase/firestore/core/SyncEngine.java | 7 +- .../firestore/core/TransactionRunner.java | 5 +- 6 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java 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 23c74406068..36f35495508 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 @@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -29,7 +30,6 @@ 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; @@ -651,7 +651,38 @@ public void testMakesDefaultMaxAttempts() { Exception e = waitForException(transactionTask); assertEquals(Code.FAILED_PRECONDITION, ((FirebaseFirestoreException) e).getCode()); - assertEquals(TransactionRunner.DEFAULT_MAX_ATTEMPTS_COUNT, count.get()); + assertEquals(TransactionOptions.DEFAULT_MAX_ATTEMPTS_COUNT, count.get()); + } + + @Test + public void testMakesOptionSpecifiedMaxAttempts() { + TransactionOptions options = new TransactionOptions.Builder().setMaxAttempts(1).build(); + + FirebaseFirestore firestore = testFirestore(); + DocumentReference doc1 = firestore.collection("counters").document(); + AtomicInteger count = new AtomicInteger(0); + waitFor(doc1.set(map("count", 15))); + Task transactionTask = + firestore.runTransaction( + options, + 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(options.getMaxAttempts(), count.get()); + } + + @Test + public void testTransactionOptionsZeroMaxAttempts_shouldThrowIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, + () -> new TransactionOptions.Builder().setMaxAttempts(0).build()); } @Test diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java index 510bb9402d6..5da093d762a 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java @@ -419,7 +419,7 @@ public Query collectionGroup(@NonNull String collectionId) { * @return The task returned from the updateFunction. */ private Task runTransaction( - Transaction.Function updateFunction, Executor executor) { + TransactionOptions options, Transaction.Function updateFunction, Executor executor) { ensureClientConfigured(); // We wrap the function they provide in order to @@ -434,7 +434,7 @@ private Task runTransaction( updateFunction.apply( new Transaction(internalTransaction, FirebaseFirestore.this))); - return client.transaction(wrappedUpdateFunction); + return client.transaction(options, wrappedUpdateFunction); } /** @@ -448,9 +448,26 @@ private Task runTransaction( @NonNull public Task runTransaction( @NonNull Transaction.Function updateFunction) { + return runTransaction(TransactionOptions.DEFAULT, updateFunction); + } + + /** + * Executes the given updateFunction and then attempts to commit the changes applied within the + * transaction. If any document read within the transaction has changed, the updateFunction will + * be retried. If it fails to commit after the maxmimum number of attempts specified in + * transactionOptions, the transaction will fail. + * + * @param options The function to execute within the transaction context. + * @param updateFunction The function to execute within the transaction context. + * @return The task returned from the updateFunction. + */ + public Task runTransaction( + @NonNull TransactionOptions options, @NonNull Transaction.Function updateFunction) { checkNotNull(updateFunction, "Provided transaction update function must not be null."); return runTransaction( - updateFunction, com.google.firebase.firestore.core.Transaction.getDefaultExecutor()); + options, + updateFunction, + com.google.firebase.firestore.core.Transaction.getDefaultExecutor()); } /** diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java new file mode 100644 index 00000000000..65f9f8ef750 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java @@ -0,0 +1,96 @@ +// Copyright 2022 Google LLC +// +// Licensed 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 com.google.firebase.firestore; + +/** + * Parameter for {@link FirebaseFirestore#runTransaction(TransactionOptions, Transaction.Function)}. + */ +public final class TransactionOptions { + + static final TransactionOptions DEFAULT = new TransactionOptions.Builder().build(); + static final int DEFAULT_MAX_ATTEMPTS_COUNT = 5; + + private final int maxAttempts; + + private TransactionOptions(int maxAttempts) { + this.maxAttempts = maxAttempts; + } + + /** A Builder for creating {@code TransactionOptions}. */ + public static final class Builder { + private int maxAttempts = DEFAULT_MAX_ATTEMPTS_COUNT; + + /** Constructs a new {@code TransactionOptions} Builder object. */ + public Builder() {} + + /** + * Constructs a new {@code TransactionOptions} Builder based on an existing {@code + * TransactionOptions} object. + */ + public Builder(TransactionOptions options) { + maxAttempts = options.maxAttempts; + } + + /** + * Set maximum number of attempts to commit, after which transaction fails. Default is 5. + * + * @return this builder + */ + public Builder setMaxAttempts(int maxAttempts) { + if (maxAttempts < 1) throw new IllegalArgumentException("Max attempts must be at least 1"); + this.maxAttempts = maxAttempts; + return this; + } + + + /** + * Build the {@code TransactionOptions} object. + * + * @return the built {@code TransactionOptions} object + */ + public TransactionOptions build() { + return new TransactionOptions(maxAttempts); + } + } + + /** + * Get maximum number of attempts to commit, after which transaction fails. Default is 5. + * + * @return maximum number of attempts + */ + public int getMaxAttempts() { + return maxAttempts; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TransactionOptions that = (TransactionOptions) o; + + return maxAttempts == that.maxAttempts; + } + + @Override + public int hashCode() { + return maxAttempts; + } + + @Override + public String toString() { + return "TransactionOptions{" + "maxAttempts=" + maxAttempts + '}'; + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java index 5554d4f0b43..464cd5913f9 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java @@ -26,6 +26,7 @@ import com.google.firebase.firestore.FirebaseFirestoreException.Code; import com.google.firebase.firestore.FirebaseFirestoreSettings; import com.google.firebase.firestore.LoadBundleTask; +import com.google.firebase.firestore.TransactionOptions; import com.google.firebase.firestore.auth.CredentialsProvider; import com.google.firebase.firestore.auth.User; import com.google.firebase.firestore.bundle.BundleReader; @@ -228,10 +229,12 @@ public Task write(final List mutations) { } /** Tries to execute the transaction in updateFunction. */ - public Task transaction(Function> updateFunction) { + public Task transaction( + TransactionOptions options, Function> updateFunction) { this.verifyNotTerminated(); return AsyncQueue.callTask( - asyncQueue.getExecutor(), () -> syncEngine.transaction(asyncQueue, updateFunction)); + asyncQueue.getExecutor(), + () -> syncEngine.transaction(asyncQueue, options, updateFunction)); } /** diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java index f51d1e88062..452d7907c76 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java @@ -26,6 +26,7 @@ import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.LoadBundleTask; import com.google.firebase.firestore.LoadBundleTaskProgress; +import com.google.firebase.firestore.TransactionOptions; import com.google.firebase.firestore.auth.User; import com.google.firebase.firestore.bundle.BundleElement; import com.google.firebase.firestore.bundle.BundleLoader; @@ -307,8 +308,10 @@ private void addUserCallback(int batchId, TaskCompletionSource userTask) { *

The Task returned is resolved when the transaction is fully committed. */ public Task transaction( - AsyncQueue asyncQueue, Function> updateFunction) { - return new TransactionRunner(asyncQueue, remoteStore, updateFunction).run(); + AsyncQueue asyncQueue, + TransactionOptions options, + Function> updateFunction) { + return new TransactionRunner(asyncQueue, remoteStore, options, updateFunction).run(); } /** Called by FirestoreClient to notify us of a new remote event. */ 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 7dd50b36ca4..70a249cc804 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 @@ -18,6 +18,7 @@ import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.TransactionOptions; import com.google.firebase.firestore.remote.Datastore; import com.google.firebase.firestore.remote.RemoteStore; import com.google.firebase.firestore.util.AsyncQueue; @@ -27,7 +28,6 @@ /** TransactionRunner encapsulates the logic needed to run and retry transactions with backoff. */ public class TransactionRunner { - public static final int DEFAULT_MAX_ATTEMPTS_COUNT = 5; private AsyncQueue asyncQueue; private RemoteStore remoteStore; private Function> updateFunction; @@ -39,12 +39,13 @@ public class TransactionRunner { public TransactionRunner( AsyncQueue asyncQueue, RemoteStore remoteStore, + TransactionOptions options, Function> updateFunction) { this.asyncQueue = asyncQueue; this.remoteStore = remoteStore; this.updateFunction = updateFunction; - this.attemptsRemaining = DEFAULT_MAX_ATTEMPTS_COUNT; + this.attemptsRemaining = options.getMaxAttempts(); backoff = new ExponentialBackoff(asyncQueue, TimerId.RETRY_TRANSACTION); } From 167632b4ee59d8a56f1253dc4c0b4eff727c41e6 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Tue, 19 Apr 2022 18:32:56 -0400 Subject: [PATCH 09/13] Add TransactionOptions to allow control over number of attempts to commit, before transaction fails. --- .../firebase/firestore/TransactionTest.java | 35 ++++++- .../firebase/firestore/FirebaseFirestore.java | 23 ++++- .../firestore/TransactionOptions.java | 95 +++++++++++++++++++ .../firestore/core/FirestoreClient.java | 7 +- .../firebase/firestore/core/SyncEngine.java | 7 +- .../firestore/core/TransactionRunner.java | 5 +- 6 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java 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 23c74406068..36f35495508 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 @@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -29,7 +30,6 @@ 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; @@ -651,7 +651,38 @@ public void testMakesDefaultMaxAttempts() { Exception e = waitForException(transactionTask); assertEquals(Code.FAILED_PRECONDITION, ((FirebaseFirestoreException) e).getCode()); - assertEquals(TransactionRunner.DEFAULT_MAX_ATTEMPTS_COUNT, count.get()); + assertEquals(TransactionOptions.DEFAULT_MAX_ATTEMPTS_COUNT, count.get()); + } + + @Test + public void testMakesOptionSpecifiedMaxAttempts() { + TransactionOptions options = new TransactionOptions.Builder().setMaxAttempts(1).build(); + + FirebaseFirestore firestore = testFirestore(); + DocumentReference doc1 = firestore.collection("counters").document(); + AtomicInteger count = new AtomicInteger(0); + waitFor(doc1.set(map("count", 15))); + Task transactionTask = + firestore.runTransaction( + options, + 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(options.getMaxAttempts(), count.get()); + } + + @Test + public void testTransactionOptionsZeroMaxAttempts_shouldThrowIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, + () -> new TransactionOptions.Builder().setMaxAttempts(0).build()); } @Test diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java index 510bb9402d6..5da093d762a 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java @@ -419,7 +419,7 @@ public Query collectionGroup(@NonNull String collectionId) { * @return The task returned from the updateFunction. */ private Task runTransaction( - Transaction.Function updateFunction, Executor executor) { + TransactionOptions options, Transaction.Function updateFunction, Executor executor) { ensureClientConfigured(); // We wrap the function they provide in order to @@ -434,7 +434,7 @@ private Task runTransaction( updateFunction.apply( new Transaction(internalTransaction, FirebaseFirestore.this))); - return client.transaction(wrappedUpdateFunction); + return client.transaction(options, wrappedUpdateFunction); } /** @@ -448,9 +448,26 @@ private Task runTransaction( @NonNull public Task runTransaction( @NonNull Transaction.Function updateFunction) { + return runTransaction(TransactionOptions.DEFAULT, updateFunction); + } + + /** + * Executes the given updateFunction and then attempts to commit the changes applied within the + * transaction. If any document read within the transaction has changed, the updateFunction will + * be retried. If it fails to commit after the maxmimum number of attempts specified in + * transactionOptions, the transaction will fail. + * + * @param options The function to execute within the transaction context. + * @param updateFunction The function to execute within the transaction context. + * @return The task returned from the updateFunction. + */ + public Task runTransaction( + @NonNull TransactionOptions options, @NonNull Transaction.Function updateFunction) { checkNotNull(updateFunction, "Provided transaction update function must not be null."); return runTransaction( - updateFunction, com.google.firebase.firestore.core.Transaction.getDefaultExecutor()); + options, + updateFunction, + com.google.firebase.firestore.core.Transaction.getDefaultExecutor()); } /** diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java new file mode 100644 index 00000000000..798cc060cf1 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java @@ -0,0 +1,95 @@ +// Copyright 2022 Google LLC +// +// Licensed 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 com.google.firebase.firestore; + +/** + * Parameter for {@link FirebaseFirestore#runTransaction(TransactionOptions, Transaction.Function)}. + */ +public final class TransactionOptions { + + static final TransactionOptions DEFAULT = new TransactionOptions.Builder().build(); + static final int DEFAULT_MAX_ATTEMPTS_COUNT = 5; + + private final int maxAttempts; + + private TransactionOptions(int maxAttempts) { + this.maxAttempts = maxAttempts; + } + + /** A Builder for creating {@code TransactionOptions}. */ + public static final class Builder { + private int maxAttempts = DEFAULT_MAX_ATTEMPTS_COUNT; + + /** Constructs a new {@code TransactionOptions} Builder object. */ + public Builder() {} + + /** + * Constructs a new {@code TransactionOptions} Builder based on an existing {@code + * TransactionOptions} object. + */ + public Builder(TransactionOptions options) { + maxAttempts = options.maxAttempts; + } + + /** + * Set maximum number of attempts to commit, after which transaction fails. Default is 5. + * + * @return this builder + */ + public Builder setMaxAttempts(int maxAttempts) { + if (maxAttempts < 1) throw new IllegalArgumentException("Max attempts must be at least 1"); + this.maxAttempts = maxAttempts; + return this; + } + + /** + * Build the {@code TransactionOptions} object. + * + * @return the built {@code TransactionOptions} object + */ + public TransactionOptions build() { + return new TransactionOptions(maxAttempts); + } + } + + /** + * Get maximum number of attempts to commit, after which transaction fails. Default is 5. + * + * @return maximum number of attempts + */ + public int getMaxAttempts() { + return maxAttempts; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TransactionOptions that = (TransactionOptions) o; + + return maxAttempts == that.maxAttempts; + } + + @Override + public int hashCode() { + return maxAttempts; + } + + @Override + public String toString() { + return "TransactionOptions{" + "maxAttempts=" + maxAttempts + '}'; + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java index 5554d4f0b43..464cd5913f9 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java @@ -26,6 +26,7 @@ import com.google.firebase.firestore.FirebaseFirestoreException.Code; import com.google.firebase.firestore.FirebaseFirestoreSettings; import com.google.firebase.firestore.LoadBundleTask; +import com.google.firebase.firestore.TransactionOptions; import com.google.firebase.firestore.auth.CredentialsProvider; import com.google.firebase.firestore.auth.User; import com.google.firebase.firestore.bundle.BundleReader; @@ -228,10 +229,12 @@ public Task write(final List mutations) { } /** Tries to execute the transaction in updateFunction. */ - public Task transaction(Function> updateFunction) { + public Task transaction( + TransactionOptions options, Function> updateFunction) { this.verifyNotTerminated(); return AsyncQueue.callTask( - asyncQueue.getExecutor(), () -> syncEngine.transaction(asyncQueue, updateFunction)); + asyncQueue.getExecutor(), + () -> syncEngine.transaction(asyncQueue, options, updateFunction)); } /** diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java index f51d1e88062..452d7907c76 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java @@ -26,6 +26,7 @@ import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.LoadBundleTask; import com.google.firebase.firestore.LoadBundleTaskProgress; +import com.google.firebase.firestore.TransactionOptions; import com.google.firebase.firestore.auth.User; import com.google.firebase.firestore.bundle.BundleElement; import com.google.firebase.firestore.bundle.BundleLoader; @@ -307,8 +308,10 @@ private void addUserCallback(int batchId, TaskCompletionSource userTask) { *

The Task returned is resolved when the transaction is fully committed. */ public Task transaction( - AsyncQueue asyncQueue, Function> updateFunction) { - return new TransactionRunner(asyncQueue, remoteStore, updateFunction).run(); + AsyncQueue asyncQueue, + TransactionOptions options, + Function> updateFunction) { + return new TransactionRunner(asyncQueue, remoteStore, options, updateFunction).run(); } /** Called by FirestoreClient to notify us of a new remote event. */ 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 7dd50b36ca4..70a249cc804 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 @@ -18,6 +18,7 @@ import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.TransactionOptions; import com.google.firebase.firestore.remote.Datastore; import com.google.firebase.firestore.remote.RemoteStore; import com.google.firebase.firestore.util.AsyncQueue; @@ -27,7 +28,6 @@ /** TransactionRunner encapsulates the logic needed to run and retry transactions with backoff. */ public class TransactionRunner { - public static final int DEFAULT_MAX_ATTEMPTS_COUNT = 5; private AsyncQueue asyncQueue; private RemoteStore remoteStore; private Function> updateFunction; @@ -39,12 +39,13 @@ public class TransactionRunner { public TransactionRunner( AsyncQueue asyncQueue, RemoteStore remoteStore, + TransactionOptions options, Function> updateFunction) { this.asyncQueue = asyncQueue; this.remoteStore = remoteStore; this.updateFunction = updateFunction; - this.attemptsRemaining = DEFAULT_MAX_ATTEMPTS_COUNT; + this.attemptsRemaining = options.getMaxAttempts(); backoff = new ExponentialBackoff(asyncQueue, TimerId.RETRY_TRANSACTION); } From 2857711ae1f822da0b07c0ad9c46fd5bf26deb69 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Tue, 19 Apr 2022 19:35:35 -0400 Subject: [PATCH 10/13] Add missing @NonNull annotation --- .../com/google/firebase/firestore/FirebaseFirestore.java | 1 + .../com/google/firebase/firestore/TransactionOptions.java | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java index 5da093d762a..50939e9a810 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java @@ -461,6 +461,7 @@ public Task runTransaction( * @param updateFunction The function to execute within the transaction context. * @return The task returned from the updateFunction. */ + @NonNull public Task runTransaction( @NonNull TransactionOptions options, @NonNull Transaction.Function updateFunction) { checkNotNull(updateFunction, "Provided transaction update function must not be null."); diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java index 798cc060cf1..d7a8e76e10e 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java @@ -14,6 +14,8 @@ package com.google.firebase.firestore; +import androidx.annotation.NonNull; + /** * Parameter for {@link FirebaseFirestore#runTransaction(TransactionOptions, Transaction.Function)}. */ @@ -39,7 +41,7 @@ public Builder() {} * Constructs a new {@code TransactionOptions} Builder based on an existing {@code * TransactionOptions} object. */ - public Builder(TransactionOptions options) { + public Builder(@NonNull TransactionOptions options) { maxAttempts = options.maxAttempts; } @@ -48,6 +50,7 @@ public Builder(TransactionOptions options) { * * @return this builder */ + @NonNull public Builder setMaxAttempts(int maxAttempts) { if (maxAttempts < 1) throw new IllegalArgumentException("Max attempts must be at least 1"); this.maxAttempts = maxAttempts; @@ -59,6 +62,7 @@ public Builder setMaxAttempts(int maxAttempts) { * * @return the built {@code TransactionOptions} object */ + @NonNull public TransactionOptions build() { return new TransactionOptions(maxAttempts); } From bee89f6eb97e82069a4788ae8d4fb27d3f1bcbba Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Thu, 21 Apr 2022 10:23:19 -0400 Subject: [PATCH 11/13] Fix comments and add api.txt change. --- firebase-firestore/api.txt | 12 ++++++++++ .../firebase/firestore/FirebaseFirestore.java | 24 ++++++++++--------- .../firestore/TransactionOptions.java | 14 +++++++---- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/firebase-firestore/api.txt b/firebase-firestore/api.txt index 8d66447e0e9..ab8b3f4192c 100644 --- a/firebase-firestore/api.txt +++ b/firebase-firestore/api.txt @@ -152,6 +152,7 @@ package com.google.firebase.firestore { method @NonNull public com.google.firebase.firestore.LoadBundleTask loadBundle(@NonNull java.nio.ByteBuffer); method @NonNull public com.google.android.gms.tasks.Task runBatch(@NonNull com.google.firebase.firestore.WriteBatch.Function); method @NonNull public com.google.android.gms.tasks.Task runTransaction(@NonNull com.google.firebase.firestore.Transaction.Function); + method @NonNull public com.google.android.gms.tasks.Task runTransaction(@NonNull com.google.firebase.firestore.TransactionOptions, @NonNull com.google.firebase.firestore.Transaction.Function); method public void setFirestoreSettings(@NonNull com.google.firebase.firestore.FirebaseFirestoreSettings); method @NonNull public com.google.android.gms.tasks.Task setIndexConfiguration(@NonNull String); method public static void setLoggingEnabled(boolean); @@ -387,6 +388,17 @@ package com.google.firebase.firestore { method @Nullable public TResult apply(@NonNull com.google.firebase.firestore.Transaction) throws com.google.firebase.firestore.FirebaseFirestoreException; } + public final class TransactionOptions { + method public int getMaxAttempts(); + } + + public static final class TransactionOptions.Builder { + ctor public TransactionOptions.Builder(); + ctor public TransactionOptions.Builder(@NonNull com.google.firebase.firestore.TransactionOptions); + method @NonNull public com.google.firebase.firestore.TransactionOptions build(); + method @NonNull public com.google.firebase.firestore.TransactionOptions.Builder setMaxAttempts(int); + } + public class WriteBatch { method @NonNull public com.google.android.gms.tasks.Task commit(); method @NonNull public com.google.firebase.firestore.WriteBatch delete(@NonNull com.google.firebase.firestore.DocumentReference); diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java index 50939e9a810..dccdde8b6c0 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java @@ -405,9 +405,10 @@ public Query collectionGroup(@NonNull String collectionId) { } /** - * Executes the given updateFunction and then attempts to commit the changes applied within the - * transaction. If any document read within the transaction has changed, the updateFunction will - * be retried. If it fails to commit after 5 attempts, the transaction will fail. + * Executes the given {@code updateFunction} and then attempts to commit the changes applied + * within the transaction. If any document read within the transaction has changed, the + * updateFunction will be retried. If it fails to commit after 5 attempts, the transaction will + * fail. * *

The maximum number of writes allowed in a single transaction is 500, but note that each * usage of {@link FieldValue#serverTimestamp()}, {@link FieldValue#arrayUnion(Object...)}, {@link @@ -438,9 +439,10 @@ private Task runTransaction( } /** - * Executes the given updateFunction and then attempts to commit the changes applied within the - * transaction. If any document read within the transaction has changed, the updateFunction will - * be retried. If it fails to commit after 5 attempts, the transaction will fail. + * Executes the given {@code updateFunction} and then attempts to commit the changes applied + * within the transaction. If any document read within the transaction has changed, the + * updateFunction will be retried. If it fails to commit after 5 attempts, the transaction will + * fail. * * @param updateFunction The function to execute within the transaction context. * @return The task returned from the updateFunction. @@ -452,12 +454,12 @@ public Task runTransaction( } /** - * Executes the given updateFunction and then attempts to commit the changes applied within the - * transaction. If any document read within the transaction has changed, the updateFunction will - * be retried. If it fails to commit after the maxmimum number of attempts specified in - * transactionOptions, the transaction will fail. + * Executes the given {@code updateFunction} and then attempts to commit the changes applied + * within the transaction. If any document read within the transaction has changed, the + * updateFunction will be retried. If it fails to commit after the maxmimum number of attempts + * specified in transactionOptions, the transaction will fail. * - * @param options The function to execute within the transaction context. + * @param options The transaction options for controlling execution. * @param updateFunction The function to execute within the transaction context. * @return The task returned from the updateFunction. */ diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java index d7a8e76e10e..5e649105183 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/TransactionOptions.java @@ -17,7 +17,8 @@ import androidx.annotation.NonNull; /** - * Parameter for {@link FirebaseFirestore#runTransaction(TransactionOptions, Transaction.Function)}. + * Options to customize transaction behavior for {@link + * FirebaseFirestore#runTransaction(TransactionOptions, Transaction.Function)}. */ public final class TransactionOptions { @@ -46,9 +47,12 @@ public Builder(@NonNull TransactionOptions options) { } /** - * Set maximum number of attempts to commit, after which transaction fails. Default is 5. + * Set maximum number of attempts to commit, after which transaction fails. * - * @return this builder + *

The default value is 5. Setting the value to less than 1 will result in an {@link + * IllegalArgumentException}. + * + * @return This builder */ @NonNull public Builder setMaxAttempts(int maxAttempts) { @@ -60,7 +64,7 @@ public Builder setMaxAttempts(int maxAttempts) { /** * Build the {@code TransactionOptions} object. * - * @return the built {@code TransactionOptions} object + * @return The built {@code TransactionOptions} object */ @NonNull public TransactionOptions build() { @@ -71,7 +75,7 @@ public TransactionOptions build() { /** * Get maximum number of attempts to commit, after which transaction fails. Default is 5. * - * @return maximum number of attempts + * @return The maximum number of attempts */ public int getMaxAttempts() { return maxAttempts; From bfe864116aee64fa2a5208e003b4dd73002f43f7 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Thu, 21 Apr 2022 18:08:46 -0400 Subject: [PATCH 12/13] Update CHANGELOG with TransactionOptions --- firebase-firestore/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/firebase-firestore/CHANGELOG.md b/firebase-firestore/CHANGELOG.md index e75953b1355..6bd04cf6f8a 100644 --- a/firebase-firestore/CHANGELOG.md +++ b/firebase-firestore/CHANGELOG.md @@ -3,6 +3,8 @@ by opting into a release at [go/firebase-android-release](http:go/firebase-android-release) (Googlers only). # Unreleased +- [changed] Added `TransactionOptions` to control how many times a transaction + will retry commits before failing. - [fixed] Fixed an issue where patching multiple fields shadows each other (#3528). # 24.1.1 From 1d62d46d6c0ae9be84f499e0e7f97650aae9f106 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Tue, 26 Apr 2022 14:45:47 -0400 Subject: [PATCH 13/13] Improve method comments for TransactionOptions. --- .../com/google/firebase/firestore/FirebaseFirestore.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java index dccdde8b6c0..44609ab6d9e 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java @@ -407,8 +407,8 @@ public Query collectionGroup(@NonNull String collectionId) { /** * Executes the given {@code updateFunction} and then attempts to commit the changes applied * within the transaction. If any document read within the transaction has changed, the - * updateFunction will be retried. If it fails to commit after 5 attempts, the transaction will - * fail. + * updateFunction will be retried. If it fails to commit after 5 attempts (the default failure + * limit), the transaction will fail. * *

The maximum number of writes allowed in a single transaction is 500, but note that each * usage of {@link FieldValue#serverTimestamp()}, {@link FieldValue#arrayUnion(Object...)}, {@link @@ -441,8 +441,9 @@ private Task runTransaction( /** * Executes the given {@code updateFunction} and then attempts to commit the changes applied * within the transaction. If any document read within the transaction has changed, the - * updateFunction will be retried. If it fails to commit after 5 attempts, the transaction will - * fail. + * updateFunction will be retried. If it fails to commit after 5 attempts (the default failure + * limit), the transaction will fail. To have a different number of retries, use the {@link + * FirebaseFirestore#runTransaction(TransactionOptions, Transaction.Function)} method instead. * * @param updateFunction The function to execute within the transaction context. * @return The task returned from the updateFunction.