joseluisll commented on PR #8688:
URL: https://github.com/apache/hadoop/pull/8688#issuecomment-6602405119

   Also there is a race that needs to be solved:
   
   **The race**
   
   After `awaitCacheInitialized(...)`, `loadFromZKCache()` applies a snapshot 
of every cached node on the thread calling `startThreads()`. By then the cache 
listener is already applying newer events on Curator's `SafeNotifyService` 
thread. Nothing orders the two, so the starting thread can apply an older 
snapshot of a token after the listener has applied a newer state:
   
   ```
   starting thread                        listener thread
   reads cached DT_n (valid)
                                          peer cancels DT_n
                                          NODE_DELETED -> 
currentTokens.remove(DT_n)
   processTokenAddOrUpdate(old bytes)
     -> currentTokens.put(DT_n)           (nothing removes it again)
   ```
   
   Effects on the instance that is starting:
   - A token cancelled by a peer during startup goes back into memory, and 
`verifyToken()` **accepts it** until it is renewed or reaches its renew date.
   - A token renewed by a peer during startup gets its **old renew date** back 
in memory, so this instance's expiry scan can later delete a token that is 
still valid in ZooKeeper.
   
   Trunk does not have this race. There, `loadFromZKCache()` reads the cache 
before it is filled (the bug this PR fixes), so the starting thread never 
applies a token and the listener applies everything in order.
   
   **Evidence**
   
   The same test runs unchanged on trunk and on this PR. A peer cancels 300 
tokens while another instance starts; afterwards, none of them may be in the 
restarted instance's memory or pass `verifyToken()`. The only timing aid is a 2 
ms sleep whenever the starting thread applies a token itself, which does 
nothing on trunk.
   
   | Version | Runs | Failed | Tokens applied by the starting thread | 
Cancelled tokens still accepted |
   |---|---|---|---|---|
   | trunk `57f78d2` | 13 | 0 | 0 | 0 |
   | this PR `ccf5b7f` | 13 | 6 | 170–209 | 1–3 per failed run |
   
   Two deterministic tests reproduce the cancel and the renew cases one at a 
time. They pause the starting thread before it applies a token, let a real peer 
change it, and use a `runSafe()` no-op as a barrier until the listener has 
handled the change. In Curator 5.2.0, listener calls and `runSafe()` tasks run 
in order on one thread. Tests and write-up:
   
https://github.com/joseluisll/hadoop/blob/zkdtsm-race-repro/ZKDTSM-RACES-REPORT.md
 (section 5)
   
   **Suggested fix (tested)**
   
   Drop `loadFromZKCache()` and let the listener apply the initial nodes too: 
no `.afterInitialized()`, and after the await only `syncTokenOwnerStats()` for 
the token cache. Curator delivers the initial nodes, then later changes, then 
`initialized()`, all in order on one thread. So when the latch opens, memory is 
complete and no older snapshot can land afterwards. With this change all 13 
tests in `TestZKDelegationTokenSecretManager` pass, including 
`testNodesLoadedAfterRestart` with its immediate assertion.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to