Files
seaweedfs/weed/mount/weedfs_file_write.go
T
Chris LuandGitHub 5553e7b876 mount: surface ENOSPC instead of endless waiting when the cluster is full (#10341)
When every volume is full, a chunk upload fails only after the assign
retry budget, the failed chunk's data is dropped, and the mount kept
accepting writes anyway. cp would crawl for hours pushing the rest of
the file through a pipeline that could not persist it, and only close()
reported an error - a generic EIO.

Poison the file handle on the first failed chunk upload so subsequent
writes fail immediately, and map "no writable volumes" / "no free
volumes" upload errors to ENOSPC so the writing process aborts with
"No space left on device". Also guard lastErr with a mutex: it was
written concurrently by uploader goroutines, and keep the first error
so later failures do not mask the root cause.
2026-07-15 22:01:19 -07:00

115 lines
3.3 KiB
Go

package mount
import (
"net/http"
"syscall"
"time"
"github.com/seaweedfs/go-fuse/v2/fuse"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/util"
)
/**
* Write data
*
* Write should return exactly the number of bytes requested
* except on error. An exception to this is when the file has
* been opened in 'direct_io' mode, in which case the return value
* of the write system call will reflect the return value of this
* operation.
*
* Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is
* expected to reset the setuid and setgid bits.
*
* fi->fh will contain the value set by the open method, or will
* be undefined if the open method didn't set any value.
*
* Valid replies:
* fuse_reply_write
* fuse_reply_err
*
* @param req request handle
* @param ino the inode number
* @param buf data to write
* @param size number of bytes to write
* @param off offset to write to
* @param fi file information
*/
func (wfs *WFS) Write(cancel <-chan struct{}, in *fuse.WriteIn, data []byte) (written uint32, code fuse.Status) {
// Check quota including uncommitted writes for real-time enforcement
if wfs.IsOverQuotaWithUncommitted() {
return 0, fuse.Status(syscall.ENOSPC)
}
fh := wfs.GetHandle(FileHandleId(in.Fh))
if fh == nil {
return 0, fuse.ENOENT
}
fh.dirtyPages.writerPattern.MonitorWriteAt(int64(in.Offset), int(in.Size))
tsNs := time.Now().UnixNano()
fhActiveLock := fh.wfs.fhLockTable.AcquireLock("Write", fh.fh, util.ExclusiveLock)
defer fh.wfs.fhLockTable.ReleaseLock(fh.fh, fhActiveLock)
entry := fh.GetEntry()
if entry == nil {
return 0, fuse.OK
}
entry.Content = nil
offset := int64(in.Offset)
oldFileSize := int64(entry.Attributes.FileSize)
newFileSize := max(offset+int64(len(data)), oldFileSize)
entry.Attributes.FileSize = uint64(newFileSize)
// POSIX: writes update mtime and ctime. Stamp the entry now so the
// flush path persists the write time rather than overwriting any
// user-set mtime (e.g., utimes/touch -m -d) at flush time.
writeNow := time.Unix(0, tsNs)
entry.Attributes.Mtime = writeNow.Unix()
entry.Attributes.MtimeNs = int32(writeNow.Nanosecond())
entry.Attributes.Ctime = writeNow.Unix()
entry.Attributes.CtimeNs = int32(writeNow.Nanosecond())
// Track uncommitted bytes for real-time quota enforcement.
// Only count the new bytes being added beyond the current file size.
if newFileSize > oldFileSize {
wfs.AddUncommittedBytes(newFileSize - oldFileSize)
}
// glog.V(4).Infof("%v write [%d,%d) %d", fh.f.fullpath(), req.Offset, req.Offset+int64(len(req.Data)), len(req.Data))
if err := fh.dirtyPages.AddPage(offset, data, fh.dirtyPages.writerPattern.IsSequentialMode(), tsNs); err != nil {
glog.Errorf("AddPage error: %v", err)
return 0, writeErrorToFuseStatus(err)
}
written = uint32(len(data))
if offset == 0 {
// detect mime type
fh.contentType = http.DetectContentType(data)
}
fh.dirtyMetadata = true
// Invalidate the mtime cache so the next Open will not set FOPEN_KEEP_CACHE.
wfs.invalidateOpenMtimeCache(in.NodeId)
// POSIX: clear SUID/SGID bits on write by non-root users.
if in.Uid != 0 {
entry.Attributes.FileMode &^= 0o6000
}
if IsDebugFileReadWrite {
// print("+")
fh.mirrorFile.WriteAt(data, offset)
}
return written, fuse.OK
}