diff --git a/backend/mpc-system/services/session-coordinator/adapters/output/postgres/session_postgres_repo.go b/backend/mpc-system/services/session-coordinator/adapters/output/postgres/session_postgres_repo.go index 323a9089..26ec866e 100644 --- a/backend/mpc-system/services/session-coordinator/adapters/output/postgres/session_postgres_repo.go +++ b/backend/mpc-system/services/session-coordinator/adapters/output/postgres/session_postgres_repo.go @@ -249,6 +249,15 @@ func (r *SessionPostgresRepo) Update(ctx context.Context, session *entities.MPCS } defer tx.Rollback() + // Lock the session row first to prevent concurrent modifications + // This ensures serializable isolation for the entire session update + _, err = tx.ExecContext(ctx, ` + SELECT id FROM mpc_sessions WHERE id = $1 FOR UPDATE + `, session.ID.UUID()) + if err != nil { + return err + } + // Update session _, err = tx.ExecContext(ctx, ` UPDATE mpc_sessions SET @@ -265,8 +274,16 @@ func (r *SessionPostgresRepo) Update(ctx context.Context, session *entities.MPCS return err } - // Update each participant individually using UPDATE to avoid lost updates - // Using individual UPDATE statements ensures concurrent updates to different participants don't conflict + // Lock all participant rows for this session to prevent concurrent modifications + // This prevents lost updates when multiple parties report completion simultaneously + _, err = tx.ExecContext(ctx, ` + SELECT id FROM participants WHERE session_id = $1 FOR UPDATE + `, session.ID.UUID()) + if err != nil { + return err + } + + // Update each participant individually for _, p := range session.Participants { // Try UPDATE first result, err := tx.ExecContext(ctx, `