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 }