Aias00 opened a new issue, #3530:
URL: https://github.com/apache/dubbo-go/issues/3530

   ### Problem
   
   `ExchangeClient.AsyncRequest` adds a `PendingResponse` to the process-global 
`pendingResponses` map and returns immediately after the write succeeds. Unlike 
the sync `Request` path, the async path does **not** enforce the `timeout` on 
the pending response: the getty layer returns immediately for callback requests 
(`if !request.TwoWay || response.Callback != nil { return nil }`), so the 
`timeout` argument is effectively ignored for async.
   
   The only removal sites for an in-flight async pending are:
   1. `Response.Handle` when the server reply arrives (the normal path).
   2. `AsyncRequest`'s own error branch — only fires if the *write* failed.
   
   If the write succeeds and the connection subsequently drops before the 
server replies (network blip, server restart, session timeout evict), `getty`'s 
`removeSession`/`close` close sessions but never iterate or fail the in-flight 
`pendingResponses`. The pending entry leaks permanently in the global 
`sync.Map`, and the user-supplied `common.AsyncCallback` is never invoked — the 
caller's `RPCResult` is never populated, so the invocation hangs from the 
caller's perspective.
   
   This is distinct from #3440, which added cleanup on the *write-error* path; 
this issue is the *connection-drop / no-reply* path.
   
   ### Current behavior
   
   ```go
   // remoting/exchange_client.go AsyncRequest
   rsp := NewPendingResponse(request.ID)
   rsp.Callback = callback
   AddPendingResponse(rsp)
   err := client.client.Request(request, timeout, rsp)
   if err != nil {
       RemovePendingResponse(SequenceType(request.ID))   // only the 
write-error path cleans up
       result.Err = err
       return err
   }
   result.Rest = rsp.response
   return nil   // no timeout enforcement; no removal/callback on no-reply
   ```
   
   `getty` `removeSession`/`close` do not reference `pendingResponses`.
   
   ### Expected behavior
   
   If no reply arrives within `timeout`, the async pending should be removed 
from the map and the `AsyncCallback` invoked with a timeout error — the same 
`timeout` contract the sync path honors. The reply path (`Response.Handle`) and 
the timeout must not double-invoke the callback.
   
   ### Suggested approach
   
   - After a successful `client.client.Request`, install 
`time.AfterFunc(timeout, ...)` that calls `RemovePendingResponse(seq)`. If it 
returns non-nil (reply has not arrived), invoke 
`pr.Callback(pr.GetCallResponse())` with a timeout error.
   - `RemovePendingResponse` is an atomic load-and-delete (`sync.Map`), so the 
timer and the reply path cannot both win the pending; the loser gets `nil` and 
skips the callback — exactly-once.
   - Guard `timeout > 0` (a zero timeout means "no timeout", preserving the old 
wait-forever behavior).
   
   ### Acceptance criteria
   
   - [ ] On no-reply within `timeout`, the async pending is removed and the 
`AsyncCallback` is invoked with a timeout error.
   - [ ] The reply path and the timeout never both invoke the callback 
(exactly-once via atomic removal).
   - [ ] `timeout == 0` preserves the previous behavior (no timer).
   - [ ] Regression test covers the no-reply timeout path (distinct from the 
write-error cleanup test).
   


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