2022-02-11 12:32:13 +08:00
|
|
|
package mount
|
|
|
|
|
|
|
|
import (
|
2022-02-12 17:54:16 +08:00
|
|
|
"context"
|
2022-08-22 03:18:13 +08:00
|
|
|
"math/rand"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
2022-02-11 19:12:52 +08:00
|
|
|
"github.com/hanwen/go-fuse/v2/fuse"
|
2022-08-22 03:18:13 +08:00
|
|
|
"google.golang.org/grpc"
|
|
|
|
|
2022-07-29 15:17:28 +08:00
|
|
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/mount/meta_cache"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/mount_pb"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/types"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/util/chunk_cache"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/util/grace"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/wdclient"
|
2022-02-11 12:32:13 +08:00
|
|
|
|
|
|
|
"github.com/hanwen/go-fuse/v2/fs"
|
|
|
|
)
|
|
|
|
|
2022-02-11 14:43:55 +08:00
|
|
|
type Option struct {
|
2022-08-22 03:20:27 +08:00
|
|
|
filerIndex int32 // align memory for atomic read/write
|
2022-02-11 14:43:55 +08:00
|
|
|
FilerAddresses []pb.ServerAddress
|
2022-08-22 03:20:27 +08:00
|
|
|
MountDirectory string
|
2022-02-11 14:43:55 +08:00
|
|
|
GrpcDialOption grpc.DialOption
|
|
|
|
FilerMountRootPath string
|
|
|
|
Collection string
|
|
|
|
Replication string
|
|
|
|
TtlSec int32
|
|
|
|
DiskType types.DiskType
|
|
|
|
ChunkSizeLimit int64
|
|
|
|
ConcurrentWriters int
|
2023-08-17 14:47:43 +08:00
|
|
|
CacheDirForRead string
|
|
|
|
CacheSizeMBForRead int64
|
|
|
|
CacheDirForWrite string
|
2022-02-11 14:43:55 +08:00
|
|
|
DataCenter string
|
|
|
|
Umask os.FileMode
|
2022-03-06 18:44:40 +08:00
|
|
|
Quota int64
|
2022-06-06 11:27:12 +08:00
|
|
|
DisableXAttr bool
|
2022-02-11 14:43:55 +08:00
|
|
|
|
|
|
|
MountUid uint32
|
|
|
|
MountGid uint32
|
|
|
|
MountMode os.FileMode
|
|
|
|
MountCtime time.Time
|
|
|
|
MountMtime time.Time
|
|
|
|
MountParentInode uint64
|
|
|
|
|
|
|
|
VolumeServerAccess string // how to access volume servers
|
|
|
|
Cipher bool // whether encrypt data on volume server
|
|
|
|
UidGidMapper *meta_cache.UidGidMapper
|
|
|
|
|
2023-08-17 14:47:43 +08:00
|
|
|
uniqueCacheDirForRead string
|
|
|
|
uniqueCacheDirForWrite string
|
2022-02-11 14:43:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type WFS struct {
|
2022-03-13 16:14:50 +08:00
|
|
|
// https://dl.acm.org/doi/fullHtml/10.1145/3310148
|
2022-02-11 19:12:52 +08:00
|
|
|
// follow https://github.com/hanwen/go-fuse/blob/master/fuse/api.go
|
|
|
|
fuse.RawFileSystem
|
2022-04-03 06:14:37 +08:00
|
|
|
mount_pb.UnimplementedSeaweedMountServer
|
2022-02-11 12:32:13 +08:00
|
|
|
fs.Inode
|
2022-02-14 14:50:44 +08:00
|
|
|
option *Option
|
|
|
|
metaCache *meta_cache.MetaCache
|
|
|
|
stats statsCache
|
|
|
|
chunkCache *chunk_cache.TieredChunkCache
|
|
|
|
signature int32
|
|
|
|
concurrentWriters *util.LimitedConcurrentExecutor
|
|
|
|
inodeToPath *InodeToPath
|
|
|
|
fhmap *FileHandleToInode
|
2022-02-15 17:44:17 +08:00
|
|
|
dhmap *DirectoryHandleToInode
|
2022-02-27 16:00:23 +08:00
|
|
|
fuseServer *fuse.Server
|
2022-03-06 14:10:43 +08:00
|
|
|
IsOverQuota bool
|
2023-09-22 02:08:26 +08:00
|
|
|
fhLockTable *util.LockTable[FileHandleId]
|
2022-02-11 14:43:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewSeaweedFileSystem(option *Option) *WFS {
|
|
|
|
wfs := &WFS{
|
2022-02-11 19:12:52 +08:00
|
|
|
RawFileSystem: fuse.NewDefaultRawFileSystem(),
|
|
|
|
option: option,
|
|
|
|
signature: util.RandomInt32(),
|
2022-03-01 04:16:53 +08:00
|
|
|
inodeToPath: NewInodeToPath(util.FullPath(option.FilerMountRootPath)),
|
2022-02-14 11:14:34 +08:00
|
|
|
fhmap: NewFileHandleToInode(),
|
2022-02-15 17:44:17 +08:00
|
|
|
dhmap: NewDirectoryHandleToInode(),
|
2023-09-22 02:08:26 +08:00
|
|
|
fhLockTable: util.NewLockTable[FileHandleId](),
|
2022-02-11 14:43:55 +08:00
|
|
|
}
|
|
|
|
|
2022-08-22 03:18:13 +08:00
|
|
|
wfs.option.filerIndex = int32(rand.Intn(len(option.FilerAddresses)))
|
2022-02-14 14:50:44 +08:00
|
|
|
wfs.option.setupUniqueCacheDirectory()
|
2023-08-17 14:47:43 +08:00
|
|
|
if option.CacheSizeMBForRead > 0 {
|
|
|
|
wfs.chunkCache = chunk_cache.NewTieredChunkCache(256, option.getUniqueCacheDirForRead(), option.CacheSizeMBForRead, 1024*1024)
|
2022-02-14 14:50:44 +08:00
|
|
|
}
|
|
|
|
|
2023-08-17 14:47:43 +08:00
|
|
|
wfs.metaCache = meta_cache.NewMetaCache(path.Join(option.getUniqueCacheDirForRead(), "meta"), option.UidGidMapper,
|
2022-03-01 04:16:53 +08:00
|
|
|
util.FullPath(option.FilerMountRootPath),
|
|
|
|
func(path util.FullPath) {
|
|
|
|
wfs.inodeToPath.MarkChildrenCached(path)
|
|
|
|
}, func(path util.FullPath) bool {
|
|
|
|
return wfs.inodeToPath.IsChildrenCached(path)
|
|
|
|
}, func(filePath util.FullPath, entry *filer_pb.Entry) {
|
2024-06-07 03:49:33 +08:00
|
|
|
// Find inode if it is not a deleted path
|
|
|
|
if inode, inode_found := wfs.inodeToPath.GetInode(filePath); inode_found {
|
|
|
|
// Find open file handle
|
|
|
|
if fh, fh_found := wfs.fhmap.FindFileHandle(inode); fh_found {
|
|
|
|
fhActiveLock := fh.wfs.fhLockTable.AcquireLock("invalidateFunc", fh.fh, util.ExclusiveLock)
|
|
|
|
defer fh.wfs.fhLockTable.ReleaseLock(fh.fh, fhActiveLock)
|
|
|
|
|
|
|
|
// Recreate dirty pages
|
|
|
|
fh.dirtyPages.Destroy()
|
|
|
|
fh.dirtyPages = newPageWriter(fh, wfs.option.ChunkSizeLimit)
|
|
|
|
|
|
|
|
// Update handle entry
|
|
|
|
newentry, status := wfs.maybeLoadEntry(filePath)
|
|
|
|
if status == fuse.OK {
|
Fix dead lock (#5815)
* reduce locks to avoid dead lock
Flush->FlushData->uplloadPipeline.FluahAll
uploaderCount>0
goroutine 1 [sync.Cond.Wait, 71 minutes]:
sync.runtime_notifyListWait(0xc0007ae4d0, 0x0)
/usr/local/go/src/runtime/sema.go:569 +0x159
sync.(*Cond).Wait(0xc001a59290?)
/usr/local/go/src/sync/cond.go:70 +0x85
github.com/seaweedfs/seaweedfs/weed/mount/page_writer.(*UploadPipeline).waitForCurrentWritersToComplete(0xc0002ee4d0)
/github/workspace/weed/mount/page_writer/upload_pipeline_lock.go:58 +0x32
github.com/seaweedfs/seaweedfs/weed/mount/page_writer.(*UploadPipeline).FlushAll(0xc0002ee4d0)
/github/workspace/weed/mount/page_writer/upload_pipeline.go:151 +0x25
github.com/seaweedfs/seaweedfs/weed/mount.(*ChunkedDirtyPages).FlushData(0xc00087e840)
/github/workspace/weed/mount/dirty_pages_chunked.go:54 +0x29
github.com/seaweedfs/seaweedfs/weed/mount.(*PageWriter).FlushData(...)
/github/workspace/weed/mount/page_writer.go:50
github.com/seaweedfs/seaweedfs/weed/mount.(*WFS).doFlush(0xc0006ad600, 0xc00030d380, 0x0, 0x0)
/github/workspace/weed/mount/weedfs_file_sync.go:101 +0x169
github.com/seaweedfs/seaweedfs/weed/mount.(*WFS).Flush(0xc0006ad600, 0xc001a594a8?, 0xc0004c1ca0)
/github/workspace/weed/mount/weedfs_file_sync.go:59 +0x48
github.com/hanwen/go-fuse/v2/fuse.doFlush(0xc0000da870?, 0xc0004c1b08)
SaveContent -> MemChunk.RLock ->
ChunkedDirtyPages.saveChunkedFileIntervalToStorage
pages.fh.AddChunks([]*filer_pb.FileChunk{chunk})
fh.entryLock.Lock()
sync.(*RWMutex).Lock(0x0?)
/usr/local/go/src/sync/rwmutex.go:146 +0x31
github.com/seaweedfs/seaweedfs/weed/mount.(*FileHandle).AddChunks(0xc00030d380, {0xc00028bdc8, 0x1, 0x1})
/github/workspace/weed/mount/filehandle.go:93 +0x45
github.com/seaweedfs/seaweedfs/weed/mount.(*ChunkedDirtyPages).saveChunkedFileIntervalToStorage(0xc00087e840, {0x2be7ac0, 0xc00018d9e0}, 0x0, 0x121, 0x17e3c624565ace45, 0x1?)
/github/workspace/weed/mount/dirty_pages_chunked.go:80 +0x2d4
github.com/seaweedfs/seaweedfs/weed/mount/page_writer.(*MemChunk).SaveContent(0xc0008d9130, 0xc0008093e0)
/github/workspace/weed/mount/page_writer/page_chunk_mem.go:115 +0x112
github.com/seaweedfs/seaweedfs/weed/mount/page_writer.(*UploadPipeline).moveToSealed.func1()
/github/workspace/weed/mount/page_writer/upload_pipeline.go:187 +0x55
github.com/seaweedfs/seaweedfs/weed/util.(*LimitedConcurrentExecutor).Execute.func1()
/github/workspace/weed/util/limited_executor.go:38 +0x62
created by github.com/seaweedfs/seaweedfs/weed/util.(*LimitedConcurrentExecutor).Execute in goroutine 1
/github/workspace/weed/util/limited_executor.go:33 +0x97
On metadata update
fh.entryLock.Lock()
fh.dirtyPages.Destroy()
up.chunksLock.Lock => each sealed chunk.FreeReference => MemChunk.Lock
goroutine 134 [sync.RWMutex.Lock, 71 minutes]:
sync.runtime_SemacquireRWMutex(0xc0007c3558?, 0xea?, 0x3fb0800?)
/usr/local/go/src/runtime/sema.go:87 +0x25
sync.(*RWMutex).Lock(0xc0007c35a8?)
/usr/local/go/src/sync/rwmutex.go:151 +0x6a
github.com/seaweedfs/seaweedfs/weed/mount/page_writer.(*MemChunk).FreeResource(0xc0008d9130)
/github/workspace/weed/mount/page_writer/page_chunk_mem.go:38 +0x2a
github.com/seaweedfs/seaweedfs/weed/mount/page_writer.(*SealedChunk).FreeReference(0xc00071cdb0, {0xc0006ba1a0, 0x20})
/github/workspace/weed/mount/page_writer/upload_pipeline.go:38 +0xb7
github.com/seaweedfs/seaweedfs/weed/mount/page_writer.(*UploadPipeline).Shutdown(0xc0002ee4d0)
/github/workspace/weed/mount/page_writer/upload_pipeline.go:220 +0x185
github.com/seaweedfs/seaweedfs/weed/mount.(*ChunkedDirtyPages).Destroy(0xc0008cea40?)
/github/workspace/weed/mount/dirty_pages_chunked.go:87 +0x17
github.com/seaweedfs/seaweedfs/weed/mount.(*PageWriter).Destroy(...)
/github/workspace/weed/mount/page_writer.go:78
github.com/seaweedfs/seaweedfs/weed/mount.NewSeaweedFileSystem.func3({0xc00069a6c0, 0x30}, 0x6?)
/github/workspace/weed/mount/weedfs.go:119 +0x17a
github.com/seaweedfs/seaweedfs/weed/mount/meta_cache.NewMetaCache.func1({0xc00069a6c0?, 0xc00069a480?}, 0x4015b40?)
/github/workspace/weed/mount/meta_cache/meta_cache.go:37 +0x1c
github.com/seaweedfs/seaweedfs/weed/mount/meta_cache.SubscribeMetaEvents.func1(0xc000661810)
/github/workspace/weed/mount/meta_cache/meta_cache_subscribe.go:43 +0x570
* use locked entry everywhere
* modifiable remote entry
* skip locking after getting lock from fhLockTable
2024-07-25 14:46:40 +08:00
|
|
|
if fh.GetEntry().GetEntry() != newentry {
|
2024-06-07 03:49:33 +08:00
|
|
|
fh.SetEntry(newentry)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-01 04:16:53 +08:00
|
|
|
})
|
2022-02-11 14:43:55 +08:00
|
|
|
grace.OnInterrupt(func() {
|
|
|
|
wfs.metaCache.Shutdown()
|
2023-08-17 14:47:43 +08:00
|
|
|
os.RemoveAll(option.getUniqueCacheDirForWrite())
|
2023-08-17 14:54:23 +08:00
|
|
|
os.RemoveAll(option.getUniqueCacheDirForRead())
|
2022-02-11 14:43:55 +08:00
|
|
|
})
|
|
|
|
|
2022-02-14 14:50:44 +08:00
|
|
|
if wfs.option.ConcurrentWriters > 0 {
|
|
|
|
wfs.concurrentWriters = util.NewLimitedConcurrentExecutor(wfs.option.ConcurrentWriters)
|
|
|
|
}
|
2022-02-11 14:43:55 +08:00
|
|
|
return wfs
|
|
|
|
}
|
|
|
|
|
2022-02-14 17:09:31 +08:00
|
|
|
func (wfs *WFS) StartBackgroundTasks() {
|
|
|
|
startTime := time.Now()
|
|
|
|
go meta_cache.SubscribeMetaEvents(wfs.metaCache, wfs.signature, wfs, wfs.option.FilerMountRootPath, startTime.UnixNano())
|
2022-03-06 18:44:40 +08:00
|
|
|
go wfs.loopCheckQuota()
|
2022-02-14 17:09:31 +08:00
|
|
|
}
|
|
|
|
|
2022-02-12 13:35:09 +08:00
|
|
|
func (wfs *WFS) String() string {
|
|
|
|
return "seaweedfs"
|
|
|
|
}
|
|
|
|
|
2022-02-27 16:00:23 +08:00
|
|
|
func (wfs *WFS) Init(server *fuse.Server) {
|
|
|
|
wfs.fuseServer = server
|
|
|
|
}
|
|
|
|
|
2022-08-04 16:35:18 +08:00
|
|
|
func (wfs *WFS) maybeReadEntry(inode uint64) (path util.FullPath, fh *FileHandle, entry *filer_pb.Entry, status fuse.Status) {
|
2022-02-18 16:45:43 +08:00
|
|
|
path, status = wfs.inodeToPath.GetPath(inode)
|
|
|
|
if status != fuse.OK {
|
|
|
|
return
|
|
|
|
}
|
2022-02-14 17:36:10 +08:00
|
|
|
var found bool
|
|
|
|
if fh, found = wfs.fhmap.FindFileHandle(inode); found {
|
2022-08-27 07:41:37 +08:00
|
|
|
entry = fh.UpdateEntry(func(entry *filer_pb.Entry) {
|
|
|
|
if entry != nil && fh.entry.Attributes == nil {
|
|
|
|
entry.Attributes = &filer_pb.FuseAttributes{}
|
|
|
|
}
|
|
|
|
})
|
2022-07-25 06:30:55 +08:00
|
|
|
} else {
|
|
|
|
entry, status = wfs.maybeLoadEntry(path)
|
2022-02-14 17:36:10 +08:00
|
|
|
}
|
2022-02-12 18:48:44 +08:00
|
|
|
return
|
2022-02-12 17:54:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (wfs *WFS) maybeLoadEntry(fullpath util.FullPath) (*filer_pb.Entry, fuse.Status) {
|
|
|
|
|
|
|
|
// glog.V(3).Infof("read entry cache miss %s", fullpath)
|
|
|
|
dir, name := fullpath.DirAndName()
|
|
|
|
|
|
|
|
// return a valid entry for the mount root
|
|
|
|
if string(fullpath) == wfs.option.FilerMountRootPath {
|
|
|
|
return &filer_pb.Entry{
|
|
|
|
Name: name,
|
|
|
|
IsDirectory: true,
|
|
|
|
Attributes: &filer_pb.FuseAttributes{
|
|
|
|
Mtime: wfs.option.MountMtime.Unix(),
|
|
|
|
FileMode: uint32(wfs.option.MountMode),
|
|
|
|
Uid: wfs.option.MountUid,
|
|
|
|
Gid: wfs.option.MountGid,
|
|
|
|
Crtime: wfs.option.MountCtime.Unix(),
|
|
|
|
},
|
|
|
|
}, fuse.OK
|
|
|
|
}
|
|
|
|
|
|
|
|
// read from async meta cache
|
2022-05-25 09:52:04 +08:00
|
|
|
meta_cache.EnsureVisited(wfs.metaCache, wfs, util.FullPath(dir))
|
2022-02-12 17:54:16 +08:00
|
|
|
cachedEntry, cacheErr := wfs.metaCache.FindEntry(context.Background(), fullpath)
|
|
|
|
if cacheErr == filer_pb.ErrNotFound {
|
|
|
|
return nil, fuse.ENOENT
|
|
|
|
}
|
2022-02-13 14:41:45 +08:00
|
|
|
return cachedEntry.ToProtoEntry(), fuse.OK
|
2022-02-12 17:54:16 +08:00
|
|
|
}
|
|
|
|
|
2022-02-14 14:50:44 +08:00
|
|
|
func (wfs *WFS) LookupFn() wdclient.LookupFileIdFunctionType {
|
|
|
|
if wfs.option.VolumeServerAccess == "filerProxy" {
|
|
|
|
return func(fileId string) (targetUrls []string, err error) {
|
|
|
|
return []string{"http://" + wfs.getCurrentFiler().ToHttpAddress() + "/?proxyChunkId=" + fileId}, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return filer.LookupFn(wfs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wfs *WFS) getCurrentFiler() pb.ServerAddress {
|
2022-08-22 03:18:13 +08:00
|
|
|
i := atomic.LoadInt32(&wfs.option.filerIndex)
|
|
|
|
return wfs.option.FilerAddresses[i]
|
2022-02-14 14:50:44 +08:00
|
|
|
}
|
|
|
|
|
2022-02-11 14:43:55 +08:00
|
|
|
func (option *Option) setupUniqueCacheDirectory() {
|
2022-10-15 13:24:14 +08:00
|
|
|
cacheUniqueId := util.Md5String([]byte(option.MountDirectory + string(option.FilerAddresses[0]) + option.FilerMountRootPath + util.Version()))[0:8]
|
2023-08-17 14:47:43 +08:00
|
|
|
option.uniqueCacheDirForRead = path.Join(option.CacheDirForRead, cacheUniqueId)
|
|
|
|
os.MkdirAll(option.uniqueCacheDirForRead, os.FileMode(0777)&^option.Umask)
|
|
|
|
option.uniqueCacheDirForWrite = filepath.Join(path.Join(option.CacheDirForWrite, cacheUniqueId), "swap")
|
|
|
|
os.MkdirAll(option.uniqueCacheDirForWrite, os.FileMode(0777)&^option.Umask)
|
2022-03-10 14:26:51 +08:00
|
|
|
}
|
|
|
|
|
2023-08-17 14:47:43 +08:00
|
|
|
func (option *Option) getUniqueCacheDirForWrite() string {
|
|
|
|
return option.uniqueCacheDirForWrite
|
2022-02-11 14:43:55 +08:00
|
|
|
}
|
|
|
|
|
2023-08-17 14:47:43 +08:00
|
|
|
func (option *Option) getUniqueCacheDirForRead() string {
|
|
|
|
return option.uniqueCacheDirForRead
|
2022-02-11 12:32:13 +08:00
|
|
|
}
|