github.com/TeaOSLab/EdgeNode@v1.3.8/internal/utils/bfs/blocks_file.go (about) 1 // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 3 package bfs 4 5 import ( 6 "errors" 7 "fmt" 8 "github.com/TeaOSLab/EdgeNode/internal/zero" 9 "io" 10 "os" 11 "path/filepath" 12 "strings" 13 "sync" 14 "sync/atomic" 15 "time" 16 ) 17 18 const BFileExt = ".b" 19 20 type BlockType string 21 22 const ( 23 BlockTypeHeader BlockType = "header" 24 BlockTypeBody BlockType = "body" 25 ) 26 27 type BlocksFile struct { 28 opt *BlockFileOptions 29 fp *os.File 30 mFile *MetaFile 31 32 isClosing bool 33 isClosed bool 34 35 mu *sync.RWMutex 36 37 writtenBytes int64 38 writingFileMap map[string]zero.Zero // hash => Zero 39 syncAt time.Time 40 41 readerPool chan *FileReader 42 countRefs int32 43 } 44 45 func NewBlocksFileWithRawFile(fp *os.File, options *BlockFileOptions) (*BlocksFile, error) { 46 options.EnsureDefaults() 47 48 var bFilename = fp.Name() 49 if !strings.HasSuffix(bFilename, BFileExt) { 50 return nil, errors.New("filename '" + bFilename + "' must has a '" + BFileExt + "' extension") 51 } 52 53 var mu = &sync.RWMutex{} 54 55 var mFilename = strings.TrimSuffix(bFilename, BFileExt) + MFileExt 56 mFile, err := OpenMetaFile(mFilename, mu) 57 if err != nil { 58 _ = fp.Close() 59 return nil, fmt.Errorf("load '%s' failed: %w", mFilename, err) 60 } 61 62 AckReadThread() 63 _, err = fp.Seek(0, io.SeekEnd) 64 ReleaseReadThread() 65 if err != nil { 66 _ = fp.Close() 67 _ = mFile.Close() 68 return nil, err 69 } 70 71 return &BlocksFile{ 72 fp: fp, 73 mFile: mFile, 74 mu: mu, 75 opt: options, 76 syncAt: time.Now(), 77 readerPool: make(chan *FileReader, 32), 78 writingFileMap: map[string]zero.Zero{}, 79 }, nil 80 } 81 82 func OpenBlocksFile(filename string, options *BlockFileOptions) (*BlocksFile, error) { 83 // TODO 考虑是否使用flock锁定,防止多进程写冲突 84 fp, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0666) 85 if err != nil { 86 if os.IsNotExist(err) { 87 var dir = filepath.Dir(filename) 88 _ = os.MkdirAll(dir, 0777) 89 90 // try again 91 fp, err = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0666) 92 } 93 94 if err != nil { 95 return nil, fmt.Errorf("open blocks file failed: %w", err) 96 } 97 } 98 99 return NewBlocksFileWithRawFile(fp, options) 100 } 101 102 func (this *BlocksFile) Filename() string { 103 return this.fp.Name() 104 } 105 106 func (this *BlocksFile) Write(hash string, blockType BlockType, b []byte, originOffset int64) (n int, err error) { 107 if len(b) == 0 { 108 return 109 } 110 111 this.mu.Lock() 112 defer this.mu.Unlock() 113 114 posBefore, err := this.currentPos() 115 if err != nil { 116 return 0, err 117 } 118 119 err = this.checkStatus() 120 if err != nil { 121 return 122 } 123 124 AckWriteThread() 125 n, err = this.fp.Write(b) 126 ReleaseWriteThread() 127 128 if err == nil { 129 if n > 0 { 130 this.writtenBytes += int64(n) 131 } 132 133 if blockType == BlockTypeHeader { 134 err = this.mFile.WriteHeaderBlockUnsafe(hash, posBefore, posBefore+int64(n)) 135 } else if blockType == BlockTypeBody { 136 err = this.mFile.WriteBodyBlockUnsafe(hash, posBefore, posBefore+int64(n), originOffset, originOffset+int64(n)) 137 } else { 138 err = errors.New("invalid block type '" + string(blockType) + "'") 139 } 140 } 141 142 return 143 } 144 145 func (this *BlocksFile) OpenFileWriter(fileHash string, bodySize int64, isPartial bool) (writer *FileWriter, err error) { 146 err = CheckHashErr(fileHash) 147 if err != nil { 148 return nil, err 149 } 150 151 this.mu.Lock() 152 defer this.mu.Unlock() 153 154 _, isWriting := this.writingFileMap[fileHash] 155 if isWriting { 156 err = ErrFileIsWriting 157 return 158 } 159 this.writingFileMap[fileHash] = zero.Zero{} 160 161 err = this.checkStatus() 162 if err != nil { 163 return 164 } 165 166 return NewFileWriter(this, fileHash, bodySize, isPartial) 167 } 168 169 func (this *BlocksFile) OpenFileReader(fileHash string, isPartial bool) (*FileReader, error) { 170 err := CheckHashErr(fileHash) 171 if err != nil { 172 return nil, err 173 } 174 175 this.mu.RLock() 176 err = this.checkStatus() 177 this.mu.RUnlock() 178 if err != nil { 179 return nil, err 180 } 181 182 // 是否存在 183 header, ok := this.mFile.CloneFileHeader(fileHash) 184 if !ok { 185 return nil, os.ErrNotExist 186 } 187 188 // TODO 对于partial content,需要传入ranges,用来判断是否有交集 189 190 if header.IsWriting { 191 return nil, ErrFileIsWriting 192 } 193 194 if !isPartial && !header.IsCompleted { 195 return nil, os.ErrNotExist 196 } 197 198 // 先尝试从Pool中获取 199 select { 200 case reader := <-this.readerPool: 201 if reader == nil { 202 return nil, ErrClosed 203 } 204 reader.Reset(header) 205 atomic.AddInt32(&this.countRefs, 1) 206 return reader, nil 207 default: 208 } 209 210 AckReadThread() 211 fp, err := os.Open(this.fp.Name()) 212 ReleaseReadThread() 213 if err != nil { 214 return nil, err 215 } 216 217 atomic.AddInt32(&this.countRefs, 1) 218 return NewFileReader(this, fp, header), nil 219 } 220 221 func (this *BlocksFile) CloseFileReader(reader *FileReader) error { 222 defer atomic.AddInt32(&this.countRefs, -1) 223 224 select { 225 case this.readerPool <- reader: 226 return nil 227 default: 228 return reader.Free() 229 } 230 } 231 232 func (this *BlocksFile) ExistFile(fileHash string) bool { 233 err := CheckHashErr(fileHash) 234 if err != nil { 235 return false 236 } 237 238 return this.mFile.ExistFile(fileHash) 239 } 240 241 func (this *BlocksFile) RemoveFile(fileHash string) error { 242 err := CheckHashErr(fileHash) 243 if err != nil { 244 return err 245 } 246 247 return this.mFile.RemoveFile(fileHash) 248 } 249 250 func (this *BlocksFile) Sync() error { 251 this.mu.Lock() 252 defer this.mu.Unlock() 253 254 err := this.checkStatus() 255 if err != nil { 256 return err 257 } 258 259 return this.sync(false) 260 } 261 262 func (this *BlocksFile) ForceSync() error { 263 this.mu.Lock() 264 defer this.mu.Unlock() 265 266 err := this.checkStatus() 267 if err != nil { 268 return err 269 } 270 271 return this.sync(true) 272 } 273 274 func (this *BlocksFile) SyncAt() time.Time { 275 return this.syncAt 276 } 277 278 func (this *BlocksFile) Compact() error { 279 // TODO 需要实现 280 return nil 281 } 282 283 func (this *BlocksFile) RemoveAll() error { 284 this.mu.Lock() 285 defer this.mu.Unlock() 286 287 this.isClosed = true 288 289 _ = this.mFile.RemoveAll() 290 291 this.closeReaderPool() 292 293 _ = this.fp.Close() 294 return os.Remove(this.fp.Name()) 295 } 296 297 // CanClose 检查是否可以关闭 298 func (this *BlocksFile) CanClose() bool { 299 this.mu.RLock() 300 defer this.mu.RUnlock() 301 302 if len(this.writingFileMap) > 0 || atomic.LoadInt32(&this.countRefs) > 0 { 303 return false 304 } 305 306 this.isClosing = true 307 return true 308 } 309 310 // Close 关闭当前文件 311 func (this *BlocksFile) Close() error { 312 this.mu.Lock() 313 defer this.mu.Unlock() 314 315 if this.isClosed { 316 return nil 317 } 318 319 // TODO 决定是否同步 320 //_ = this.sync(true) 321 322 this.isClosed = true 323 324 _ = this.mFile.Close() 325 326 this.closeReaderPool() 327 328 return this.fp.Close() 329 } 330 331 // IsClosing 判断当前文件是否正在关闭或者已关闭 332 func (this *BlocksFile) IsClosing() bool { 333 return this.isClosed || this.isClosing 334 } 335 336 func (this *BlocksFile) IncrRef() { 337 atomic.AddInt32(&this.countRefs, 1) 338 } 339 340 func (this *BlocksFile) DecrRef() { 341 atomic.AddInt32(&this.countRefs, -1) 342 } 343 344 func (this *BlocksFile) TestReaderPool() chan *FileReader { 345 return this.readerPool 346 } 347 348 func (this *BlocksFile) removeWritingFile(hash string) { 349 this.mu.Lock() 350 delete(this.writingFileMap, hash) 351 this.mu.Unlock() 352 } 353 354 func (this *BlocksFile) checkStatus() error { 355 if this.isClosed || this.isClosing { 356 return fmt.Errorf("check status failed: %w", ErrClosed) 357 } 358 return nil 359 } 360 361 func (this *BlocksFile) currentPos() (int64, error) { 362 return this.fp.Seek(0, io.SeekCurrent) 363 } 364 365 func (this *BlocksFile) sync(force bool) error { 366 if !force { 367 if this.writtenBytes < this.opt.BytesPerSync { 368 return nil 369 } 370 } 371 372 if this.writtenBytes > 0 { 373 AckWriteThread() 374 err := this.fp.Sync() 375 ReleaseWriteThread() 376 if err != nil { 377 return err 378 } 379 } 380 381 this.writtenBytes = 0 382 383 this.syncAt = time.Now() 384 385 if force { 386 return this.mFile.SyncUnsafe() 387 } 388 389 return nil 390 } 391 392 func (this *BlocksFile) closeReaderPool() { 393 for { 394 select { 395 case reader := <-this.readerPool: 396 if reader != nil { 397 _ = reader.Free() 398 } 399 default: 400 return 401 } 402 } 403 }