Skip to content

v2node v0.4.1:DelUsers 执行时 LinkManager.CloseAll 自死锁,进而阻塞 XUDPManager,导致 goroutine 与堆内存持续增长 #48

Description

@Shannon-x

v2node v0.4.1:DelUsers 执行时 LinkManager.CloseAll 自死锁,进而阻塞 XUDPManager,导致 goroutine 与堆内存持续增长

环境信息

  • 程序:/usr/local/v2node/v2node server
  • v2node 模块版本:github.com/wyx2685/v2node v0.4.1
  • v2node 提交(来自 Go build info):927701c30de32a7ab2a6091792e4697b2c9619e2
  • 构建时间(来自 Go build info):2026-06-01T14:08:26Z
  • 二进制中依赖的 xray-core:github.com/xtls/xray-core v1.260425.0 => github.com/wyx2685/xray-core 13357f3bc706dbae675223a42016a57ed27c3510
  • 运行环境:Linux amd64,Go build info 显示 go1.26.1

现象描述

程序运行数天后,v2node 内存占用增长到约 1.7 GiB RSS,goroutine 数量增长到约 6 万个。

最新一次 pprof 采集数据:

timestamp=2026-07-02T21:12:50+08:00
pid=1996821
rss_kb=1801764
goroutines=60952
HeapAlloc=1601754560
HeapObjects=5600817
Stack=267845632

2026-07-02 当天自动采集的增长趋势:

12:00 rss=1658036KB heap=1176779504 goroutines=45863 xudp_lock_wait=43042
15:35 rss=1705000KB heap=1359599896 goroutines=53563 xudp_lock_wait=49824
19:39 rss=1811028KB heap=1530883504 goroutines=59431 xudp_lock_wait=56831
21:12 rss=1801764KB heap=1601754560 goroutines=60952 xudp_lock_wait=59631

从数据来看,这是真实的堆内存和 goroutine 持续增长,而不仅仅是 Go 运行时未将 RSS 归还给操作系统的现象。

关键 goroutine 证据

有 4 个 goroutine 卡在同一条 v2node 死锁路径上,示例如下:

goroutine 11583089 [sync.Mutex.Lock, 4584 minutes]:
sync.(*RWMutex).Lock
github.com/wyx2685/v2node/core/app/dispatcher.(*LinkManager).RemoveWriter
    core/app/dispatcher/linkmanager.go:36
github.com/wyx2685/v2node/core/app/dispatcher.(*ManagedWriter).Close
    core/app/dispatcher/linkmanager.go:20
github.com/wyx2685/v2node/core/app/dispatcher.(*LinkManager).CloseAll
    core/app/dispatcher/linkmanager.go:45
github.com/wyx2685/v2node/core.(*V2Core).DelUsers
    core/user.go:68
github.com/wyx2685/v2node/node.(*Controller).nodeInfoMonitor
    node/task.go:116

这基本可以确定问题路径是:CloseAll() 持有 LinkManager.mu 锁的同时调用 common.Close(w),而该调用会进入 ManagedWriter.Close(),后者又会调用 RemoveWriter(),试图再次获取同一把 LinkManager.mu 锁。由于 Go 的 sync.RWMutex 不可重入,该 goroutine 因此自锁死。

对应源码(二进制所对应的提交):

  • ManagedWriter.Close() 在关闭 writer 之前会调用 w.manager.RemoveWriter(w)
    func (w *ManagedWriter) Close() error {
    w.manager.RemoveWriter(w)
    return common.Close(w.writer)
  • RemoveWriter() 内部执行 m.mu.Lock()
    func (m *LinkManager) RemoveWriter(writer *ManagedWriter) {
    m.mu.Lock()
    defer m.mu.Unlock()
    delete(m.links, writer)
  • CloseAll() 在遍历并调用 common.Close(w) 期间持有 m.mu.Lock()
    func (m *LinkManager) CloseAll() {
    m.mu.Lock()
    defer m.mu.Unlock()
    for w, r := range m.links {
    common.Close(w)
    common.Interrupt(r)
  • DelUsers() 会调用 lm.CloseAll()

    v2node/core/user.go

    Lines 64 to 69 in 927701c

    tc.Delete(user)
    }
    if v, ok := vc.dispatcher.LinkManagers.Load(user); ok {
    lm := v.(*dispatcher.LinkManager)
    lm.CloseAll()
    vc.dispatcher.LinkManagers.Delete(user)

对 XUDPManager 的连锁影响

有一个 xray-core 的 XUDP 清理 goroutine 也被同样卡住:

goroutine 24 [sync.Mutex.Lock, 3042 minutes]:
github.com/wyx2685/v2node/core/app/dispatcher.(*LinkManager).RemoveWriter
github.com/wyx2685/v2node/core/app/dispatcher.(*ManagedWriter).Close
github.com/xtls/xray-core/app/dispatcher.(*SizeStatWriter).Close
github.com/xtls/xray-core/common/mux.(*XUDP).Interrupt
    common/mux/session.go:227
github.com/xtls/xray-core/common/mux.init.0.func1
    common/mux/session.go:244

在所使用的 xray-core fork 中,XUDP 清理循环在持有全局 XUDPManager.Lock() 的情况下调用了 x.Interrupt()

由于 x.Interrupt() 会走到上面 v2node 的 ManagedWriter.Close() 死锁路径,这个清理 goroutine 将永远无法释放全局的 XUDPManager.Lock()

此后,几乎所有 mux/XUDP worker 都会堆积在这里:

github.com/xtls/xray-core/common/mux.(*ServerWorker).handleStatusNew
    common/mux/server.go:200

最新一次采集中的统计:

common/mux/server.go:200 lock wait: 59631
created by mux.NewServerWorker: 59651

相关 xray-core 代码:

这就解释了内存持续增长的原因:大量被阻塞的 mux worker 会持续占用其 goroutine 栈以及已分配的缓冲区。heap pprof 显示,主要的内存滞留集中在 xray-core/common/bufbytespoolcommon/muxproxy/freedom 以及 VLESS 入站相关路径。

预期行为

删除带有活跃连接的用户时,应当能够正常关闭/中断相关连接,而不应导致 LinkManager 死锁;某一个连接关闭卡住,也不应该永久阻塞全局的 XUDP manager。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions