mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-27 15:30:45 +01:00
* mount: drain OFD locks when a file description is released The kernel scopes OFD fcntl locks to the open file description but reports no lock_owner for them at RELEASE — it only sends one for flock holders — so an OFD write lock taken on a file handle outlived the holder and blocked every later open of that file (e.g. etcd failing to reopen its WAL after a restart). The mount now records the owner of each granted non-flock lock under its file handle and releases those owners on RELEASE, which also covers any POSIX locks left over when FLUSH could not run. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * mount: give every open its own fh value over the shared handle AcquireFileHandle reuses one FileHandle per inode, so every open reported the same fh to the kernel. A RELEASE for one description could then not be told apart from a sibling still holding the inode open — draining lock owners recorded under the shared fh dropped locks belonging to descriptions that stay open. Each open now registers its own fh value in fh2inode, all resolving to the shared FileHandle, and RELEASE removes exactly the id that is closing. This also stops a duplicated RELEASE on one id from double-decrementing the open count. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * mount: forget flushed lock owners so a later release cannot re-drop locks POSIX lock owners are process-scoped: Flush releases every lock the process holds on the inode, but the owner stayed recorded under each open's fh for the Release-time drain. If the process took a fresh lock through another open before an old description finished closing, the stale record made that RELEASE drop the new lock too. Flush now drops the released owner from every recorded open of the inode, so the Release drain only ever fires for owners whose locks were never flushed. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * mount: keep close-time lock owner records when the release fails A routed POSIX owner release that errors keeps the flush hint for a retry, but Flush also dropped the owner from every open's release records unconditionally — so the final Release skipped the retry and the owner filer's lock was stranded until lease reaping. Only forget the owner once its locks were actually released. --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
150 lines
4.0 KiB
Go
150 lines
4.0 KiB
Go
package mount
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
)
|
|
|
|
type FileHandleToInode struct {
|
|
sync.RWMutex
|
|
inode2fh map[uint64]*FileHandle
|
|
fh2inode map[FileHandleId]uint64
|
|
}
|
|
|
|
func NewFileHandleToInode() *FileHandleToInode {
|
|
return &FileHandleToInode{
|
|
inode2fh: make(map[uint64]*FileHandle),
|
|
fh2inode: make(map[FileHandleId]uint64),
|
|
}
|
|
}
|
|
|
|
func (i *FileHandleToInode) GetFileHandle(fh FileHandleId) *FileHandle {
|
|
i.RLock()
|
|
defer i.RUnlock()
|
|
inode, found := i.fh2inode[fh]
|
|
if found {
|
|
return i.inode2fh[inode]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (i *FileHandleToInode) FindFileHandle(inode uint64) (fh *FileHandle, found bool) {
|
|
i.RLock()
|
|
defer i.RUnlock()
|
|
fh, found = i.inode2fh[inode]
|
|
return
|
|
}
|
|
|
|
// MarkInodeRenamed sets isRenamed on any file handle associated with the
|
|
// given inode. This prevents the async flush from recreating a renamed
|
|
// file's metadata under its old path.
|
|
func (i *FileHandleToInode) MarkInodeRenamed(inode uint64) {
|
|
i.RLock()
|
|
defer i.RUnlock()
|
|
if fh, ok := i.inode2fh[inode]; ok {
|
|
fh.isRenamed = true
|
|
}
|
|
}
|
|
|
|
// AcquireFileHandle fully initializes a handle — entry and the log position it
|
|
// reflects — before exposing it in the map, and reports whether one already
|
|
// existed. An existing handle only gets its counter bumped: its entry belongs
|
|
// to whoever holds the handle lock, so callers install there (AcquireHandle)
|
|
// rather than under this one.
|
|
// Every open gets its own fh value over the shared FileHandle so that
|
|
// RELEASE — which identifies nothing else about the dying description — can
|
|
// scope per-open state (e.g. OFD lock owners) to the open that is closing.
|
|
func (i *FileHandleToInode) AcquireFileHandle(wfs *WFS, inode uint64, entry *filer_pb.Entry, versionTsNs int64, signature int32) (*FileHandle, FileHandleId, bool) {
|
|
i.Lock()
|
|
defer i.Unlock()
|
|
fh, found := i.inode2fh[inode]
|
|
if !found {
|
|
fh = newFileHandle(wfs, FileHandleId(util.RandomUint64()), inode, entry)
|
|
fh.entryVersionTsNs.Store(versionTsNs)
|
|
fh.entryVersionSignature.Store(signature)
|
|
i.inode2fh[inode] = fh
|
|
i.fh2inode[fh.fh] = inode
|
|
return fh, fh.fh, false
|
|
}
|
|
fh.counter++
|
|
openFh := FileHandleId(util.RandomUint64())
|
|
fh.fhs[openFh] = struct{}{}
|
|
i.fh2inode[openFh] = inode
|
|
return fh, openFh, true
|
|
}
|
|
|
|
func (i *FileHandleToInode) ReleaseByHandle(fh FileHandleId) *FileHandle {
|
|
i.Lock()
|
|
defer i.Unlock()
|
|
|
|
inode, found := i.fh2inode[fh]
|
|
if !found {
|
|
return nil
|
|
}
|
|
// The released id never resolves again: a duplicate RELEASE on it must
|
|
// not decrement the counter a second time.
|
|
delete(i.fh2inode, fh)
|
|
|
|
fhHandle, fhFound := i.inode2fh[inode]
|
|
if !fhFound {
|
|
return nil
|
|
}
|
|
delete(fhHandle.fhs, fh)
|
|
|
|
// If the counter is already <= 0, a prior Release already started the
|
|
// drain. Return nil to prevent double-processing.
|
|
if fhHandle.counter <= 0 {
|
|
return nil
|
|
}
|
|
|
|
fhHandle.counter--
|
|
if fhHandle.counter <= 0 {
|
|
if fhHandle.asyncFlushPending {
|
|
// Handle stays in fhMap so rename/unlink can still find it
|
|
// via FindFileHandle during the background drain.
|
|
return fhHandle
|
|
}
|
|
delete(i.inode2fh, inode)
|
|
for openFh := range fhHandle.fhs {
|
|
delete(i.fh2inode, openFh)
|
|
}
|
|
fhHandle.fhs = nil
|
|
return fhHandle
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveFileHandle removes a handle from both maps. Called after an async
|
|
// drain completes to clean up the handle that was intentionally kept in the
|
|
// maps during the flush.
|
|
func (i *FileHandleToInode) RemoveFileHandle(fh FileHandleId, inode uint64) {
|
|
i.Lock()
|
|
defer i.Unlock()
|
|
if fhHandle, ok := i.inode2fh[inode]; ok {
|
|
for openFh := range fhHandle.fhs {
|
|
delete(i.fh2inode, openFh)
|
|
}
|
|
}
|
|
delete(i.inode2fh, inode)
|
|
delete(i.fh2inode, fh)
|
|
}
|
|
|
|
// openFhIds returns the live fh values minted for an inode's shared handle —
|
|
// one per still-open description.
|
|
func (i *FileHandleToInode) openFhIds(inode uint64) []FileHandleId {
|
|
i.RLock()
|
|
defer i.RUnlock()
|
|
fh := i.inode2fh[inode]
|
|
if fh == nil {
|
|
return nil
|
|
}
|
|
ids := make([]FileHandleId, 0, len(fh.fhs))
|
|
for id := range fh.fhs {
|
|
ids = append(ids, id)
|
|
}
|
|
return ids
|
|
}
|