You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言os.ReadDir读取运行时已删除文件的异常问题

问题分析与解决方案

问题根源

  • 全局变量files是程序运行期间的持久存储,每次调用GetTopXFiles时只会追加新扫描到的文件,不会清理之前存储的旧数据。删除文件后,旧的FileDisplay记录依然留在files.Files中,导致返回结果包含已删除的文件。
  • 新创建的文件会被正常扫描并追加到files.Files,所以能被识别;但删除操作仅删除磁盘上的文件,不会同步更新files中的缓存数据,只有重启程序时files被重新初始化,才会清除旧记录。

解决方案

方案1:每次扫描前清空全局缓存

修改GetTopXFiles函数,在开始扫描前清空files.Files,确保每次扫描都是全新结果:

func GetTopXFiles(mountpoint string, limit int) ([]FileDisplay, error) {
    log.SetOutput(io.Discard)
    if mountpoint == "" {
        return nil, fmt.Errorf("Path cannot be empty")
    }
    if limit < 1 {
        return nil, fmt.Errorf("Limit must be 1 or greater")
    }
    if mountpoint[len(mountpoint)-1:] != "/" {
        mountpoint = mountpoint + "/"
    }
    mount, err := filesystem.FindMount(mountpoint)
    if err != nil {
        return nil, err
    }
    device = mount.Device

    // 关键修改:清空之前的文件缓存
    files.Lock()
    files.Files = nil
    files.Unlock()

    entries, err := os.ReadDir(mountpoint)
    if err != nil {
        return nil, err
    }
    var wg sync.WaitGroup
    getFiles(mountpoint, entries, &wg)
    wg.Wait()
    sort.Sort(bySize(files.Files))
    var shortFiles []FileDisplay
    if len(files.Files) > limit {
        shortFiles = files.Files[len(files.Files)-limit:]
    } else {
        shortFiles = files.Files
    }

    return shortFiles, nil
}

方案2:移除全局变量,改用局部存储(更推荐)

全局变量容易引发并发问题和状态不一致,建议将files改为GetTopXFiles内的局部变量,彻底避免缓存残留:

// 删除全局的files变量
// var files fileDisplay

func GetTopXFiles(mountpoint string, limit int) ([]FileDisplay, error) {
    log.SetOutput(io.Discard)
    if mountpoint == "" {
        return nil, fmt.Errorf("Path cannot be empty")
    }
    if limit < 1 {
        return nil, fmt.Errorf("Limit must be 1 or greater")
    }
    if mountpoint[len(mountpoint)-1:] != "/" {
        mountpoint = mountpoint + "/"
    }
    mount, err := filesystem.FindMount(mountpoint)
    if err != nil {
        return nil, err
    }
    device := mount.Device // 改为局部变量,避免全局污染

    // 局部存储扫描结果
    var localFiles fileDisplay
    entries, err := os.ReadDir(mountpoint)
    if err != nil {
        return nil, err
    }
    var wg sync.WaitGroup
    // 修改getFiles和handleEntry,传入localFiles替代全局files
    getFiles(mountpoint, entries, &wg, device, &localFiles)
    wg.Wait()
    sort.Sort(bySize(localFiles.Files))
    var shortFiles []FileDisplay
    if len(localFiles.Files) > limit {
        shortFiles = localFiles.Files[len(localFiles.Files)-limit:]
    } else {
        shortFiles = localFiles.Files
    }

    return shortFiles, nil
}

// 修改getFiles函数,添加device和localFiles参数
func getFiles(start string, entries []fs.DirEntry, wg *sync.WaitGroup, device string, localFiles *fileDisplay) {
    for _, entry := range entries {
        wg.Add(1)
        go handleEntry(start, entry, wg, device, localFiles)
    }
}

// 修改handleEntry函数,使用传入的device和localFiles
func handleEntry(start string, entry fs.DirEntry, wg *sync.WaitGroup, device string, localFiles *fileDisplay) {
    defer wg.Done()
    var file FileDisplay
    mount, err := filesystem.FindMount(start + entry.Name())
    if err != nil {
        logrus.Errorln(err, start+entry.Name())
        return
    }
    if mount.Device == device {
        if entry.Type().IsRegular() {
            fileInfo, err := os.Stat(start + entry.Name())
            if err != nil {
                // 如果文件已删除,这里会报错,直接跳过
                logrus.Errorln(err, start+entry.Name())
                return
            }
            file.Path = start + entry.Name()
            file.Size = fileInfo.Size()
            localFiles.Append(file) // 使用局部的localFiles
        } else if entry.IsDir() {
            entries, err := os.ReadDir(start + entry.Name())
            if err != nil {
                logrus.Errorln(err, start+entry.Name())
                return
            }
            logrus.Info("Searching ", start+entry.Name())
            getFiles(start+entry.Name()+"/", entries, wg, device, localFiles)
        }
    }
}

额外优化:删除文件后同步清理缓存(可选)

如果需要在调用Delete后立即更新缓存,可以修改FileDisplay.Delete方法,删除文件后从files.Files中移除对应记录(仅适用于方案1使用全局变量的情况):

func (f *FileDisplay) Delete() error {
    err := os.Remove(f.Path)
    if err != nil {
        return err
    }
    // 清理缓存中的记录
    files.Lock()
    defer files.Unlock()
    for i, file := range files.Files {
        if file.Path == f.Path {
            files.Files = append(files.Files[:i], files.Files[i+1:]...)
            break
        }
    }
    return nil
}

内容的提问来源于stack exchange,提问作者Brandon Kauffman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 23:54:31