From fb58aea8e1eef9e681421842998efb2b272ee283 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 2 Oct 2026 09:30:09 +0200 Subject: [PATCH] vendor: go.etcd.io/etcd/* v3.6.15 server: fix fsync snap directory when saving a received snapshot db release notes: https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md#v3615-2026-09-22 Signed-off-by: Sebastiaan van Stijn --- go.mod | 8 ++-- go.sum | 16 ++++---- .../go.etcd.io/etcd/api/v3/version/version.go | 2 +- .../etcd/client/pkg/v3/fileutil/purge.go | 5 +++ .../etcd/server/v3/etcdserver/api/snap/db.go | 37 ++++++++++++++++++- .../v3/etcdserver/api/snap/snapshotter.go | 10 +++-- vendor/modules.txt | 16 ++++---- 7 files changed, 67 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index de414791f8..67c33638cf 100644 --- a/go.mod +++ b/go.mod @@ -274,10 +274,10 @@ require ( github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300 // indirect github.com/zmap/zlint/v3 v3.5.0 // indirect - go.etcd.io/etcd/api/v3 v3.6.14 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.14 // indirect - go.etcd.io/etcd/pkg/v3 v3.6.14 // indirect - go.etcd.io/etcd/server/v3 v3.6.14 // indirect + go.etcd.io/etcd/api/v3 v3.6.15 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.15 // indirect + go.etcd.io/etcd/pkg/v3 v3.6.15 // indirect + go.etcd.io/etcd/server/v3 v3.6.15 // indirect go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.71.0 // indirect diff --git a/go.sum b/go.sum index 02f0b168e3..76c9d50f07 100644 --- a/go.sum +++ b/go.sum @@ -708,14 +708,14 @@ github.com/zmap/zlint/v3 v3.5.0 h1:Eh2B5t6VKgVH0DFmTwOqE50POvyDhUaU9T2mJOe1vfQ= github.com/zmap/zlint/v3 v3.5.0/go.mod h1:JkNSrsDJ8F4VRtBZcYUQSvnWFL7utcjDIn+FE64mlBI= go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU= go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk= -go.etcd.io/etcd/api/v3 v3.6.14 h1:3EEwTzQPiCyhLtacyl2ZkC0pMJWowghi61nJ9JSpO1w= -go.etcd.io/etcd/api/v3 v3.6.14/go.mod h1:L4HXnXoJ5NqXSxiwB4RihT5gGJJVvHEEOpEZ37g1Uj4= -go.etcd.io/etcd/client/pkg/v3 v3.6.14 h1:kqZf/BCRDWk9u5cNwBn1mTA+4GIZAU0POFPHmWHvo/I= -go.etcd.io/etcd/client/pkg/v3 v3.6.14/go.mod h1:Po3WXW01VRS7/gSDf8xjiY2rJTLmAwq/YmKAEz6u1+E= -go.etcd.io/etcd/pkg/v3 v3.6.14 h1:MAgY3G8aKMcjBIRay/4JjvCceuRAiEERrxTzkMtBlaA= -go.etcd.io/etcd/pkg/v3 v3.6.14/go.mod h1:grZHgzt+JCM8hnwSFrEAGxcl/VXksW0dRXfU0R2RmF8= -go.etcd.io/etcd/server/v3 v3.6.14 h1:LfN38zdvhpYnmyHG6shLWkfmMukkoyM19KUEZx1ywpM= -go.etcd.io/etcd/server/v3 v3.6.14/go.mod h1:yj1SNtvmNLLl7JUQY3vpaUBm24qAzTVixJn/1OKG6i4= +go.etcd.io/etcd/api/v3 v3.6.15 h1:Nysf/QR7vx8bx5oUR/yeMdy0YqtXoeELxn6UvNANrsQ= +go.etcd.io/etcd/api/v3 v3.6.15/go.mod h1:LlBr6CBsOUN/D011XFeIysDxI7JTQuegCX4DgseoOIw= +go.etcd.io/etcd/client/pkg/v3 v3.6.15 h1:6nqIEsCDLjZDh1fgHuQCSjVFv7pzdSYUq8zzpr9V/28= +go.etcd.io/etcd/client/pkg/v3 v3.6.15/go.mod h1:kCC9d5MnlhpVsgf2JVt2c3ydApI6sZo40vlnr79jGKU= +go.etcd.io/etcd/pkg/v3 v3.6.15 h1:LyJ+AOftel5J97A6i5eiDSplXH89y2TpeaHunUBe2sg= +go.etcd.io/etcd/pkg/v3 v3.6.15/go.mod h1:Cka235NdNGX93CscaD5LtEApThVO2HHnBl76pwmWGQs= +go.etcd.io/etcd/server/v3 v3.6.15 h1:IFlqNq0ia29PAcT73gfZjaYO/R+QCOavhenkAP4Y1N0= +go.etcd.io/etcd/server/v3 v3.6.15/go.mod h1:zuF7XwGYkIj7fFtUMYp+AFpa+SdnNj05sbsabmUnGrY= go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= diff --git a/vendor/go.etcd.io/etcd/api/v3/version/version.go b/vendor/go.etcd.io/etcd/api/v3/version/version.go index 436b9ffb9a..a7aa831c50 100644 --- a/vendor/go.etcd.io/etcd/api/v3/version/version.go +++ b/vendor/go.etcd.io/etcd/api/v3/version/version.go @@ -26,7 +26,7 @@ import ( var ( // MinClusterVersion is the min cluster version this etcd binary is compatible with. MinClusterVersion = "3.0.0" - Version = "3.6.14" + Version = "3.6.15" APIVersion = "unknown" // Git SHA Value will be set during build diff --git a/vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/purge.go b/vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/purge.go index 026ea03230..e40bfd534f 100644 --- a/vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/purge.go +++ b/vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/purge.go @@ -74,6 +74,11 @@ func purgeFile(lg *zap.Logger, dirname string, suffix string, max uint, interval } } if err = os.Remove(f); err != nil { + if flock { + if closeErr := l.Close(); closeErr != nil { + lg.Error("failed to unlock/close", zap.String("path", l.Name()), zap.Error(closeErr)) + } + } lg.Error("failed to remove file", zap.String("path", f), zap.Error(err)) errC <- err return diff --git a/vendor/go.etcd.io/etcd/server/v3/etcdserver/api/snap/db.go b/vendor/go.etcd.io/etcd/server/v3/etcdserver/api/snap/db.go index e8add5e327..8e106d4d81 100644 --- a/vendor/go.etcd.io/etcd/server/v3/etcdserver/api/snap/db.go +++ b/vendor/go.etcd.io/etcd/server/v3/etcdserver/api/snap/db.go @@ -30,8 +30,10 @@ import ( var ErrNoDBSnapshot = errors.New("snap: snapshot file doesn't exist") -// SaveDBFrom saves snapshot of the database from the given reader. It -// guarantees the save operation is atomic. +// SaveDBFrom atomically saves a database snapshot from r. Before a successful +// return, it syncs the file and containing directory so the rename is durable +// on storage that honors fsync. The receiver processes the Raft message only +// afterward, so its WAL snapshot record cannot precede snap.db. func (s *Snapshotter) SaveDBFrom(r io.Reader, id uint64) (int64, error) { start := time.Now() @@ -54,6 +56,11 @@ func (s *Snapshotter) SaveDBFrom(r io.Reader, id uint64) (int64, error) { fn := s.dbFilePath(id) if fileutil.Exist(fn) { os.Remove(f.Name()) + // A prior SaveDBFrom can have renamed this file and crashed before + // syncing the directory. Sync again before reporting success. + if err = s.fsyncDir(s.dir); err != nil { + return n, err + } return n, nil } err = os.Rename(f.Name(), fn) @@ -62,6 +69,19 @@ func (s *Snapshotter) SaveDBFrom(r io.Reader, id uint64) (int64, error) { return n, err } + // gofail: var snapDBRenameBeforeDirSync struct{} + + // The file is synced above. Sync the directory to make the renamed entry + // durable before the snapshot receiver processes the Raft message. + if err = s.fsyncDir(s.dir); err != nil { + s.lg.Warn( + "failed to fsync snap directory after saving database snapshot", + zap.String("path", fn), + zap.Error(err), + ) + return n, err + } + s.lg.Info( "saved database snapshot to disk", zap.String("path", fn), @@ -97,3 +117,16 @@ func (s *Snapshotter) DBFilePath(id uint64) (string, error) { func (s *Snapshotter) dbFilePath(id uint64) string { return filepath.Join(s.dir, fmt.Sprintf("%016x.snap.db", id)) } + +// fsyncSnapDir makes a renamed snapshot database durable. On Linux, syncing +// the file alone does not necessarily persist its containing directory entry. +func fsyncSnapDir(dir string) error { + // gofail: var snapDBDirSyncError string + // return errors.New(snapDBDirSyncError) + d, err := fileutil.OpenDir(dir) + if err != nil { + return err + } + defer d.Close() + return fileutil.Fsync(d) +} diff --git a/vendor/go.etcd.io/etcd/server/v3/etcdserver/api/snap/snapshotter.go b/vendor/go.etcd.io/etcd/server/v3/etcdserver/api/snap/snapshotter.go index 0de6f9b91e..223bc888f6 100644 --- a/vendor/go.etcd.io/etcd/server/v3/etcdserver/api/snap/snapshotter.go +++ b/vendor/go.etcd.io/etcd/server/v3/etcdserver/api/snap/snapshotter.go @@ -51,8 +51,9 @@ var ( ) type Snapshotter struct { - lg *zap.Logger - dir string + lg *zap.Logger + dir string + fsyncDir func(string) error } func New(lg *zap.Logger, dir string) *Snapshotter { @@ -60,8 +61,9 @@ func New(lg *zap.Logger, dir string) *Snapshotter { lg = zap.NewNop() } return &Snapshotter{ - lg: lg, - dir: dir, + lg: lg, + dir: dir, + fsyncDir: fsyncSnapDir, } } diff --git a/vendor/modules.txt b/vendor/modules.txt index a19d85ae4b..cf35ed1836 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1764,26 +1764,26 @@ go.etcd.io/bbolt go.etcd.io/bbolt/errors go.etcd.io/bbolt/internal/common go.etcd.io/bbolt/internal/freelist -# go.etcd.io/etcd/api/v3 v3.6.14 -## explicit; go 1.25.0 +# go.etcd.io/etcd/api/v3 v3.6.15 +## explicit; go 1.26 go.etcd.io/etcd/api/v3/authpb go.etcd.io/etcd/api/v3/etcdserverpb go.etcd.io/etcd/api/v3/membershippb go.etcd.io/etcd/api/v3/mvccpb go.etcd.io/etcd/api/v3/version go.etcd.io/etcd/api/v3/versionpb -# go.etcd.io/etcd/client/pkg/v3 v3.6.14 -## explicit; go 1.25.0 +# go.etcd.io/etcd/client/pkg/v3 v3.6.15 +## explicit; go 1.26 go.etcd.io/etcd/client/pkg/v3/fileutil go.etcd.io/etcd/client/pkg/v3/verify -# go.etcd.io/etcd/pkg/v3 v3.6.14 -## explicit; go 1.25.0 +# go.etcd.io/etcd/pkg/v3 v3.6.15 +## explicit; go 1.26 go.etcd.io/etcd/pkg/v3/crc go.etcd.io/etcd/pkg/v3/idutil go.etcd.io/etcd/pkg/v3/ioutil go.etcd.io/etcd/pkg/v3/pbutil -# go.etcd.io/etcd/server/v3 v3.6.14 -## explicit; go 1.25.0 +# go.etcd.io/etcd/server/v3 v3.6.15 +## explicit; go 1.26 go.etcd.io/etcd/server/v3/etcdserver/api/snap go.etcd.io/etcd/server/v3/etcdserver/api/snap/snappb go.etcd.io/etcd/server/v3/storage/wal