17
17
import static com .google .firebase .firestore .util .Assert .fail ;
18
18
import static com .google .firebase .firestore .util .Assert .hardAssert ;
19
19
20
+ import androidx .annotation .NonNull ;
20
21
import androidx .annotation .Nullable ;
21
22
import androidx .annotation .VisibleForTesting ;
23
+ import com .google .android .gms .tasks .OnCompleteListener ;
22
24
import com .google .android .gms .tasks .Task ;
23
25
import com .google .android .gms .tasks .TaskCompletionSource ;
24
26
import com .google .android .gms .tasks .Tasks ;
47
49
import com .google .firebase .firestore .remote .RemoteStore ;
48
50
import com .google .firebase .firestore .remote .TargetChange ;
49
51
import com .google .firebase .firestore .util .AsyncQueue ;
52
+ import com .google .firebase .firestore .util .ExponentialBackoff ;
50
53
import com .google .firebase .firestore .util .Logger ;
51
54
import com .google .firebase .firestore .util .Util ;
52
55
import io .grpc .Status ;
@@ -250,9 +253,10 @@ private void addUserCallback(int batchId, TaskCompletionSource<Void> userTask) {
250
253
/**
251
254
* Takes an updateFunction in which a set of reads and writes can be performed atomically. In the
252
255
* updateFunction, the client can read and write values using the supplied transaction object.
253
- * After the updateFunction, all changes will be committed. If some other client has changed any
254
- * of the data referenced, then the updateFunction will be called again. If the updateFunction
255
- * still fails after the given number of retries, then the transaction will be rejected.
256
+ * After the updateFunction, all changes will be committed. If a retryable error occurs (ex: some
257
+ * other client has changed any of the data referenced), then the updateFunction will be called
258
+ * again after a backoff. If the updateFunction still fails after the given number of retires,
259
+ * then the transaction will be rejected.
256
260
*
257
261
* <p>The transaction object passed to the updateFunction contains methods for accessing documents
258
262
* and collections. Unlike other datastore access, data accessed with the transaction will not
@@ -261,36 +265,58 @@ private void addUserCallback(int batchId, TaskCompletionSource<Void> userTask) {
261
265
*
262
266
* <p>The Task returned is resolved when the transaction is fully committed.
263
267
*/
264
- public <TResult > Task <TResult > transaction (
265
- AsyncQueue asyncQueue , Function <Transaction , Task <TResult >> updateFunction , int retries ) {
268
+ public <TResult > void transaction (
269
+ AsyncQueue asyncQueue ,
270
+ ExponentialBackoff backoff ,
271
+ TaskCompletionSource <TResult > txTaskSource ,
272
+ Function <Transaction , Task <TResult >> updateFunction ,
273
+ int retries ) {
266
274
hardAssert (retries >= 0 , "Got negative number of retries for transaction." );
267
- final Transaction transaction = remoteStore .createTransaction ();
268
- return updateFunction
269
- .apply (transaction )
270
- .continueWithTask (
271
- asyncQueue .getExecutor (),
272
- userTask -> {
273
- if (!userTask .isSuccessful ()) {
274
- if (retries > 0 && isRetryableTransactionError (userTask .getException ())) {
275
- return transaction (asyncQueue , updateFunction , retries - 1 );
276
- }
277
- return userTask ;
278
- }
279
- return transaction
280
- .commit ()
281
- .continueWithTask (
282
- asyncQueue .getExecutor (),
283
- commitTask -> {
284
- if (commitTask .isSuccessful ()) {
285
- return Tasks .forResult (userTask .getResult ());
286
- }
287
- Exception e = commitTask .getException ();
288
- if (retries > 0 && isRetryableTransactionError (e )) {
289
- return transaction (asyncQueue , updateFunction , retries - 1 );
275
+
276
+ backoff .backoffAndRun (
277
+ () -> {
278
+ final Transaction transaction = remoteStore .createTransaction ();
279
+ updateFunction
280
+ .apply (transaction )
281
+ .addOnCompleteListener (
282
+ asyncQueue .getExecutor (),
283
+ new OnCompleteListener <TResult >() {
284
+ @ Override
285
+ public void onComplete (@ NonNull Task <TResult > userTask ) {
286
+ if (!userTask .isSuccessful ()) {
287
+ if (retries > 0 && isRetryableTransactionError (userTask .getException ())) {
288
+ transaction (
289
+ asyncQueue , backoff , txTaskSource , updateFunction , retries - 1 );
290
+ } else {
291
+ txTaskSource .setException (userTask .getException ());
290
292
}
291
- return Tasks .forException (e );
292
- });
293
- });
293
+ } else {
294
+ transaction
295
+ .commit ()
296
+ .addOnCompleteListener (
297
+ asyncQueue .getExecutor (),
298
+ new OnCompleteListener <Void >() {
299
+ @ Override
300
+ public void onComplete (@ NonNull Task <Void > commitTask ) {
301
+ if (commitTask .isSuccessful ()) {
302
+ txTaskSource .setResult (userTask .getResult ());
303
+ } else if (retries > 0
304
+ && isRetryableTransactionError (commitTask .getException ())) {
305
+ transaction (
306
+ asyncQueue ,
307
+ backoff ,
308
+ txTaskSource ,
309
+ updateFunction ,
310
+ retries - 1 );
311
+ } else {
312
+ txTaskSource .setException (commitTask .getException ());
313
+ }
314
+ }
315
+ });
316
+ }
317
+ }
318
+ });
319
+ });
294
320
}
295
321
296
322
/** Called by FirestoreClient to notify us of a new remote event. */
0 commit comments