11import 'dart:async' ;
2+ import 'dart:math' as math;
23
34import 'package:Prism/core/firestore/firestore_client.dart' ;
45import 'package:Prism/core/firestore/firestore_error.dart' ;
@@ -7,6 +8,10 @@ import 'package:Prism/core/firestore/firestore_telemetry.dart';
78import 'package:Prism/logger/logger.dart' ;
89import 'package:cloud_firestore/cloud_firestore.dart' ;
910
11+ const _transientFirestoreCodes = {'unavailable' , 'deadline-exceeded' , 'internal' , 'resource-exhausted' };
12+
13+ bool _isTransientFirestoreError (FirestoreError e) => e.code != null && _transientFirestoreCodes.contains (e.code);
14+
1015class _RawQueryDoc {
1116 const _RawQueryDoc (this .id, this .data);
1217
@@ -539,48 +544,61 @@ class FirestoreTrackedClient implements FirestoreClient {
539544 required String sourceTag,
540545 required String collection,
541546 String ? docId,
547+ int maxRetries = 2 ,
542548 }) async {
543549 final Stopwatch sw = Stopwatch ()..start ();
544- try {
545- final T result = await _firestore.runTransaction <T >((Transaction transaction) {
546- final _FirestoreTransactionBridge bridge = _FirestoreTransactionBridge (_firestore, transaction);
547- return action (bridge);
548- });
549- await _emitTelemetry (
550- FirestoreTelemetryEvent (
551- timestamp: DateTime .now (),
552- sourceTag: sourceTag,
553- operation: FirestoreOperation .transaction,
554- collection: collection,
555- filtersHash: docId == null ? collection : '$collection :$docId ' ,
556- durationMs: sw.elapsedMilliseconds,
557- docId: docId,
558- success: true ,
559- ),
560- );
561- return result;
562- } catch (error) {
563- final FirestoreError mapped = mapFirestoreError (error);
564- if (mapped.code == 'permission-denied' ) {
565- logger.w (
566- '[Firestore] permission-denied on transaction — collection: $collection , sourceTag: $sourceTag ' ,
567- error: mapped,
550+ int attempt = 0 ;
551+ while (true ) {
552+ try {
553+ final T result = await _firestore.runTransaction <T >((Transaction transaction) {
554+ final _FirestoreTransactionBridge bridge = _FirestoreTransactionBridge (_firestore, transaction);
555+ return action (bridge);
556+ });
557+ await _emitTelemetry (
558+ FirestoreTelemetryEvent (
559+ timestamp: DateTime .now (),
560+ sourceTag: sourceTag,
561+ operation: FirestoreOperation .transaction,
562+ collection: collection,
563+ filtersHash: docId == null ? collection : '$collection :$docId ' ,
564+ durationMs: sw.elapsedMilliseconds,
565+ docId: docId,
566+ success: true ,
567+ ),
568568 );
569+ return result;
570+ } catch (error) {
571+ final FirestoreError mapped = mapFirestoreError (error);
572+ if (_isTransientFirestoreError (mapped) && attempt < maxRetries) {
573+ attempt++ ;
574+ final int delayMs = (500 * math.pow (2 , attempt - 1 )).round ();
575+ logger.w (
576+ '[Firestore] transient error (${mapped .code }) on transaction — retrying ($attempt /$maxRetries ) after ${delayMs }ms, sourceTag: $sourceTag ' ,
577+ );
578+ await Future <void >.delayed (Duration (milliseconds: delayMs));
579+ continue ;
580+ }
581+ if (mapped.code == 'permission-denied' ) {
582+ logger.w (
583+ '[Firestore] permission-denied on transaction — collection: $collection , sourceTag: $sourceTag ' ,
584+ error: mapped,
585+ );
586+ }
587+ await _emitTelemetry (
588+ FirestoreTelemetryEvent (
589+ timestamp: DateTime .now (),
590+ sourceTag: sourceTag,
591+ operation: FirestoreOperation .transaction,
592+ collection: collection,
593+ filtersHash: docId == null ? collection : '$collection :$docId ' ,
594+ durationMs: sw.elapsedMilliseconds,
595+ docId: docId,
596+ success: false ,
597+ errorCode: mapped.code,
598+ ),
599+ );
600+ throw mapped;
569601 }
570- await _emitTelemetry (
571- FirestoreTelemetryEvent (
572- timestamp: DateTime .now (),
573- sourceTag: sourceTag,
574- operation: FirestoreOperation .transaction,
575- collection: collection,
576- filtersHash: docId == null ? collection : '$collection :$docId ' ,
577- durationMs: sw.elapsedMilliseconds,
578- docId: docId,
579- success: false ,
580- errorCode: mapped.code,
581- ),
582- );
583- throw mapped;
584602 }
585603 }
586604
0 commit comments