From 8bec31a03554ef53c0944e6e1e004bb2914b5897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Thu, 18 Sep 2025 19:13:49 +0000 Subject: [PATCH 1/2] Fix ss2022 gouroutine leak --- common/singbridge/pipe.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/common/singbridge/pipe.go b/common/singbridge/pipe.go index d04ebda44e02..b9dce39f8f05 100644 --- a/common/singbridge/pipe.go +++ b/common/singbridge/pipe.go @@ -4,8 +4,10 @@ import ( "context" "io" "net" + "time" "github.com/sagernet/sing/common/bufio" + "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/transport" ) @@ -33,8 +35,26 @@ func (w *PipeConnWrapper) Close() error { return nil } +// This Read implemented a timeout to avoid goroutine leak. +// as a temporarily solution func (w *PipeConnWrapper) Read(b []byte) (n int, err error) { - return w.R.Read(b) + type readResult struct { + n int + err error + } + c := make(chan readResult, 1) + go func() { + n, err := w.R.Read(b) + c <- readResult{n: n, err: err} + }() + select { + case result := <-c: + return result.n, result.err + case <-time.After(300 * time.Second): + common.Interrupt(w.R) + common.Close(w.R) + return 0, io.EOF + } } func (w *PipeConnWrapper) Write(p []byte) (n int, err error) { From 5f86aafc931741761a9ac040639afc6326d5eff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sat, 20 Sep 2025 08:07:17 +0000 Subject: [PATCH 2/2] ErrReadTimeout --- common/singbridge/pipe.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/singbridge/pipe.go b/common/singbridge/pipe.go index b9dce39f8f05..d6c0f3d8852a 100644 --- a/common/singbridge/pipe.go +++ b/common/singbridge/pipe.go @@ -51,9 +51,9 @@ func (w *PipeConnWrapper) Read(b []byte) (n int, err error) { case result := <-c: return result.n, result.err case <-time.After(300 * time.Second): - common.Interrupt(w.R) common.Close(w.R) - return 0, io.EOF + common.Interrupt(w.R) + return 0, buf.ErrReadTimeout } }