Class TwoPhaseParticipant
Each participant represents a single Raft group that holds a slice of the
transaction's keys. The coordinator (running on the raft-0 leader) sends
TwoPhaseCommands.PrepareGroup and TwoPhaseCommands.DecideGroup
commands scoped to this group; this class ensures both commands are
durably appended to the participant group's OWN Raft log BEFORE being
applied to local state. Owner-shard linearizable reads on the same group
are therefore fenced against committed cross-group transactions — which is
the correctness gap BLK-001 targets.
Flow
- Inbound PREPARE_GROUP (
submitPrepareGroup(TwoPhaseCommands.PrepareGroup)): replicate the command into the group's Raft log viaRaftNodeApi.submitCommand(byte[]). The apply path (below) does the actual work. - Apply PREPARE_GROUP (
applyPrepareGroup(TwoPhaseCommands.PrepareGroup)): decode the per-groupReplicatedTransactionCommandslice, register a pending intent keyed by txId, schedule the orphaned-intent timeout, and replyPrepareAck(COMMIT)to the coordinator. - Inbound DECIDE_GROUP (
submitDecideGroup(TwoPhaseCommands.DecideGroup, int)): replicate into the group's Raft log. - Apply DECIDE_GROUP (
applyDecideGroup(TwoPhaseCommands.DecideGroup, int)): on COMMIT, apply the per-group operations atomically viaCrossGroupTransactionExecutor.applyCommittedTransaction(Transaction, Function); on ABORT, just drop the intent. Cancel the orphaned-intent timers and sendDecideAckback.
Orphaned intents
If a PREPARE applied but no DECIDE arrives withinorphanedIntentTimeout the participant repeatedly sends a
DecideQuery to the coordinator
(best-effort). It does not unilaterally decide ABORT: in a CP/bank
production posture, blocking a prepared key is safer than allowing one
group to abort while another eventually commits.
Thread-safety
All public methods are safe for concurrent invocation. Per-txId state is protected with a monitor held briefly across transitions.-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intMaximum reply-dispatch attempts per (txId, type).static final DurationDelay between reply-dispatch retry attempts when the raft-0 leader is unknown (transient post-election propagation gap) or when the transport rejects the send.static final DurationHow long a participant retains completed decisions for duplicate DECIDE idempotency and snapshot replay.static final DurationDefault orphaned-intent timeout before the participant starts chasing the coordinator.static final DurationRetry interval between orphan DECIDE_QUERY attempts. -
Constructor Summary
ConstructorsConstructorDescriptionTwoPhaseParticipant(@NonNull String nodeId, @NonNull RaftGroupManagerApi raftGroupManager, @NonNull PeerMessageDispatch dispatch, @NonNull CrossGroupTransactionExecutor applyExecutor, @NonNull Function<Integer, @Nullable DataStructureRegistry> registryResolver, @NonNull ScheduledExecutorService scheduler) TwoPhaseParticipant(@NonNull String nodeId, @NonNull RaftGroupManagerApi raftGroupManager, @NonNull PeerMessageDispatch dispatch, @NonNull CrossGroupTransactionExecutor applyExecutor, @NonNull Function<Integer, @Nullable DataStructureRegistry> registryResolver, @NonNull ScheduledExecutorService scheduler, @NonNull Duration orphanedIntentTimeout, @NonNull Duration unilateralAbortGrace) TwoPhaseParticipant(@NonNull String nodeId, @NonNull RaftGroupManagerApi raftGroupManager, @NonNull PeerMessageDispatch dispatch, @NonNull CrossGroupTransactionExecutor applyExecutor, @NonNull Function<Integer, @Nullable DataStructureRegistry> registryResolver, @NonNull ScheduledExecutorService scheduler, @NonNull Duration orphanedIntentTimeout, @NonNull Duration unilateralAbortGrace, @NonNull Duration completedDecisionRetention) -
Method Summary
Modifier and TypeMethodDescription@NonNull TransactionResultapplyDecideGroup(@NonNull TwoPhaseCommands.DecideGroup cmd, int groupId) Apply a committedTwoPhaseCommands.DecideGrouplog entry.voidapplyPrepareGroup(@NonNull TwoPhaseCommands.PrepareGroup cmd) Apply a committedTwoPhaseCommands.PrepareGrouplog entry.intbooleanintvoidrestoreCompletedDecisions(@Nullable Object rawRecords) voidrestoreCompletedDecisionsForGroup(int groupId, @Nullable Object rawRecords) voidsetNearCacheInvalidationManager(@Nullable NearCacheInvalidationManager manager) snapshotCompletedDecisionsForGroup(int groupId) booleansubmitDecideGroup(@NonNull TwoPhaseCommands.DecideGroup cmd, int groupId) Replicate a receivedTwoPhaseCommands.DecideGroupinto the participant group's Raft log.booleansubmitPrepareGroup(@NonNull TwoPhaseCommands.PrepareGroup cmd) Replicate a receivedTwoPhaseCommands.PrepareGroupinto the participant group's Raft log.
-
Field Details
-
DEFAULT_ORPHANED_INTENT_TIMEOUT
Default orphaned-intent timeout before the participant starts chasing the coordinator. -
DEFAULT_UNILATERAL_ABORT_GRACE
Retry interval between orphan DECIDE_QUERY attempts. -
DEFAULT_ACK_RETRY_DELAY
Delay between reply-dispatch retry attempts when the raft-0 leader is unknown (transient post-election propagation gap) or when the transport rejects the send. Symmetric toTwoPhaseCoordinator.DEFAULT_DISPATCH_RETRY_DELAY. -
DEFAULT_ACK_MAX_ATTEMPTS
public static final int DEFAULT_ACK_MAX_ATTEMPTSMaximum reply-dispatch attempts per (txId, type). At the default delay this bounds total wall time at 5s — well under the 30s coord phase timeout, so retry exhaustion still lets the phase timeout fire with the same semantics as before.- See Also:
-
DEFAULT_COMPLETED_DECISION_RETENTION
How long a participant retains completed decisions for duplicate DECIDE idempotency and snapshot replay. After this safety window expires, a duplicate COMMIT for an unknown transaction fails closed instead of being acknowledged from unbounded memory.
-
-
Constructor Details
-
TwoPhaseParticipant
public TwoPhaseParticipant(@NonNull String nodeId, @NonNull RaftGroupManagerApi raftGroupManager, @NonNull PeerMessageDispatch dispatch, @NonNull CrossGroupTransactionExecutor applyExecutor, @NonNull Function<Integer, @Nullable DataStructureRegistry> registryResolver, @NonNull ScheduledExecutorService scheduler) -
TwoPhaseParticipant
public TwoPhaseParticipant(@NonNull String nodeId, @NonNull RaftGroupManagerApi raftGroupManager, @NonNull PeerMessageDispatch dispatch, @NonNull CrossGroupTransactionExecutor applyExecutor, @NonNull Function<Integer, @Nullable DataStructureRegistry> registryResolver, @NonNull ScheduledExecutorService scheduler, @NonNull Duration orphanedIntentTimeout, @NonNull Duration unilateralAbortGrace) -
TwoPhaseParticipant
public TwoPhaseParticipant(@NonNull String nodeId, @NonNull RaftGroupManagerApi raftGroupManager, @NonNull PeerMessageDispatch dispatch, @NonNull CrossGroupTransactionExecutor applyExecutor, @NonNull Function<Integer, @Nullable DataStructureRegistry> registryResolver, @NonNull ScheduledExecutorService scheduler, @NonNull Duration orphanedIntentTimeout, @NonNull Duration unilateralAbortGrace, @NonNull Duration completedDecisionRetention)
-
-
Method Details
-
setNearCacheInvalidationManager
-
submitPrepareGroup
Replicate a receivedTwoPhaseCommands.PrepareGroupinto the participant group's Raft log. The actual intent registration + reply happens on the apply thread viaapplyPrepareGroup(TwoPhaseCommands.PrepareGroup).- Returns:
trueiff the participant's raft-N node accepted the submit. Afalsereturn typically means this node is no longer the raft-N leader; the caller should let the coordinator retry after a NOT_LEADER bounce.
-
submitDecideGroup
Replicate a receivedTwoPhaseCommands.DecideGroupinto the participant group's Raft log. The participant must supply thegroupIdbecause theTwoPhaseCommands.DecideGrouprecord itself does not carry one — the inbound dispatcher extracts it fromMessage.mapName()which the coordinator always stamps with"raft-<groupId>". -
applyPrepareGroup
Apply a committedTwoPhaseCommands.PrepareGrouplog entry. This runs on the Raft apply thread of the participant's group.Registers a pending intent, schedules the orphaned-intent timeout, and dispatches
TwoPhaseCommands.PrepareAckback to the coordinator. -
applyDecideGroup
public @NonNull TransactionResult applyDecideGroup(@NonNull TwoPhaseCommands.DecideGroup cmd, int groupId) Apply a committedTwoPhaseCommands.DecideGrouplog entry. OnCOMMIT, runs the per-group operations atomically viaCrossGroupTransactionExecutor.applyCommittedTransaction(Transaction, Function). OnABORT, simply drops the intent. COMMIT apply failures are fail-closed: the participant keeps the intent pending and does not acknowledge, so coordinator retry or local recovery can re-drive the decision.Returns the
TransactionResultfor the apply so the caller (state machine applier in CacheNode) can include the per-group outputs in any follow-up response. -
pendingCount
public int pendingCount()- Returns:
- the number of in-flight prepared-but-not-decided transactions on this node. Primarily for metrics and tests.
-
hasPreparedIntents
public boolean hasPreparedIntents() -
completedCountForTesting
public int completedCountForTesting() -
snapshotCompletedDecisionsForGroup
-
restoreCompletedDecisions
-
restoreCompletedDecisionsForGroup
-