github.com/nutsdb/nutsdb@v1.0.4/fd_manager.go (about)

     1  package nutsdb
     2  
     3  import (
     4  	"math"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"sync"
     9  )
    10  
    11  const (
    12  	DefaultMaxFileNums = 256
    13  )
    14  
    15  const (
    16  	TooManyFileOpenErrSuffix = "too many open files"
    17  )
    18  
    19  // fdManager hold a fd cache in memory, it lru based cache.
    20  type fdManager struct {
    21  	lock               sync.Mutex
    22  	cache              map[string]*FdInfo
    23  	fdList             *doubleLinkedList
    24  	size               int
    25  	cleanThresholdNums int
    26  	maxFdNums          int
    27  }
    28  
    29  // newFdm will return a fdManager object
    30  func newFdm(maxFdNums int, cleanThreshold float64) (fdm *fdManager) {
    31  	fdm = &fdManager{
    32  		cache:     map[string]*FdInfo{},
    33  		fdList:    initDoubleLinkedList(),
    34  		size:      0,
    35  		maxFdNums: DefaultMaxFileNums,
    36  	}
    37  	fdm.cleanThresholdNums = int(math.Floor(0.5 * float64(fdm.maxFdNums)))
    38  	if maxFdNums > 0 {
    39  		fdm.maxFdNums = maxFdNums
    40  	}
    41  
    42  	if cleanThreshold > 0.0 && cleanThreshold < 1.0 {
    43  		fdm.cleanThresholdNums = int(math.Floor(cleanThreshold * float64(fdm.maxFdNums)))
    44  	}
    45  	return fdm
    46  }
    47  
    48  // FdInfo holds base fd info
    49  type FdInfo struct {
    50  	fd    *os.File
    51  	path  string
    52  	using uint
    53  	next  *FdInfo
    54  	prev  *FdInfo
    55  }
    56  
    57  // getFd go through this method to get fd.
    58  func (fdm *fdManager) getFd(path string) (fd *os.File, err error) {
    59  	fdm.lock.Lock()
    60  	defer fdm.lock.Unlock()
    61  	cleanPath := filepath.Clean(path)
    62  	if fdInfo := fdm.cache[cleanPath]; fdInfo == nil {
    63  		fd, err = os.OpenFile(cleanPath, os.O_CREATE|os.O_RDWR, 0o644)
    64  		if err == nil {
    65  			// if the numbers of fd in cache larger than the cleanThreshold in config, we will clean useless fd in cache
    66  			if fdm.size >= fdm.cleanThresholdNums {
    67  				err = fdm.cleanUselessFd()
    68  			}
    69  			// if the numbers of fd in cache larger than the max numbers of fd in config, we will not add this fd to cache
    70  			if fdm.size >= fdm.maxFdNums {
    71  				return fd, nil
    72  			}
    73  			// add this fd to cache
    74  			fdm.addToCache(fd, cleanPath)
    75  			return fd, nil
    76  		} else {
    77  			// determine if there are too many open files, we will first clean useless fd in cache and try open this file again
    78  			if strings.HasSuffix(err.Error(), TooManyFileOpenErrSuffix) {
    79  				cleanErr := fdm.cleanUselessFd()
    80  				// if something wrong in cleanUselessFd, we will return "open too many files" err, because we want user not the main err is that
    81  				if cleanErr != nil {
    82  					return nil, err
    83  				}
    84  				// try open this file again,if it still returns err, we will show this error to user
    85  				fd, err = os.OpenFile(cleanPath, os.O_CREATE|os.O_RDWR, 0o644)
    86  				if err != nil {
    87  					return nil, err
    88  				}
    89  				// add to cache if open this file successfully
    90  				fdm.addToCache(fd, cleanPath)
    91  			}
    92  			return fd, err
    93  		}
    94  	} else {
    95  		fdInfo.using++
    96  		fdm.fdList.moveNodeToFront(fdInfo)
    97  		return fdInfo.fd, nil
    98  	}
    99  }
   100  
   101  // addToCache add fd to cache
   102  func (fdm *fdManager) addToCache(fd *os.File, cleanPath string) {
   103  	fdInfo := &FdInfo{
   104  		fd:    fd,
   105  		using: 1,
   106  		path:  cleanPath,
   107  	}
   108  	fdm.fdList.addNode(fdInfo)
   109  	fdm.size++
   110  	fdm.cache[cleanPath] = fdInfo
   111  }
   112  
   113  // reduceUsing when RWManager object close, it will go through this method let fdm know it return the fd to cache
   114  func (fdm *fdManager) reduceUsing(path string) {
   115  	fdm.lock.Lock()
   116  	defer fdm.lock.Unlock()
   117  	cleanPath := filepath.Clean(path)
   118  	node, isExist := fdm.cache[cleanPath]
   119  	if !isExist {
   120  		panic("unexpected the node is not in cache")
   121  	}
   122  	node.using--
   123  }
   124  
   125  // close means the cache.
   126  func (fdm *fdManager) close() error {
   127  	fdm.lock.Lock()
   128  	defer fdm.lock.Unlock()
   129  	node := fdm.fdList.tail.prev
   130  	for node != fdm.fdList.head {
   131  		err := node.fd.Close()
   132  		if err != nil {
   133  			return err
   134  		}
   135  		delete(fdm.cache, node.path)
   136  		fdm.size--
   137  		node = node.prev
   138  	}
   139  	fdm.fdList.head.next = fdm.fdList.tail
   140  	fdm.fdList.tail.prev = fdm.fdList.head
   141  	return nil
   142  }
   143  
   144  type doubleLinkedList struct {
   145  	head *FdInfo
   146  	tail *FdInfo
   147  	size int
   148  }
   149  
   150  func initDoubleLinkedList() *doubleLinkedList {
   151  	list := &doubleLinkedList{
   152  		head: &FdInfo{},
   153  		tail: &FdInfo{},
   154  		size: 0,
   155  	}
   156  	list.head.next = list.tail
   157  	list.tail.prev = list.head
   158  	return list
   159  }
   160  
   161  func (list *doubleLinkedList) addNode(node *FdInfo) {
   162  	list.head.next.prev = node
   163  	node.next = list.head.next
   164  	list.head.next = node
   165  	node.prev = list.head
   166  	list.size++
   167  }
   168  
   169  func (list *doubleLinkedList) removeNode(node *FdInfo) {
   170  	node.prev.next = node.next
   171  	node.next.prev = node.prev
   172  	node.prev = nil
   173  	node.next = nil
   174  }
   175  
   176  func (list *doubleLinkedList) moveNodeToFront(node *FdInfo) {
   177  	list.removeNode(node)
   178  	list.addNode(node)
   179  }
   180  
   181  func (fdm *fdManager) cleanUselessFd() error {
   182  	cleanNums := fdm.cleanThresholdNums
   183  	node := fdm.fdList.tail.prev
   184  	for node != nil && node != fdm.fdList.head && cleanNums > 0 {
   185  		nextItem := node.prev
   186  		if node.using == 0 {
   187  			fdm.fdList.removeNode(node)
   188  			err := node.fd.Close()
   189  			if err != nil {
   190  				return err
   191  			}
   192  			fdm.size--
   193  			delete(fdm.cache, node.path)
   194  			cleanNums--
   195  		}
   196  		node = nextItem
   197  	}
   198  	return nil
   199  }
   200  
   201  func (fdm *fdManager) closeByPath(path string) error {
   202  	fdm.lock.Lock()
   203  	defer fdm.lock.Unlock()
   204  	fdInfo, ok := fdm.cache[path]
   205  	if !ok {
   206  		return nil
   207  	}
   208  	delete(fdm.cache, path)
   209  
   210  	fdm.fdList.removeNode(fdInfo)
   211  	return fdInfo.fd.Close()
   212  }