3.0 KiB
phases/commit
Purpose
Phase-2 commit for a single participant. Once every participant has
returned commit from phase 1 and the coordinator has persisted the
all-voted-commit state to the ledger, this module emits
COMMIT PREPARED '<gid>' against each writer participant and records
2pc.commit_duration_ms.
Phase 2 is intentionally unbounded by timeout — the protocol cannot
tolerate giving up on a commit-prepared mid-flight, because doing so
would leave the system in in_doubt state and require the recovery
runner to resolve it on the next boot.
Features
- Skips participants that did not actually prepare (
outcome.prepared === false) — read-only voters and abort-voters never see commit SQL. - Emits a
2pc.commitspan withtx.id,participant.id, andgidattributes. - Records
2pc.commit_duration_mstagged withoutcome: "committed"on success. - Failure preserves the underlying cause on a thrown
TwoPcError(TWOPC_COMMIT_FAILED); the coordinator escalates toTWOPC_IN_DOUBTonce it has tried every participant.
Usage: Basic
import { commitPhaseTwo } from "@suaveplan/api-2pc/phases";
await commitPhaseTwo({
txId,
participant,
outcome: preparedOutcome,
instruments,
});
Usage: Advanced
The coordinator collects per-participant failures rather than short-circuiting — every participant gets attempted exactly once so operators can see the full picture in the ledger:
import { TWOPC_CODES, TwoPcError } from "@suaveplan/api-2pc/error-codes";
import { commitPhaseTwo } from "@suaveplan/api-2pc/phases";
const failures: TwoPcError[] = [];
for (const outcome of preparedOutcomes) {
if (!outcome.prepared) continue;
try {
await commitPhaseTwo({
txId,
participant: registry.get(outcome.participantId)!,
outcome,
instruments,
});
} catch (err) {
if (err instanceof TwoPcError) failures.push(err);
else throw err;
}
}
if (failures.length > 0) {
// ledger.markInDoubt(txId); recovery resolves on next boot
}
API Reference
interface CommitPhaseInputtxId: stringparticipant: RegisteredTwoPcParticipantoutcome: ParticipantPrepareOutcome— the phase-1 result.instruments: TwoPcInstruments
function commitPhaseTwo(input): Promise<void>- no-op for
outcome.prepared === false - throws
TwoPcError(TWOPC_COMMIT_FAILED)on SQL failure.
- no-op for
Implementation Notes
The gid stored on the prepare outcome is the single source of truth — this module never rebuilds it. Reusing the prepare-time value defends against the unlikely case where a host-config change between phases would have produced a different gid: the SQL the coordinator needs to send is exactly the one it prepared with.
The SQL escape (replace(/'/g, "''")) matches the prepare-phase
treatment so a gid containing the unlikely quote character would
still produce valid SQL. The escape is cheap; the defence-in-depth
costs nothing on the happy path.