Skip to content

Feat: UpdateTransaction - #86

Merged
Tech-Matt merged 24 commits into
mainfrom
feat/UpdateTransaction
Aug 28, 2026
Merged

Feat: UpdateTransaction#86
Tech-Matt merged 24 commits into
mainfrom
feat/UpdateTransaction

Conversation

@alanmasu

Copy link
Copy Markdown
Owner

This pull request introduces the new UpdateTransaction class to support update operations in the distributed system, along with related protocol messages and integration into the Replica actor. The changes also include utility methods and minor visibility adjustments to facilitate this new transaction type.

Major additions and changes:

Update Transaction Implementation

  • Added a new UpdateTransaction class in UpdateTransaction.java, which encapsulates the logic for update operations, including state management, protocol messages (UpdateMsg, UpdateAckMsg, WriteOkMsg, etc.), and coordinator/replica state machines.

Integration with Replica

  • Registered UpdateMsg handling in the Replica actor's createReceive method, and implemented the onUpdateMsg handler to schedule an UpdateTransaction when appropriate. [

Utility and Refactoring

  • Added an isCoordinator() method to Replica to allow transactions to check if the current replica is the coordinator.
  • Changed the visibility of TransactionId fields in Transaction.java to public to enable access from other classes like UpdateTransaction.

Issue linked

@alanmasu
alanmasu requested a review from Tech-Matt August 18, 2026 10:30
…saction

* Continued the intrgration of the `UpdateTransaction` with the system,
* Fixed initialization of `HeartbeatTransaction`'s `TransactionId` to use the proper `Replica` method
* Created the `TestUpdateTransaction` class to test the `UpdateTransaction` functionality, this test:
    - a basic write operation issued to a non-coordinator replica
    - a basic write operation issued to a non-coordinator replica that is crashed, expecting a timeout response
    - a basic write operation issued to a non-coordinator replica followed by a crash of the coordinator replica, expecting a timeout response
* Corrected the `transaction_Write.mmd` chart to be compliant to the implementation

Fixes #55, Related to #59, #60, #61
@alanmasu
alanmasu marked this pull request as ready for review August 23, 2026 08:13
@alanmasu alanmasu moved this from Backlog to In review in Distributed Systems Project Aug 23, 2026
Comment thread src/main/java/it/unitn/ds/Replica.java Outdated
Comment on lines 176 to 180
this.heartbeatTransaction = new HeartbeatTransaction(
heartbeatTransactionId,
getNextTransactionId(),
this,
getEpochPair()
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using getNextTransactionId() here will fail.
Each replica currently creates its heartbeat transaction using getNextTransactionId(). Since every replica starts its transaction counter at zero, the coordinator creates an ID such as <Replica_0, 0>, while a follower creates <Replica_1, 0>.

However, the coordinator broadcasts HeartbeatMsg using the coordinator's ID. When a follower receives that message, Replica.onMessage() searches its active transactions for the same ID. The follower owns <Replica_1, 0>, so it cannot find a matching transaction and silently ignores the heartbeat. Its watchdog may then expire even though the coordinator is still sending heartbeats.

Could we create the heartbeat transaction using the coordinator's ActorRef and the coordinator-term sequence?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good catch... SO the tests are not correct because the regression is still Green!

Comment thread src/main/java/it/unitn/ds/UpdateTransaction.java Outdated
Comment thread src/main/java/it/unitn/ds/Replica.java
I've fixed the destination provided when the `UpdateTransaction` is issued on the replicas that dosn't initiated the transaction.

Corrected also the test to ensure this behaviour
Reordered the replica cases to check first if the replica is the coordinator or not.
…RDINATOR_BEAT_INTERVAL`

I changed the original function to use a parameter for the heartbeat interval instead the class constant.
This is to speedup tests, infact having an interval of 1s could result in really long tests timings
Before, if a `WriteTransaction` is issued on the coord directly would fail due to non complete termination protocol. Now is fixed

Fixes #89
- Now each replica contains a `Map<EpochPair, UpdateTransactio>` where the updates are stored.
- Each `Replica` epochPair is updated after the each update

@alanmasu alanmasu left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to implement some test too but didn't had time today...

Comment thread src/main/java/it/unitn/ds/Replica.java Outdated
Comment on lines 176 to 180
this.heartbeatTransaction = new HeartbeatTransaction(
heartbeatTransactionId,
getNextTransactionId(),
this,
getEpochPair()
);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good catch... SO the tests are not correct because the regression is still Green!

Comment thread src/main/java/it/unitn/ds/Replica.java
Comment thread src/main/java/it/unitn/ds/Replica.java
@alanmasu
alanmasu requested a review from Tech-Matt August 25, 2026 22:20
Comment thread src/main/java/it/unitn/ds/UpdateTransaction.java
Comment thread src/main/java/it/unitn/ds/UpdateTransaction.java Outdated
replica.unicast(updateMsg, this.destination.get());
this.timeout = replica.scheduleToItself(
replica.getMaxLatency() * 3,
new UpdateTimeoutMsg(this.getId(), this.startEpochPair, replica.getSelf()));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we handle UpdateTimeoutMsg and WriteOkTimeoutMsg before considering the crash handling complete?

Both timeouts are scheduled, but replicaStateMachine() currently has no active branch for either message—the only relevant code is commented out. If the coordinator crashes after a replica forwards a write, or after it receives UPDATE, the timeout is delivered and then ignored; the transaction remains active and no election/recovery path begins.

The specification requires these two timeout cases to detect coordinator failure. When adding the handler, it may also be worth following the heartbeat transaction’s stale-timeout protection, since cancelling an Akka timer does not guarantee an already-queued timeout cannot arrive later.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, is commented due to no ElectionTransaction is ready! When this will be done, the code is mostly ready

Comment thread src/main/java/it/unitn/ds/UpdateTransaction.java Outdated
There was no call to `replica.setPosition(int, int)`
…sg and improved test cases for crash scenarios

@alanmasu alanmasu left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me it's ok. We need to complete afte the ElectionTransaction for complete the whole state machine, BTW I think this could be merged and completed after!

Comment thread src/main/java/it/unitn/ds/UpdateTransaction.java Outdated
Comment thread src/main/java/it/unitn/ds/UpdateTransaction.java Outdated
replica.unicast(updateMsg, this.destination.get());
this.timeout = replica.scheduleToItself(
replica.getMaxLatency() * 3,
new UpdateTimeoutMsg(this.getId(), this.startEpochPair, replica.getSelf()));

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, is commented due to no ElectionTransaction is ready! When this will be done, the code is mostly ready

Comment thread src/main/java/it/unitn/ds/UpdateTransaction.java
An UpdateTransaction has two different identities with different jobs.
TransactionId routes messages to the correct local FSM, while EpochPair
identifies and orders the replicated update across every replica. The update
protocol therefore requires one non-null coordinator-assigned EpochPair to
remain unchanged through UPDATE, ACK, WRITEOK, local application, history,
and completion of the parent WriteTransaction.

Why this was needed

The previous implementation reused startEpochPair as the update identity.
That value only described the initiating replica state when its local
transaction was created; it was not a unique identity allocated by the
coordinator. If two writes reached the coordinator before either committed,
both could start from the same committed pair and therefore broadcast the
same EpochPair for two different updates.

Termination then discarded the pair carried by WRITEOK and independently
incremented each replica local EpochPair. Consequently, the pair used by
UPDATE, ACK, and WRITEOK could differ from the pair stored in updateHistory.
Replicas starting from different local states could also record the same
update under different keys. That breaks the identity needed to order writes,
correlate recovery history, and determine which update was committed.

The coordinator also counted ACK messages rather than distinct replicas and
did not verify that an ACK belonged to the active update pair. A duplicate or
stale ACK could therefore contribute to quorum. Participants similarly
accepted WRITEOK without checking that it matched the update they had
acknowledged.

Timeout handling had two separate defects. A participant already waiting for
WRITEOK scheduled UpdateTimeoutMsg instead of WriteOkTimeoutMsg, and the
state-machine branches for both timeout messages were commented out. A fired
timeout was therefore ignored and the transaction remained in its waiting
state. The old delay was also based only on raw maximum link latency and did
not account for FIFO channel backlog or replica-count tolerance.

Finally, the success regression asserted only the client WriteResult. Client
success alone does not prove that every replica applied the update with the
same index and value or that the replicated identity was recorded correctly.

What changed

Add a coordinator-side update sequence allocator to Replica. The allocator
tracks the current allocation epoch and the next sequence number separately
from the last committed EpochPair. reserveNextUpdateEpochPair is restricted
to the coordinator and advances the reservation immediately, allowing
multiple in-flight updates to receive unique pairs even before an earlier
update commits. setEpochPair keeps the allocator synchronized when commit or
future recovery changes the replica epoch.

Add updateEpochPair to UpdateTransaction. startEpochPair remains the snapshot
from transaction creation, while updateEpochPair represents the mutable,
coordinator-assigned identity of this replicated write. The coordinator now
reserves this pair before broadcasting UPDATE, participants adopt it from
UPDATE, and UPDATE, ACK, WRITEOK, WriteFinish, and history all carry the same
pair.

Validate protocol messages before changing state. UPDATE and WRITEOK must
carry a non-null pair. The coordinator accepts ACKs only while WAITING_ACK,
only for the active update pair, and only once per non-null sender. A
participant ignores WRITEOK for a different pair. These guards prevent stale
or duplicate messages from advancing quorum or committing the wrong update.

Commit the exact pair contained in WRITEOK instead of calculating a new local
pair. Keep the existing setPosition call, set the replica EpochPair to the
coordinator pair, propagate that pair to the parent WriteTransaction, and
store history under an explicit pair argument. This makes the wire identity,
committed replica state, and updateHistory key agree on every replica.

Use phase-specific timeout messages and a shared update timeout bound based on
four times getMaxLatencyPlusTolerance. WAITING_UPDATE handles only a matching
UpdateTimeoutMsg, while WAITING_WRITEOK handles only a matching
WriteOkTimeoutMsg. A valid timeout cancels and clears its timer and moves the
transaction to WAITING_ELECTION. State and pair checks also make already
queued stale timer messages harmless after the FSM changes phase.

How this solves the protocol failure

For a committed state such as <0,4>, concurrent coordinator reservations now
produce <0,5>, <0,6>, and so on instead of reusing <0,4>. For an update
assigned <0,5>, every protocol step now refers to exactly <0,5>:

  coordinator reserves <0,5>
  coordinator broadcasts UPDATE(<0,5>)
  participants return ACK(<0,5>)
  coordinator broadcasts WRITEOK(<0,5>) after distinct matching quorum
  every replica applies the value and adopts <0,5>
  every replica records updateHistory[<0,5>]

Reservation may leave a sequence gap if an in-flight update never commits,
but it never reuses an identity for another update. Preserving uniqueness is
required for ordering and recovery and is safer than forcing contiguous
sequence numbers.

Tests

Add focused protocol tests proving that coordinator reservations are unique,
allocation follows epoch changes, termination uses the WRITEOK pair for both
replica state and history, and both participant timeout phases enter
WAITING_ELECTION.

Strengthen the existing 3-node and 5-node regression cases for coordinator 0
and coordinator 1 by requiring an UpdateApplied callback with the correct
replica ID, index, and value from every replica, rather than relying only on
the final client result.

Verified with:

  gradle test --no-daemon --tests it.unitn.ds.TestUpdateTransactionProtocol
    --tests it.unitn.ds.regression.TestUpdateTransaction
  gradle regression --no-daemon
  gradle spotlessJava --no-daemon

The focused run passed all 15 cases, the complete regression task passed, and
the formatting task completed successfully.

Scope limitation

This branch does not yet contain ElectionTransaction or Replica.startElection.
Timeouts now reach the correct WAITING_ELECTION integration state, but this
commit does not start an election or recover and replay the pending update.
That handoff must be connected by the separate election/recovery work before
claiming complete timeout recovery.
Integrate PR #83 read-transaction changes and resolve the Replica overlap by retaining both ReadMsg and UpdateMsg receive registrations and handlers. Focused read and update tests pass together after the resolution.
Remove the explicit null assignment from timeout handling, add diagnostic messages to convergence assertions, and apply the repository Java formatter. Static analysis now reports zero problems and the focused UpdateTransaction suite remains green.
@Tech-Matt
Tech-Matt merged commit 6874f61 into main Aug 28, 2026
1 check passed
@github-project-automation github-project-automation Bot moved this from In review to Done in Distributed Systems Project Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants