lolly/internal/handler/fileinfo_cache.go
xfy 5f470993ff perf(handler): use RWMutex for FileInfoCache
- Change FileInfoCache.mu from sync.Mutex to sync.RWMutex
- Get method uses RLock for concurrent read access
- Stats method uses RLock for read-only operation
- Double-check pattern for lock upgrades (TTL expiry, LRU move)

This improves concurrent read performance for the FileInfo cache,
which is read-heavy in static file serving scenarios.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 16:15:39 +08:00

153 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package handler 提供 HTTP 请求处理器,包括路由、静态文件服务和零拷贝传输。
//
// 该文件实现 FileInfo 缓存,用于减少 os.Stat 调用。
// 替代原 fd 池设计,避免 fd 所有权问题。
//
// 设计说明:
// - 使用 TTL-only 新鲜度策略:缓存命中时不验证 ModTime
// - 理由:每次验证 ModTime 仍需 os.Stat 调用,违背缓存目的
// - 风险TTL 内文件修改可能返回旧 FileInfo但静态文件通常不频繁修改
//
// 作者xfy
package handler
import (
"container/list"
"os"
"sync"
"time"
)
const (
fileInfoCacheMaxEntries = 2000
fileInfoCacheTTL = 10 * time.Second
)
// fileInfoEntry FileInfo 缓存条目
type fileInfoEntry struct {
path string
info os.FileInfo
cachedAt time.Time
element *list.Element
}
// FileInfoCache FileInfo 缓存O(1) LRU
type FileInfoCache struct {
entries map[string]*fileInfoEntry
lruList *list.List
mu sync.RWMutex
}
// NewFileInfoCache 创建 FileInfo 缓存
func NewFileInfoCache() *FileInfoCache {
return &FileInfoCache{
entries: make(map[string]*fileInfoEntry, fileInfoCacheMaxEntries),
lruList: list.New(),
}
}
// Get 获取缓存的 FileInfo
func (c *FileInfoCache) Get(filePath string) (os.FileInfo, bool) {
c.mu.RLock()
entry, ok := c.entries[filePath]
if !ok {
c.mu.RUnlock()
return nil, false
}
// 检查 TTL只读检查
if time.Since(entry.cachedAt) > fileInfoCacheTTL {
c.mu.RUnlock()
// 升级为写锁删除过期条目
c.mu.Lock()
// double-check可能已被其他请求删除或更新
if entry, ok := c.entries[filePath]; ok && time.Since(entry.cachedAt) > fileInfoCacheTTL {
c.lruList.Remove(entry.element)
delete(c.entries, filePath)
}
c.mu.Unlock()
return nil, false
}
c.mu.RUnlock()
// LRU 移动需要写锁
c.mu.Lock()
// double-check条目可能已被删除
if entry, ok := c.entries[filePath]; ok {
c.lruList.MoveToFront(entry.element)
c.mu.Unlock()
return entry.info, true
}
c.mu.Unlock()
return nil, false
}
// Set 缓存 FileInfo
func (c *FileInfoCache) Set(filePath string, info os.FileInfo) {
c.mu.Lock()
defer c.mu.Unlock()
// 已存在,更新
if entry, ok := c.entries[filePath]; ok {
entry.info = info
entry.cachedAt = time.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,
cachedAt: time.Now(),
}
entry.element = c.lruList.PushFront(entry)
c.entries[filePath] = entry
}
// Delete 删除缓存条目
func (c *FileInfoCache) Delete(filePath string) {
c.mu.Lock()
defer c.mu.Unlock()
if entry, ok := c.entries[filePath]; ok {
c.lruList.Remove(entry.element)
delete(c.entries, filePath)
}
}
// Clear 清空缓存
func (c *FileInfoCache) Clear() {
c.mu.Lock()
defer c.mu.Unlock()
c.entries = make(map[string]*fileInfoEntry, fileInfoCacheMaxEntries)
c.lruList = list.New()
}
// Stats 返回缓存统计
func (c *FileInfoCache) Stats() FileInfoCacheStats {
c.mu.RLock()
defer c.mu.RUnlock()
return FileInfoCacheStats{
Entries: len(c.entries),
}
}
// FileInfoCacheStats FileInfo 缓存统计
type FileInfoCacheStats struct {
Entries int
}