[ 
https://issues.apache.org/jira/browse/CASSANDRA-21476?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18091646#comment-18091646
 ] 

Abe Ratnofsky commented on CASSANDRA-21476:
-------------------------------------------

Looks like a valid race to me, thanks for catching! Are you able to author a 
patch and I can +1?

> Possible race condition in CassandraRoleManager#invalidRoleDisconnectTask
> -------------------------------------------------------------------------
>
>                 Key: CASSANDRA-21476
>                 URL: https://issues.apache.org/jira/browse/CASSANDRA-21476
>             Project: Apache Cassandra
>          Issue Type: Bug
>            Reporter: Stefan Miklosovic
>            Priority: Normal
>
> I found this when I was running multiplexer for CASSANDRA-21262 and I saw 
> {{disconnectsAttemptedOnPeriodWithJitter}} failing occasionally. 
> The bellow is the dump from Claude:
> This is a genuine production race condition in the self-rescheduling task, 
> not just a flaky-test-timing issue. Here's the mechanism.
>   How the task works
>   The disconnect task reschedules itself (CassandraRoleManager.java:857-871):
> {code}
>   protected void invalidRoleDisconnectTask(LongSupplier delayMillis, 
> ScheduledExecutorService executor) {
>       disconnectInvalidRoles();                       // ← increments the 
> counter in the test
>       long nextDelayMillis = delayMillis.getAsLong();
>       this.invalidRoleDisconnectTask = executor.schedule(   // ← reschedules 
> ITSELF, overwrites the field
>           () -> invalidRoleDisconnectTask(delayMillis, executor), 
> nextDelayMillis, MILLISECONDS);
>   }
> {code}
>   And stopping it (via setInvalidClientDisconnectPeriodMillis(0) → 
> scheduleDisconnectInvalidRoleTask(), lines 873-895):
> {code}
>   if (this.invalidRoleDisconnectTask != null)
>       this.invalidRoleDisconnectTask.cancel(true);    // cancel whatever 
> future is in the field
>   long period = getInvalidClientDisconnectPeriodMillis();
>   if (period <= 0) { logger.info("...disabled"); return; }   // period 0 → 
> stop here
> {code}
>   The race (lost cancel → orphaned chain)
>   Two threads touch this.invalidRoleDisconnectTask with no shared lock: the 
> test thread (calling setPeriod(0)) and the scheduler thread (running a task 
> that's mid-flight). The
>   breaking interleaving:
>   1. Scheduler thread is inside invalidRoleDisconnectTask, has already 
> executed disconnectInvalidRoles() but not yet reached line 870. The field 
> still holds the currently running
>   future F1.
>   2. Test thread runs scheduleDisconnectInvalidRoleTask(): reads F1, calls 
> F1.cancel(true) — but F1 is already running, so cancel does nothing useful — 
> then sees period == 0 and
>   returns.
>   3. Scheduler thread now executes line 870: this.invalidRoleDisconnectTask = 
> executor.schedule(...F2...) — installs a fresh future F2 and overwrites the 
> field.
>   4. F2 was never cancelled (the test thread already finished and is gone). 
> It fires, runs disconnectInvalidRoles(), reschedules F3, and so on — the 
> chain runs forever.
>   Two things make it worse:
>   - The period is captured by value in the closure. delayMillis = () -> 
> period + ThreadLocalRandom... (line 889) closes over the local period. So an 
> escaped chain keeps using its
>   original 100 ms period regardless of the later change to 0 — only 
> cancellation can stop it, and cancellation is exactly what was lost.
>   - cancel(true) can't unwind a self-reschedule. Cancelling future Fn doesn't 
> stop an already-running Fn from scheduling Fn+1



--
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