From 39e39a080844bc29d0d087886a8012e22b08505a Mon Sep 17 00:00:00 2001 From: elricchen Date: Mon, 21 Apr 2025 16:59:14 +0800 Subject: [PATCH 1/6] feat(transport/stdio.go): :sparkles: add send sigterm and sigkill to stdio client in close --- client/transport/close_on_other.go | 30 +++++++++++++++++++++ client/transport/close_on_windows.go | 39 ++++++++++++++++++++++++++++ client/transport/stdio.go | 16 +++++++++++- go.mod | 9 +++++++ go.sum | 24 +++++++++++++++++ 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 client/transport/close_on_other.go create mode 100644 client/transport/close_on_windows.go diff --git a/client/transport/close_on_other.go b/client/transport/close_on_other.go new file mode 100644 index 00000000..a0d26ef2 --- /dev/null +++ b/client/transport/close_on_other.go @@ -0,0 +1,30 @@ +//go:build !windows +// +build !windows + +package transport + +import ( + "fmt" + + "github.com/shirou/gopsutil/v3/process" +) + +func killByPid(pid int) error { + proc, err := process.NewProcess(int32(pid)) + if err != nil { + return err + } + // first send SIGTERM + err = proc.Terminate() + if err != nil { + fmt.Printf("Failed to send SIGTERM to pid %d: %v\n", pid, err) + } + + _, err = process.Status() + if err == nil { + // kill ok + return nil + } + // send SIGKILL + return process.Kill() +} diff --git a/client/transport/close_on_windows.go b/client/transport/close_on_windows.go new file mode 100644 index 00000000..77968552 --- /dev/null +++ b/client/transport/close_on_windows.go @@ -0,0 +1,39 @@ +//go:build windows +// +build windows + +package transport + +import ( + "fmt" + "os" + + "github.com/shirou/gopsutil/v3/process" +) + +func killByPid(pid int) error { + proc, err := process.NewProcess(int32(pid)) + if err != nil { + return err + } + // 获取所有子进程(递归) + children, err := proc.Children() + if err == nil { + for _, child := range children { + err = killByPid(int(child.Pid)) // 递归杀子进程 + if err != nil { + fmt.Printf("Failed to kill pid %d: %v\n", child.Pid, err) + } + } + } + + // 杀掉当前进程 + p, err := os.FindProcess(int(pid)) + if err == nil { + // windows does not support SIGTERM, so we just use Kill() + err = p.Kill() + if err != nil { + fmt.Printf("Failed to kill pid %d: %v\n", pid, err) + } + } + return err +} diff --git a/client/transport/stdio.go b/client/transport/stdio.go index 85a300a1..29d29bee 100644 --- a/client/transport/stdio.go +++ b/client/transport/stdio.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "sync" + "time" "github.com/mark3labs/mcp-go/mcp" ) @@ -107,7 +108,20 @@ func (c *Stdio) Close() error { if err := c.stderr.Close(); err != nil { return fmt.Errorf("failed to close stderr: %w", err) } - return c.cmd.Wait() + + // Wait for the process to exit with a timeout + errChan := make(chan error, 1) + go func() { + errChan <- c.cmd.Wait() + }() + + select { + case err := <-errChan: + return err + case <-time.After(3 * time.Second): + err := killByPid(c.cmd.Process.Pid) + return err + } } // OnNotification registers a handler function to be called when notifications are received. diff --git a/go.mod b/go.mod index 9b9fe2d4..d0692686 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,15 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + golang.org/x/sys v0.20.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 31ed86d1..ad9d59dd 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -10,16 +13,37 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From 2c494424e9c5e99d6bcf9f4e9bd0b536f857e5eb Mon Sep 17 00:00:00 2001 From: Crayzero Date: Mon, 21 Apr 2025 16:59:14 +0800 Subject: [PATCH 2/6] feat(transport/stdio.go): :sparkles: add send sigterm and sigkill to stdio client in close --- client/transport/close_on_other.go | 30 +++++++++++++++++++++ client/transport/close_on_windows.go | 39 ++++++++++++++++++++++++++++ client/transport/stdio.go | 16 +++++++++++- go.mod | 9 +++++++ go.sum | 24 +++++++++++++++++ 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 client/transport/close_on_other.go create mode 100644 client/transport/close_on_windows.go diff --git a/client/transport/close_on_other.go b/client/transport/close_on_other.go new file mode 100644 index 00000000..a0d26ef2 --- /dev/null +++ b/client/transport/close_on_other.go @@ -0,0 +1,30 @@ +//go:build !windows +// +build !windows + +package transport + +import ( + "fmt" + + "github.com/shirou/gopsutil/v3/process" +) + +func killByPid(pid int) error { + proc, err := process.NewProcess(int32(pid)) + if err != nil { + return err + } + // first send SIGTERM + err = proc.Terminate() + if err != nil { + fmt.Printf("Failed to send SIGTERM to pid %d: %v\n", pid, err) + } + + _, err = process.Status() + if err == nil { + // kill ok + return nil + } + // send SIGKILL + return process.Kill() +} diff --git a/client/transport/close_on_windows.go b/client/transport/close_on_windows.go new file mode 100644 index 00000000..77968552 --- /dev/null +++ b/client/transport/close_on_windows.go @@ -0,0 +1,39 @@ +//go:build windows +// +build windows + +package transport + +import ( + "fmt" + "os" + + "github.com/shirou/gopsutil/v3/process" +) + +func killByPid(pid int) error { + proc, err := process.NewProcess(int32(pid)) + if err != nil { + return err + } + // 获取所有子进程(递归) + children, err := proc.Children() + if err == nil { + for _, child := range children { + err = killByPid(int(child.Pid)) // 递归杀子进程 + if err != nil { + fmt.Printf("Failed to kill pid %d: %v\n", child.Pid, err) + } + } + } + + // 杀掉当前进程 + p, err := os.FindProcess(int(pid)) + if err == nil { + // windows does not support SIGTERM, so we just use Kill() + err = p.Kill() + if err != nil { + fmt.Printf("Failed to kill pid %d: %v\n", pid, err) + } + } + return err +} diff --git a/client/transport/stdio.go b/client/transport/stdio.go index 85a300a1..29d29bee 100644 --- a/client/transport/stdio.go +++ b/client/transport/stdio.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "sync" + "time" "github.com/mark3labs/mcp-go/mcp" ) @@ -107,7 +108,20 @@ func (c *Stdio) Close() error { if err := c.stderr.Close(); err != nil { return fmt.Errorf("failed to close stderr: %w", err) } - return c.cmd.Wait() + + // Wait for the process to exit with a timeout + errChan := make(chan error, 1) + go func() { + errChan <- c.cmd.Wait() + }() + + select { + case err := <-errChan: + return err + case <-time.After(3 * time.Second): + err := killByPid(c.cmd.Process.Pid) + return err + } } // OnNotification registers a handler function to be called when notifications are received. diff --git a/go.mod b/go.mod index 9b9fe2d4..d0692686 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,15 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + golang.org/x/sys v0.20.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 31ed86d1..ad9d59dd 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -10,16 +13,37 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From 1a5dc5ae486dd28f89f35a3a6bbb78157b96cf85 Mon Sep 17 00:00:00 2001 From: Crayzero Date: Mon, 21 Apr 2025 20:28:39 +0800 Subject: [PATCH 3/6] fix(close_on_other.go): :bug: fix kill process on linux/unix --- client/transport/close_on_other.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/transport/close_on_other.go b/client/transport/close_on_other.go index a0d26ef2..22bd0db0 100644 --- a/client/transport/close_on_other.go +++ b/client/transport/close_on_other.go @@ -20,11 +20,11 @@ func killByPid(pid int) error { fmt.Printf("Failed to send SIGTERM to pid %d: %v\n", pid, err) } - _, err = process.Status() + _, err = proc.Status() if err == nil { // kill ok return nil } // send SIGKILL - return process.Kill() + return proc.Kill() } From 90cade4193ccbe5c9e273d9c835c45687eb0137c Mon Sep 17 00:00:00 2001 From: Crayzero Date: Mon, 21 Apr 2025 20:29:02 +0800 Subject: [PATCH 4/6] test(stdio_test.go): :white_check_mark: add test cases for close and check process --- client/transport/stdio_test.go | 37 +++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/client/transport/stdio_test.go b/client/transport/stdio_test.go index aa728ec6..dd54e8dd 100644 --- a/client/transport/stdio_test.go +++ b/client/transport/stdio_test.go @@ -13,6 +13,7 @@ import ( "time" "github.com/mark3labs/mcp-go/mcp" + "github.com/shirou/gopsutil/v3/process" ) func compileTestServer(outputPath string) error { @@ -52,7 +53,6 @@ func TestStdio(t *testing.T) { if err != nil { t.Fatalf("Failed to start Stdio transport: %v", err) } - defer stdio.Close() t.Run("SendRequest", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5000000000*time.Second) @@ -290,6 +290,41 @@ func TestStdio(t *testing.T) { } }) + t.Run("closeAndCheckProcess", func(t *testing.T) { + stdio.Close() + // Wait a bit to ensure process has exited + time.Sleep(100 * time.Millisecond) + exists, err := process.PidExists(int32(stdio.cmd.Process.Pid)) + if err != nil { + t.Errorf("Failed to check if process exists: %v", err) + } + if exists { + t.Fatalf("Expected process to be closed, but it still exists") + } + }) + + t.Run("sendRequestAfterClose", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + params := map[string]interface{}{ + "string": "hello world", + "array": []interface{}{1, 2, 3}, + } + + request := JSONRPCRequest{ + JSONRPC: "2.0", + ID: 1, + Method: "debug/echo", + Params: params, + } + + // Send the request + _, err := stdio.SendRequest(ctx, request) + if err == nil { + t.Fatalf("SendRequest should failed:") + } + }) } func TestStdioErrors(t *testing.T) { From ee3e77397685c42f8f41d1d7cc208546af611cdd Mon Sep 17 00:00:00 2001 From: elricchen Date: Wed, 23 Apr 2025 11:27:52 +0800 Subject: [PATCH 5/6] chore(close_on_windows.go): :bulb: update comment to english --- client/transport/close_on_windows.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/transport/close_on_windows.go b/client/transport/close_on_windows.go index 77968552..92e43820 100644 --- a/client/transport/close_on_windows.go +++ b/client/transport/close_on_windows.go @@ -15,18 +15,18 @@ func killByPid(pid int) error { if err != nil { return err } - // 获取所有子进程(递归) + // get all subprocess recursively children, err := proc.Children() if err == nil { for _, child := range children { - err = killByPid(int(child.Pid)) // 递归杀子进程 + err = killByPid(int(child.Pid)) // kill all subprocesses if err != nil { fmt.Printf("Failed to kill pid %d: %v\n", child.Pid, err) } } } - // 杀掉当前进程 + // kill current process p, err := os.FindProcess(int(pid)) if err == nil { // windows does not support SIGTERM, so we just use Kill() From b5ef487a70c429335f8354c64130e09ac19fb61a Mon Sep 17 00:00:00 2001 From: elricchen Date: Wed, 23 Apr 2025 11:50:01 +0800 Subject: [PATCH 6/6] refactor(close_on_other.go): :recycle: change signature of killProcess(int) to killProcess(*os.Process) --- client/transport/close_on_other.go | 37 +++++++++++++++++----------- client/transport/close_on_windows.go | 10 ++++++++ client/transport/stdio.go | 2 +- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/client/transport/close_on_other.go b/client/transport/close_on_other.go index 22bd0db0..d175d494 100644 --- a/client/transport/close_on_other.go +++ b/client/transport/close_on_other.go @@ -4,27 +4,34 @@ package transport import ( + "errors" "fmt" - - "github.com/shirou/gopsutil/v3/process" + "os" + "strings" + "syscall" + "time" ) -func killByPid(pid int) error { - proc, err := process.NewProcess(int32(pid)) +// killprocess kills the process on non-windows platforms. +func killProcess(proc *os.Process) error { + err := proc.Signal(syscall.SIGTERM) if err != nil { - return err + fmt.Printf("Failed to send SIGTERM to pid %d: %v\n", proc.Pid, err) } - // first send SIGTERM - err = proc.Terminate() - if err != nil { - fmt.Printf("Failed to send SIGTERM to pid %d: %v\n", pid, err) + // wait for a short time to allow the process to terminate gracefully + time.Sleep(200 * time.Millisecond) + // check if the process is still running + if err := proc.Signal(syscall.Signal(0)); err != nil { + if errors.Is(err, os.ErrProcessDone) { + return nil + } + // check if the process is gone + // on some platforms, this may return "no such process" if the process is already gone + if strings.Contains(err.Error(), "no such process") { + return nil + } } - _, err = proc.Status() - if err == nil { - // kill ok - return nil - } - // send SIGKILL + // if the process is still running, kill it return proc.Kill() } diff --git a/client/transport/close_on_windows.go b/client/transport/close_on_windows.go index 92e43820..6f6cd3a5 100644 --- a/client/transport/close_on_windows.go +++ b/client/transport/close_on_windows.go @@ -10,6 +10,8 @@ import ( "github.com/shirou/gopsutil/v3/process" ) +// killByPid kills the process by pid on windows. +// It kills all subprocesses recursively. func killByPid(pid int) error { proc, err := process.NewProcess(int32(pid)) if err != nil { @@ -37,3 +39,11 @@ func killByPid(pid int) error { } return err } + +// KillProcess kills the process on windows. +func killProcess(p *os.Process) error { + if p == nil { + return nil + } + return killByPid(p.Pid) +} diff --git a/client/transport/stdio.go b/client/transport/stdio.go index 29d29bee..80913cb6 100644 --- a/client/transport/stdio.go +++ b/client/transport/stdio.go @@ -119,7 +119,7 @@ func (c *Stdio) Close() error { case err := <-errChan: return err case <-time.After(3 * time.Second): - err := killByPid(c.cmd.Process.Pid) + err := killProcess(c.cmd.Process) return err } }