Files
seaweedfs/weed/mount/weedfs_stats.go
T
Chris LuandDevin 6d692ab013 mount: fail writes over to the next filer instead of hanging on a dead one (#11696)
* mount: bound each filer call so a wedged filer cannot hold the failover walk

WithFilerClient callers run their RPCs on context.Background(), so a filer
whose transport stalls without refusing (stopped peer, black-holed
connection) blocks the call forever and the walk never reaches the
remaining filer addresses. Run each attempt under a timeout; on expiry
drop the cached channel so the abandoned call is unblocked and the next
address is tried.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* mount: time out wedged mutation streams and rotate to the next filer

doUnary and Rename wait on stream Send/Recv under context.Background(); a
dead transport never delivers an error, so writes hang without ever
reaching the unary fallback. Bound the waits and tear the stream down on
timeout; teardown also advances the sticky filer index so the next stream
opens against a different filer instead of redialing the dead one.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* mount: cover filer failover past a wedged filer

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* mount: cancel filer attempts by context and unwind them before retry

Review follow-ups on the filer failover:

- callFiler derives a per-attempt context instead of only abandoning the
  wait, so a timed-out attempt's RPCs die on their own without tearing
  down the shared cached channel. The walk now also waits for the
  attempt's callback to unwind before trying the next filer, so two runs
  of the same closure cannot race on the caller's captured results.
  Callbacks that ignore the attempt context are still forced out by
  dropping the channel, as a last resort after a grace period.
- teardownStream captures the dying stream's filer index and rotates it
  while holding m.mu, so it can never rotate a successor generation's
  filer by mistake.
- The unary rename fallback bounds silence on its dedicated stream the
  way the mux does, instead of trusting Recv on a wedged filer.
- downloadRemoteEntry opts out of the metadata-call bound: a healthy
  whole-file download can legitimately outlast it.

* mount: count only wire silence against the rename timeout

Applying a rename event can outlast the silence bound on a healthy
filer — a directory rename migrates an open child's DLM lock under
another writer. Stop the mux's response timer for the local work and
re-arm it before the next wait; the fallback stream's watchdog defers
while the handler runs instead of cancelling a live stream.

* mount: restart the rename fallback's silence bound after each event

The bound covers the wait for the next response, not the rename's whole
lifetime — a healthy rename that keeps sending events can outlast it.
Re-arm after each handled event; the defer-while-handling stays for
local work that outlasts the interval on its own.

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-11 20:18:22 +08:00

131 lines
4.1 KiB
Go

package mount
import (
"context"
"fmt"
"math"
"time"
"github.com/seaweedfs/go-fuse/v2/fuse"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
)
const blockSize = 512
type statsCache struct {
filer_pb.StatisticsResponse
lastChecked int64 // unix time in seconds
}
// diskSizes reports the sizes df and the quota work from. By default that is
// the space the cluster gives up to the data, counting every replica and every
// EC shard. Under -df.logical it is the data itself, with the free space
// converted to how much more of it the mount's replication setting allows.
func (wfs *WFS) diskSizes() (totalSize, usedSize uint64) {
// a filer older than the logical sizes sends zeros, so keep reporting the
// raw ones rather than an empty filesystem
if wfs.option.LogicalDiskUsage && wfs.stats.LogicalTotalSize > 0 {
return wfs.stats.LogicalTotalSize, wfs.stats.LogicalUsedSize
}
return wfs.stats.TotalSize, wfs.stats.UsedSize
}
func (wfs *WFS) StatFs(cancel <-chan struct{}, in *fuse.InHeader, out *fuse.StatfsOut) (code fuse.Status) {
// glog.V(4).Infof("reading fs stats")
if wfs.stats.lastChecked < time.Now().Unix()-20 {
err := wfs.withFilerClient(context.Background(), false, wfs.filerRPCWait(), func(ctx context.Context, client filer_pb.SeaweedFilerClient) error {
request := &filer_pb.StatisticsRequest{
Collection: wfs.option.Collection,
Replication: wfs.option.Replication,
Ttl: fmt.Sprintf("%ds", wfs.option.TtlSec),
DiskType: string(wfs.option.DiskType),
}
glog.V(4).Infof("reading filer stats: %+v", request)
resp, err := client.Statistics(ctx, request)
if err != nil {
glog.V(0).Infof("reading filer stats %v: %v", request, err)
return err
}
glog.V(4).Infof("read filer stats: %+v", resp)
wfs.stats.TotalSize = resp.TotalSize
wfs.stats.UsedSize = resp.UsedSize
wfs.stats.LogicalTotalSize = resp.LogicalTotalSize
wfs.stats.LogicalUsedSize = resp.LogicalUsedSize
wfs.stats.FileCount = resp.FileCount
wfs.stats.lastChecked = time.Now().Unix()
return nil
})
if err != nil {
// the last known sizes beat the empty filesystem a bare return reports
glog.V(0).Infof("filer Statistics: %v", err)
}
}
totalDiskSize, usedDiskSize := wfs.diskSizes()
actualFileCount := wfs.stats.FileCount
if wfs.option.Quota > 0 && totalDiskSize > uint64(wfs.option.Quota) {
totalDiskSize = uint64(wfs.option.Quota)
if usedDiskSize > totalDiskSize {
totalDiskSize = usedDiskSize
}
}
// http://man.he.net/man2/statfs
/*
struct statfs {
__fsword_t f_type; // Type of filesystem (see below)
__fsword_t f_bsize; // Optimal transfer block size
fsblkcnt_t f_blocks; // Total data blocks in filesystem
fsblkcnt_t f_bfree; // Free blocks in filesystem
fsblkcnt_t f_bavail; // Free blocks available to
unprivileged user
fsfilcnt_t f_files; // Total file nodes in filesystem
fsfilcnt_t f_ffree; // Free file nodes in filesystem
fsid_t f_fsid; // Filesystem ID
__fsword_t f_namelen; // Maximum length of filenames
__fsword_t f_frsize; // Fragment size (since Linux 2.6)
__fsword_t f_flags; // Mount flags of filesystem
(since Linux 2.6.36)
__fsword_t f_spare[xxx];
// Padding bytes reserved for future use
};
*/
// Compute the total number of available blocks
out.Blocks = totalDiskSize / blockSize
if out.Blocks <= 0 {
out.Blocks = 1
}
// Compute the number of used blocks
numBlocks := usedDiskSize / blockSize
remainingBlocks := int64(out.Blocks) - int64(numBlocks)
if remainingBlocks < 0 {
remainingBlocks = 0
}
// Report the number of free and available blocks for the block size
out.Bfree = uint64(remainingBlocks)
out.Bavail = uint64(remainingBlocks)
out.Bsize = uint32(blockSize)
// Report the total number of possible files in the file system (and those free)
out.Files = math.MaxInt64
out.Ffree = math.MaxInt64 - actualFileCount
// Report the maximum length of a name and the minimum fragment size
out.NameLen = 255
out.Frsize = uint32(blockSize)
return fuse.OK
}