lolly/internal/handler/fileinfo_cache.go
xfy 8cc3fdef6f perf(handler): optimize static file serving performance
- Add pathPrefixLen for zero-allocation path stripping
- Precompute ETag in FileCache.Set, reuse on cache hits
- Add MIME LRU cache with O(1) operations using container/list
- Remove sharded cache (eager LRU was slower than single-lock)
- Add FileInfo cache to reduce os.Stat calls (TTL-only strategy)
- Adjust test expectations for normalized root path format

Benchmark results: Lookup 12.7µs → 10.6µs (16% improvement)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 14:17:56 +08:00

139 lines
3.0 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.Mutex
}
// 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.Lock()
defer c.mu.Unlock()
entry, ok := c.entries[filePath]
if !ok {
return nil, false
}
// 检查 TTL
if time.Since(entry.cachedAt) > fileInfoCacheTTL {
// 过期,删除并返回未命中
c.lruList.Remove(entry.element)
delete(c.entries, filePath)
return nil, false
}
// 命中,移动到 LRU 头部
c.lruList.MoveToFront(entry.element)
return entry.info, true
}
// 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.Lock()
defer c.mu.Unlock()
return FileInfoCacheStats{
Entries: len(c.entries),
}
}
// FileInfoCacheStats FileInfo 缓存统计
type FileInfoCacheStats struct {
Entries int
}