// Package handler 提供 HTTP 请求处理器,包括路由、静态文件服务和零拷贝传输。 // // 该文件实现 FileInfo 缓存,用于减少 os.Stat 调用。 // 替代原 fd 池设计,避免 fd 所有权问题。 // // 设计说明: // - 使用 TTL-only 新鲜度策略:缓存命中时不验证 ModTime // - 理由:每次验证 ModTime 仍需 os.Stat 调用,违背缓存目的 // - 风险:TTL 内文件修改可能返回旧 FileInfo,但静态文件通常不频繁修改 // - 支持负缓存:缓存文件不存在的结果,避免重复 stat 不存在的路径 // // 作者:xfy package handler import ( "container/list" "os" "sync" "time" ) const ( fileInfoCacheMaxEntries = 2000 defaultFileInfoCacheTTL = 10 * time.Second defaultFileNotFoundCacheTTL = 2 * time.Second ) // fileInfoEntry FileInfo 缓存条目 type fileInfoEntry struct { path string info os.FileInfo notFound bool cachedAt time.Time element *list.Element } // FileInfoCache FileInfo 缓存(O(1) LRU) type FileInfoCache struct { entries map[string]*fileInfoEntry lruList *list.List ttl time.Duration notFoundTTL time.Duration mu sync.RWMutex } // NewFileInfoCache 创建新的 FileInfo 缓存 func NewFileInfoCache() *FileInfoCache { return &FileInfoCache{ entries: make(map[string]*fileInfoEntry), lruList: list.New(), ttl: defaultFileInfoCacheTTL, notFoundTTL: defaultFileNotFoundCacheTTL, } } // SetTTL 设置 FileInfo 缓存 TTL。 func (c *FileInfoCache) SetTTL(ttl time.Duration) { c.mu.Lock() defer c.mu.Unlock() c.ttl = ttl } // Get 获取缓存的 FileInfo(向后兼容) // // 返回值: // - info: 缓存的 FileInfo // - ok: 是否命中缓存(仅对存在的文件返回 true) func (c *FileInfoCache) Get(filePath string) (os.FileInfo, bool) { info, hit, exists := c.GetWithNotFound(filePath) if !hit || !exists { return nil, false } return info, true } // GetWithNotFound 获取缓存结果,包含负缓存(文件不存在)信息。 // // 返回值: // - info: 缓存的 FileInfo(仅当 exists=true 时有效) // - hit: 是否命中缓存(包括正缓存和负缓存) // - exists: 文件是否存在(false 表示命中了负缓存) func (c *FileInfoCache) GetWithNotFound(filePath string) (os.FileInfo, bool, bool) { c.mu.RLock() entry, ok := c.entries[filePath] if !ok { c.mu.RUnlock() return nil, false, false } ttl := c.ttl if ttl <= 0 { // ttl=0 表示禁用 fileInfoCache,总是返回未命中 c.mu.RUnlock() return nil, false, false } if entry.notFound { if c.notFoundTTL > 0 { ttl = c.notFoundTTL } else { ttl = defaultFileNotFoundCacheTTL } } if time.Since(entry.cachedAt) > ttl { c.mu.RUnlock() c.mu.Lock() if e, ok := c.entries[filePath]; ok && time.Since(e.cachedAt) > ttl { c.lruList.Remove(e.element) delete(c.entries, filePath) } c.mu.Unlock() return nil, false, false } info := entry.info notFound := entry.notFound c.mu.RUnlock() return info, true, !notFound } // Set 缓存 FileInfo(向后兼容) func (c *FileInfoCache) Set(filePath string, info os.FileInfo) { c.SetWithNotFound(filePath, info, false) } // SetWithNotFound 缓存 FileInfo,支持负缓存。 // // 参数: // - filePath: 文件路径 // - info: FileInfo(notFound=true 时可为 nil) // - notFound: true 表示文件不存在 func (c *FileInfoCache) SetWithNotFound(filePath string, info os.FileInfo, notFound bool) { c.mu.Lock() defer c.mu.Unlock() now := time.Now() // 已存在,更新 if entry, ok := c.entries[filePath]; ok { entry.info = info entry.notFound = notFound entry.cachedAt = now c.lruList.MoveToFront(entry.element) return } // 淘汰最久未用的 if c.lruList.Len() >= fileInfoCacheMaxEntries { if oldest := c.lruList.Back(); oldest != nil { if entry, ok := oldest.Value.(*fileInfoEntry); ok { delete(c.entries, entry.path) } c.lruList.Remove(oldest) } } // 插入新条目 entry := &fileInfoEntry{ path: filePath, info: info, notFound: notFound, cachedAt: now, } entry.element = c.lruList.PushFront(entry) c.entries[filePath] = entry } // FileInfoCacheStats FileInfo 缓存统计 type FileInfoCacheStats struct { Entries int }