This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 6bbf674d5 fix(rmqctl): drain buffered notifications before stdio exit 
(#5033)
6bbf674d5 is described below

commit 6bbf674d540f558891ab9cb998570f320f413cfd
Author: Hanabi <[email protected]>
AuthorDate: Sat Sep 26 18:11:27 2026 +0800

    fix(rmqctl): drain buffered notifications before stdio exit (#5033)
    
    test(rmqctl): cover queued notifications after input finishes
    
    fix(rmqctl): drain buffered notifications before stdio exit
---
 rmqctl/cmd/mcp_stdio.go            | 16 +++++++++++++++-
 rmqctl/cmd/mcp_stdio_proxy_test.go | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/rmqctl/cmd/mcp_stdio.go b/rmqctl/cmd/mcp_stdio.go
index 97f9de6c4..f5f96d5b4 100644
--- a/rmqctl/cmd/mcp_stdio.go
+++ b/rmqctl/cmd/mcp_stdio.go
@@ -162,7 +162,21 @@ func (p *stdioProxy) run(ctx context.Context) error {
                        p.handleResult(result)
                }
        }
-       return p.inputErr
+       // A call can queue notifications before its result, but select may 
consume
+       // the result first. Forward the remaining notifications before exiting.
+       for {
+               select {
+               case <-ctx.Done():
+                       return nil
+               case message, ok := <-p.session.Notifications():
+                       if !ok {
+                               return p.inputErr
+                       }
+                       p.handleNotification(message)
+               default:
+                       return p.inputErr
+               }
+       }
 }
 
 func (p *stdioProxy) handleNotification(message json.RawMessage) {
diff --git a/rmqctl/cmd/mcp_stdio_proxy_test.go 
b/rmqctl/cmd/mcp_stdio_proxy_test.go
index 24b5c6ee9..41f5cf73c 100644
--- a/rmqctl/cmd/mcp_stdio_proxy_test.go
+++ b/rmqctl/cmd/mcp_stdio_proxy_test.go
@@ -124,3 +124,36 @@ func TestStdioProxyForwardsServerNotification(t 
*testing.T) {
                t.Fatalf("stdout missing call response:\n%s", output)
        }
 }
+
+func TestStdioProxyDrainsNotificationsAfterInputAndCallsFinish(t *testing.T) {
+       readErr := errors.New("input read failed")
+       for _, tc := range []struct {
+               name string
+               err  error
+       }{
+               {name: "EOF"},
+               {name: "read error", err: readErr},
+       } {
+               t.Run(tc.name, func(t *testing.T) {
+                       session := newFakeMcpSession()
+                       defer session.Close()
+                       first := 
`{"jsonrpc":"2.0","method":"notifications/progress","params":{"progress":50}}`
+                       last := 
`{"jsonrpc":"2.0","method":"notifications/progress","params":{"progress":100}}`
+                       session.pushNotification(first)
+                       session.pushNotification(last)
+                       stdout := &bytes.Buffer{}
+                       proxy := newStdioProxy(strings.NewReader(""), stdout, 
&bytes.Buffer{}, session)
+                       defer close(proxy.done)
+                       // Model the select loop having consumed EOF and the 
last call result
+                       // before the notifications queued by that call.
+                       proxy.inputDone = true
+                       proxy.inputErr = tc.err
+                       if err := proxy.run(context.Background()); 
!errors.Is(err, tc.err) {
+                               t.Fatalf("run error = %v, want %v", err, tc.err)
+                       }
+                       if got, want := stdout.String(), first+"\n"+last+"\n"; 
got != want {
+                               t.Fatalf("stdout = %q, want %q", got, want)
+                       }
+               })
+       }
+}

Reply via email to