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
相关产品推荐
相关产品推荐

