mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-08 15:27:43 +02:00
* filer: bound metadata log flush retries during shutdown On SIGTERM the filer could hang forever in Shutdown: the final LocalMetaLogBuffer flush retries appendToFile indefinitely, and with the master already down each AssignVolume attempt just kept failing. WaitForShutdown never returned, the interrupt hook never reached os.Exit, and the half-dead filer kept its ports bound. Thread a context through appendToFile/assignAndUpload and switch to a 15s-bounded context once the filer is stopping: the flush abandons with a log line instead of retrying forever. Normal operation keeps the unbounded retry so no metadata is dropped while running. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: share one shutdown deadline across all pending meta log flushes Review feedback on the per-flush timeout: a deadline armed at flush start could already be expired when shutdown arrived, the first append of a flush still ran unbounded, each queued window got a fresh budget (16 windows * 15s), and an abandoned window still advanced the flushed watermark as if it had landed. Rework to a single shared flush context on the Filer, cancelled once by Shutdown via AfterFunc. Every append - the in-flight one and every queued window - observes the same deadline, so the whole drain is bounded at 15s. A flush that gives up reports its dropped bytes through the new LogBuffer.NoteFlushDropped, and loopFlush then skips the offset/timestamp advance and subscriber notifications for that window. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: bound append attempts and commit uploaded pieces detached Store operations check then drop request cancellation, so a stalled backend could still hold flushFn past the shutdown deadline; run each append attempt on its own goroutine and give up on it at the deadline. Once a piece is uploaded, commit its entry on a detached context so the expired deadline cannot strand the chunk. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: keep shutdown flushes synchronous Detaching the append attempt let flushFn return while the goroutine still held the pooled flush buffer and could commit after the metadata store closed; abandonment is only safe for the cancelable assign/upload phase, which the shared flush context already bounds. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
121 lines
3.8 KiB
Go
121 lines
3.8 KiB
Go
package filer
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/operation"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
)
|
|
|
|
func (f *Filer) appendToFile(ctx context.Context, targetFile string, data []byte) error {
|
|
|
|
assignResult, uploadResult, err2 := f.assignAndUpload(ctx, targetFile, data)
|
|
if err2 != nil {
|
|
return err2
|
|
}
|
|
|
|
// The piece is already uploaded; commit it on a detached context so an
|
|
// expired shutdown deadline does not strand the chunk.
|
|
ctx = context.WithoutCancel(ctx)
|
|
|
|
// find out existing entry
|
|
fullpath := util.FullPath(targetFile)
|
|
entry, err := f.FindEntry(ctx, fullpath)
|
|
var offset int64 = 0
|
|
if err == filer_pb.ErrNotFound {
|
|
entry = &Entry{
|
|
FullPath: fullpath,
|
|
Attr: Attr{
|
|
Crtime: time.Now(),
|
|
Mtime: time.Now(),
|
|
Mode: os.FileMode(0644),
|
|
Uid: OS_UID,
|
|
Gid: OS_GID,
|
|
},
|
|
}
|
|
} else if err != nil {
|
|
return fmt.Errorf("find %s: %v", fullpath, err)
|
|
} else {
|
|
offset = int64(TotalSize(entry.GetChunks()))
|
|
}
|
|
|
|
// append to existing chunks
|
|
entry.Chunks = append(entry.GetChunks(), uploadResult.ToPbFileChunk(assignResult.Fid, offset, time.Now().UnixNano()))
|
|
|
|
// update the entry
|
|
err = f.CreateEntry(ctx, entry, nil, false, false, nil, false, f.MaxFilenameLength)
|
|
|
|
return err
|
|
}
|
|
|
|
// resolveMetadataLogAssignDiskType returns the disk type and matched path rule for
|
|
// metadata log volume assigns. Disk type uses the rule when set, otherwise
|
|
// Filer.DefaultDiskType (from -filer.disk), mirroring resolveAssignStorageOption.
|
|
func (f *Filer) resolveMetadataLogAssignDiskType(targetFile string) (string, *filer_pb.FilerConf_PathConf) {
|
|
if f.FilerConf == nil {
|
|
return f.DefaultDiskType, &filer_pb.FilerConf_PathConf{}
|
|
}
|
|
rule := f.FilerConf.MatchStorageRule(targetFile)
|
|
return util.Nvl(rule.DiskType, f.DefaultDiskType), rule
|
|
}
|
|
|
|
// metaLogCollectionFor resolves the system metadata log's collection: the
|
|
// filer.options.metaLog.collection override, then the filer default, then the
|
|
// matched storage rule.
|
|
func (f *Filer) metaLogCollectionFor(ruleCollection string) string {
|
|
return util.Nvl(f.metaLogTargetCollection, f.metaLogCollection, ruleCollection)
|
|
}
|
|
|
|
func (f *Filer) metaLogReplicationFor(ruleReplication string) string {
|
|
return util.Nvl(f.metaLogTargetReplication, f.metaLogReplication, ruleReplication)
|
|
}
|
|
|
|
func (f *Filer) assignAndUpload(ctx context.Context, targetFile string, data []byte) (*operation.AssignResult, *operation.UploadResult, error) {
|
|
// assign a volume location
|
|
diskType, rule := f.resolveMetadataLogAssignDiskType(targetFile)
|
|
assignRequest := &operation.VolumeAssignRequest{
|
|
Count: 1,
|
|
Collection: f.metaLogCollectionFor(rule.Collection),
|
|
Replication: f.metaLogReplicationFor(rule.Replication),
|
|
DiskType: diskType,
|
|
WritableVolumeCount: rule.VolumeGrowthCount,
|
|
ExpectedDataSize: uint64(len(data)),
|
|
}
|
|
|
|
assignResult, err := operation.Assign(ctx, f.GetMaster, f.GrpcDialOption, assignRequest)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("AssignVolume: %w", err)
|
|
}
|
|
if assignResult.Error != "" {
|
|
return nil, nil, fmt.Errorf("AssignVolume error: %v", assignResult.Error)
|
|
}
|
|
|
|
// upload data
|
|
targetUrl := "http://" + assignResult.Url + "/" + assignResult.Fid
|
|
uploadOption := &operation.UploadOption{
|
|
UploadUrl: targetUrl,
|
|
Filename: "",
|
|
Cipher: f.Cipher,
|
|
IsInputCompressed: false,
|
|
MimeType: "",
|
|
PairMap: nil,
|
|
Jwt: assignResult.Auth,
|
|
}
|
|
|
|
uploader, err := operation.NewUploader()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("upload data %s: %v", targetUrl, err)
|
|
}
|
|
|
|
uploadResult, err := uploader.UploadData(ctx, data, uploadOption)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("upload data %s: %v", targetUrl, err)
|
|
}
|
|
// println("uploaded to", targetUrl)
|
|
return assignResult, uploadResult, nil
|
|
}
|