He-Pin opened a new issue, #3690:
URL: https://github.com/apache/pekko/issues/3690

   ## Summary
   
   `OutboundHandshake` grabs an envelope from upstream and holds it in 
`pendingMessage` while it cannot push downstream. Its `postStop` does not 
account for that held envelope, so if the stage stops while holding one, the 
envelope is neither pushed, nor returned to the send queue, nor reported — and 
it bypasses the safeguard that quarantines an association stopped with pending 
system messages.
   
   ## Evidence
   
   `remote/src/main/scala/org/apache/pekko/remote/artery/Handshake.scala:82`:
   
   ```scala
         private var pendingMessage: OptionVal[OutboundEnvelope] = 
OptionVal.None
   ```
   
   It is populated in `onPush` (`:112-131`) — at `:129` on the first push, 
because `pushHandshakeReq()` has just consumed the downstream availability, and 
at `:125` when `out` is not available:
   
   ```scala
           if (injectHandshakeTickScheduled) {
             // out is always available here, except for if a liveness 
HandshakeReq was just pushed
             if (isAvailable(out))
               push(out, grab(in))
             else {
               if (pendingMessage.isDefined)
                 throw new IllegalStateException(s"pendingMessage expected to 
be empty")
               pendingMessage = OptionVal.Some(grab(in))
             }
           } else {
             pushHandshakeReq()
             pendingMessage = OptionVal.Some(grab(in))
           }
   ```
   
   It is only drained in `onPull` (`:133-143`):
   
   ```scala
             case Completed =>
               pendingMessage match {
                 case OptionVal.Some(p) =>
                   push(out, p)
                   pendingMessage = OptionVal.None
   ```
   
   `postStop` (`:106-109`) does not touch it:
   
   ```scala
         override def postStop(): Unit = {
           
outboundContext.associationState.removeUniqueRemoteAddressListener(uniqueRemoteAddressListener)
           super.postStop()
         }
   ```
   
   By the time the envelope is in `pendingMessage` it has already been 
`grab`bed from `in`, i.e. removed from the upstream `SendQueue`. The queue's 
own stop handler therefore cannot see it — 
`remote/src/main/scala/org/apache/pekko/remote/artery/Association.scala:793-798`:
   
   ```scala
       def sendQueuePostStop[T](pending: Vector[OutboundEnvelope]): Unit = {
         sendToDeadLetters(pending)
         val systemMessagesCount = pending.count(env => 
env.message.isInstanceOf[SystemMessage])
         if (systemMessagesCount > 0)
           quarantine(s"SendQueue stopped with [$systemMessagesCount] pending 
system messages.")
       }
   ```
   
   ## Impact
   
   An envelope lost this way skips both `sendToDeadLetters` and the 
`systemMessagesCount > 0` quarantine check. If the lost envelope carried a 
`SystemMessage` (`Watch`, `Unwatch`, `Terminate`), the association is torn down 
without the quarantine that exists precisely to prevent silent system-message 
loss, so the peer may keep believing the watch or termination is in flight.
   
   The window is narrow — at most one envelope per stream, and only when the 
stage stops while holding it — so this is unlikely to be observed often. It is 
worth closing because the existing safeguard at `Association.scala:796-797` 
shows the intent is that no pending system message should ever disappear 
quietly.
   
   ## Suggested direction
   
   In `postStop`, hand the held envelope back rather than dropping it: route it 
through the same path `sendQueuePostStop` uses (dead letters plus the 
pending-system-message accounting), or expose it so `Association` can include 
it in that count.
   
   ## Test
   
   Deterministically triggering this needs a way to stop the stage while 
`pendingMessage` is non-empty — for example a stream test that withholds 
downstream demand after the first element and then aborts the association, 
asserting the envelope reaches dead letters (and that a `SystemMessage` payload 
triggers quarantine).
   


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