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  }