Skip to content

[Bug] DFS v2 下 sal_socket() 失败时 socket() 泄漏 dfs_vnode #11710

Description

@GYM-png

问题描述

在启用 RT_USING_DFS_V2 时,components/net/sal/socket/net_sockets.c 中的 socket() 会先分配 dfs_filedfs_vnode,再调用 sal_socket()

如果 sal_socket() 返回失败,当前代码只调用 fd_release(fd)

d->vnode = (struct dfs_vnode *)rt_malloc(sizeof(struct dfs_vnode));
if (!d->vnode)
{
    fd_release(fd);
    rt_set_errno(-ENOMEM);
    return -1;
}
dfs_vnode_init(d->vnode, FT_SOCKET, dfs_net_get_fops());

socket = sal_socket(domain, type, protocol);
if (socket < 0)
{
    fd_release(fd);
    rt_set_errno(-ENOMEM);
    return -1;
}

但 DFS v2 的 fdt_fd_release()file->ref_count == 1 时只调用:

dfs_file_destroy(file);

dfs_file_destroy() 只释放 dfs_file 本身及其 mmap_context,不会释放 file->vnode。因此,sal_socket() 每失败一次,都会泄漏一个 sizeof(struct dfs_vnode) 大小的堆块。

影响范围

  • DFS v1:当前 master 中不会发生此泄漏。dfs_vnode_init() 已在 sal_socket() 之前调用,并且 DFS v1 的 fd_release() 会递减并释放 vnode。
  • DFS v2:fd_release() 不处理 vnode,因此失败路径会稳定泄漏。
  • fd 槽位和 dfs_file 可以正常释放,所以问题不会直接表现为 fd 耗尽,而是可用堆空间持续减少。

复现方式

启用以下配置:

#define RT_USING_DFS
#define RT_USING_DFS_V2
#define DFS_USING_POSIX
#define RT_USING_SAL

循环调用一个能够稳定使 sal_socket() 失败的参数,例如:

for (int i = 0; i < 1000; i++)
{
    int fd = socket(-1, SOCK_STREAM, 0);
    RT_ASSERT(fd < 0);
}

在循环前后查看 RT-Thread 堆使用情况,可以观察到可用堆空间持续减少,每次失败对应一个未释放的 dfs_vnode

根因分析

DFS v1 和 DFS v2 的 fd_release() 对 vnode 的所有权处理不同:

  • DFS v1 的 fdt_fd_release() 会递减 vnode->ref_count,并在引用计数归零时释放 vnode。
  • DFS v2 的 fdt_fd_release() 只销毁 dfs_file,不会调用 dfs_vnode_destroy(),也不会直接释放 vnode。

net_sockets.c 当前假设 fd_release() 会同时释放 d->vnode,该假设只适用于 DFS v1。

这个行为可能与 PR #7818 / commit 0b966bfca0bc369c0f8cc1fdd7ad7155ef4d0384 有关。该提交删除了失败分支中原有的 rt_free(d->vnode),提交说明是 vnode 会由 fd_release() 释放;但这一结论没有覆盖 DFS v2。

相关提交:
0b966bf

建议修复

一种影响范围较小、同时兼容 DFS v1 和 DFS v2 的处理方式,是在 sal_socket() 失败时显式释放并置空 vnode,然后再释放 fd:

else
{
    rt_free(d->vnode);
    d->vnode = RT_NULL;
    fd_release(fd);
    rt_set_errno(-ENOMEM);
    return -1;
}

置空 d->vnode 可以避免 DFS v1 的 fd_release() 再次释放同一对象。

另一种方案是在 DFS v2 的 fd/vnode 生命周期管理中统一处理 vnode 引用,但该方案影响范围更大,需要确认 fd_release() 的通用资源所有权约定。

参考代码

  • components/net/sal/socket/net_sockets.c
  • components/dfs/dfs_v1/src/dfs.c
  • components/dfs/dfs_v2/src/dfs.c

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions