mirror of
				https://github.com/TeaOSLab/EdgeNode.git
				synced 2025-11-04 16:00:25 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			211 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
 | 
						||
 | 
						||
package caches
 | 
						||
 | 
						||
import (
 | 
						||
	"fmt"
 | 
						||
	"github.com/TeaOSLab/EdgeNode/internal/goman"
 | 
						||
	"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
 | 
						||
	memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
 | 
						||
	"github.com/fsnotify/fsnotify"
 | 
						||
	"github.com/iwind/TeaGo/logs"
 | 
						||
	"github.com/iwind/TeaGo/types"
 | 
						||
	"path/filepath"
 | 
						||
	"runtime"
 | 
						||
	"sync"
 | 
						||
	"time"
 | 
						||
)
 | 
						||
 | 
						||
const (
 | 
						||
	maxOpenFileSize = 256 << 20
 | 
						||
)
 | 
						||
 | 
						||
type OpenFileCache struct {
 | 
						||
	poolMap  map[string]*OpenFilePool // file path => Pool
 | 
						||
	poolList *linkedlist.List[*OpenFilePool]
 | 
						||
	watcher  *fsnotify.Watcher
 | 
						||
 | 
						||
	locker sync.RWMutex
 | 
						||
 | 
						||
	maxCount     int
 | 
						||
	capacitySize int64
 | 
						||
 | 
						||
	count    int
 | 
						||
	usedSize int64
 | 
						||
}
 | 
						||
 | 
						||
func NewOpenFileCache(maxCount int) (*OpenFileCache, error) {
 | 
						||
	if maxCount <= 0 {
 | 
						||
		maxCount = 16384
 | 
						||
	}
 | 
						||
 | 
						||
	var cache = &OpenFileCache{
 | 
						||
		maxCount:     maxCount,
 | 
						||
		poolMap:      map[string]*OpenFilePool{},
 | 
						||
		poolList:     linkedlist.NewList[*OpenFilePool](),
 | 
						||
		capacitySize: (int64(memutils.SystemMemoryGB()) << 30) / 16,
 | 
						||
	}
 | 
						||
 | 
						||
	watcher, err := fsnotify.NewWatcher()
 | 
						||
	if err != nil {
 | 
						||
		return nil, err
 | 
						||
	}
 | 
						||
	cache.watcher = watcher
 | 
						||
 | 
						||
	goman.New(func() {
 | 
						||
		for event := range watcher.Events {
 | 
						||
			if runtime.GOOS == "linux" || event.Op&fsnotify.Chmod != fsnotify.Chmod {
 | 
						||
				cache.Close(event.Name)
 | 
						||
			}
 | 
						||
		}
 | 
						||
	})
 | 
						||
 | 
						||
	return cache, nil
 | 
						||
}
 | 
						||
 | 
						||
func (this *OpenFileCache) Get(filename string) *OpenFile {
 | 
						||
	filename = filepath.Clean(filename)
 | 
						||
 | 
						||
	this.locker.RLock()
 | 
						||
	pool, ok := this.poolMap[filename]
 | 
						||
	this.locker.RUnlock()
 | 
						||
	if ok {
 | 
						||
		file, consumed, consumedSize := pool.Get()
 | 
						||
		if consumed {
 | 
						||
			this.locker.Lock()
 | 
						||
			this.count--
 | 
						||
			this.usedSize -= consumedSize
 | 
						||
 | 
						||
			// pool如果为空,也不需要从列表中删除,避免put时需要重新创建
 | 
						||
 | 
						||
			this.locker.Unlock()
 | 
						||
		}
 | 
						||
 | 
						||
		return file
 | 
						||
	}
 | 
						||
	return nil
 | 
						||
}
 | 
						||
 | 
						||
func (this *OpenFileCache) Put(filename string, file *OpenFile) {
 | 
						||
	filename = filepath.Clean(filename)
 | 
						||
 | 
						||
	if file.size > maxOpenFileSize {
 | 
						||
		return
 | 
						||
	}
 | 
						||
 | 
						||
	this.locker.Lock()
 | 
						||
	defer this.locker.Unlock()
 | 
						||
 | 
						||
	// 如果超过当前容量,则关闭最早的
 | 
						||
	if this.count >= this.maxCount || this.usedSize+file.size >= this.capacitySize {
 | 
						||
		this.consumeHead()
 | 
						||
		return
 | 
						||
	}
 | 
						||
 | 
						||
	pool, ok := this.poolMap[filename]
 | 
						||
	var success bool
 | 
						||
	if ok {
 | 
						||
		success = pool.Put(file)
 | 
						||
	} else {
 | 
						||
		_ = this.watcher.Add(filename)
 | 
						||
		pool = NewOpenFilePool(filename)
 | 
						||
		pool.version = file.version
 | 
						||
		this.poolMap[filename] = pool
 | 
						||
		success = pool.Put(file)
 | 
						||
	}
 | 
						||
	this.poolList.Push(pool.linkItem)
 | 
						||
 | 
						||
	// 检查长度
 | 
						||
	if success {
 | 
						||
		this.count++
 | 
						||
		this.usedSize += file.size
 | 
						||
	}
 | 
						||
}
 | 
						||
 | 
						||
func (this *OpenFileCache) Close(filename string) {
 | 
						||
	filename = filepath.Clean(filename)
 | 
						||
 | 
						||
	this.locker.Lock()
 | 
						||
 | 
						||
	pool, ok := this.poolMap[filename]
 | 
						||
	if ok {
 | 
						||
		// 设置关闭状态
 | 
						||
		pool.SetClosing()
 | 
						||
 | 
						||
		delete(this.poolMap, filename)
 | 
						||
		this.poolList.Remove(pool.linkItem)
 | 
						||
		_ = this.watcher.Remove(filename)
 | 
						||
		this.count -= pool.Len()
 | 
						||
		this.usedSize -= pool.usedSize
 | 
						||
	}
 | 
						||
 | 
						||
	this.locker.Unlock()
 | 
						||
 | 
						||
	// 在locker之外,提升性能
 | 
						||
	if ok {
 | 
						||
		pool.Close()
 | 
						||
	}
 | 
						||
}
 | 
						||
 | 
						||
func (this *OpenFileCache) CloseAll() {
 | 
						||
	this.locker.Lock()
 | 
						||
	for _, pool := range this.poolMap {
 | 
						||
		pool.Close()
 | 
						||
	}
 | 
						||
	this.poolMap = map[string]*OpenFilePool{}
 | 
						||
	this.poolList.Reset()
 | 
						||
	_ = this.watcher.Close()
 | 
						||
	this.count = 0
 | 
						||
	this.usedSize = 0
 | 
						||
	this.locker.Unlock()
 | 
						||
}
 | 
						||
 | 
						||
func (this *OpenFileCache) SetCapacity(capacityBytes int64) {
 | 
						||
	this.capacitySize = capacityBytes
 | 
						||
}
 | 
						||
 | 
						||
func (this *OpenFileCache) Debug() {
 | 
						||
	var ticker = time.NewTicker(5 * time.Second)
 | 
						||
	goman.New(func() {
 | 
						||
		for range ticker.C {
 | 
						||
			logs.Println("==== " + types.String(this.count) + ", " + fmt.Sprintf("%.4fMB", float64(this.usedSize)/(1<<20)) + " ====")
 | 
						||
			this.poolList.Range(func(item *linkedlist.Item[*OpenFilePool]) (goNext bool) {
 | 
						||
				logs.Println(filepath.Base(item.Value.Filename()), item.Value.Len())
 | 
						||
				return true
 | 
						||
			})
 | 
						||
		}
 | 
						||
	})
 | 
						||
}
 | 
						||
 | 
						||
func (this *OpenFileCache) consumeHead() {
 | 
						||
	var delta = 1
 | 
						||
 | 
						||
	if this.count > 100 {
 | 
						||
		delta = 2
 | 
						||
	}
 | 
						||
 | 
						||
	for i := 0; i < delta; i++ {
 | 
						||
		var head = this.poolList.Head()
 | 
						||
		if head == nil {
 | 
						||
			break
 | 
						||
		}
 | 
						||
 | 
						||
		var headPool = head.Value
 | 
						||
		headFile, consumed, consumedSize := headPool.Get()
 | 
						||
		if consumed {
 | 
						||
			this.count--
 | 
						||
			this.usedSize -= consumedSize
 | 
						||
 | 
						||
			if headFile != nil {
 | 
						||
				_ = headFile.Close()
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		if headPool.Len() == 0 {
 | 
						||
			delete(this.poolMap, headPool.filename)
 | 
						||
			this.poolList.Remove(head)
 | 
						||
			_ = this.watcher.Remove(headPool.filename)
 | 
						||
		}
 | 
						||
	}
 | 
						||
}
 |