sync method
Sync operation (a touchpoint invocation) under the retry policy.
Implementation
Future<SyncOutcome> sync(
String behavior,
Future<dynamic> Function() operation,
) async {
final startMs = clock.elapsedMs;
final failures = <SyncFailure>[];
var repaired = false;
Object? lastError;
for (var attempt = 1; attempt <= policy.maxAttempts; attempt++) {
try {
final result = await operation();
// Partial-write detection: the write half-landed.
if (result is Map && result[kPartialWriteMarker] == true) {
failures.add(
SyncFailure('partial', 'partial-write', atAttempt: attempt),
);
if (policy.repairPartialWrites) {
// Repair: re-push once (the next loop iteration re-invokes).
repaired = true;
if (attempt < policy.maxAttempts) {
_waitBackoff(attempt);
continue;
}
return _outcome(
behavior,
succeeded: false,
attempts: attempt,
result: null,
failures: failures,
startMs: startMs,
repaired: repaired,
stoppedBy: 'partial-write (budget exhausted while repairing)',
);
}
return _outcome(
behavior,
succeeded: false,
attempts: attempt,
result: null,
failures: failures,
startMs: startMs,
repaired: false,
stoppedBy: 'partial-write (repair disabled)',
);
}
// Clean write: success with the complete ledger.
return _outcome(
behavior,
succeeded: true,
attempts: attempt,
result: result,
failures: failures,
startMs: startMs,
repaired: repaired,
stoppedBy: null,
);
} on SimulatedAuthException catch (e) {
// Auth-class failures surface honestly — never a blind retry
// loop against an expired session (the auth-expiry storm class).
failures.add(SyncFailure('auth', 'auth-${e.code}', atAttempt: attempt));
return _outcome(
behavior,
succeeded: false,
attempts: attempt,
result: null,
failures: failures,
startMs: startMs,
repaired: repaired,
stoppedBy: 'auth-${e.code}',
);
} on SimulatedHttpException catch (e) {
// Retryable class: network flaps and transport failures.
lastError = e;
failures.add(
SyncFailure('http', 'http-${e.statusCode}', atAttempt: attempt),
);
if (attempt < policy.maxAttempts) {
_waitBackoff(attempt);
continue;
}
} catch (e) {
// Other retryable transport errors.
lastError = e;
failures.add(SyncFailure('http', 'transport', atAttempt: attempt));
if (attempt < policy.maxAttempts) {
_waitBackoff(attempt);
continue;
}
}
}
return _outcome(
behavior,
succeeded: false,
attempts: policy.maxAttempts,
result: null,
failures: failures,
startMs: startMs,
repaired: repaired,
stoppedBy: 'budget-exhausted (${lastError ?? "retry budget"})',
);
}