aglinxinyuan opened a new issue, #7456:
URL: https://github.com/apache/texera/issues/7456

   ### Describe the bug
   
   `SessionState`'s registry is a plain unsynchronized `mutable.HashMap`, and 
it is iterated from several threads other than the websocket thread that 
mutates it.
   
   ```scala
   // SessionState.scala:32
   private val sessionIdToSessionState = new mutable.HashMap[String, 
SessionState]()
   
   // :47 — hands out a live view, not a snapshot
   def getAllSessionStates: Iterable[SessionState] = 
sessionIdToSessionState.values
   ```
   
   `setState` / `removeState` are called from the websocket container's 
`@OnOpen` / `@OnClose`, while `getAllSessionStates` is iterated from at least 
three other places:
   
   - `ClusterListener.scala:141` — a Pekko cluster-event callback, on the 
cluster dispatcher
   - `Coordinator.scala:126`
   - `RegionExecutionManager.scala:667`
   
   `.values` is a live view over the map, so a session opening or closing while 
one of those broadcasts is in flight can throw 
`ConcurrentModificationException`, and in the worst case corrupt the map's 
internal state.
   
   ### To Reproduce
   
   Observed directly: while writing `ClusterListenerSpec`, a leftover listener 
iterating `getAllSessionStates` during a cluster membership event threw 
`ConcurrentModificationException` when a test registered a session 
concurrently. In production the same window is a client connecting or 
disconnecting while a cluster-status, execution-status or region-completion 
broadcast fans out — narrow, but entirely reachable on a busy server.
   
   ### Expected behavior
   
   Either make the registry thread-safe (`TrieMap`, or a `ConcurrentHashMap` 
wrapper) or have `getAllSessionStates` return a snapshot:
   
   ```scala
   def getAllSessionStates: Iterable[SessionState] = 
sessionIdToSessionState.synchronized {
     sessionIdToSessionState.values.toList
   }
   ```
   
   A snapshot is the smaller change and is what the three call sites actually 
want — each of them fans a message out to whoever was connected at that moment.
   
   ### Additional context
   
   Note that `removeState` also throws on an unknown session id 
(`sessionIdToSessionState(sId)` on line 43 before the `remove`), which makes a 
double-close fail rather than no-op. Worth considering in the same fix, though 
it is a separate defect.
   


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

Reply via email to