runTransaction function

Future<void> runTransaction(
  1. Future<void> handler(
    1. FirestoreTransaction tx
    )
)

Runs handler in a transaction, retrying it if Firestore says to.

The handler may run more than once — that is what a transaction is — so it must not have effects outside the writes it records on the handle.

Implementation

Future<void> runTransaction(
  Future<void> Function(FirestoreTransaction tx) handler,
) {
  final done = Completer<void>();
  final receive = RawReceivePort();
  late FirestoreTransaction tx;
  var txnId = 0;

  receive.handler = (Object? message) async {
    final bytes = message! as Uint8List;
    final seq = ByteData.sublistView(bytes).getInt64(8, Endian.host);

    if (seq < 0) {
      receive.close();
      final reason = decodeSnapshotValue(bytes);
      if (!done.isCompleted) {
        done.completeError(
          FirestoreException(-1, '${reason ?? "failed"}', 'transaction'),
        );
      }
      return;
    }
    if (seq == 0) {
      receive.close();
      if (!done.isCompleted) done.complete();
      return;
    }

    // seq > 0 is an attempt. A retry arrives here again, so the buffer is
    // cleared rather than accumulating what the previous attempt recorded.
    tx._reset();
    try {
      await handler(tx);
    } catch (e) {
      // The handler decided against it. Abort rather than commit a partial
      // set of writes, and let the error surface as the transaction's.
      fdbFsTxnAbort(txnId);
      if (!done.isCompleted) done.completeError(e);
      return;
    }
    final encoded = tx._encodeWrites();
    final buf = calloc<Uint8>(encoded.isEmpty ? 1 : encoded.length);
    if (encoded.isNotEmpty) {
      buf.asTypedList(encoded.length).setAll(0, encoded);
    }
    fdbFsTxnCommit(txnId, buf, encoded.length);
    calloc.free(buf);
  };

  txnId = fdbFsTxnBegin(receive.sendPort.nativePort);
  if (txnId <= 0) {
    receive.close();
    return Future.error(StateError('transaction failed to start ($txnId)'));
  }
  tx = FirestoreTransaction._(txnId);
  return done.future;
}