github.com/TeaOSLab/EdgeNode@v1.3.8/internal/caches/open_file_cache.go (about)

     1  // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
     2  
     3  package caches
     4  
     5  import (
     6  	"fmt"
     7  	"github.com/TeaOSLab/EdgeNode/internal/goman"
     8  	"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
     9  	memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
    10  	"github.com/fsnotify/fsnotify"
    11  	"github.com/iwind/TeaGo/logs"
    12  	"github.com/iwind/TeaGo/types"
    13  	"path/filepath"
    14  	"runtime"
    15  	"sync"
    16  	"time"
    17  )
    18  
    19  const (
    20  	maxOpenFileSize = 256 << 20
    21  )
    22  
    23  type OpenFileCache struct {
    24  	poolMap  map[string]*OpenFilePool // file path => Pool
    25  	poolList *linkedlist.List[*OpenFilePool]
    26  	watcher  *fsnotify.Watcher
    27  
    28  	locker sync.RWMutex
    29  
    30  	maxCount     int
    31  	capacitySize int64
    32  
    33  	count    int
    34  	usedSize int64
    35  }
    36  
    37  func NewOpenFileCache(maxCount int) (*OpenFileCache, error) {
    38  	if maxCount <= 0 {
    39  		maxCount = 16384
    40  	}
    41  
    42  	var cache = &OpenFileCache{
    43  		maxCount:     maxCount,
    44  		poolMap:      map[string]*OpenFilePool{},
    45  		poolList:     linkedlist.NewList[*OpenFilePool](),
    46  		capacitySize: (int64(memutils.SystemMemoryGB()) << 30) / 16,
    47  	}
    48  
    49  	watcher, err := fsnotify.NewWatcher()
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	cache.watcher = watcher
    54  
    55  	goman.New(func() {
    56  		for event := range watcher.Events {
    57  			if runtime.GOOS == "linux" || event.Op&fsnotify.Chmod != fsnotify.Chmod {
    58  				cache.Close(event.Name)
    59  			}
    60  		}
    61  	})
    62  
    63  	return cache, nil
    64  }
    65  
    66  func (this *OpenFileCache) Get(filename string) *OpenFile {
    67  	filename = filepath.Clean(filename)
    68  
    69  	this.locker.RLock()
    70  	pool, ok := this.poolMap[filename]
    71  	this.locker.RUnlock()
    72  	if ok {
    73  		file, consumed, consumedSize := pool.Get()
    74  		if consumed {
    75  			this.locker.Lock()
    76  			this.count--
    77  			this.usedSize -= consumedSize
    78  
    79  			// pool如果为空,也不需要从列表中删除,避免put时需要重新创建
    80  
    81  			this.locker.Unlock()
    82  		}
    83  
    84  		return file
    85  	}
    86  	return nil
    87  }
    88  
    89  func (this *OpenFileCache) Put(filename string, file *OpenFile) {
    90  	filename = filepath.Clean(filename)
    91  
    92  	if file.size > maxOpenFileSize {
    93  		return
    94  	}
    95  
    96  	this.locker.Lock()
    97  	defer this.locker.Unlock()
    98  
    99  	// 如果超过当前容量,则关闭最早的
   100  	if this.count >= this.maxCount || this.usedSize+file.size >= this.capacitySize {
   101  		this.consumeHead()
   102  		return
   103  	}
   104  
   105  	pool, ok := this.poolMap[filename]
   106  	var success bool
   107  	if ok {
   108  		success = pool.Put(file)
   109  	} else {
   110  		_ = this.watcher.Add(filename)
   111  		pool = NewOpenFilePool(filename)
   112  		pool.version = file.version
   113  		this.poolMap[filename] = pool
   114  		success = pool.Put(file)
   115  	}
   116  	this.poolList.Push(pool.linkItem)
   117  
   118  	// 检查长度
   119  	if success {
   120  		this.count++
   121  		this.usedSize += file.size
   122  	}
   123  }
   124  
   125  func (this *OpenFileCache) Close(filename string) {
   126  	filename = filepath.Clean(filename)
   127  
   128  	this.locker.Lock()
   129  
   130  	pool, ok := this.poolMap[filename]
   131  	if ok {
   132  		// 设置关闭状态
   133  		pool.SetClosing()
   134  
   135  		delete(this.poolMap, filename)
   136  		this.poolList.Remove(pool.linkItem)
   137  		_ = this.watcher.Remove(filename)
   138  		this.count -= pool.Len()
   139  		this.usedSize -= pool.usedSize
   140  	}
   141  
   142  	this.locker.Unlock()
   143  
   144  	// 在locker之外,提升性能
   145  	if ok {
   146  		pool.Close()
   147  	}
   148  }
   149  
   150  func (this *OpenFileCache) CloseAll() {
   151  	this.locker.Lock()
   152  	for _, pool := range this.poolMap {
   153  		pool.Close()
   154  	}
   155  	this.poolMap = map[string]*OpenFilePool{}
   156  	this.poolList.Reset()
   157  	_ = this.watcher.Close()
   158  	this.count = 0
   159  	this.usedSize = 0
   160  	this.locker.Unlock()
   161  }
   162  
   163  func (this *OpenFileCache) SetCapacity(capacityBytes int64) {
   164  	this.capacitySize = capacityBytes
   165  }
   166  
   167  func (this *OpenFileCache) Debug() {
   168  	var ticker = time.NewTicker(5 * time.Second)
   169  	goman.New(func() {
   170  		for range ticker.C {
   171  			logs.Println("==== " + types.String(this.count) + ", " + fmt.Sprintf("%.4fMB", float64(this.usedSize)/(1<<20)) + " ====")
   172  			this.poolList.Range(func(item *linkedlist.Item[*OpenFilePool]) (goNext bool) {
   173  				logs.Println(filepath.Base(item.Value.Filename()), item.Value.Len())
   174  				return true
   175  			})
   176  		}
   177  	})
   178  }
   179  
   180  func (this *OpenFileCache) consumeHead() {
   181  	var delta = 1
   182  
   183  	if this.count > 100 {
   184  		delta = 2
   185  	}
   186  
   187  	for i := 0; i < delta; i++ {
   188  		var head = this.poolList.Head()
   189  		if head == nil {
   190  			break
   191  		}
   192  
   193  		var headPool = head.Value
   194  		headFile, consumed, consumedSize := headPool.Get()
   195  		if consumed {
   196  			this.count--
   197  			this.usedSize -= consumedSize
   198  
   199  			if headFile != nil {
   200  				_ = headFile.Close()
   201  			}
   202  		}
   203  
   204  		if headPool.Len() == 0 {
   205  			delete(this.poolMap, headPool.filename)
   206  			this.poolList.Remove(head)
   207  			_ = this.watcher.Remove(headPool.filename)
   208  		}
   209  	}
   210  }