github.com/matrixorigin/matrixone@v1.2.0/pkg/fileservice/local_fs.go (about)

     1  // Copyright 2022 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package fileservice
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"errors"
    21  	"io"
    22  	"io/fs"
    23  	"os"
    24  	pathpkg "path"
    25  	"path/filepath"
    26  	gotrace "runtime/trace"
    27  	"sort"
    28  	"strings"
    29  	"sync"
    30  	"sync/atomic"
    31  	"time"
    32  
    33  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    34  	"github.com/matrixorigin/matrixone/pkg/fileservice/memorycache"
    35  	"github.com/matrixorigin/matrixone/pkg/logutil"
    36  	"github.com/matrixorigin/matrixone/pkg/perfcounter"
    37  	metric "github.com/matrixorigin/matrixone/pkg/util/metric/v2"
    38  	"github.com/matrixorigin/matrixone/pkg/util/trace"
    39  	"github.com/matrixorigin/matrixone/pkg/util/trace/impl/motrace/statistic"
    40  	"go.uber.org/zap"
    41  )
    42  
    43  // LocalFS is a FileService implementation backed by local file system
    44  type LocalFS struct {
    45  	name     string
    46  	rootPath string
    47  
    48  	sync.RWMutex
    49  	dirFiles map[string]*os.File
    50  
    51  	allocator   CacheDataAllocator
    52  	memCache    *MemCache
    53  	diskCache   *DiskCache
    54  	remoteCache *RemoteCache
    55  	asyncUpdate bool
    56  
    57  	perfCounterSets []*perfcounter.CounterSet
    58  
    59  	ioMerger *IOMerger
    60  }
    61  
    62  var _ FileService = new(LocalFS)
    63  
    64  func NewLocalFS(
    65  	ctx context.Context,
    66  	name string,
    67  	rootPath string,
    68  	cacheConfig CacheConfig,
    69  	perfCounterSets []*perfcounter.CounterSet,
    70  ) (*LocalFS, error) {
    71  
    72  	// get absolute path
    73  	if rootPath != "" {
    74  		var err error
    75  		rootPath, err = filepath.Abs(rootPath)
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  
    80  		// ensure dir
    81  		f, err := os.Open(rootPath)
    82  		if os.IsNotExist(err) {
    83  			// not exists, create
    84  			err := os.MkdirAll(rootPath, 0755)
    85  			if err != nil {
    86  				return nil, err
    87  			}
    88  
    89  		} else if err != nil {
    90  			// stat error
    91  			return nil, err
    92  
    93  		} else {
    94  			defer f.Close()
    95  		}
    96  
    97  	}
    98  
    99  	fs := &LocalFS{
   100  		name:            name,
   101  		rootPath:        rootPath,
   102  		dirFiles:        make(map[string]*os.File),
   103  		asyncUpdate:     true,
   104  		perfCounterSets: perfCounterSets,
   105  		ioMerger:        NewIOMerger(),
   106  	}
   107  
   108  	if err := fs.initCaches(ctx, cacheConfig); err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	if fs.memCache != nil {
   113  		fs.allocator = fs.memCache
   114  	} else {
   115  		fs.allocator = DefaultCacheDataAllocator
   116  	}
   117  
   118  	return fs, nil
   119  }
   120  
   121  func (l *LocalFS) initCaches(ctx context.Context, config CacheConfig) error {
   122  	config.setDefaults()
   123  
   124  	if config.RemoteCacheEnabled {
   125  		if config.QueryClient == nil {
   126  			return moerr.NewInternalError(ctx, "query client is nil")
   127  		}
   128  		l.remoteCache = NewRemoteCache(config.QueryClient, config.KeyRouterFactory)
   129  		logutil.Info("fileservice: remote cache initialized",
   130  			zap.Any("fs-name", l.name),
   131  		)
   132  	}
   133  
   134  	if *config.MemoryCapacity > DisableCacheCapacity { // 1 means disable
   135  		l.memCache = NewMemCache(
   136  			NewMemoryCache(int64(*config.MemoryCapacity), true, &config.CacheCallbacks),
   137  			l.perfCounterSets,
   138  		)
   139  		logutil.Info("fileservice: memory cache initialized",
   140  			zap.Any("fs-name", l.name),
   141  			zap.Any("config", config),
   142  		)
   143  	}
   144  
   145  	if config.enableDiskCacheForLocalFS {
   146  		if *config.DiskCapacity > DisableCacheCapacity && config.DiskPath != nil {
   147  			var err error
   148  			l.diskCache, err = NewDiskCache(
   149  				ctx,
   150  				*config.DiskPath,
   151  				int(*config.DiskCapacity),
   152  				l.perfCounterSets,
   153  			)
   154  			if err != nil {
   155  				return err
   156  			}
   157  			logutil.Info("fileservice: disk cache initialized",
   158  				zap.Any("fs-name", l.name),
   159  				zap.Any("config", config),
   160  			)
   161  		}
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  func (l *LocalFS) Name() string {
   168  	return l.name
   169  }
   170  
   171  func (l *LocalFS) Write(ctx context.Context, vector IOVector) error {
   172  	if err := ctx.Err(); err != nil {
   173  		return err
   174  	}
   175  
   176  	metric.FSWriteLocalCounter.Add(float64(len(vector.Entries)))
   177  
   178  	var err error
   179  	var bytesWritten int
   180  	start := time.Now()
   181  	ctx, span := trace.Start(ctx, "LocalFS.Write", trace.WithKind(trace.SpanKindLocalFSVis))
   182  	defer func() {
   183  		// cover another func to catch the err when process Write
   184  		span.End(trace.WithFSReadWriteExtra(vector.FilePath, err, int64(bytesWritten)))
   185  		metric.LocalWriteIODurationHistogram.Observe(time.Since(start).Seconds())
   186  		metric.LocalWriteIOBytesHistogram.Observe(float64(bytesWritten))
   187  	}()
   188  
   189  	path, err := ParsePathAtService(vector.FilePath, l.name)
   190  	if err != nil {
   191  		return err
   192  	}
   193  	nativePath := l.toNativeFilePath(path.File)
   194  
   195  	// check existence
   196  	_, err = os.Stat(nativePath)
   197  	if err == nil {
   198  		// existed
   199  		err = moerr.NewFileAlreadyExistsNoCtx(path.File)
   200  		return err
   201  	}
   202  
   203  	bytesWritten, err = l.write(ctx, vector)
   204  	return err
   205  }
   206  
   207  func (l *LocalFS) write(ctx context.Context, vector IOVector) (bytesWritten int, err error) {
   208  	if err := ctx.Err(); err != nil {
   209  		return 0, err
   210  	}
   211  
   212  	path, err := ParsePathAtService(vector.FilePath, l.name)
   213  	if err != nil {
   214  		return 0, err
   215  	}
   216  	nativePath := l.toNativeFilePath(path.File)
   217  
   218  	// sort
   219  	sort.Slice(vector.Entries, func(i, j int) bool {
   220  		return vector.Entries[i].Offset < vector.Entries[j].Offset
   221  	})
   222  
   223  	// size
   224  	var size int64
   225  	if len(vector.Entries) > 0 {
   226  		last := vector.Entries[len(vector.Entries)-1]
   227  		size = int64(last.Offset + last.Size)
   228  	}
   229  
   230  	// write
   231  	f, err := os.CreateTemp(
   232  		l.rootPath,
   233  		".tmp.*",
   234  	)
   235  	if err != nil {
   236  		return 0, err
   237  	}
   238  	fileWithChecksum, put := NewFileWithChecksumOSFile(ctx, f, _BlockContentSize, l.perfCounterSets)
   239  	defer put.Put()
   240  
   241  	r := newIOEntriesReader(ctx, vector.Entries)
   242  
   243  	var buf []byte
   244  	putBuf := ioBufferPool.Get(&buf)
   245  	defer putBuf.Put()
   246  	n, err := io.CopyBuffer(fileWithChecksum, r, buf)
   247  	if err != nil {
   248  		return 0, err
   249  	}
   250  	if n != size {
   251  		sizeUnknown := false
   252  		for _, entry := range vector.Entries {
   253  			if entry.Size < 0 {
   254  				sizeUnknown = true
   255  				break
   256  			}
   257  		}
   258  		if !sizeUnknown {
   259  			return 0, moerr.NewSizeNotMatchNoCtx(path.File)
   260  		}
   261  	}
   262  	bytesWritten = int(n)
   263  	if err := f.Sync(); err != nil {
   264  		return 0, err
   265  	}
   266  	if err := f.Close(); err != nil {
   267  		return 0, err
   268  	}
   269  
   270  	// ensure parent dir
   271  	parentDir, _ := filepath.Split(nativePath)
   272  	err = l.ensureDir(parentDir)
   273  	if err != nil {
   274  		return 0, err
   275  	}
   276  
   277  	// move
   278  	if err := os.Rename(f.Name(), nativePath); err != nil {
   279  		return 0, err
   280  	}
   281  
   282  	if err := l.syncDir(parentDir); err != nil {
   283  		return 0, err
   284  	}
   285  
   286  	return
   287  }
   288  
   289  func (l *LocalFS) Read(ctx context.Context, vector *IOVector) (err error) {
   290  	if err := ctx.Err(); err != nil {
   291  		return err
   292  	}
   293  
   294  	bytesCounter := new(atomic.Int64)
   295  	start := time.Now()
   296  	defer func() {
   297  		LocalReadIODuration := time.Since(start)
   298  
   299  		metric.LocalReadIODurationHistogram.Observe(LocalReadIODuration.Seconds())
   300  		metric.LocalReadIOBytesHistogram.Observe(float64(bytesCounter.Load()))
   301  	}()
   302  
   303  	if len(vector.Entries) == 0 {
   304  		return moerr.NewEmptyVectorNoCtx()
   305  	}
   306  
   307  	stats := statistic.StatsInfoFromContext(ctx)
   308  
   309  	startLock := time.Now()
   310  	_, task := gotrace.NewTask(ctx, "LocalFS.Read: wait io")
   311  	done, wait := l.ioMerger.Merge(vector.ioMergeKey())
   312  	if done != nil {
   313  		defer done()
   314  	} else {
   315  		wait()
   316  	}
   317  	task.End()
   318  	stats.AddLocalFSReadIOMergerTimeConsumption(time.Since(startLock))
   319  
   320  	allocator := l.allocator
   321  	if vector.Policy.Any(SkipMemoryCache) {
   322  		allocator = DefaultCacheDataAllocator
   323  	}
   324  	for i := range vector.Entries {
   325  		vector.Entries[i].allocator = allocator
   326  	}
   327  
   328  	for _, cache := range vector.Caches {
   329  		cache := cache
   330  		if err := readCache(ctx, cache, vector); err != nil {
   331  			return err
   332  		}
   333  		defer func() {
   334  			if err != nil {
   335  				return
   336  			}
   337  			err = cache.Update(ctx, vector, false)
   338  		}()
   339  	}
   340  
   341  	if l.memCache != nil {
   342  		if err := readCache(ctx, l.memCache, vector); err != nil {
   343  			return err
   344  		}
   345  		defer func() {
   346  			if err != nil {
   347  				return
   348  			}
   349  			err = l.memCache.Update(ctx, vector, l.asyncUpdate)
   350  		}()
   351  	}
   352  
   353  	ioStart := time.Now()
   354  	defer func() {
   355  		stats.AddIOAccessTimeConsumption(time.Since(ioStart))
   356  	}()
   357  
   358  	if l.diskCache != nil {
   359  		if err := readCache(ctx, l.diskCache, vector); err != nil {
   360  			return err
   361  		}
   362  		defer func() {
   363  			if err != nil {
   364  				return
   365  			}
   366  			err = l.diskCache.Update(ctx, vector, l.asyncUpdate)
   367  		}()
   368  	}
   369  
   370  	if l.remoteCache != nil {
   371  		if err := readCache(ctx, l.remoteCache, vector); err != nil {
   372  			return err
   373  		}
   374  	}
   375  
   376  	err = l.read(ctx, vector, bytesCounter)
   377  	if err != nil {
   378  		return err
   379  	}
   380  
   381  	return nil
   382  }
   383  
   384  func (l *LocalFS) ReadCache(ctx context.Context, vector *IOVector) (err error) {
   385  	if err := ctx.Err(); err != nil {
   386  		return err
   387  	}
   388  
   389  	ctx, span := trace.Start(ctx, "LocalFS.ReadCache")
   390  	defer span.End()
   391  
   392  	if len(vector.Entries) == 0 {
   393  		return moerr.NewEmptyVectorNoCtx()
   394  	}
   395  
   396  	startLock := time.Now()
   397  	done, wait := l.ioMerger.Merge(vector.ioMergeKey())
   398  	if done != nil {
   399  		defer done()
   400  	} else {
   401  		wait()
   402  	}
   403  	statistic.StatsInfoFromContext(ctx).AddLocalFSReadCacheIOMergerTimeConsumption(time.Since(startLock))
   404  
   405  	for _, cache := range vector.Caches {
   406  		cache := cache
   407  		if err := readCache(ctx, cache, vector); err != nil {
   408  			return err
   409  		}
   410  		defer func() {
   411  			if err != nil {
   412  				return
   413  			}
   414  			err = cache.Update(ctx, vector, false)
   415  		}()
   416  	}
   417  
   418  	if l.memCache != nil {
   419  		if err := readCache(ctx, l.memCache, vector); err != nil {
   420  			return err
   421  		}
   422  	}
   423  
   424  	return nil
   425  }
   426  
   427  func (l *LocalFS) read(ctx context.Context, vector *IOVector, bytesCounter *atomic.Int64) (err error) {
   428  	if vector.allDone() {
   429  		// all cache hit
   430  		return nil
   431  	}
   432  
   433  	path, err := ParsePathAtService(vector.FilePath, l.name)
   434  	if err != nil {
   435  		return err
   436  	}
   437  	nativePath := l.toNativeFilePath(path.File)
   438  
   439  	file, err := os.Open(nativePath)
   440  	if os.IsNotExist(err) {
   441  		return moerr.NewFileNotFoundNoCtx(path.File)
   442  	}
   443  	if err != nil {
   444  		return err
   445  	}
   446  	defer file.Close()
   447  
   448  	for i, entry := range vector.Entries {
   449  		if entry.Size == 0 {
   450  			return moerr.NewEmptyRangeNoCtx(path.File)
   451  		}
   452  
   453  		if entry.done {
   454  			continue
   455  		}
   456  
   457  		if entry.WriterForRead != nil {
   458  			fileWithChecksum, put := NewFileWithChecksumOSFile(ctx, file, _BlockContentSize, l.perfCounterSets)
   459  			defer put.Put()
   460  
   461  			if entry.Offset > 0 {
   462  				_, err = fileWithChecksum.Seek(int64(entry.Offset), io.SeekStart)
   463  				if err != nil {
   464  					return err
   465  				}
   466  			}
   467  			r := (io.Reader)(fileWithChecksum)
   468  			if entry.Size > 0 {
   469  				r = io.LimitReader(r, int64(entry.Size))
   470  			}
   471  			r = &countingReader{
   472  				R: r,
   473  				C: bytesCounter,
   474  			}
   475  
   476  			if entry.ToCacheData != nil {
   477  				r = io.TeeReader(r, entry.WriterForRead)
   478  				counter := new(atomic.Int64)
   479  				cr := &countingReader{
   480  					R: r,
   481  					C: counter,
   482  				}
   483  				var bs memorycache.CacheData
   484  				bs, err = entry.ToCacheData(cr, nil, DefaultCacheDataAllocator)
   485  				if err != nil {
   486  					return err
   487  				}
   488  				vector.Entries[i].CachedData = bs
   489  				if entry.Size > 0 && counter.Load() != entry.Size {
   490  					return moerr.NewUnexpectedEOFNoCtx(path.File)
   491  				}
   492  
   493  			} else {
   494  				var buf []byte
   495  				put := ioBufferPool.Get(&buf)
   496  				defer put.Put()
   497  				n, err := io.CopyBuffer(entry.WriterForRead, r, buf)
   498  				if err != nil {
   499  					return err
   500  				}
   501  				if entry.Size > 0 && n != int64(entry.Size) {
   502  					return moerr.NewUnexpectedEOFNoCtx(path.File)
   503  				}
   504  			}
   505  
   506  		} else if entry.ReadCloserForRead != nil {
   507  			file, err = os.Open(nativePath)
   508  			if os.IsNotExist(err) {
   509  				return moerr.NewFileNotFoundNoCtx(path.File)
   510  			}
   511  			if err != nil {
   512  				return err
   513  			}
   514  			fileWithChecksum := NewFileWithChecksum(ctx, file, _BlockContentSize, l.perfCounterSets)
   515  
   516  			if entry.Offset > 0 {
   517  				_, err = fileWithChecksum.Seek(int64(entry.Offset), io.SeekStart)
   518  				if err != nil {
   519  					return err
   520  				}
   521  			}
   522  			r := (io.Reader)(fileWithChecksum)
   523  			if entry.Size > 0 {
   524  				r = io.LimitReader(r, int64(entry.Size))
   525  			}
   526  			r = &countingReader{
   527  				R: r,
   528  				C: bytesCounter,
   529  			}
   530  
   531  			if entry.ToCacheData == nil {
   532  				*entry.ReadCloserForRead = &readCloser{
   533  					r:         r,
   534  					closeFunc: file.Close,
   535  				}
   536  
   537  			} else {
   538  				buf := new(bytes.Buffer)
   539  				*entry.ReadCloserForRead = &readCloser{
   540  					r: io.TeeReader(r, buf),
   541  					closeFunc: func() error {
   542  						defer file.Close()
   543  						var bs memorycache.CacheData
   544  						bs, err = entry.ToCacheData(buf, buf.Bytes(), DefaultCacheDataAllocator)
   545  						if err != nil {
   546  							return err
   547  						}
   548  						vector.Entries[i].CachedData = bs
   549  						return nil
   550  					},
   551  				}
   552  			}
   553  
   554  		} else {
   555  			fileWithChecksum, put := NewFileWithChecksumOSFile(ctx, file, _BlockContentSize, l.perfCounterSets)
   556  			defer put.Put()
   557  
   558  			if entry.Offset > 0 {
   559  				_, err = fileWithChecksum.Seek(int64(entry.Offset), io.SeekStart)
   560  				if err != nil {
   561  					return err
   562  				}
   563  			}
   564  			r := (io.Reader)(fileWithChecksum)
   565  			if entry.Size > 0 {
   566  				r = io.LimitReader(r, int64(entry.Size))
   567  			}
   568  			r = &countingReader{
   569  				R: r,
   570  				C: bytesCounter,
   571  			}
   572  
   573  			if entry.Size < 0 {
   574  				var data []byte
   575  				data, err = io.ReadAll(r)
   576  				if err != nil {
   577  					return err
   578  				}
   579  				entry.Data = data
   580  				entry.Size = int64(len(data))
   581  
   582  			} else {
   583  				if int64(len(entry.Data)) < entry.Size {
   584  					entry.Data = make([]byte, entry.Size)
   585  				}
   586  				var n int
   587  				n, err = io.ReadFull(r, entry.Data)
   588  				if err != nil {
   589  					return err
   590  				}
   591  				if int64(n) != entry.Size {
   592  					return moerr.NewUnexpectedEOFNoCtx(path.File)
   593  				}
   594  			}
   595  
   596  			if err = entry.setCachedData(); err != nil {
   597  				return err
   598  			}
   599  
   600  			vector.Entries[i] = entry
   601  
   602  		}
   603  
   604  	}
   605  
   606  	return nil
   607  
   608  }
   609  
   610  func (l *LocalFS) List(ctx context.Context, dirPath string) (ret []DirEntry, err error) {
   611  	if err := ctx.Err(); err != nil {
   612  		return nil, err
   613  	}
   614  
   615  	ctx, span := trace.Start(ctx, "LocalFS.List", trace.WithKind(trace.SpanKindLocalFSVis))
   616  	defer func() {
   617  		span.AddExtraFields([]zap.Field{zap.String("list", dirPath)}...)
   618  		span.End()
   619  	}()
   620  
   621  	_ = ctx
   622  
   623  	path, err := ParsePathAtService(dirPath, l.name)
   624  	if err != nil {
   625  		return nil, err
   626  	}
   627  	nativePath := l.toNativeFilePath(path.File)
   628  
   629  	f, err := os.Open(nativePath)
   630  	if os.IsNotExist(err) {
   631  		err = nil
   632  		return
   633  	}
   634  	if err != nil {
   635  		return nil, err
   636  	}
   637  	defer f.Close()
   638  
   639  	entries, err := f.ReadDir(-1)
   640  	for _, entry := range entries {
   641  		name := entry.Name()
   642  		if strings.HasPrefix(name, ".") {
   643  			continue
   644  		}
   645  		info, err := entry.Info()
   646  		if err != nil {
   647  			return nil, err
   648  		}
   649  		fileSize := info.Size()
   650  		nBlock := ceilingDiv(fileSize, _BlockSize)
   651  		contentSize := fileSize - _ChecksumSize*nBlock
   652  
   653  		isDir, err := entryIsDir(nativePath, name, info)
   654  		if err != nil {
   655  			return nil, err
   656  		}
   657  		ret = append(ret, DirEntry{
   658  			Name:  name,
   659  			IsDir: isDir,
   660  			Size:  contentSize,
   661  		})
   662  	}
   663  
   664  	sort.Slice(ret, func(i, j int) bool {
   665  		return ret[i].Name < ret[j].Name
   666  	})
   667  
   668  	if err != nil {
   669  		return ret, err
   670  	}
   671  
   672  	return
   673  }
   674  
   675  func (l *LocalFS) StatFile(ctx context.Context, filePath string) (*DirEntry, error) {
   676  	if err := ctx.Err(); err != nil {
   677  		return nil, err
   678  	}
   679  
   680  	ctx, span := trace.Start(ctx, "LocalFS.StatFile", trace.WithKind(trace.SpanKindLocalFSVis))
   681  	defer func() {
   682  		span.AddExtraFields([]zap.Field{zap.String("stat", filePath)}...)
   683  		span.End()
   684  	}()
   685  
   686  	path, err := ParsePathAtService(filePath, l.name)
   687  	if err != nil {
   688  		return nil, err
   689  	}
   690  	nativePath := l.toNativeFilePath(path.File)
   691  
   692  	stat, err := os.Stat(nativePath)
   693  	if os.IsNotExist(err) {
   694  		return nil, moerr.NewFileNotFound(ctx, filePath)
   695  	}
   696  
   697  	if stat.IsDir() {
   698  		return nil, moerr.NewFileNotFound(ctx, filePath)
   699  	}
   700  
   701  	fileSize := stat.Size()
   702  	nBlock := ceilingDiv(fileSize, _BlockSize)
   703  	contentSize := fileSize - _ChecksumSize*nBlock
   704  
   705  	return &DirEntry{
   706  		Name:  pathpkg.Base(filePath),
   707  		IsDir: false,
   708  		Size:  contentSize,
   709  	}, nil
   710  }
   711  
   712  func (l *LocalFS) PrefetchFile(ctx context.Context, filePath string) error {
   713  	return nil
   714  }
   715  
   716  func (l *LocalFS) Delete(ctx context.Context, filePaths ...string) error {
   717  	if err := ctx.Err(); err != nil {
   718  		return err
   719  	}
   720  
   721  	ctx, span := trace.Start(ctx, "LocalFS.Delete", trace.WithKind(trace.SpanKindLocalFSVis))
   722  	defer func() {
   723  		span.AddExtraFields([]zap.Field{zap.String("delete", strings.Join(filePaths, "|"))}...)
   724  		span.End()
   725  	}()
   726  
   727  	for _, filePath := range filePaths {
   728  		if err := l.deleteSingle(ctx, filePath); err != nil {
   729  			return err
   730  		}
   731  	}
   732  
   733  	return errors.Join(
   734  		func() error {
   735  			if l.memCache == nil {
   736  				return nil
   737  			}
   738  			return l.memCache.DeletePaths(ctx, filePaths)
   739  		}(),
   740  		func() error {
   741  			if l.diskCache == nil {
   742  				return nil
   743  			}
   744  			return l.diskCache.DeletePaths(ctx, filePaths)
   745  		}(),
   746  		func() error {
   747  			if l.remoteCache == nil {
   748  				return nil
   749  			}
   750  			return l.remoteCache.DeletePaths(ctx, filePaths)
   751  		}(),
   752  	)
   753  }
   754  
   755  func (l *LocalFS) deleteSingle(_ context.Context, filePath string) error {
   756  	path, err := ParsePathAtService(filePath, l.name)
   757  	if err != nil {
   758  		return err
   759  	}
   760  	nativePath := l.toNativeFilePath(path.File)
   761  
   762  	_, err = os.Stat(nativePath)
   763  	if err != nil {
   764  		if os.IsNotExist(err) {
   765  			// ignore not found error
   766  			return nil
   767  		}
   768  		return err
   769  	}
   770  
   771  	err = os.Remove(nativePath)
   772  	if err != nil {
   773  		return err
   774  	}
   775  
   776  	parentDir, _ := filepath.Split(nativePath)
   777  	err = l.syncDir(parentDir)
   778  	if err != nil {
   779  		return err
   780  	}
   781  
   782  	return nil
   783  }
   784  
   785  func (l *LocalFS) ensureDir(nativePath string) error {
   786  	nativePath = filepath.Clean(nativePath)
   787  	if nativePath == "" {
   788  		return nil
   789  	}
   790  
   791  	// check existence by l.dirFiles
   792  	l.RLock()
   793  	_, ok := l.dirFiles[nativePath]
   794  	if ok {
   795  		// dir existed
   796  		l.RUnlock()
   797  		return nil
   798  	}
   799  	l.RUnlock()
   800  
   801  	// check existence by fstat
   802  	_, err := os.Stat(nativePath)
   803  	if err == nil {
   804  		// existed
   805  		return nil
   806  	}
   807  
   808  	// ensure parent
   809  	parent, _ := filepath.Split(nativePath)
   810  	if parent != nativePath {
   811  		if err := l.ensureDir(parent); err != nil {
   812  			return err
   813  		}
   814  	}
   815  
   816  	// create
   817  	if err := os.Mkdir(nativePath, 0755); err != nil {
   818  		if os.IsExist(err) {
   819  			// existed
   820  			return nil
   821  		}
   822  		return err
   823  	}
   824  
   825  	// sync parent dir
   826  	if err := l.syncDir(parent); err != nil {
   827  		return err
   828  	}
   829  
   830  	return nil
   831  }
   832  
   833  func (l *LocalFS) syncDir(nativePath string) error {
   834  	l.Lock()
   835  	f, ok := l.dirFiles[nativePath]
   836  	if !ok {
   837  		var err error
   838  		f, err = os.Open(nativePath)
   839  		if err != nil {
   840  			l.Unlock()
   841  			return err
   842  		}
   843  		l.dirFiles[nativePath] = f
   844  	}
   845  	l.Unlock()
   846  	if err := f.Sync(); err != nil {
   847  		return err
   848  	}
   849  	return nil
   850  }
   851  
   852  func (l *LocalFS) toNativeFilePath(filePath string) string {
   853  	return filepath.Join(l.rootPath, toOSPath(filePath))
   854  }
   855  
   856  var _ MutableFileService = new(LocalFS)
   857  
   858  func (l *LocalFS) NewMutator(ctx context.Context, filePath string) (Mutator, error) {
   859  	path, err := ParsePathAtService(filePath, l.name)
   860  	if err != nil {
   861  		return nil, err
   862  	}
   863  	nativePath := l.toNativeFilePath(path.File)
   864  	f, err := os.OpenFile(nativePath, os.O_RDWR, 0644)
   865  	if os.IsNotExist(err) {
   866  		return nil, moerr.NewFileNotFoundNoCtx(path.File)
   867  	}
   868  	return &LocalFSMutator{
   869  		osFile:           f,
   870  		fileWithChecksum: NewFileWithChecksum(ctx, f, _BlockContentSize, l.perfCounterSets),
   871  	}, nil
   872  }
   873  
   874  type LocalFSMutator struct {
   875  	osFile           *os.File
   876  	fileWithChecksum *FileWithChecksum[*os.File]
   877  }
   878  
   879  func (l *LocalFSMutator) Mutate(ctx context.Context, entries ...IOEntry) error {
   880  
   881  	ctx, span := trace.Start(ctx, "LocalFS.Mutate")
   882  	defer span.End()
   883  
   884  	return l.mutate(ctx, 0, entries...)
   885  }
   886  
   887  func (l *LocalFSMutator) Append(ctx context.Context, entries ...IOEntry) error {
   888  	ctx, span := trace.Start(ctx, "LocalFS.Append")
   889  	defer span.End()
   890  
   891  	offset, err := l.fileWithChecksum.Seek(0, io.SeekEnd)
   892  	if err != nil {
   893  		return err
   894  	}
   895  	return l.mutate(ctx, offset, entries...)
   896  }
   897  
   898  func (l *LocalFSMutator) mutate(ctx context.Context, baseOffset int64, entries ...IOEntry) error {
   899  	if err := ctx.Err(); err != nil {
   900  		return err
   901  	}
   902  
   903  	// write
   904  	for _, entry := range entries {
   905  
   906  		if entry.ReaderForWrite != nil {
   907  			// seek and copy
   908  			_, err := l.fileWithChecksum.Seek(entry.Offset+baseOffset, 0)
   909  			if err != nil {
   910  				return err
   911  			}
   912  			var buf []byte
   913  			put := ioBufferPool.Get(&buf)
   914  			defer put.Put()
   915  			n, err := io.CopyBuffer(l.fileWithChecksum, entry.ReaderForWrite, buf)
   916  			if err != nil {
   917  				return err
   918  			}
   919  			if int64(n) != entry.Size {
   920  				return moerr.NewSizeNotMatchNoCtx("")
   921  			}
   922  
   923  		} else {
   924  			// WriteAt
   925  			n, err := l.fileWithChecksum.WriteAt(entry.Data, int64(entry.Offset+baseOffset))
   926  			if err != nil {
   927  				return err
   928  			}
   929  			if int64(n) != entry.Size {
   930  				return moerr.NewSizeNotMatchNoCtx("")
   931  			}
   932  		}
   933  	}
   934  
   935  	return nil
   936  }
   937  
   938  func (l *LocalFSMutator) Close() error {
   939  	// sync
   940  	if err := l.osFile.Sync(); err != nil {
   941  		return err
   942  	}
   943  
   944  	// close
   945  	if err := l.osFile.Close(); err != nil {
   946  		return err
   947  	}
   948  
   949  	return nil
   950  }
   951  
   952  var _ ReplaceableFileService = new(LocalFS)
   953  
   954  func (l *LocalFS) Replace(ctx context.Context, vector IOVector) error {
   955  	ctx, span := trace.Start(ctx, "LocalFS.Replace")
   956  	defer span.End()
   957  	_, err := l.write(ctx, vector)
   958  	if err != nil {
   959  		return err
   960  	}
   961  	return nil
   962  }
   963  
   964  var _ CachingFileService = new(LocalFS)
   965  
   966  func (l *LocalFS) Close() {
   967  	l.FlushCache()
   968  }
   969  
   970  func (l *LocalFS) FlushCache() {
   971  	if l.memCache != nil {
   972  		l.memCache.Flush()
   973  	}
   974  	if l.diskCache != nil {
   975  		l.diskCache.Flush()
   976  	}
   977  }
   978  
   979  func (l *LocalFS) SetAsyncUpdate(b bool) {
   980  	l.asyncUpdate = b
   981  }
   982  
   983  func entryIsDir(path string, name string, entry fs.FileInfo) (bool, error) {
   984  	if entry.IsDir() {
   985  		return true, nil
   986  	}
   987  	if entry.Mode().Type()&fs.ModeSymlink > 0 {
   988  		stat, err := os.Stat(filepath.Join(path, name))
   989  		if err != nil {
   990  			if os.IsNotExist(err) {
   991  				// invalid sym link
   992  				return false, nil
   993  			}
   994  			return false, err
   995  		}
   996  		return entryIsDir(path, name, stat)
   997  	}
   998  	return false, nil
   999  }