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>
This commit is contained in:
xfy 2026-04-30 16:15:39 +08:00
parent 3c96f12f74
commit 5f470993ff

View File

@ -35,7 +35,7 @@ type fileInfoEntry struct {
type FileInfoCache struct {
entries map[string]*fileInfoEntry
lruList *list.List
mu sync.Mutex
mu sync.RWMutex
}
// NewFileInfoCache 创建 FileInfo 缓存
@ -48,25 +48,39 @@ func NewFileInfoCache() *FileInfoCache {
// Get 获取缓存的 FileInfo
func (c *FileInfoCache) Get(filePath string) (os.FileInfo, bool) {
c.mu.Lock()
defer c.mu.Unlock()
c.mu.RLock()
entry, ok := c.entries[filePath]
if !ok {
c.mu.RUnlock()
return nil, false
}
// 检查 TTL
// 检查 TTL(只读检查)
if time.Since(entry.cachedAt) > fileInfoCacheTTL {
// 过期,删除并返回未命中
c.lruList.Remove(entry.element)
delete(c.entries, filePath)
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
}
// 命中,移动到 LRU 头部
c.lruList.MoveToFront(entry.element)
return entry.info, true
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
@ -124,8 +138,8 @@ func (c *FileInfoCache) Clear() {
// Stats 返回缓存统计
func (c *FileInfoCache) Stats() FileInfoCacheStats {
c.mu.Lock()
defer c.mu.Unlock()
c.mu.RLock()
defer c.mu.RUnlock()
return FileInfoCacheStats{
Entries: len(c.entries),