[ 
https://issues.apache.org/jira/browse/HDDS-16452?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Siyao Meng updated HDDS-16452:
------------------------------
    Description: 
h3. Finding
A checkpoint served by InterSCMGrpcService.download during an apply window 
ships data at index i under stale durable index i-1; a follower installs it and 
durably holds data ahead of its recorded index (CheckpointConsistent violated 
across replicas), then re-applies the entry on catch-up. Reproduced through the 
real concurrent apply/download APIs on a real store. For non-idempotent SCM 
transactions (e.g. deleted-block accounting, corroborated by a separate tracked 
issue) this diverges across SCM replicas with no automatic recovery; idempotent 
transactions self-heal via replay.

h3. Classification
* Verdict: REPRODUCED
* Severity: Critical
* Source: Specula TLA+ model checking and confirmation debate, finding MC-2

h3. Reproduce
{noformat}
Ozone commit: 9fbf9ee0cb1bd2f5f5d437b6719ebbe5309351fb
Specula:      v1.1.0 (commit c6aa3dfa)
Target:       scm-ha-tx-buffer
Guidance:     
campaigns/ozone-9fbf9ee/targets/018-scm-ha-tx-buffer/.prompt-extra.md
{noformat}
{code:none}
specula run --agent=claude-code --effort=medium --keep-original 
--max-parallel=2 \
  --enable-reviews --confirm-debate --tlc-memory-limit=28G --tlc-worker-limit=8 
\
  "scm-ha-tx-buffer|apache/ozone|Java|Use the target-specific .prompt-extra.md"
{code}
Discovered under HDDS-16432 (Specula TLA+ verification effort). The TLA+ 
specification, counterexample, and confirmation debate live in the Specula run 
artifacts.

Generated with Specula (Claude Opus 4.8).

h3. Steps to reproduce

Precondition: two threads run concurrently on a leader SCM, the Ratis apply 
thread in SCMStateMachine.applyTransaction and the InterSCM gRPC download 
thread in InterSCMGrpcService.download.

1. The apply thread opens an apply window: beginApplyingTransaction, then 
process() stages transaction i's data via addToBuffer (the batch now holds data 
i), but has not yet reached updateLatestTrxInfo, so the durable transaction 
index is still i-1.
2. A follower requests a checkpoint. InterSCMGrpcService.download calls the 
unguarded SCMHADBTransactionBuffer.flush(), which takes the write lock and 
commits the current batch atomically: {data i, transactionInfo i-1}. The DB now 
holds data i under the stale index i-1.
3. download serves that checkpoint DB. The follower installs it 
(notifyInstallSnapshotFromLeader, reinitialize, transactionBuffer.init()) and 
reloads the persisted index i-1 while its DB already contains data i 
(CheckpointConsistent / DurableIndexMatchesData violated), then re-applies 
entry i on catch-up.

For idempotent SCM transactions the replay self-heals, so the harm is masked. 
For a non-idempotent replicated transaction (e.g. deleted-block accounting) the 
re-apply diverges across SCM replicas with no automatic recovery.

Reproduced with a focused test that drives the real download() against a real 
store and buffer with an apply in progress: on the unguarded flush() the store 
is left holding data i under index i-1; the assertion fails without the fix and 
passes with it.

h3. Root cause

hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/InterSCMGrpcService.java:
 download() calls SCMHADBTransactionBuffer.flush(), which commits whatever is 
in the current batch under the current (not yet advanced) transaction index. 
flush() takes only the write lock; unlike flushIfNeeded it does not check 
applyingTransactions, so it is not deferred during an apply. The apply window 
is not atomic (addToBuffer releases the read lock on return, 
updateLatestTrxInfo holds no lock), so a flush can land between staging data i 
and advancing the index. HDDS-15065 added the applyingTransactions guard and 
moved the periodic/monitor flush onto it, but the download caller was left on 
the unguarded flush().

h3. Suggested fix

Route the download flush through the existing guarded flushIfNeeded(0) instead 
of flush(). While a transaction apply is in progress it defers (returns without 
committing), so the checkpoint is served from the last self consistent durable 
state and the follower catches the missing transactions up through the Ratis 
log. When no apply is in progress it flushes pending data as before. This 
reuses the same guard HDDS-15065 established for the periodic path. See 
[^HDDS-16452.001.patch].

Patch is an AI-drafted proposal (Specula + Claude); pending human build, full 
test, and review before merge.


  was:
h3. Finding
A checkpoint served by InterSCMGrpcService.download during an apply window 
ships data at index i under stale durable index i-1; a follower installs it and 
durably holds data ahead of its recorded index (CheckpointConsistent violated 
across replicas), then re-applies the entry on catch-up. Reproduced through the 
real concurrent apply/download APIs on a real store. For non-idempotent SCM 
transactions (e.g. deleted-block accounting, corroborated by a separate tracked 
issue) this diverges across SCM replicas with no automatic recovery; idempotent 
transactions self-heal via replay.

h3. Classification
* Verdict: REPRODUCED
* Severity: Critical
* Source: Specula TLA+ model checking and confirmation debate, finding MC-2

h3. Reproduce
{noformat}
Ozone commit: 9fbf9ee0cb1bd2f5f5d437b6719ebbe5309351fb
Specula:      v1.1.0 (commit c6aa3dfa)
Target:       scm-ha-tx-buffer
Guidance:     
campaigns/ozone-9fbf9ee/targets/018-scm-ha-tx-buffer/.prompt-extra.md
{noformat}
{code:none}
specula run --agent=claude-code --effort=medium --keep-original 
--max-parallel=2 \
  --enable-reviews --confirm-debate --tlc-memory-limit=28G --tlc-worker-limit=8 
\
  "scm-ha-tx-buffer|apache/ozone|Java|Use the target-specific .prompt-extra.md"
{code}
Discovered under HDDS-16432 (Specula TLA+ verification effort). The TLA+ 
specification, counterexample, and confirmation debate live in the Specula run 
artifacts.

Generated with Specula (Claude Opus 4.8).


> Inter SCM checkpoint served during an apply window ships data ahead of the 
> durable index, diverging replicas
> ------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-16452
>                 URL: https://issues.apache.org/jira/browse/HDDS-16452
>             Project: Apache Ozone
>          Issue Type: Bug
>            Reporter: Siyao Meng
>            Priority: Critical
>         Attachments: HDDS-16452.001.patch
>
>
> h3. Finding
> A checkpoint served by InterSCMGrpcService.download during an apply window 
> ships data at index i under stale durable index i-1; a follower installs it 
> and durably holds data ahead of its recorded index (CheckpointConsistent 
> violated across replicas), then re-applies the entry on catch-up. Reproduced 
> through the real concurrent apply/download APIs on a real store. For 
> non-idempotent SCM transactions (e.g. deleted-block accounting, corroborated 
> by a separate tracked issue) this diverges across SCM replicas with no 
> automatic recovery; idempotent transactions self-heal via replay.
> h3. Classification
> * Verdict: REPRODUCED
> * Severity: Critical
> * Source: Specula TLA+ model checking and confirmation debate, finding MC-2
> h3. Reproduce
> {noformat}
> Ozone commit: 9fbf9ee0cb1bd2f5f5d437b6719ebbe5309351fb
> Specula:      v1.1.0 (commit c6aa3dfa)
> Target:       scm-ha-tx-buffer
> Guidance:     
> campaigns/ozone-9fbf9ee/targets/018-scm-ha-tx-buffer/.prompt-extra.md
> {noformat}
> {code:none}
> specula run --agent=claude-code --effort=medium --keep-original 
> --max-parallel=2 \
>   --enable-reviews --confirm-debate --tlc-memory-limit=28G 
> --tlc-worker-limit=8 \
>   "scm-ha-tx-buffer|apache/ozone|Java|Use the target-specific 
> .prompt-extra.md"
> {code}
> Discovered under HDDS-16432 (Specula TLA+ verification effort). The TLA+ 
> specification, counterexample, and confirmation debate live in the Specula 
> run artifacts.
> Generated with Specula (Claude Opus 4.8).
> h3. Steps to reproduce
> Precondition: two threads run concurrently on a leader SCM, the Ratis apply 
> thread in SCMStateMachine.applyTransaction and the InterSCM gRPC download 
> thread in InterSCMGrpcService.download.
> 1. The apply thread opens an apply window: beginApplyingTransaction, then 
> process() stages transaction i's data via addToBuffer (the batch now holds 
> data i), but has not yet reached updateLatestTrxInfo, so the durable 
> transaction index is still i-1.
> 2. A follower requests a checkpoint. InterSCMGrpcService.download calls the 
> unguarded SCMHADBTransactionBuffer.flush(), which takes the write lock and 
> commits the current batch atomically: {data i, transactionInfo i-1}. The DB 
> now holds data i under the stale index i-1.
> 3. download serves that checkpoint DB. The follower installs it 
> (notifyInstallSnapshotFromLeader, reinitialize, transactionBuffer.init()) and 
> reloads the persisted index i-1 while its DB already contains data i 
> (CheckpointConsistent / DurableIndexMatchesData violated), then re-applies 
> entry i on catch-up.
> For idempotent SCM transactions the replay self-heals, so the harm is masked. 
> For a non-idempotent replicated transaction (e.g. deleted-block accounting) 
> the re-apply diverges across SCM replicas with no automatic recovery.
> Reproduced with a focused test that drives the real download() against a real 
> store and buffer with an apply in progress: on the unguarded flush() the 
> store is left holding data i under index i-1; the assertion fails without the 
> fix and passes with it.
> h3. Root cause
> hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/InterSCMGrpcService.java:
>  download() calls SCMHADBTransactionBuffer.flush(), which commits whatever is 
> in the current batch under the current (not yet advanced) transaction index. 
> flush() takes only the write lock; unlike flushIfNeeded it does not check 
> applyingTransactions, so it is not deferred during an apply. The apply window 
> is not atomic (addToBuffer releases the read lock on return, 
> updateLatestTrxInfo holds no lock), so a flush can land between staging data 
> i and advancing the index. HDDS-15065 added the applyingTransactions guard 
> and moved the periodic/monitor flush onto it, but the download caller was 
> left on the unguarded flush().
> h3. Suggested fix
> Route the download flush through the existing guarded flushIfNeeded(0) 
> instead of flush(). While a transaction apply is in progress it defers 
> (returns without committing), so the checkpoint is served from the last self 
> consistent durable state and the follower catches the missing transactions up 
> through the Ratis log. When no apply is in progress it flushes pending data 
> as before. This reuses the same guard HDDS-15065 established for the periodic 
> path. See [^HDDS-16452.001.patch].
> Patch is an AI-drafted proposal (Specula + Claude); pending human build, full 
> test, and review before merge.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to