From a89c3079931248508d3e90ee2fa545ddd11573ea Mon Sep 17 00:00:00 2001 From: veto9292 Date: Sat, 17 Oct 2026 17:56:56 +0330 Subject: [PATCH 1/3] fix: end the observer goroutines of every service the core drops daemon.StartedService starts five observer goroutines that only Close ends. The core called CloseService alone, which stops the box but leaves them running, so every start leaked five goroutines: on stop, on a start that failed or was cancelled, and in the CLI instance and the tunnel service. closeService now does both, and every place that drops a service uses it. Co-Authored-By: Claude Opus 5.5 --- v2/hcore/custom.go | 2 +- v2/hcore/independent_instance.go | 2 +- v2/hcore/service.go | 10 ++++++++++ v2/hcore/start.go | 2 +- v2/hcore/stop.go | 2 +- v2/hcore/tunnelservice/tunnel_service.go | 3 +++ 6 files changed, 17 insertions(+), 4 deletions(-) diff --git a/v2/hcore/custom.go b/v2/hcore/custom.go index 81e0a5263..9b8012fbf 100644 --- a/v2/hcore/custom.go +++ b/v2/hcore/custom.go @@ -15,7 +15,7 @@ func StopAndAlert(msgType MessageType, message string) { SetCoreStatus(CoreStates_STOPPED, msgType, message) if ss := static.StartedService; ss != nil { - ss.CloseService() + closeService(ss) static.StartedService = nil } } diff --git a/v2/hcore/independent_instance.go b/v2/hcore/independent_instance.go index 4e38d067b..d512092fc 100644 --- a/v2/hcore/independent_instance.go +++ b/v2/hcore/independent_instance.go @@ -73,7 +73,7 @@ func RunInstance(ctx context.Context, hiddifySettings *config.HiddifyOptions, si // dialer, err := s.libbox.GetInstance().Router().Dialer(context.Background()) func (s *HiddifyInstance) Close() error { - return s.StartedService.CloseService() + return closeService(s.StartedService) } func (s *HiddifyInstance) GetContent(url string) (string, error) { diff --git a/v2/hcore/service.go b/v2/hcore/service.go index 83e5e6526..d70f70881 100644 --- a/v2/hcore/service.go +++ b/v2/hcore/service.go @@ -44,9 +44,11 @@ func NewService(ctx context.Context, options option.Options) (*daemon.StartedSer content, err := json.MarshalContext(ctx, options) if err != nil { + closeService(instance) return nil, err } if err := instance.StartOrReloadService(ctx, string(content), nil); err != nil { + closeService(instance) return nil, err } @@ -59,6 +61,14 @@ func NewService(ctx context.Context, options option.Options) (*daemon.StartedSer return instance, nil } +// closeService stops ss and ends its observer goroutines. CloseService alone +// stops only the box and leaves five goroutines running for every service built. +func closeService(ss *daemon.StartedService) error { + err := ss.CloseService() + ss.Close() + return err +} + func (h *HiddifyInstance) UrlTestHistory() *urltest.HistoryStorage { ins := h.Instance() diff --git a/v2/hcore/start.go b/v2/hcore/start.go index 1f5998a9a..b79b2d3e9 100644 --- a/v2/hcore/start.go +++ b/v2/hcore/start.go @@ -204,7 +204,7 @@ func startServiceLocked(ctx context.Context, in *StartRequest) (coreResponse *Co } if err := contextErr(ctx); err != nil { // cancelled while the service was starting: stop it again - if closeErr := instance.CloseService(); closeErr != nil { + if closeErr := closeService(instance); closeErr != nil { Log(LogLevel_ERROR, LogType_CORE, "closing cancelled service: ", closeErr.Error()) } return cancelledStart(err) diff --git a/v2/hcore/stop.go b/v2/hcore/stop.go index 51bd1802e..9df8c24e4 100644 --- a/v2/hcore/stop.go +++ b/v2/hcore/stop.go @@ -32,7 +32,7 @@ func Stop() (coreResponse *CoreInfoResponse, err error) { return SetCoreStatus(CoreStates_STOPPED, MessageType_ALREADY_STOPPED, ""), nil } - if err := ss.CloseService(); err != nil { + if err := closeService(ss); err != nil { static.StartedService = nil dumpGoroutinesToFile(fmt.Sprint(sWorkingPath, "/data/goroutine-stop.log")) return errorWrapper(MessageType_UNEXPECTED_ERROR, err) diff --git a/v2/hcore/tunnelservice/tunnel_service.go b/v2/hcore/tunnelservice/tunnel_service.go index 94cf97152..a30c8e696 100644 --- a/v2/hcore/tunnelservice/tunnel_service.go +++ b/v2/hcore/tunnelservice/tunnel_service.go @@ -111,6 +111,8 @@ func (s *TunnelService) Stop(ctx context.Context, _ *hcommon.Empty) (*TunnelResp }, nil } err := s.box.CloseService() + // CloseService leaves the box's observer goroutines running; Close ends them. + s.box.Close() if err != nil { return &TunnelResponse{ Message: err.Error(), @@ -131,6 +133,7 @@ func (s *TunnelService) Status(ctx context.Context, _ *hcommon.Empty) (*TunnelRe func (s *TunnelService) Exit(ctx context.Context, _ *hcommon.Empty) (*TunnelResponse, error) { if s.box != nil { s.box.CloseService() + s.box.Close() } go func() { <-time.After(time.Second * 1) From 1f38ad752d0fdfae46e17fbd8d83e5aaf3ddd153 Mon Sep 17 00:00:00 2001 From: veto9292 Date: Tue, 20 Oct 2026 21:02:54 +0330 Subject: [PATCH 2/3] fix(chain): lower the WARP hop MTU to 1000 The extra-security WARP hop sends its packets inside the main proxy, which adds its own header. With MTU 1280, behind a shadowsocks server every packet above about 1200 bytes was lost: the tunnel connected and small requests worked, but Chrome's large TLS ClientHello never got through, so no site loaded. 1000 passed every size up to 4 KB on the same path. Co-Authored-By: Claude Opus 5.5 --- v2/config/chain.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/v2/config/chain.go b/v2/config/chain.go index 3a6e7aeb2..e493eec29 100644 --- a/v2/config/chain.go +++ b/v2/config/chain.go @@ -129,7 +129,9 @@ func chainWarpEndpoint(tag, chainStatus string, warp ChainWarpOptions, detour st Detour: detour, // register the WARP account along the same path }, Noise: chainWarpNoise(warp), - MTU: 1280, + // its packets travel inside the main proxy, which adds its own header: 1280 lost every + // packet above ~1200 bytes behind a shadowsocks server, 1000 passed + MTU: 1000, } options.Detour = detour if warp.CleanIP != "" { From 7ba1573c686078863838f65e40f2bdb9cf20ad61 Mon Sep 17 00:00:00 2001 From: Hiddify <114227601+hiddify-com@users.noreply.github.com> Date: Wed, 21 Oct 2026 22:27:11 +0200 Subject: [PATCH 3/3] Increase MTU value from 1000 to 1280 --- v2/config/chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/config/chain.go b/v2/config/chain.go index e493eec29..4cbfce827 100644 --- a/v2/config/chain.go +++ b/v2/config/chain.go @@ -131,7 +131,7 @@ func chainWarpEndpoint(tag, chainStatus string, warp ChainWarpOptions, detour st Noise: chainWarpNoise(warp), // its packets travel inside the main proxy, which adds its own header: 1280 lost every // packet above ~1200 bytes behind a shadowsocks server, 1000 passed - MTU: 1000, + MTU: 1280, } options.Detour = detour if warp.CleanIP != "" {