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

Artur Malchanau updated CASSANDRA-21819:
----------------------------------------
    Description: 
h3. Summary

When an auto-repair task exceeds {{repair_session_timeout{}}}, AutoRepair 
"cancels" it with {{Future.cancel(true)}} and retries the same ranges.
That cancel is a no-op: {{RepairCoordinator.run()}} only wires an asynchronous 
future chain and returns right away, so the executor future is already done and 
{{cancel()}} returns {{false}}.
The repair keeps running, or keeps hanging, in the background:
 * the incremental repair (IR) session stays {{REPAIRING}} on every 
participant, and its sstables stay {{pendingRepair}};
 * every retry, and every later incremental repair of the same ranges, fails in 
the prepare phase with {{...encountered intersecting sstables belonging to 
another incremental repair session (<old id>)}};
 * the {{RepairSession}} stays registered on the coordinator.

Nothing ever cleans this up:
 * LocalSessions never auto-fails the session, because the participants keep 
answering each other's status requests with {{REPAIRING}}, and since 
CASSANDRA-20312 each such answer refreshes {{lastUpdate}};
 * the coordinator has no timeout of its own on merkle-tree responses.

In our production cluster the session stayed hung for 7 days, until an operator 
ran {{nodetool repair_admin cancel}}. Incremental repair of the affected ranges 
was blocked for that whole time.

h3. Root cause

[AutoRepair.java (5.0.9), lines 
344-398|https://github.com/apache/cassandra/blob/cassandra-5.0.9/src/java/org/apache/cassandra/repair/autorepair/AutoRepair.java#L344-L398]:
{code:java}
f = repairRunnableExecutors.get(repairType).submit(task);    // task = 
RepairCoordinator
listener.await(config.getRepairSessionTimeout(repairType)); // times out -> 
success = false
...
boolean cancellationStatus = f.cancel(true);                // always false: 
run() already returned
// sleep repair_retry_backoff, then submit a NEW RepairCoordinator for the same 
ranges
{code}
[RepairCoordinator.run()/runMayThrow()|https://github.com/apache/cassandra/blob/cassandra-5.0.9/src/java/org/apache/cassandra/repair/RepairCoordinator.java#L273-L309]
 starts prepare -> repair -> finalize as a future chain and returns. 
Nothing in AutoRepair ever fails the parent repair, its {{RepairSession}}s, or 
the IR {{CoordinatorSession}}.
As a result, no {{FailSession}} is sent and the participants keep the sstables 
in {{pendingRepair}}.

The same logic is present on the current {{cassandra-5.0}}, {{cassandra-6.0}} 
and {{trunk}} branches.
Trunk only adds an {{isShutDown}} branch, which uses the same 
{{f.cancel(true)}}.

h3. How the session got stuck in production (trigger; not the bug itself)

5.0.9, three DCs, incremental auto-repair, {{repair.retries}} not configured 
(default: disabled).
 * 10:16:48 UTC: the coordinator (node C) starts IR session 
{{8681eb40-cd38-11f1-b81f-4153cf3313e6}} with 9 participants. Prepare succeeds, 
and all 54 VALIDATION_REQs (6 tables x 9 replicas) are acknowledged.
 * 10:17:00: the LARGE_MESSAGES connections from two remote-DC replicas to node 
C fail to connect ({{connection timed out after 2000 ms}}). While a connection 
is failing to connect, {{OutboundConnection}} rejects any message that doesn't 
fit the per-connection send queue, so three 8 MiB merkle trees are dropped:
{noformat}
WARN  [AntiEntropyStage:1] 2026-10-21T10:17:00,569 NoSpamLogger.java:107 - 
/R1:7000->/C:7000-LARGE_MESSAGES-[no-channel] overloaded; dropping 8.019MiB 
message (queue: 4.488MiB local, 500.008KiB endpoint, 500.008KiB global)
WARN  [AntiEntropyStage:1] 2026-10-21T10:17:00,298 NoSpamLogger.java:107 - 
/R2:7000->/C:7000-LARGE_MESSAGES-[no-channel] overloaded; dropping 8.315MiB 
message (queue: 12.507MiB local, 8.507MiB endpoint, 8.507MiB global)
{noformat}
The connections came back about 3s later. With retries disabled, the drop is 
never retried and the coordinator is never told; this matches CASSANDRA-20560, 
which was closed as expected without retries. The 
{{Connection.LargeMessageDroppedTasksDueToOverload}} metrics confirm 2+1 drops 
at exactly that time.

 * 13:16:48: AutoRepair's 3h timeout fires; the cancel is a no-op, and the 
retry immediately collides:
{noformat}
WARN  [AutoRepair-Repair-incremental:1] 2026-10-21T13:16:48,372 
AutoRepair.java:370 - Repair failed for range 
-2779633433731331207--2763034864604948441 for cell tables [...] with 
cancellationStatus: false retrying after 30 seconds...
ERROR [AntiEntropyStage:1] 2026-10-21T13:17:59,740 AutoRepair.java:581 - Repair 
failure for repair INCREMENTAL: [...] Repair command #89 failed with error 
Incremental repair session cf929050-cd51-11f1-b81f-4153cf3313e6 has failed
{noformat}

 * For the next 7 days, every incremental auto-repair touching those ranges 
failed with:
{noformat}
WARN  [Repair-46a1cca0-...:2] 2026-10-28T08:55:08,385 
PendingAntiCompaction.java:210 - Prepare phase for incremental repair session 
46a1cca0-d2ad-11f1-b81f-4153cf3313e6 has failed because it encountered 
intersecting sstables belonging to another incremental repair session 
(8681eb40-cd38-11f1-b81f-4153cf3313e6). ...
{noformat}

 ** {{repair_admin list}} on all 9 participants showed {{8681eb40...}} as 
{{{}REPAIRING{}}}, with "last activity" always below one hour; the status-check 
keep-alive kept it alive.
 ** {{Table.BytesPendingRepair}} on the coordinator stayed at about 364 MB the 
whole time.
 ** A second session in the same cluster 
({{{}349f6f90-cd39-11f1-9a8e-0962703fd491{}}}, a different coordinator, a 
dropped merkle tree in the same minute) had been hung in the same way.

h3. Steps to reproduce

The attached in-JVM dtest {{AutoRepairSessionTimeoutTest}} reproduces this 
deterministically in about 2 minutes:
 # 3 nodes, RF=3, one table with 1000 rows, flushed.
 # Incremental auto-repair runs on node1 only, with {{{}repair_session_timeout: 
60s{}}}, {{repair_max_retries: 1}} and {{{}repair_retry_backoff: 1s{}}}. These 
two are set in the incremental override, because the global values are ignored 
on 5.0.9 (CASSANDRA-21748).
 # The LocalSessions timers are shortened (status check 5s, auto-fail 15s, 
cleanup 5s) to show that they don't recover the session either.
 # A message filter drops exactly one {{VALIDATION_RSP}} (node2 -> node1) for 
the test table. {{repair.retries}} is left at its default (disabled).

{code:java}
git checkout cassandra-5.0.9
git apply AutoRepairSessionTimeoutTest-repro.patch
export JAVA_HOME=<JDK 17>
ant jar
ant test-jvm-dtest-some 
-Dtest.name=org.apache.cassandra.distributed.test.repair.AutoRepairSessionTimeoutTest
{code}
Observed on 5.0.9 (all intermediate checks pass; the final check, which asserts 
the expected behaviour, fails):
{noformat}
[P1] T0: dropped VALIDATION_RSP 2->1 for incremental repair session 
d76f47ca-d2f9-11f1-89c1-17ed1be92f21
[P2] session d76f47ca-... still REPAIRING on all nodes at T0+35s        (> 2x 
the 15s LocalSessions auto-fail timeout)
[P3] node1: WARN  ... AutoRepair.java:370 - Repair failed for range 
-9223372036854775808--3074457345618258603 for distributed_test_keyspace tables 
[tbl] with cancellationStatus: false retrying after 1 seconds...
[P4] WARN  ... PendingAntiCompaction.java:210 - Prepare phase for incremental 
repair session fbcc35b0-... has failed because it encountered intersecting 
sstables belonging to another incremental repair session 
(d76f47ca-d2f9-11f1-89c1-17ed1be92f21) ...
[P5] node1: ERROR ... AutoRepair.java:394 - Repair failed for range ... after 2 
retries, total assignments: 2,processed assignments: 1, cancellationStatus: 
false
[FINAL] node1 ActiveRepairService sessionCount=1
junit.framework.AssertionFailedError: AutoRepair gave up after 
repair_session_timeout, but incremental repair session 
d76f47ca-d2f9-11f1-89c1-17ed1be92f21 is still REPAIRING on node1; its 
pendingRepair sstables block every later incremental repair of these ranges 
expected:<[FAILED]> but was:<[REPAIRING]>
{noformat}
Notes on the test:
 * It uses the in-JVM message path, without the NETWORK/GOSSIP features.
 * The matcher finds the table by scanning the raw message bytes for the 
RepairJobDesc keyspace/table strings, because a {{ValidationResponse}} can't be 
deserialized in the test classloader.
 * It has only been run on 5.0.9 so far.

h3. Expected behavior

When AutoRepair gives up on a repair after {{repair_session_timeout}}, that 
repair should actually be aborted:
 * the IR session is failed on all participants ({{FailSession}}), so its 
{{pendingRepair}} sstables are released back to unrepaired;
 * the coordinator's {{RepairSession}}/{{ParentRepairSession}} are removed;
 * the retry starts from a clean state.

h3. Suggested fix
 # On timeout, abort the specific repair instead of calling {{f.cancel(true)}}. 
Use the parent session id ({{RepairCoordinator.state.id}}) to:
 ** force-shutdown that parent's {{RepairSession}}s, so the coordinator chain 
completes exceptionally;
 ** fail the IR {{CoordinatorSession}}, which sends {{FailSession}} to the 
participants;
 ** remove the {{ParentRepairSession}}.
This may need a small {{RepairCoordinator}}/{{ActiveRepairService}} API for 
aborting a single repair, like {{terminateSessions()}} but scoped to one repair.
 # Log the real outcome of the abort; the current {{cancellationStatus}} is 
always {{false}} and misleading.
 # Optionally: don't start a retry for the same ranges while the previous IR 
session is still non-terminal.
 # Optionally: when incremental auto-repair is enabled while {{repair.retries}} 
(merkle_tree_response) is disabled, warn at startup or document it prominently. 
A single dropped merkle tree otherwise hangs the repair indefinitely 
(CASSANDRA-20560).

  was:
h3. Summary

When an auto-repair task exceeds {{repair_session_timeout{}}}, AutoRepair 
"cancels" it with {{Future.cancel(true)}} and retries the same ranges.
That cancel is a no-op: {{RepairCoordinator.run()}} only wires an asynchronous 
future chain and returns right away, so the executor future is already done and 
{{cancel()}} returns {{false}}.
The repair keeps running, or keeps hanging, in the background:
 * the incremental repair (IR) session stays {{REPAIRING}} on every 
participant, and its sstables stay {{pendingRepair}};
 * every retry, and every later incremental repair of the same ranges, fails in 
the prepare phase with {{...encountered intersecting sstables belonging to 
another incremental repair session (<old id>)}};
 * the {{RepairSession}} stays registered on the coordinator.

Nothing ever cleans this up:
 * LocalSessions never auto-fails the session, because the participants keep 
answering each other's status requests with {{REPAIRING}}, and since 
CASSANDRA-20312 each such answer refreshes {{lastUpdate}};
 * the coordinator has no timeout of its own on merkle-tree responses.

In our production cluster the session stayed hung for 7 days, until an operator 
ran {{nodetool repair_admin cancel}}. Incremental repair of the affected ranges 
was blocked for that whole time.
h3. Root cause

[AutoRepair.java (5.0.9), lines 
344-398|https://github.com/apache/cassandra/blob/cassandra-5.0.9/src/java/org/apache/cassandra/repair/autorepair/AutoRepair.java#L344-L398]:
{code:java}
f = repairRunnableExecutors.get(repairType).submit(task);    // task = 
RepairCoordinator
listener.await(config.getRepairSessionTimeout(repairType)); // times out -> 
success = false
...
boolean cancellationStatus = f.cancel(true);                // always false: 
run() already returned
// sleep repair_retry_backoff, then submit a NEW RepairCoordinator for the same 
ranges
{code}
[RepairCoordinator.run()/runMayThrow()|https://github.com/apache/cassandra/blob/cassandra-5.0.9/src/java/org/apache/cassandra/repair/RepairCoordinator.java#L273-L309]
 starts prepare -> repair -> finalize as a future chain and returns. Nothing in 
AutoRepair ever fails the parent repair, its {{{}RepairSession}}s, or the IR 
{{CoordinatorSession{}}}. As a result, no {{FailSession}} is sent and the 
participants keep the sstables in {{{}pendingRepair{}}}.

The same logic is present on the current {{cassandra-5.0}}, {{cassandra-6.0}} 
and {{trunk}} branches.
Trunk only adds an {{isShutDown}} branch, which uses the same 
{{f.cancel(true)}}.

h3. How the session got stuck in production (trigger; not the bug itself)

5.0.9, three DCs, incremental auto-repair, {{repair.retries}} not configured 
(default: disabled).
 * 10:16:48 UTC: the coordinator (node C) starts IR session 
{{8681eb40-cd38-11f1-b81f-4153cf3313e6}} with 9 participants. Prepare succeeds, 
and all 54 VALIDATION_REQs (6 tables x 9 replicas) are acknowledged.
 * 10:17:00: the LARGE_MESSAGES connections from two remote-DC replicas to node 
C fail to connect ({{connection timed out after 2000 ms}}). While a connection 
is failing to connect, {{OutboundConnection}} rejects any message that doesn't 
fit the per-connection send queue, so three 8 MiB merkle trees are dropped:
{noformat}
WARN  [AntiEntropyStage:1] 2026-10-21T10:17:00,569 NoSpamLogger.java:107 - 
/R1:7000->/C:7000-LARGE_MESSAGES-[no-channel] overloaded; dropping 8.019MiB 
message (queue: 4.488MiB local, 500.008KiB endpoint, 500.008KiB global)
WARN  [AntiEntropyStage:1] 2026-10-21T10:17:00,298 NoSpamLogger.java:107 - 
/R2:7000->/C:7000-LARGE_MESSAGES-[no-channel] overloaded; dropping 8.315MiB 
message (queue: 12.507MiB local, 8.507MiB endpoint, 8.507MiB global)
{noformat}
The connections came back about 3s later. With retries disabled, the drop is 
never retried and the coordinator is never told; this matches CASSANDRA-20560, 
which was closed as expected without retries. The 
{{Connection.LargeMessageDroppedTasksDueToOverload}} metrics confirm 2+1 drops 
at exactly that time.

 * 13:16:48: AutoRepair's 3h timeout fires; the cancel is a no-op, and the 
retry immediately collides:
{noformat}
WARN  [AutoRepair-Repair-incremental:1] 2026-10-21T13:16:48,372 
AutoRepair.java:370 - Repair failed for range 
-2779633433731331207--2763034864604948441 for cell tables [...] with 
cancellationStatus: false retrying after 30 seconds...
ERROR [AntiEntropyStage:1] 2026-10-21T13:17:59,740 AutoRepair.java:581 - Repair 
failure for repair INCREMENTAL: [...] Repair command #89 failed with error 
Incremental repair session cf929050-cd51-11f1-b81f-4153cf3313e6 has failed
{noformat}

 * For the next 7 days, every incremental auto-repair touching those ranges 
failed with:
{noformat}
WARN  [Repair-46a1cca0-...:2] 2026-10-28T08:55:08,385 
PendingAntiCompaction.java:210 - Prepare phase for incremental repair session 
46a1cca0-d2ad-11f1-b81f-4153cf3313e6 has failed because it encountered 
intersecting sstables belonging to another incremental repair session 
(8681eb40-cd38-11f1-b81f-4153cf3313e6). ...
{noformat}

 ** {{repair_admin list}} on all 9 participants showed {{8681eb40...}} as 
{{{}REPAIRING{}}}, with "last activity" always below one hour; the status-check 
keep-alive kept it alive.
 ** {{Table.BytesPendingRepair}} on the coordinator stayed at about 364 MB the 
whole time.
 ** A second session in the same cluster 
({{{}349f6f90-cd39-11f1-9a8e-0962703fd491{}}}, a different coordinator, a 
dropped merkle tree in the same minute) had been hung in the same way.

h3. Steps to reproduce

The attached in-JVM dtest {{AutoRepairSessionTimeoutTest}} reproduces this 
deterministically in about 2 minutes:
 # 3 nodes, RF=3, one table with 1000 rows, flushed.
 # Incremental auto-repair runs on node1 only, with {{{}repair_session_timeout: 
60s{}}}, {{repair_max_retries: 1}} and {{{}repair_retry_backoff: 1s{}}}. These 
two are set in the incremental override, because the global values are ignored 
on 5.0.9 (CASSANDRA-21748).
 # The LocalSessions timers are shortened (status check 5s, auto-fail 15s, 
cleanup 5s) to show that they don't recover the session either.
 # A message filter drops exactly one {{VALIDATION_RSP}} (node2 -> node1) for 
the test table. {{repair.retries}} is left at its default (disabled).

{code:java}
git checkout cassandra-5.0.9
git apply AutoRepairSessionTimeoutTest-repro.patch
export JAVA_HOME=<JDK 17>
ant jar
ant test-jvm-dtest-some 
-Dtest.name=org.apache.cassandra.distributed.test.repair.AutoRepairSessionTimeoutTest
{code}
Observed on 5.0.9 (all intermediate checks pass; the final check, which asserts 
the expected behaviour, fails):
{noformat}
[P1] T0: dropped VALIDATION_RSP 2->1 for incremental repair session 
d76f47ca-d2f9-11f1-89c1-17ed1be92f21
[P2] session d76f47ca-... still REPAIRING on all nodes at T0+35s        (> 2x 
the 15s LocalSessions auto-fail timeout)
[P3] node1: WARN  ... AutoRepair.java:370 - Repair failed for range 
-9223372036854775808--3074457345618258603 for distributed_test_keyspace tables 
[tbl] with cancellationStatus: false retrying after 1 seconds...
[P4] WARN  ... PendingAntiCompaction.java:210 - Prepare phase for incremental 
repair session fbcc35b0-... has failed because it encountered intersecting 
sstables belonging to another incremental repair session 
(d76f47ca-d2f9-11f1-89c1-17ed1be92f21) ...
[P5] node1: ERROR ... AutoRepair.java:394 - Repair failed for range ... after 2 
retries, total assignments: 2,processed assignments: 1, cancellationStatus: 
false
[FINAL] node1 ActiveRepairService sessionCount=1
junit.framework.AssertionFailedError: AutoRepair gave up after 
repair_session_timeout, but incremental repair session 
d76f47ca-d2f9-11f1-89c1-17ed1be92f21 is still REPAIRING on node1; its 
pendingRepair sstables block every later incremental repair of these ranges 
expected:<[FAILED]> but was:<[REPAIRING]>
{noformat}
Notes on the test:
 * It uses the in-JVM message path, without the NETWORK/GOSSIP features.
 * The matcher finds the table by scanning the raw message bytes for the 
RepairJobDesc keyspace/table strings, because a {{ValidationResponse}} can't be 
deserialized in the test classloader.
 * It has only been run on 5.0.9 so far.

h3. Expected behaviour

When AutoRepair gives up on a repair after {{{}repair_session_timeout{}}}, that 
repair should actually be aborted:
 * the IR session is failed on all participants ({{{}FailSession{}}}), so its 
{{pendingRepair}} sstables are released back to unrepaired;
 * the coordinator's {{{}RepairSession{}}}/{{{}ParentRepairSession{}}} are 
removed;
 * the retry starts from a clean state.

h3. Suggested fix
 # On timeout, abort the specific repair instead of calling 
{{{}f.cancel(true){}}}. Use the parent session id 
({{{}RepairCoordinator.state.id{}}}) to:
 ** force-shutdown that parent's \{{RepairSession}}s, so the coordinator chain 
completes exceptionally;
 ** fail the IR {{{}CoordinatorSession{}}}, which sends {{FailSession}} to the 
participants;
 ** remove the {{{}ParentRepairSession{}}}.
This may need a small {{{}RepairCoordinator{}}}/{{{}ActiveRepairService{}}} API 
for aborting a single repair, like {{terminateSessions()}} but scoped to one 
repair.
 # Log the real outcome of the abort; the current {{cancellationStatus}} is 
always {{false}} and misleading.
 # Optionally: don't start a retry for the same ranges while the previous IR 
session is still non-terminal.
 # Optionally: when incremental auto-repair is enabled while {{repair.retries}} 
(merkle_tree_response) is disabled, warn at startup or document it prominently. 
A single dropped merkle tree otherwise hangs the repair indefinitely 
(CASSANDRA-20560).


> AutoRepair repair_session_timeout does not abort the timed-out repair, 
> leaking a hung incremental repair session that blocks all later incremental 
> repairs of its ranges
> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CASSANDRA-21819
>                 URL: https://issues.apache.org/jira/browse/CASSANDRA-21819
>             Project: Apache Cassandra
>          Issue Type: Bug
>          Components: Consistency/Repair
>            Reporter: Artur Malchanau
>            Priority: Normal
>         Attachments: AutoRepairSessionTimeoutTest-repro.patch
>
>
> h3. Summary
> When an auto-repair task exceeds {{repair_session_timeout{}}}, AutoRepair 
> "cancels" it with {{Future.cancel(true)}} and retries the same ranges.
> That cancel is a no-op: {{RepairCoordinator.run()}} only wires an 
> asynchronous future chain and returns right away, so the executor future is 
> already done and {{cancel()}} returns {{false}}.
> The repair keeps running, or keeps hanging, in the background:
>  * the incremental repair (IR) session stays {{REPAIRING}} on every 
> participant, and its sstables stay {{pendingRepair}};
>  * every retry, and every later incremental repair of the same ranges, fails 
> in the prepare phase with {{...encountered intersecting sstables belonging to 
> another incremental repair session (<old id>)}};
>  * the {{RepairSession}} stays registered on the coordinator.
> Nothing ever cleans this up:
>  * LocalSessions never auto-fails the session, because the participants keep 
> answering each other's status requests with {{REPAIRING}}, and since 
> CASSANDRA-20312 each such answer refreshes {{lastUpdate}};
>  * the coordinator has no timeout of its own on merkle-tree responses.
> In our production cluster the session stayed hung for 7 days, until an 
> operator ran {{nodetool repair_admin cancel}}. Incremental repair of the 
> affected ranges was blocked for that whole time.
> h3. Root cause
> [AutoRepair.java (5.0.9), lines 
> 344-398|https://github.com/apache/cassandra/blob/cassandra-5.0.9/src/java/org/apache/cassandra/repair/autorepair/AutoRepair.java#L344-L398]:
> {code:java}
> f = repairRunnableExecutors.get(repairType).submit(task);    // task = 
> RepairCoordinator
> listener.await(config.getRepairSessionTimeout(repairType)); // times out -> 
> success = false
> ...
> boolean cancellationStatus = f.cancel(true);                // always false: 
> run() already returned
> // sleep repair_retry_backoff, then submit a NEW RepairCoordinator for the 
> same ranges
> {code}
> [RepairCoordinator.run()/runMayThrow()|https://github.com/apache/cassandra/blob/cassandra-5.0.9/src/java/org/apache/cassandra/repair/RepairCoordinator.java#L273-L309]
>  starts prepare -> repair -> finalize as a future chain and returns. 
> Nothing in AutoRepair ever fails the parent repair, its {{RepairSession}}s, 
> or the IR {{CoordinatorSession}}.
> As a result, no {{FailSession}} is sent and the participants keep the 
> sstables in {{pendingRepair}}.
> The same logic is present on the current {{cassandra-5.0}}, {{cassandra-6.0}} 
> and {{trunk}} branches.
> Trunk only adds an {{isShutDown}} branch, which uses the same 
> {{f.cancel(true)}}.
> h3. How the session got stuck in production (trigger; not the bug itself)
> 5.0.9, three DCs, incremental auto-repair, {{repair.retries}} not configured 
> (default: disabled).
>  * 10:16:48 UTC: the coordinator (node C) starts IR session 
> {{8681eb40-cd38-11f1-b81f-4153cf3313e6}} with 9 participants. Prepare 
> succeeds, and all 54 VALIDATION_REQs (6 tables x 9 replicas) are acknowledged.
>  * 10:17:00: the LARGE_MESSAGES connections from two remote-DC replicas to 
> node C fail to connect ({{connection timed out after 2000 ms}}). While a 
> connection is failing to connect, {{OutboundConnection}} rejects any message 
> that doesn't fit the per-connection send queue, so three 8 MiB merkle trees 
> are dropped:
> {noformat}
> WARN  [AntiEntropyStage:1] 2026-10-21T10:17:00,569 NoSpamLogger.java:107 - 
> /R1:7000->/C:7000-LARGE_MESSAGES-[no-channel] overloaded; dropping 8.019MiB 
> message (queue: 4.488MiB local, 500.008KiB endpoint, 500.008KiB global)
> WARN  [AntiEntropyStage:1] 2026-10-21T10:17:00,298 NoSpamLogger.java:107 - 
> /R2:7000->/C:7000-LARGE_MESSAGES-[no-channel] overloaded; dropping 8.315MiB 
> message (queue: 12.507MiB local, 8.507MiB endpoint, 8.507MiB global)
> {noformat}
> The connections came back about 3s later. With retries disabled, the drop is 
> never retried and the coordinator is never told; this matches 
> CASSANDRA-20560, which was closed as expected without retries. The 
> {{Connection.LargeMessageDroppedTasksDueToOverload}} metrics confirm 2+1 
> drops at exactly that time.
>  * 13:16:48: AutoRepair's 3h timeout fires; the cancel is a no-op, and the 
> retry immediately collides:
> {noformat}
> WARN  [AutoRepair-Repair-incremental:1] 2026-10-21T13:16:48,372 
> AutoRepair.java:370 - Repair failed for range 
> -2779633433731331207--2763034864604948441 for cell tables [...] with 
> cancellationStatus: false retrying after 30 seconds...
> ERROR [AntiEntropyStage:1] 2026-10-21T13:17:59,740 AutoRepair.java:581 - 
> Repair failure for repair INCREMENTAL: [...] Repair command #89 failed with 
> error Incremental repair session cf929050-cd51-11f1-b81f-4153cf3313e6 has 
> failed
> {noformat}
>  * For the next 7 days, every incremental auto-repair touching those ranges 
> failed with:
> {noformat}
> WARN  [Repair-46a1cca0-...:2] 2026-10-28T08:55:08,385 
> PendingAntiCompaction.java:210 - Prepare phase for incremental repair session 
> 46a1cca0-d2ad-11f1-b81f-4153cf3313e6 has failed because it encountered 
> intersecting sstables belonging to another incremental repair session 
> (8681eb40-cd38-11f1-b81f-4153cf3313e6). ...
> {noformat}
>  ** {{repair_admin list}} on all 9 participants showed {{8681eb40...}} as 
> {{{}REPAIRING{}}}, with "last activity" always below one hour; the 
> status-check keep-alive kept it alive.
>  ** {{Table.BytesPendingRepair}} on the coordinator stayed at about 364 MB 
> the whole time.
>  ** A second session in the same cluster 
> ({{{}349f6f90-cd39-11f1-9a8e-0962703fd491{}}}, a different coordinator, a 
> dropped merkle tree in the same minute) had been hung in the same way.
> h3. Steps to reproduce
> The attached in-JVM dtest {{AutoRepairSessionTimeoutTest}} reproduces this 
> deterministically in about 2 minutes:
>  # 3 nodes, RF=3, one table with 1000 rows, flushed.
>  # Incremental auto-repair runs on node1 only, with 
> {{{}repair_session_timeout: 60s{}}}, {{repair_max_retries: 1}} and 
> {{{}repair_retry_backoff: 1s{}}}. These two are set in the incremental 
> override, because the global values are ignored on 5.0.9 (CASSANDRA-21748).
>  # The LocalSessions timers are shortened (status check 5s, auto-fail 15s, 
> cleanup 5s) to show that they don't recover the session either.
>  # A message filter drops exactly one {{VALIDATION_RSP}} (node2 -> node1) for 
> the test table. {{repair.retries}} is left at its default (disabled).
> {code:java}
> git checkout cassandra-5.0.9
> git apply AutoRepairSessionTimeoutTest-repro.patch
> export JAVA_HOME=<JDK 17>
> ant jar
> ant test-jvm-dtest-some 
> -Dtest.name=org.apache.cassandra.distributed.test.repair.AutoRepairSessionTimeoutTest
> {code}
> Observed on 5.0.9 (all intermediate checks pass; the final check, which 
> asserts the expected behaviour, fails):
> {noformat}
> [P1] T0: dropped VALIDATION_RSP 2->1 for incremental repair session 
> d76f47ca-d2f9-11f1-89c1-17ed1be92f21
> [P2] session d76f47ca-... still REPAIRING on all nodes at T0+35s        (> 2x 
> the 15s LocalSessions auto-fail timeout)
> [P3] node1: WARN  ... AutoRepair.java:370 - Repair failed for range 
> -9223372036854775808--3074457345618258603 for distributed_test_keyspace 
> tables [tbl] with cancellationStatus: false retrying after 1 seconds...
> [P4] WARN  ... PendingAntiCompaction.java:210 - Prepare phase for incremental 
> repair session fbcc35b0-... has failed because it encountered intersecting 
> sstables belonging to another incremental repair session 
> (d76f47ca-d2f9-11f1-89c1-17ed1be92f21) ...
> [P5] node1: ERROR ... AutoRepair.java:394 - Repair failed for range ... after 
> 2 retries, total assignments: 2,processed assignments: 1, cancellationStatus: 
> false
> [FINAL] node1 ActiveRepairService sessionCount=1
> junit.framework.AssertionFailedError: AutoRepair gave up after 
> repair_session_timeout, but incremental repair session 
> d76f47ca-d2f9-11f1-89c1-17ed1be92f21 is still REPAIRING on node1; its 
> pendingRepair sstables block every later incremental repair of these ranges 
> expected:<[FAILED]> but was:<[REPAIRING]>
> {noformat}
> Notes on the test:
>  * It uses the in-JVM message path, without the NETWORK/GOSSIP features.
>  * The matcher finds the table by scanning the raw message bytes for the 
> RepairJobDesc keyspace/table strings, because a {{ValidationResponse}} can't 
> be deserialized in the test classloader.
>  * It has only been run on 5.0.9 so far.
> h3. Expected behavior
> When AutoRepair gives up on a repair after {{repair_session_timeout}}, that 
> repair should actually be aborted:
>  * the IR session is failed on all participants ({{FailSession}}), so its 
> {{pendingRepair}} sstables are released back to unrepaired;
>  * the coordinator's {{RepairSession}}/{{ParentRepairSession}} are removed;
>  * the retry starts from a clean state.
> h3. Suggested fix
>  # On timeout, abort the specific repair instead of calling 
> {{f.cancel(true)}}. Use the parent session id 
> ({{RepairCoordinator.state.id}}) to:
>  ** force-shutdown that parent's {{RepairSession}}s, so the coordinator chain 
> completes exceptionally;
>  ** fail the IR {{CoordinatorSession}}, which sends {{FailSession}} to the 
> participants;
>  ** remove the {{ParentRepairSession}}.
> This may need a small {{RepairCoordinator}}/{{ActiveRepairService}} API for 
> aborting a single repair, like {{terminateSessions()}} but scoped to one 
> repair.
>  # Log the real outcome of the abort; the current {{cancellationStatus}} is 
> always {{false}} and misleading.
>  # Optionally: don't start a retry for the same ranges while the previous IR 
> session is still non-terminal.
>  # Optionally: when incremental auto-repair is enabled while 
> {{repair.retries}} (merkle_tree_response) is disabled, warn at startup or 
> document it prominently. A single dropped merkle tree otherwise hangs the 
> repair indefinitely (CASSANDRA-20560).



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

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

Reply via email to