github.com/zhiqiangxu/util@v0.0.0-20230112053021-0a7aee056cd5/mapped/file.go (about)

     1  package mapped
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"io"
     7  	"net"
     8  	"os"
     9  	"sync"
    10  	"sync/atomic"
    11  	"syscall"
    12  	"time"
    13  
    14  	"github.com/zhiqiangxu/util"
    15  	"github.com/zhiqiangxu/util/logger"
    16  	"go.uber.org/zap"
    17  )
    18  
    19  type fileInterface interface {
    20  	Flags() int
    21  	Resize(newSize int64) (err error)
    22  	Write(data []byte) (n int, err error)
    23  	WriteBuffers(*net.Buffers) (int64, error)
    24  	GetWrotePosition() int64
    25  	Read(offset int64, data []byte) (n int, err error)
    26  	ReadRLocked(offset int64, data []byte) (n int, err error)
    27  	RLock()
    28  	RUnlock()
    29  	Commit() int64
    30  	DoneWrite() int64
    31  	MLock() (err error)
    32  	MUnlock() (err error)
    33  	IsFull() bool
    34  	Shrink() (err error)
    35  	Sync() (err error)
    36  	LastModified() (t time.Time, err error)
    37  	Close() (err error)
    38  	Remove() error
    39  	MappedBytes() []byte
    40  }
    41  
    42  var _ fileInterface = (*File)(nil)
    43  
    44  // File for mmaped file
    45  // Write/Resize should be called sequentially
    46  // Commit/Write is concurrent safe
    47  type File struct {
    48  
    49  	// 有些字段仅在可写时有意义,trade some memory for better locality
    50  	cwmu           sync.Mutex
    51  	wrotePosition  int64
    52  	commitPosition int64 // 仅在有写缓冲的情况使用
    53  	writeBuffer    *bytes.Buffer
    54  	pool           *sync.Pool
    55  
    56  	mu       sync.RWMutex
    57  	fileSize int64
    58  	fileName string
    59  	fmap     []byte
    60  	flock    sync.RWMutex
    61  	file     *os.File
    62  	flags    int
    63  	wmm      bool
    64  }
    65  
    66  // OpenFile opens a mmaped file
    67  func OpenFile(fileName string, fileSize int64, flags int, wmm bool, pool *sync.Pool) (f *File, err error) {
    68  	f = &File{fileSize: fileSize, fileName: fileName, flags: flags, wmm: wmm}
    69  	if flags&(os.O_RDWR|os.O_WRONLY) != 0 {
    70  		// 写场景可配置缓冲池
    71  		if pool != nil {
    72  			f.pool = pool
    73  			f.writeBuffer = pool.Get().(*bytes.Buffer)
    74  		}
    75  	} else {
    76  		// 只读场景不需要缓冲池
    77  		if pool != nil {
    78  			err = errPoolForReadonly
    79  			return
    80  		}
    81  	}
    82  
    83  	// 新建,或打开已有文件,取决于flags
    84  	err = f.init()
    85  	return
    86  }
    87  
    88  // CreateFile creates a mmaped file
    89  func CreateFile(fileName string, fileSize int64, wmm bool, pool *sync.Pool) (f *File, err error) {
    90  	// 新建
    91  	return OpenFile(fileName, fileSize, os.O_RDWR|os.O_CREATE|os.O_EXCL, wmm, pool)
    92  }
    93  
    94  // Flags for get file flags
    95  func (f *File) Flags() int {
    96  	return f.flags
    97  }
    98  
    99  var (
   100  	errPoolForReadonly = errors.New("pool for readonly file")
   101  	// ErrWriteBeyond when write beyond
   102  	ErrWriteBeyond = errors.New("write beyond")
   103  	// ErrReadBeyond when read beyond
   104  	ErrReadBeyond = errors.New("read beyond")
   105  )
   106  
   107  // init仅在构造函数中调用,所以不需要考虑并发
   108  func (f *File) init() (err error) {
   109  
   110  	f.file, err = os.OpenFile(f.fileName, f.flags, 0600)
   111  	if err != nil {
   112  		return
   113  	}
   114  	defer func() {
   115  		if err != nil {
   116  			// 如果出错,及时释放资源
   117  			f.file.Close()
   118  			f.returnWriteBuffer()
   119  		}
   120  	}()
   121  
   122  	stat, err := f.file.Stat()
   123  	if err != nil {
   124  		return
   125  	}
   126  
   127  	// 此时f.fileSize的意义:
   128  	// 如果是新建,表示期望的大小
   129  	// 如果是打开已有文件,表示从哪里继续写
   130  	if f.flags&os.O_EXCL != 0 {
   131  		// 新建
   132  		err = f.file.Truncate(f.fileSize)
   133  		if err != nil {
   134  			return
   135  		}
   136  	} else {
   137  		// 打开已有文件
   138  
   139  		fileSize := stat.Size()
   140  		offset := f.fileSize
   141  
   142  		// offset > 实际大小,写超
   143  		if offset > fileSize {
   144  			err = ErrWriteBeyond
   145  			return
   146  		}
   147  
   148  		// offset <= 实际大小,以实际大小为准
   149  		f.fileSize = fileSize
   150  
   151  		if !f.wmm {
   152  			_, err = f.file.Seek(offset, io.SeekStart)
   153  			if err != nil {
   154  				return
   155  			}
   156  		}
   157  		f.wrotePosition = offset
   158  		if f.writeBuffer != nil {
   159  			f.commitPosition = offset
   160  		}
   161  	}
   162  
   163  	f.fmap, err = util.Mmap(f.file, f.wmm, f.fileSize)
   164  	if err != nil {
   165  		return
   166  	}
   167  
   168  	return
   169  }
   170  
   171  // MLock for the whole file
   172  func (f *File) MLock() (err error) {
   173  	err = util.MLock(f.fmap, len(f.fmap))
   174  	return
   175  }
   176  
   177  // MUnlock for the whole file
   178  // The memory lock on an address range is automatically removed if the address range is unmapped via munmap(2).
   179  func (f *File) MUnlock() (err error) {
   180  	err = util.MUnlock(f.fmap, len(f.fmap))
   181  	return
   182  }
   183  
   184  // IsFull tells whether file is full
   185  func (f *File) IsFull() bool {
   186  	return f.wrotePosition >= f.fileSize
   187  }
   188  
   189  // Resize will do truncate and remmap
   190  func (f *File) Resize(newSize int64) (err error) {
   191  	f.mu.Lock()
   192  	defer f.mu.RUnlock()
   193  
   194  	if f.fileSize == newSize {
   195  		return
   196  	}
   197  
   198  	err = f.file.Truncate(newSize)
   199  	if err != nil {
   200  		return
   201  	}
   202  
   203  	if f.fmap != nil {
   204  		err = util.MSync(f.fmap, int64(len(f.fmap)), syscall.MS_SYNC)
   205  		if err != nil {
   206  			return
   207  		}
   208  
   209  		err = util.Munmap(f.fmap)
   210  		if err != nil {
   211  			return
   212  		}
   213  	}
   214  
   215  	f.fmap, err = util.Mmap(f.file, f.wmm, newSize)
   216  	if err != nil {
   217  		return
   218  	}
   219  
   220  	f.fileSize = newSize
   221  	if f.wrotePosition > newSize {
   222  		f.wrotePosition = newSize
   223  	}
   224  	return
   225  }
   226  
   227  // GetWrotePosition for wrote position
   228  func (f *File) GetWrotePosition() int64 {
   229  	return atomic.LoadInt64(&f.wrotePosition)
   230  }
   231  
   232  func (f *File) addAndGetWrotePosition(n int64) (new int64) {
   233  	new = f.wrotePosition + n
   234  	atomic.StoreInt64(&f.wrotePosition, new)
   235  	return
   236  }
   237  
   238  func (f *File) getReadPosition() int64 {
   239  	if f.writeBuffer != nil {
   240  		return f.getCommitPosition()
   241  	}
   242  
   243  	return f.GetWrotePosition()
   244  }
   245  
   246  func (f *File) getCommitPosition() int64 {
   247  	return atomic.LoadInt64(&f.commitPosition)
   248  }
   249  
   250  func (f *File) addAndGetCommitPosition(n int64) (new int64) {
   251  	new = f.commitPosition + n
   252  	atomic.StoreInt64(&f.commitPosition, new)
   253  	return
   254  }
   255  
   256  func (f *File) Write(data []byte) (n int, err error) {
   257  	if f.wrotePosition+int64(len(data)) > f.fileSize {
   258  		err = ErrWriteBeyond
   259  		return
   260  	}
   261  
   262  	n, err = f.doWrite(data)
   263  	return
   264  }
   265  
   266  // WriteBuffers for writev
   267  func (f *File) WriteBuffers(buffs *net.Buffers) (n int64, err error) {
   268  	total := 0
   269  	for _, buf := range *buffs {
   270  		total += len(buf)
   271  	}
   272  
   273  	if f.wrotePosition+int64(total) > f.fileSize {
   274  		err = ErrWriteBeyond
   275  		return
   276  	}
   277  
   278  	if f.writeBuffer != nil {
   279  		f.cwmu.Lock()
   280  		n, err = buffs.WriteTo(f.writeBuffer)
   281  		f.cwmu.Unlock()
   282  		f.addAndGetWrotePosition(n)
   283  
   284  		return
   285  	}
   286  
   287  	// 写共享内存
   288  	if f.wmm {
   289  		for _, buf := range *buffs {
   290  			copy(f.fmap[f.wrotePosition+n:], buf)
   291  			n += int64(len(buf))
   292  		}
   293  		f.addAndGetWrotePosition(n)
   294  		nbuf := len(*buffs)
   295  		*buffs = (*buffs)[nbuf-1:]
   296  
   297  		return
   298  	}
   299  
   300  	// 写文件
   301  	n, err = buffs.WriteTo(f.file)
   302  	f.addAndGetWrotePosition(n)
   303  
   304  	return
   305  }
   306  
   307  func (f *File) doWrite(data []byte) (n int, err error) {
   308  
   309  	// 写缓冲区
   310  	if f.writeBuffer != nil {
   311  		f.cwmu.Lock()
   312  		n, err = f.writeBuffer.Write(data)
   313  		f.cwmu.Unlock()
   314  		f.addAndGetWrotePosition(int64(n))
   315  		return
   316  	}
   317  
   318  	// 写共享内存
   319  	if f.wmm {
   320  		copy(f.fmap[f.wrotePosition:], data)
   321  		n = len(data)
   322  		f.addAndGetWrotePosition(int64(n))
   323  		return
   324  	}
   325  
   326  	// 写文件
   327  	n, err = f.file.Write(data)
   328  	f.addAndGetWrotePosition(int64(n))
   329  	return
   330  
   331  }
   332  
   333  func (f *File) commitLocked() (commitOffset int64) {
   334  
   335  	if /*returnWriteBuffer may have been called*/ f.writeBuffer == nil || f.writeBuffer.Len() == 0 {
   336  		commitOffset = atomic.LoadInt64(&f.wrotePosition)
   337  		return
   338  	}
   339  
   340  	n := int64(f.writeBuffer.Len())
   341  	// 从缓冲区到共享内存或者文件
   342  
   343  	if f.wmm {
   344  		copy(f.fmap[f.commitPosition:], f.writeBuffer.Bytes())
   345  		commitOffset = f.addAndGetCommitPosition(n)
   346  		f.writeBuffer.Reset()
   347  		return
   348  	}
   349  
   350  	util.TryUntilSuccess(func() bool {
   351  		_, err := f.writeBuffer.WriteTo(f.file)
   352  		if err != nil {
   353  			logger.Instance().Error("Commit WriteTo", zap.Error(err))
   354  			return false
   355  		}
   356  		return true
   357  	}, time.Second)
   358  
   359  	return f.addAndGetCommitPosition(n)
   360  }
   361  
   362  // Commit buffer to os if any
   363  func (f *File) Commit() int64 {
   364  	if f.writeBuffer == nil {
   365  		return f.GetWrotePosition()
   366  	}
   367  
   368  	f.cwmu.Lock()
   369  	defer f.cwmu.Unlock()
   370  
   371  	return f.commitLocked()
   372  
   373  }
   374  
   375  // DoneWrite = Commit + returnWriteBuffer
   376  func (f *File) DoneWrite() (commitOffset int64) {
   377  	if f.writeBuffer == nil {
   378  		commitOffset = f.GetWrotePosition()
   379  		return
   380  	}
   381  
   382  	f.cwmu.Lock()
   383  	defer f.cwmu.Unlock()
   384  
   385  	commitOffset = f.commitLocked()
   386  
   387  	f.returnWriteBuffer()
   388  	return
   389  }
   390  
   391  // Shrink resize file to wrote position
   392  func (f *File) Shrink() (err error) {
   393  	f.Commit()
   394  
   395  	err = f.Resize(f.wrotePosition)
   396  	return
   397  }
   398  
   399  // Sync from os to disk
   400  func (f *File) Sync() (err error) {
   401  	if f.wmm {
   402  		err = util.MSync(f.fmap, 0, len(f.fmap))
   403  		return
   404  	}
   405  
   406  	err = f.file.Sync()
   407  	return
   408  }
   409  
   410  // LastModified returns last modified time
   411  func (f *File) LastModified() (t time.Time, err error) {
   412  	stat, err := f.file.Stat()
   413  	if err != nil {
   414  		return
   415  	}
   416  	t = stat.ModTime()
   417  	return
   418  }
   419  
   420  // Read bytes from offset
   421  func (f *File) Read(offset int64, data []byte) (int, error) {
   422  	f.mu.RLock()
   423  	defer f.mu.RUnlock()
   424  
   425  	return f.ReadRLocked(offset, data)
   426  }
   427  
   428  // ReadRLocked when already holding the lock
   429  func (f *File) ReadRLocked(offset int64, data []byte) (n int, err error) {
   430  	readPosition := f.getReadPosition()
   431  	if offset > readPosition {
   432  		err = ErrReadBeyond
   433  		return
   434  	}
   435  
   436  	readTo := offset + int64(len(data)) - 1
   437  	if readTo > readPosition {
   438  		readTo = readPosition
   439  	}
   440  	copy(data, f.fmap[offset:readTo+1])
   441  	n = int(readTo - offset + 1)
   442  
   443  	return
   444  }
   445  
   446  // RLock for read
   447  func (f *File) RLock() {
   448  	f.mu.RLock()
   449  }
   450  
   451  // RUnlock for read
   452  func (f *File) RUnlock() {
   453  	f.mu.RUnlock()
   454  }
   455  
   456  // Close the mapped file
   457  func (f *File) Close() (err error) {
   458  	err = f.file.Close()
   459  	if err != nil {
   460  		return
   461  	}
   462  	err = util.Munmap(f.fmap)
   463  	if err != nil {
   464  		return
   465  	}
   466  	f.fmap = nil
   467  
   468  	f.cwmu.Lock()
   469  	f.returnWriteBuffer()
   470  	f.cwmu.Unlock()
   471  	return
   472  }
   473  
   474  // Remove the file
   475  func (f *File) Remove() error {
   476  	return os.Remove(f.fileName)
   477  }
   478  
   479  // MappedBytes is valid until next Resize
   480  func (f *File) MappedBytes() []byte {
   481  	f.mu.RLock()
   482  	defer f.mu.RUnlock()
   483  
   484  	return f.fmap
   485  }
   486  
   487  // return stuff to pools for write mode
   488  func (f *File) returnWriteBuffer() {
   489  	if f.writeBuffer == nil {
   490  		return
   491  	}
   492  
   493  	f.writeBuffer.Reset()
   494  	f.pool.Put(f.writeBuffer)
   495  	f.writeBuffer = nil
   496  	f.pool = nil
   497  
   498  	return
   499  }