Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package rqlite for openSUSE:Factory checked in at 2026-06-25 10:52:13 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rqlite (Old) and /work/SRC/openSUSE:Factory/.rqlite.new.2088 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rqlite" Thu Jun 25 10:52:13 2026 rev:51 rq:1361486 version:10.2.4 Changes: -------- --- /work/SRC/openSUSE:Factory/rqlite/rqlite.changes 2026-05-30 22:58:25.484985549 +0200 +++ /work/SRC/openSUSE:Factory/.rqlite.new.2088/rqlite.changes 2026-06-25 10:56:23.427685899 +0200 @@ -1,0 +2,11 @@ +Tue Jun 23 18:39:20 UTC 2026 - Andreas Stieger <[email protected]> + +- Update to version 10.2.4: + * Fix some non-AWS S3-compatible stores +- includes changes from 10.2.3 + * Check snapshot store integrity before first actual use + * Disable HTTP timeout for boot and restore operations via the shell + * HTTP layer ignores ErrNoWALToSnapshot when snapshotting + * Close Snapshot Streamer ASAP when restoring to local node + +------------------------------------------------------------------- Old: ---- rqlite-10.2.0.tar.xz New: ---- rqlite-10.2.4.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rqlite.spec ++++++ --- /var/tmp/diff_new_pack.e2c0B0/_old 2026-06-25 10:56:24.427720384 +0200 +++ /var/tmp/diff_new_pack.e2c0B0/_new 2026-06-25 10:56:24.431720523 +0200 @@ -17,7 +17,7 @@ Name: rqlite -Version: 10.2.0 +Version: 10.2.4 Release: 0 Summary: Distributed relational database built on SQLite License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.e2c0B0/_old 2026-06-25 10:56:24.475722040 +0200 +++ /var/tmp/diff_new_pack.e2c0B0/_new 2026-06-25 10:56:24.479722178 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/rqlite/rqlite.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v10.2.0</param> + <param name="revision">v10.2.4</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">enable</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.e2c0B0/_old 2026-06-25 10:56:24.503723005 +0200 +++ /var/tmp/diff_new_pack.e2c0B0/_new 2026-06-25 10:56:24.507723143 +0200 @@ -1,7 +1,7 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/rqlite/rqlite.git</param> - <param name="changesrevision">6809aa3b6f373b86aa4e1265b9c47bf5d09e1819</param> + <param name="changesrevision">d950fea281016fdf4006da72afa93ec04671f99a</param> </service> </servicedata> (No newline at EOF) ++++++ rqlite-10.2.0.tar.xz -> rqlite-10.2.4.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/CHANGELOG.md new/rqlite-10.2.4/CHANGELOG.md --- old/rqlite-10.2.0/CHANGELOG.md 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/CHANGELOG.md 2026-06-21 21:25:37.000000000 +0200 @@ -1,3 +1,26 @@ +## v10.2.4 (June 21st 2026) +### Implementation changes and bug fixes +- [PR #2700](https://github.com/rqlite/rqlite/pull/2700): Unit test Snapshot Store `Verify`. +- [PR #2701](https://github.com/rqlite/rqlite/pull/2701): Set RequestChecksumCalculationWhenRequired for S3-compatible stores. Fixes issue [#2699](https://github.com/rqlite/rqlite/issues/2699). Thanks @NerdyShawn + +## v10.2.3 (June 20th 2026) +### Implementation changes and bug fixes +- [PR #2695](https://github.com/rqlite/rqlite/pull/2695): Check snapshot store integrity before first actual use. Triggered by [#2687](https://github.com/rqlite/rqlite/issues/2687). +- [PR #2696](https://github.com/rqlite/rqlite/pull/2696): Disable HTTP timeout for boot and restore operations via the shell. +- [PR #2697](https://github.com/rqlite/rqlite/pull/2697): HTTP layer ignores `ErrNoWALToSnapshot` when snapshotting. +- [PR #2698](https://github.com/rqlite/rqlite/pull/2698): Close Snapshot Streamer ASAP when restoring to local node. + +## v10.2.2 (June 19th 2026) +### Implementation changes and bug fixes +- [PR #2694](https://github.com/rqlite/rqlite/pull/2694): Force-close in-flight node-to-node connections during shutdown, so a stalled Raft snapshot transfer cannot block a node from shutting down. Triggered by [#2687](https://github.com/rqlite/rqlite/issues/2687). +- [PR #2693](https://github.com/rqlite/rqlite/pull/2693): Export `Check` method on Snapshot Store. + +## v10.2.1 (June 17th 2026) +### Implementation changes and bug fixes +- [PR #2686](https://github.com/rqlite/rqlite/pull/2686): Limit number of connections to cluster service. Fixes issue [#2617](https://github.com/rqlite/rqlite/issues/2617). Thanks @shrtyk +- [PR #2688](https://github.com/rqlite/rqlite/pull/2688): Upgrade dependencies via `go get`. +- [PR #2689](https://github.com/rqlite/rqlite/pull/2689): Upgrade to SQLite 3.35.2. + ## v10.2.0 (May 29th 2026) ### New features - [PR #2661](https://github.com/rqlite/rqlite/pull/2661), [PR #2679](https://github.com/rqlite/rqlite/pull/2679), [PR #2683](https://github.com/rqlite/rqlite/pull/2683), [PR #2685](https://github.com/rqlite/rqlite/pull/2685): Support verifying mTLS peer Common Name. Thanks @hifi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/auto/aws/s3.go new/rqlite-10.2.4/auto/aws/s3.go --- old/rqlite-10.2.0/auto/aws/s3.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/auto/aws/s3.go 2026-06-21 21:25:37.000000000 +0200 @@ -58,6 +58,7 @@ // Load the default config cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region), + config.WithRequestChecksumCalculation(aws.RequestChecksumCalculationWhenRequired), ) if err != nil { return nil, fmt.Errorf("unable to load SDK config, %v", err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/cluster/service.go new/rqlite-10.2.4/cluster/service.go --- old/rqlite-10.2.0/cluster/service.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/cluster/service.go 2026-06-21 21:25:37.000000000 +0200 @@ -5,6 +5,7 @@ "compress/gzip" "context" "encoding/binary" + "errors" "expvar" "fmt" "io" @@ -19,11 +20,17 @@ "github.com/rqlite/rqlite/v10/auth" "github.com/rqlite/rqlite/v10/cluster/proto" command "github.com/rqlite/rqlite/v10/command/proto" + "github.com/rqlite/rqlite/v10/internal/rsync" pb "google.golang.org/protobuf/proto" ) -// stats captures stats for the Cluster service. -var stats *expvar.Map +var ( + // stats captures stats for the Cluster service. + stats *expvar.Map + + // ErrServiceOpen is returned when the service is already open. + ErrServiceOpen = errors.New("service already open") +) const ( numGetNodeAPIRequest = "num_get_node_api_req" @@ -39,6 +46,7 @@ numStepdownRequest = "num_stepdown_req" numBroadcastHWMRequest = "num_broadcast_hwm_req" numHWMUpdateDropped = "num_hwm_update_dropped" + numConnRejected = "num_conn_rejected" numClientRetries = "num_client_retries" numGetNodeAPIRequestRetries = "num_get_node_api_req_retries" @@ -66,6 +74,10 @@ // connection is closed, preventing stalled clients from holding // goroutines indefinitely. connReadTimeout = 30 * time.Second + + // maxConcurrentConns bounds the number of connections the service handles + // concurrently, preventing connection floods from spawning unbounded goroutines. + maxConcurrentConns = 512 ) func init() { @@ -97,6 +109,7 @@ stats.Add(numClientReadTimeouts, 0) stats.Add(numClientWriteTimeouts, 0) stats.Add(numClientForceNewConn, 0) + stats.Add(numConnRejected, 0) } // Dialer is the interface dialers must implement. @@ -165,14 +178,21 @@ credentialStore CredentialStore mu sync.RWMutex - https bool // Serving HTTPS? - apiAddr string // host:port this node serves the HTTP API. - version string // Version of software this node is running. + https bool // Serving HTTPS? + apiAddr string // host:port this node serves the HTTP API. + version string // Version of software this node is running. + open *rsync.AtomicBool // Tracks whether the service is currently open. // connTimeout is the maximum time to wait for data on a // connection. Must be set before Open is called. connTimeout time.Duration + // connLimit is the maximum number of concurrent service connections. + connLimit int + + // connLimiterCh limits the number of in-service connections. + connLimiterCh chan struct{} + hwmMu sync.RWMutex hwmUpdateC chan<- uint64 // Channel for HWM updates @@ -189,11 +209,20 @@ logger: log.New(os.Stderr, "[cluster] ", log.LstdFlags), credentialStore: credentialStore, connTimeout: connReadTimeout, + connLimit: maxConcurrentConns, + open: rsync.NewAtomicBool(), } } // Open opens the Service. func (s *Service) Open() error { + if s.open.Is() { + return ErrServiceOpen + } + + s.connLimiterCh = make(chan struct{}, s.connLimit) + s.open.Set() + go s.serve() s.logger.Println("service listening on", s.addr) return nil @@ -201,8 +230,11 @@ // Close closes the service. func (s *Service) Close() error { - s.ln.Close() - return nil + err := s.ln.Close() + if err == nil { + s.open.Unset() + } + return err } // RegisterHWMUpdate registers a channel to receive highwater mark update requests. @@ -275,6 +307,21 @@ return st, nil } +// SetConnectionLimit sets the maximum number of concurrent service connections. +// It must be called before Open. Non-positive values use the default limit. +func (s *Service) SetConnectionLimit(n int) error { + if s.open.Is() { + return ErrServiceOpen + } + + if n <= 0 { + n = maxConcurrentConns + } + + s.connLimit = n + return nil +} + func (s *Service) serve() error { for { conn, err := s.ln.Accept() @@ -282,7 +329,16 @@ return err } - go s.handleConn(conn) + select { + case s.connLimiterCh <- struct{}{}: + go func() { + defer func() { <-s.connLimiterCh }() + s.handleConn(conn) + }() + default: + _ = conn.Close() + stats.Add(numConnRejected, 1) + } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/cluster/service_test.go new/rqlite-10.2.4/cluster/service_test.go --- old/rqlite-10.2.0/cluster/service_test.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/cluster/service_test.go 2026-06-21 21:25:37.000000000 +0200 @@ -3,6 +3,7 @@ import ( "context" "crypto/tls" + "errors" "fmt" "io" "net" @@ -539,6 +540,100 @@ } } +func Test_ServiceSetConnectionLimitOpen(t *testing.T) { + ml := mustNewMockTransport() + s := New(ml, mustNewMockDatabase(), mustNewMockManager(), mustNewMockCredentialStore()) + + if err := s.Open(); err != nil { + t.Fatalf("failed to open cluster service") + } + defer s.Close() + + if err := s.SetConnectionLimit(1); !errors.Is(err, ErrServiceOpen) { + t.Fatalf("expected ErrServiceOpen, got: %v", err) + } +} + +func Test_ServiceConnLimiterRejectAndRelease(t *testing.T) { + ml := mustNewMockTransport() + s := New(ml, mustNewMockDatabase(), mustNewMockManager(), mustNewMockCredentialStore()) + if err := s.SetConnectionLimit(1); err != nil { + t.Fatalf("failed to set connection limit: %s", err) + } + + if err := s.Open(); err != nil { + t.Fatalf("failed to open cluster service") + } + defer s.Close() + + md := &mockDialer{Dialer: ml} + t.Cleanup(md.CloseAll) + + c1 := NewClient(md, 30*time.Second) + _, err := c1.GetNodeMeta(context.Background(), s.Addr(), noRetries, 5*time.Second) + if err != nil { + t.Fatalf("failed to get node metadata on first connection: %s", err) + } + + // The first client's pooled connection remains open and holds the limiter slot. + c2 := NewClient(md, 30*time.Second) + _, err = c2.GetNodeMeta(context.Background(), s.Addr(), noRetries, 500*time.Millisecond) + if err == nil { + t.Fatalf("expected request on rejected connection to fail") + } + if netErr, ok := errors.AsType[net.Error](err); ok && netErr.Timeout() { + t.Fatalf("expected connection to be rejected without timing out, got: %v", err) + } + + // Releasing the idle connection should free its limiter slot. + if err := md.CloseConn(0); err != nil { + t.Fatalf("failed to close first connection: %s", err) + } + + // A new client request should be accepted once capacity is available again. + c3 := NewClient(md, 30*time.Second) + _, err = c3.GetNodeMeta(context.Background(), s.Addr(), noRetries, 500*time.Millisecond) + if err != nil { + t.Fatalf("failed to get node metadata after release: %s", err) + } +} + +type mockDialer struct { + Dialer + mu sync.Mutex + conns []net.Conn +} + +func (d *mockDialer) Dial(address string, timeout time.Duration) (net.Conn, error) { + conn, err := d.Dialer.Dial(address, timeout) + if err != nil { + return nil, err + } + + d.mu.Lock() + d.conns = append(d.conns, conn) + d.mu.Unlock() + + return conn, nil +} + +func (d *mockDialer) CloseConn(index int) error { + d.mu.Lock() + defer d.mu.Unlock() + if index < 0 || index >= len(d.conns) { + return fmt.Errorf("connection %d not tracked", index) + } + return d.conns[index].Close() +} + +func (d *mockDialer) CloseAll() { + d.mu.Lock() + defer d.mu.Unlock() + for _, conn := range d.conns { + _ = conn.Close() + } +} + type mockTransport struct { tn net.Listener remoteEncrypted bool diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/cmd/rqlite/backup.go new/rqlite-10.2.4/cmd/rqlite/backup.go --- old/rqlite-10.2.0/cmd/rqlite/backup.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/cmd/rqlite/backup.go 2026-06-21 21:25:37.000000000 +0200 @@ -137,9 +137,12 @@ contentType = "application/octet-stream" } + // Restoring uploads the whole file and waits for the node to load it, which + // can take far longer than a normal request, so use a client without an + // overall request timeout to avoid aborting the upload mid-transfer. u := fmt.Sprintf("%sdb/load", client.Prefix) headers := http.Header{"Content-Type": {contentType}} - resp, err := client.PostWithHeaders(u, bytes.NewReader(restoreFile), headers) + resp, err := client.WithoutTimeout().PostWithHeaders(u, bytes.NewReader(restoreFile), headers) if err != nil { return err } @@ -192,8 +195,12 @@ } defer fd.Close() + // Booting streams a potentially large SQLite file, and the node must finish + // loading it before responding, so this can take far longer than a normal + // request. Use a client without an overall request timeout to avoid aborting + // the upload mid-transfer. u := fmt.Sprintf("%sboot", client.Prefix) - resp, err := client.Post(u, fd) + resp, err := client.WithoutTimeout().Post(u, fd) if err != nil { return err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/cmd/rqlite/http/client.go new/rqlite-10.2.4/cmd/rqlite/http/client.go --- old/rqlite-10.2.0/cmd/rqlite/http/client.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/cmd/rqlite/http/client.go 2026-06-21 21:25:37.000000000 +0200 @@ -103,6 +103,21 @@ } } +// WithoutTimeout returns a shallow copy of the Client whose underlying +// http.Client has no overall request timeout, while sharing the same Transport, +// hosts, credentials, and configuration as the receiver. It is intended for +// long-running streaming uploads (e.g. boot and restore) where a fixed +// whole-request deadline is inappropriate; liveness is instead governed by the +// Transport's connection-level timeouts. The receiver is left unmodified. +func (c *Client) WithoutTimeout() *Client { + httpClient := *c.Client + httpClient.Timeout = 0 + + clientCopy := *c + clientCopy.Client = &httpClient + return &clientCopy +} + // GetDirect sends a GET request to the exact URL provided, with authentication // but without host override or failover. Use this when you need to target a // specific node rather than any node in the cluster. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/cmd/rqlite/main.go new/rqlite-10.2.4/cmd/rqlite/main.go --- old/rqlite-10.2.0/cmd/rqlite/main.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/cmd/rqlite/main.go 2026-06-21 21:25:37.000000000 +0200 @@ -877,7 +877,14 @@ func createHostList(argv *argT) []string { var hosts = make([]string, 0) hosts = append(hosts, address6(argv)) - hosts = append(hosts, strings.Split(argv.Alternatives, ",")...) + // Alternatives is a comma-separated list. Skip empty entries so a blank or + // trailing-comma value doesn't add a bogus "" host to the failover list + // (strings.Split("", ",") returns [""]). + for _, a := range strings.Split(argv.Alternatives, ",") { + if a = strings.TrimSpace(a); a != "" { + hosts = append(hosts, a) + } + } return hosts } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/cmd/rqlited/main.go new/rqlite-10.2.4/cmd/rqlited/main.go --- old/rqlite-10.2.0/cmd/rqlited/main.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/cmd/rqlited/main.go 2026-06-21 21:25:37.000000000 +0200 @@ -270,7 +270,15 @@ str.Stepdown(true, "") } muxLn.Close() - defer mux.Close() + + // Close the mux, which force-closes any in-flight node-to-node connections + // (e.g. a Raft snapshot transfer from a peer that is itself shutting down). + // Such internode reads have no deadline, so this must happen before + // str.Close below, otherwise the pre-close snapshot and Raft shutdown could + // block indefinitely on the Raft run goroutine. + if err := mux.Close(); err != nil { + log.Printf("failed to close mux during shutdown: %s", err.Error()) + } if err := str.Close(true); err != nil { log.Printf("failed to close store: %s", err.Error()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/go.mod new/rqlite-10.2.4/go.mod --- old/rqlite-10.2.0/go.mod 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/go.mod 2026-06-21 21:25:37.000000000 +0200 @@ -3,15 +3,15 @@ go 1.26 require ( - github.com/aws/aws-sdk-go-v2 v1.41.7 - github.com/aws/aws-sdk-go-v2/config v1.32.17 - github.com/aws/aws-sdk-go-v2/credentials v1.19.16 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.18 - github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 + github.com/aws/aws-sdk-go-v2 v1.42.0 + github.com/aws/aws-sdk-go-v2/config v1.32.25 + github.com/aws/aws-sdk-go-v2/credentials v1.19.24 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.28 + github.com/aws/aws-sdk-go-v2/service/s3 v1.104.0 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/raft v1.7.3 github.com/klauspost/compress v1.18.6 - github.com/mattn/go-sqlite3 v1.14.44 + github.com/mattn/go-sqlite3 v1.14.45 github.com/mkideal/cli v0.2.7 github.com/mkideal/pkg v0.1.3 github.com/peterh/liner v1.2.2 @@ -19,26 +19,26 @@ github.com/rqlite/rqlite-disco-clients v0.0.0-20250205044118-8ada2b350099 github.com/rqlite/sql v0.0.0-20260224021119-1b2524a41372 go.etcd.io/bbolt v1.4.3 - golang.org/x/net v0.55.0 + golang.org/x/net v0.56.0 google.golang.org/protobuf v1.36.11 ) require ( github.com/armon/go-metrics v0.5.4 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect - github.com/aws/smithy-go v1.25.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.2.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.31.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 // indirect + github.com/aws/smithy-go v1.27.2 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect @@ -47,7 +47,7 @@ github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect - github.com/hashicorp/consul/api v1.34.2 // indirect + github.com/hashicorp/consul/api v1.34.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect @@ -59,27 +59,27 @@ github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/serf v0.10.2 // indirect github.com/labstack/gommon v0.5.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-isatty v0.0.22 // indirect - github.com/mattn/go-runewidth v0.0.23 // indirect + github.com/mattn/go-runewidth v0.0.24 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mkideal/expr v0.1.0 // indirect - go.etcd.io/etcd/api/v3 v3.6.11 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.11 // indirect - go.etcd.io/etcd/client/v3 v3.6.11 // indirect + go.etcd.io/etcd/api/v3 v3.6.12 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.12 // indirect + go.etcd.io/etcd/client/v3 v3.6.12 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect - golang.org/x/crypto v0.51.0 // indirect - golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/term v0.43.0 // indirect - golang.org/x/text v0.37.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 // indirect - google.golang.org/grpc v1.81.0 // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/term v0.44.0 // indirect + golang.org/x/text v0.38.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260615183401-62b3387ff324 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260615183401-62b3387ff324 // indirect + google.golang.org/grpc v1.81.1 // indirect ) replace ( github.com/armon/go-metrics => github.com/hashicorp/go-metrics v0.5.1 - github.com/mattn/go-sqlite3 => github.com/rqlite/go-sqlite3 v1.47.0 + github.com/mattn/go-sqlite3 => github.com/rqlite/go-sqlite3 v1.48.0 ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/go.sum new/rqlite-10.2.4/go.sum --- old/rqlite-10.2.0/go.sum 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/go.sum 2026-06-21 21:25:37.000000000 +0200 @@ -5,44 +5,44 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= -github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY= -github.com/aws/aws-sdk-go-v2/config v1.32.17 h1:FpL4/758/diKwqbytU0prpuiu60fgXKUWCpDJtApclU= -github.com/aws/aws-sdk-go-v2/config v1.32.17/go.mod h1:OXqUMzgXytfoF9JaKkhrOYsyh72t9G+MJH8mMRaexOE= -github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU= -github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.18 h1:9XFUd2lkr7VrbE4Qtrhm7AtNhGgZeGFI5QLZtQIflj8= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.18/go.mod h1:trImuKdWelQIJALvyGj6sKolJ1W8t628JOoTdDGVL9Q= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 h1:etqBTKY581iwLL/H/S2sVgk3C9lAsTJFeXWFDsDcWOU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg= -github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk= -github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio= -github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= -github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/aws-sdk-go-v2 v1.42.0 h1:XvXMJTkFQtpBKIWZnmr9ZEOc2InWM2yldjXEJ/bymhA= +github.com/aws/aws-sdk-go-v2 v1.42.0/go.mod h1:27+ACypSLljLAEKsCYOmrjKh83vuTRkuAe9Uv/3A4bg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 h1:p1BBrg/Hhp6uK7zpejeI8QFXHJeC/mynzi04Sl03k9g= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13/go.mod h1:8cIfkE9MDhkRZGpQ22aV6/lkYeYSozpz16Smrs5x4Ls= +github.com/aws/aws-sdk-go-v2/config v1.32.25 h1:ACCejvStYoilgwrfegSt5ZntCbPrk52qfwyNcnl3omM= +github.com/aws/aws-sdk-go-v2/config v1.32.25/go.mod h1:LJyU8sDRbXUxFn8xMJIGP+v9QYYwveNLI8a/giAOiAs= +github.com/aws/aws-sdk-go-v2/credentials v1.19.24 h1:2hQqYCV9yqyePQ9o6dCrZc/zO8U3TwPr9mIKlZnPu/I= +github.com/aws/aws-sdk-go-v2/credentials v1.19.24/go.mod h1:IDwpACtwqHLISdzfwUUNq4P9DsB/h5BLg4FwJPNfqFY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 h1:r6qZHbT+wxgWO/e9vYNUEtg7lv5+UN3pRqKhLXvnArg= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29/go.mod h1:QRnaRcTVGKPGRy8w78HMQtKUGRYcnMZAANATkeVA6Mo= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.28 h1:ez4y5o7sa0uaRI8BquYOXtZpioUPhbQEh7Igm88oV9U= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.28/go.mod h1:TpmZOrQA12XKEpVypgBGZSQBsm1WUTndCiSnbDsbvug= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 h1:f3vKqSo13fhTYb+JEcXwXefZQE26I1FB5eTSniU67ko= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29/go.mod h1:MzoLFUArKGpGD+ukmPiTPG1X5x4o6M2kq4v2dr1FiEc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 h1:RdwIf/CuUsvJX3RgJagbOyotl/cxoLY4xviKuE7p2GY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29/go.mod h1:71wt8W2EgswdZy9Mf9KNnzxZ3TiZlv4caKghPktDOkA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 h1:VTGy885W5DKBxWRUJbym9hytNaYzsyaPkCHGRRMAOhU= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30/go.mod h1:AS0HycUvJRFvTt613AYDOgO2jzw+00cVSMny8XB3yMY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 h1:ZD2+BSw9vFsNlKYIasSNt3uDbjqqXIBcM13UJv/Lx2k= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12/go.mod h1:Ms4zlcVBbXbiP7EVLhl+lgjvA/a7YphqQ3Ih3174EmI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22 h1:V51LGlOq/1VsDsHUdoklAQi7rMmx4qQubvFYAlP2254= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22/go.mod h1:4Pzhyz8hJOm2bepgl+NjvRx8vlUFAIIvJnZ/MkcNPpU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 h1:DRebniUGZ2MqiiIVmQJ04vIXr918hubdHMnarSLEWyU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29/go.mod h1:LfRkPCD8YHDM2E5eTkos2UpwYeZnBcVarTa8L59bJHA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29 h1:hiME6pBzC7OTl9LMtlyTWBuEl1f4QBcUmFDKC7MLXtc= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29/go.mod h1:G7RP+uhagpKtKhd1BM9N6JQqjCcGEU47K5lBVZQyRQw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.104.0 h1:ta8csKy5vN91F3i5gGR85lFV0srBqySEji7Jroes6rE= +github.com/aws/aws-sdk-go-v2/service/s3 v1.104.0/go.mod h1:77ZAgynvx1txMvDG8gGWoWkO1augYDxkp9JElWFgjQU= +github.com/aws/aws-sdk-go-v2/service/signin v1.2.0 h1:3nXpRcFwRCW8n7HgO2QGy0Dc20eQNfBuUemGQhpF8m8= +github.com/aws/aws-sdk-go-v2/service/signin v1.2.0/go.mod h1:LxYujSTLPRlp2vTtcUO/+1ilrew8ytt6SvQyOgejzFQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.31.3 h1:ey1XLTYXb9PcLt4535632o5kCGXNXEhNb620Dqwuylo= +github.com/aws/aws-sdk-go-v2/service/sso v1.31.3/go.mod h1:Lk7PlmoTYryQmyBG0EXqj5BcUbj3whXdU2s3yGI3EAc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 h1:yLr03zQE/5Eu5l3QU0Si+xMbLMbSDF2YXsigqXngs6g= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6/go.mod h1:Q5N6icH+KJZDLh+ESNwzdv6cZ6vLFF/egy3IOxWhmz4= +github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 h1:VrIhKRCSK1umelSgB9RghvA9RTUYeQffyAS5ApXehNI= +github.com/aws/aws-sdk-go-v2/service/sts v1.43.3/go.mod h1:r8wkDOuLaaMFqFiYAb8dGY2A3gJCOujMc6CFOVC4Zhc= +github.com/aws/smithy-go v1.27.2 h1:y9NPmSE6am6LjEFPfqHqG/jJk7AauQvhCJONKh7kpzk= +github.com/aws/smithy-go v1.27.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -109,8 +109,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= -github.com/hashicorp/consul/api v1.34.2 h1:B5jqSSKwWyY8U8WiGS5vmPEPkkF0bAvrECykdZkDR80= -github.com/hashicorp/consul/api v1.34.2/go.mod h1:+gAdHQa2zvgYX3ZfcgITtnYCSj6AgS/cgotvCKaE+b8= +github.com/hashicorp/consul/api v1.34.3 h1:OiZaQnwkS6uvutie3CF6NFXj8uScNezDlsU9MEqKT0s= +github.com/hashicorp/consul/api v1.34.3/go.mod h1:A4wKd7yw7Wz4zn07p74+o0bLBi5dXsSDMMcMCEinY40= github.com/hashicorp/consul/sdk v0.18.1 h1:RDTeBvAeOveI2xI86sV+8WkaN7OkP4zz+cG3fOobDCM= github.com/hashicorp/consul/sdk v0.18.1/go.mod h1:XdP2tEJmAvlK4jgoKTTtohGkRJlS4mU44mv9/sjU21s= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -181,8 +181,8 @@ github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -190,8 +190,8 @@ github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= -github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU= +github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= @@ -237,8 +237,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/rqlite/go-sqlite3 v1.47.0 h1:doFNzjRdGAmxuHsH1LoLPX6IBaaht4uNoMag9IHK5Qo= -github.com/rqlite/go-sqlite3 v1.47.0/go.mod h1:X4XrrRWGScIJdfec2Tj0/M8C8RhUUN8Ysn+AcOovZwM= +github.com/rqlite/go-sqlite3 v1.48.0 h1:3z/QU1WEFrywWvhjhLvZ+SBkaoIQZtkNcHgpCaxE1eg= +github.com/rqlite/go-sqlite3 v1.48.0/go.mod h1:XIq9SQymduohsJwPy++jWepF9QJ5gT/S3SkUivYSVZ0= github.com/rqlite/raft-boltdb/v2 v2.0.0-20230523104317-c08e70f4de48 h1:NZ62M+kT0JqhyFUMc8I4SMmfmD4NGJxhb2ePJQXjryc= github.com/rqlite/raft-boltdb/v2 v2.0.0-20230523104317-c08e70f4de48/go.mod h1:CRnsxgy5G8fAf5J+AM0yrsSdxXHKkIYOaq2sm+Q4DYc= github.com/rqlite/rqlite-disco-clients v0.0.0-20250205044118-8ada2b350099 h1:5cqkVLdl6sGJSY3kiF2dqaA3bD+8OS5FUdZqO0BxXLU= @@ -268,12 +268,12 @@ go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= -go.etcd.io/etcd/api/v3 v3.6.11 h1:XFGTgrJ8nak3kB4NgMG8t7NT+lEeuuvKQAqUHKVgkWQ= -go.etcd.io/etcd/api/v3 v3.6.11/go.mod h1:HYfTh0jyh+uFgp6gMbxJteIDYY97yMuYz85Rnw6Gy9o= -go.etcd.io/etcd/client/pkg/v3 v3.6.11 h1:e41mp315Yn3QMGPmEzCyLsMINgJXTY/dX8kM++1csxU= -go.etcd.io/etcd/client/pkg/v3 v3.6.11/go.mod h1:DysuMe/inqRyC/1tjRR6hReH/VV9Lufs27YKSKBWWJg= -go.etcd.io/etcd/client/v3 v3.6.11 h1:LAByD96VmmeuairkvdAcE0RZnrmGz/q3ceeWePo9bwc= -go.etcd.io/etcd/client/v3 v3.6.11/go.mod h1:vOTDMCo+fGPEClJqcFEFSqZ+8e7WKV7AyqJjX//HR2w= +go.etcd.io/etcd/api/v3 v3.6.12 h1:OLOZUKEuAA36TR48F0cIaa8FdzrWygjyfrJxXg4iDgs= +go.etcd.io/etcd/api/v3 v3.6.12/go.mod h1:p14EIQXHbuOQbVvL/WEes5uqKnxP9AgKJgpjbMVvzvE= +go.etcd.io/etcd/client/pkg/v3 v3.6.12 h1:36zzB+pQOdHbhN+kH2iJz/K8bJn0ZLtLfPPO7jozTDo= +go.etcd.io/etcd/client/pkg/v3 v3.6.12/go.mod h1:hh2+ZXtfLzs3o6mn92ntgNPBrTJJOvXqICM5g3L3DMY= +go.etcd.io/etcd/client/v3 v3.6.12 h1:kMSP6JcPZMqSJiX+TXdUIBU/4eXEZWBAaui4VihMbIc= +go.etcd.io/etcd/client/v3 v3.6.12/go.mod h1:CMs6fJWYiZQk4ytFjd4lE1diOvvRMmtbbn/alZXd3dQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= @@ -299,14 +299,14 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= -golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= -golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M= +golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -318,8 +318,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -327,8 +327,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -352,23 +352,23 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk= +golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -376,12 +376,12 @@ gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 h1:U8orV30l6KpDsi9dxU0CoJZGbjS8EEpw+6ba+XwGPQA= -google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348/go.mod h1:Yzdzr5OOZFgSsEV2D/Xi9NL3bszpXFAg0hFJiRohcD8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 h1:pfIbyB44sWzHiCpRqIen67ZQnVXSfIxWrqUMk1qwODE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= -google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/genproto/googleapis/api v0.0.0-20260615183401-62b3387ff324 h1:g0RAkxK/smSu/iRwC/KIX1mwUoVJtk2OjbgaeS4DmUM= +google.golang.org/genproto/googleapis/api v0.0.0-20260615183401-62b3387ff324/go.mod h1:Z4WJ5pJOYWFWcHEQUelD5QaZDknIQkpIL/+fyJOT9+A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260615183401-62b3387ff324 h1:9HZDLIdYBJXAnaFOr9WHrKVycfpY+75s9HGadC0305A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260615183401-62b3387ff324/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/http/service.go new/rqlite-10.2.4/http/service.go --- old/rqlite-10.2.0/http/service.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/http/service.go 2026-06-21 21:25:37.000000000 +0200 @@ -826,7 +826,7 @@ err := s.store.Snapshot(uint64(qp.TrailingLogs(0))) if err != nil { - if err == store.ErrNothingNewToSnapshot { + if errors.Is(err, store.ErrNothingNewToSnapshot) || errors.Is(err, store.ErrNoWALToSnapshot) { w.WriteHeader(http.StatusNoContent) return } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/snapshot/store.go new/rqlite-10.2.4/snapshot/store.go --- old/rqlite-10.2.0/snapshot/store.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/snapshot/store.go 2026-06-21 21:25:37.000000000 +0200 @@ -196,6 +196,19 @@ catalog *SnapshotCatalog + // verifyOnce ensures the CRC32 integrity check of all snapshot files runs at + // most once over the lifetime of the Store, the first time snapshot data is + // about to be used. verifyErr caches the result so a corruption verdict is + // sticky and a successful check is never recomputed. + verifyOnce sync.Once + verifyErr error + + // fatalFn is invoked when snapshot data is found to be corrupt during + // verification. By default it terminates the process, since continuing risks + // using corrupt data (the cluster is expected to provide fault tolerance). + // Tests set it to nil so the error propagates and can be asserted on instead. + fatalFn func(error) + // Multi-reader single-writer lock for the Store, which must be held // if snapshots are deleted i.e. reaped. Simply creating or reading // a snapshot requires only a read lock. @@ -225,6 +238,7 @@ return nil, err } + logger := log.New(os.Stderr, "[snapshot-store] ", log.LstdFlags) str := &Store{ dir: dir, fullNeededPath: filepath.Join(dir, fullNeededFile), @@ -238,19 +252,16 @@ reapCh: make(chan struct{}, 1), reapDoneCh: make(chan struct{}), observers: newObserverSet(), - logger: log.New(os.Stderr, "[snapshot-store] ", log.LstdFlags), + logger: logger, + fatalFn: func(err error) { + logger.Fatalf("fatal snapshot integrity error, exiting process: %s", err) + }, } str.logger.Printf("store initialized using %s", dir) - emp, err := fsutil.DirIsEmpty(dir) - if err != nil { + if err := str.check(); err != nil { return nil, err } - if !emp { - if err := str.check(); err != nil { - return nil, fmt.Errorf("check failed: %s", err) - } - } // Kick off the reaper goroutine. str.wg.Go(str.reapLoop) @@ -380,6 +391,14 @@ } }() + // The data files are about to be read, so verify their integrity first + // (runs at most once over the Store's lifetime). On corruption this hard + // exits in production via fatalFn; with fatalFn disabled (tests) the error + // is returned. + if err := s.ensureVerified(); err != nil { + return nil, nil, err + } + snapSet, err := s.getSnapshots() if err != nil { return nil, nil, fmt.Errorf("scanning snapshots: %w", err) @@ -506,6 +525,16 @@ return 0, 0, fmt.Errorf("reading reap plan: %w", err) } return s.executeReapPlan(p, s.reapPlanPath) + } else { + // A reap reads and rewrites the data files, so verify their integrity + // first (runs at most once over the Store's lifetime). We only do this if + // a reap wasn't previously interrupted (by a crash most likely), as an + // interrupted reap leaves data in an undefined state. On corruption this + // hard exits in production via fatalFn; with fatalFn disabled (tests) the + // error is returned. + if err := s.ensureVerified(); err != nil { + return 0, 0, err + } } // Scan store. @@ -768,31 +797,30 @@ } } -// check checks the Store for any inconsistencies, and repairs -// any inconsistencies it finds. Inconsistencies can happen -// if the system crashes during snapshotting or reaping. +// check repairs any structural inconsistencies in the Store left behind by a +// crash during snapshotting or reaping: it discards an incomplete reap plan, +// resumes an interrupted reap, and removes leftover temporary directories. +// +// Check does NOT verify file checksums. That is done lazily, exactly once, the +// first time snapshot data is about to be used -- see Verify, Open, and Reap. +// +// Check must be called on an idle Store or the results are undefined. +// If called on a empty Store, then it returns without an error. func (s *Store) check() error { + emp, err := fsutil.DirIsEmpty(s.dir) + if err != nil { + return err + } + if emp { + return nil + } + // Remove any incomplete plan file from an interrupted plan write. os.Remove(tmpName(s.reapPlanPath)) - reapPlanFound := fsutil.FileExists(s.reapPlanPath) - - // If no reap was interrupted, verify the CRC32 of every data file - // in every snapshot directory. If a reap was interrupted, the files - // may be in an inconsistent state and the CRC32 validation may fail, - // so we skip it in that case and just resume the reap to fix any - // inconsistencies. - if !reapPlanFound { - if err := s.checkCRCs(); err != nil { - return err - } - } - - // Resume an interrupted reap if such a plan file exists. Only then should - // we remove any leftover temporary directories, since they may be needed - // for the reap to complete. If a reap plan was found, skip CRC validation - // since files may have been left in an inconsistent state by the crash. - if reapPlanFound { + // Resume an interrupted reap if a plan file exists, before any temporary + // directories are removed below, since the reap may still need them. + if fsutil.FileExists(s.reapPlanPath) { s.logger.Printf("found interrupted reap plan at %s, resuming", s.reapPlanPath) p, err := plan.ReadFromFile(s.reapPlanPath) if err != nil { @@ -850,6 +878,35 @@ return nil } +// ensureVerified runs the CRC32 integrity check of every snapshot data file +// exactly once over the lifetime of the Store, the first time snapshot data is +// about to be used. The result is cached: a corruption verdict is returned to +// every subsequent caller, and a successful check is never recomputed. +// +// If the check fails, fatalFn is invoked. In production this terminates the +// process and ensureVerified never returns; tests set fatalFn to nil so the +// error is returned and can be asserted on instead. +// +// The check reads every data file, so the caller must hold the Store read or +// write lock so it does not run concurrently with a reap. +func (s *Store) ensureVerified() error { + s.verifyOnce.Do(func() { + err := s.checkCRCs() + if err != nil && s.fatalFn != nil { + s.fatalFn(err) // terminates the process in production; never returns + } + s.verifyErr = err // reached only when fatalFn is nil (tests) + }) + return s.verifyErr +} + +// Verify runs the CRC32 integrity check. +func (s *Store) Verify() error { + s.mrsw.BeginReadBlocking() + defer s.mrsw.EndRead() + return s.checkCRCs() +} + // getSnapshots returns the set of snapshots in the Store. func (s *Store) getSnapshots() (SnapshotSet, error) { return s.catalog.Scan(s.dir) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/snapshot/store_test.go new/rqlite-10.2.4/snapshot/store_test.go --- old/rqlite-10.2.0/snapshot/store_test.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/snapshot/store_test.go 2026-06-21 21:25:37.000000000 +0200 @@ -331,6 +331,57 @@ } } +func Test_Store_Verify(t *testing.T) { + dir := t.TempDir() + store, err := NewStore(dir) + if err != nil { + t.Fatalf("Failed to create new store: %v", err) + } + defer store.Close() + + // Create a full snapshot, then an incremental. + createSnapshotInStore(t, store, "2-1017-1704807719996", 1017, 2, 1, "testdata/db-and-wals/backup.db") + createSnapshotInStore(t, store, "2-1131-1704807720976", 1131, 2, 1, "", "testdata/db-and-wals/wal-00") + + // The default fatalFn would terminate the process on corruption; disable it + // so the integrity failure surfaces as a returned error we can assert on. + store.fatalFn = nil + + // Ensure Verify passes. + if err := store.Verify(); err != nil { + t.Fatalf("verify of store failed: %s", err) + } + + // Corrupt the full snapshot's DB file by flipping the last bit. + dbPath := filepath.Join(store.Dir(), "2-1017-1704807719996", dbfileName) + fd, err := os.OpenFile(dbPath, os.O_RDWR, 0) + if err != nil { + t.Fatalf("failed to open file: %s", err) + } + defer fd.Close() + if _, err := fd.Seek(-1, io.SeekEnd); err != nil { + t.Fatalf("failed to seek: %s", err) + } + var b [1]byte + if _, err := fd.Read(b[:]); err != nil { + t.Fatalf("failed to read: %s", err) + } + b[0] ^= 0x01 + + // Read advanced the offset; step back before writing. + if _, err := fd.Seek(-1, io.SeekCurrent); err != nil { + t.Fatalf("failed to seek: %s", err) + } + if _, err := fd.Write(b[:]); err != nil { + t.Fatalf("failed to write: %s", err) + } + + // Ensure Verify fails. + if err := store.Verify(); err == nil { + t.Fatalf("verify of store should have failed") + } +} + // Test_Store_EndToEndCycle tests an end-to-end cycle of creating a Store, // creating sinks, and writing various types of snapshots to other Stores. func Test_Store_EndToEndCycle(t *testing.T) { @@ -945,6 +996,10 @@ createSnapshotInStore(t, store, "2-1017-1704807719996", 1017, 2, 1, "testdata/db-and-wals/backup.db") createSnapshotInStore(t, store, "2-1131-1704807720976", 1131, 2, 1, "", "testdata/db-and-wals/wal-00") + // The default fatalFn would terminate the process on corruption; disable it + // so the integrity failure surfaces as a returned error we can assert on. + store.fatalFn = nil + // Corrupt the full snapshot's DB file. dbPath := filepath.Join(store.Dir(), "2-1017-1704807719996", dbfileName) if err := os.WriteFile(dbPath, []byte("corrupted"), 0644); err != nil { @@ -970,6 +1025,10 @@ createSnapshotInStore(t, store, "2-1017-1704807719996", 1017, 2, 1, "testdata/db-and-wals/backup.db") createSnapshotInStore(t, store, "2-1131-1704807720976", 1131, 2, 1, "", "testdata/db-and-wals/wal-00") + // The default fatalFn would terminate the process on corruption; disable it + // so the integrity failure surfaces as a returned error we can assert on. + store.fatalFn = nil + // Find and corrupt the WAL file in the incremental snapshot. walPattern := filepath.Join(store.Dir(), "2-1131-1704807720976", "*.wal") matches, err := filepath.Glob(walPattern) @@ -1033,7 +1092,7 @@ t.Fatalf("Failed to write file in tmp dir: %v", err) } - // Re-open the store — check() should clean up the .tmp directory. + // Re-open the store — Check() should clean up the .tmp directory. store2, err := NewStore(dir) if err != nil { t.Fatalf("Failed to re-open store: %v", err) @@ -1110,6 +1169,10 @@ } p.AddCheckpoint(filepath.Join(fullPath, dbfileName), walMatches) p.NCheckpointed = len(walMatches) + // Mirror Reap()'s plan: refresh the data.db CRC32 sidecar after the + // checkpoint rewrites the file, otherwise the consolidated snapshot would + // fail integrity verification when it is later opened. + p.AddCalcCRC32(filepath.Join(fullPath, dbfileName), filepath.Join(fullPath, dbfileName)+crcSuffix) p.AddRemoveAll(incPath) p.NReaped = 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/store/store.go new/rqlite-10.2.4/store/store.go --- old/rqlite-10.2.0/store/store.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/store/store.go 2026-06-21 21:25:37.000000000 +0200 @@ -810,6 +810,18 @@ } } + // If Raft will restore from a snapshot on start, verify the snapshot's + // integrity now, so the cost and any corruption are surfaced explicitly at + // startup before Raft reads it. If we won't restore on start (e.g. the clean + // snapshot fast path), this is skipped: the check then runs lazily the first + // time snapshot data is actually used -- a reap, a snapshot transfer to a + // peer, or a later restore. + if !raftConfig.NoSnapshotRestoreOnStart { + if err := snapshotStore.Verify(); err != nil { + return fmt.Errorf("snapshot integrity check failed: %s", err) + } + } + // Instantiate the Raft system. ra, err := raft.NewRaft(raftConfig, NewFSM(s), s.raftLog, s.raftStable, s.snapshotStore, s.raftTn) if err != nil { @@ -1966,7 +1978,7 @@ cw := progress.NewCountingWriter(f) cm := progress.StartCountingMonitor(func(n int64) { - s.logger.Printf("boot process installed %d bytes", n) + s.logger.Printf("boot process installed %d bytes (%s)", n, humanize.Bytes(uint64(n))) }, cw) n, err := func() (int64, error) { defer cm.StopAndWait() @@ -2768,9 +2780,17 @@ defer os.Remove(tmpPath) if _, err := snapshot.Restore(rc, tmpPath); err != nil { + rc.Close() return fmt.Errorf("error restoring database from snapshot: %v", err) } + // The snapshot stream is now fully drained into tmpPath, so close the reader + // immediately. Do this so we're not holding onto the underlying LockingStreamer + // while we calculate fingerprint (which can take measurable time for large databases). + if err := rc.Close(); err != nil { + s.logger.Printf("error closing snapshot reader after restore: %s", err) + } + // Any existing SQLite file is about to be invalid, so mark that we can't // fast-restart with it. if err := fsutil.RemoveFile(s.cleanSnapshotPath); err != nil { @@ -2810,7 +2830,6 @@ stats.Add(numRestores, 1) s.logger.Printf("node restored in %s", time.Since(startT)) - rc.Close() return nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/tcp/mux.go new/rqlite-10.2.4/tcp/mux.go --- old/rqlite-10.2.0/tcp/mux.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/tcp/mux.go 2026-06-21 21:25:37.000000000 +0200 @@ -78,6 +78,12 @@ wg sync.WaitGroup + // conns tracks every demultiplexed connection currently handed off to a + // listener, so that they can be force-closed during shutdown. Each + // connection deregisters itself when closed. + connsMu sync.Mutex + conns map[*trackedConn]struct{} + // The amount of time to wait for the first header byte. Timeout time.Duration @@ -100,6 +106,7 @@ ln: ln, addr: addr, m: make(map[byte]*listener), + conns: make(map[*trackedConn]struct{}), Timeout: DefaultTimeout, Logger: log.New(os.Stderr, "[mux] ", log.LstdFlags), }, nil @@ -178,9 +185,15 @@ return err } + // Track the connection so it can be force-closed during shutdown, even + // if it stalls before sending a header byte. It deregisters itself when + // closed. + tc := &trackedConn{Conn: conn, mux: mux} + mux.registerConn(tc) + // Demux in a goroutine to mux.wg.Add(1) - go mux.handleConn(conn) + go mux.handleConn(tc) } } @@ -220,10 +233,67 @@ } // Close closes the mux. It does not close any listeners created by the mux, nor -// does it close the listener it was passed at creation time. It cleans up -// other resources however. +// does it close the listener it was passed at creation time. It force-closes +// any demultiplexed connections still in flight. func (mux *Mux) Close() error { - return nil + return mux.CloseConns() +} + +// CloseConns force-closes every demultiplexed connection currently handed off +// to a listener. It is used during shutdown to unblock any consumer (such as +// Raft) that is parked in a deadline-less read on a connection whose peer has +// stopped sending -- for example a node receiving a snapshot from a peer that +// is itself shutting down. Without this such a read has no upper bound and can +// block shutdown indefinitely. +func (mux *Mux) CloseConns() error { + mux.connsMu.Lock() + conns := make([]*trackedConn, 0, len(mux.conns)) + for c := range mux.conns { + conns = append(conns, c) + } + mux.connsMu.Unlock() + + var lastErr error + for _, c := range conns { + if err := c.Close(); err != nil { + lastErr = err + } + } + return lastErr +} + +// registerConn adds a connection to the set of tracked connections. +func (mux *Mux) registerConn(c *trackedConn) { + mux.connsMu.Lock() + defer mux.connsMu.Unlock() + mux.conns[c] = struct{}{} +} + +// removeConn removes a connection from the set of tracked connections. +func (mux *Mux) removeConn(c *trackedConn) { + mux.connsMu.Lock() + defer mux.connsMu.Unlock() + delete(mux.conns, c) +} + +// trackedConn wraps an accepted connection so the Mux can force-close it during +// shutdown. It deregisters itself from the Mux on Close so the tracking set does +// not grow without bound over the lifetime of the node. +type trackedConn struct { + net.Conn + mux *Mux + once sync.Once +} + +// Close removes the connection from the Mux's tracking set and closes the +// underlying connection. It is safe to call multiple times. +func (c *trackedConn) Close() error { + c.mux.removeConn(c) + var err error + c.once.Do(func() { + err = c.Conn.Close() + }) + return err } func (mux *Mux) handleConn(conn net.Conn) { @@ -260,7 +330,8 @@ return } - // Send connection to handler. The handler is responsible for closing the connection. + // Send connection to handler. The handler is responsible for closing the + // connection. handler.c <- conn } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rqlite-10.2.0/tcp/mux_test.go new/rqlite-10.2.4/tcp/mux_test.go --- old/rqlite-10.2.0/tcp/mux_test.go 2026-05-29 15:30:17.000000000 +0200 +++ new/rqlite-10.2.4/tcp/mux_test.go 2026-06-21 21:25:37.000000000 +0200 @@ -392,6 +392,117 @@ return m.Addr } +// Test_MuxCloseConns verifies that CloseConns actually closes the underlying +// socket of a tracked connection (not merely drops it from the tracking set), +// so a peer parked in a read on it is unblocked. This is the property that lets +// a node shut down while parked reading a Raft snapshot body from a peer that +// has gone silent. See https://github.com/rqlite/rqlite/issues/2687. +func Test_MuxCloseConns(t *testing.T) { + tcpListener := mustTCPListener("127.0.0.1:0") + defer tcpListener.Close() + + mux, err := NewMux(tcpListener, nil) + if err != nil { + t.Fatalf("failed to create mux: %s", err.Error()) + } + if !testing.Verbose() { + mux.Logger = log.New(io.Discard, "", 0) + } + go mux.Serve() + + // Connecting is enough for the Mux to accept and track the connection. + client, err := net.Dial("tcp", tcpListener.Addr().String()) + if err != nil { + t.Fatalf("failed to dial mux: %s", err.Error()) + } + defer client.Close() + if !waitForTrackedConns(mux, 1, 5*time.Second) { + t.Fatalf("connection was not tracked, got %d", numTrackedConns(mux)) + } + + // Park a read on the connection; nothing is ever sent on it. + readReturned := make(chan error, 1) + go func() { + var b [1]byte + _, err := client.Read(b[:]) + readReturned <- err + }() + + // Force-close all connections; the parked read must return promptly. + if err := mux.CloseConns(); err != nil { + t.Fatalf("CloseConns returned error: %s", err.Error()) + } + select { + case err := <-readReturned: + if err == nil { + t.Fatal("expected blocked read to return an error after CloseConns") + } + case <-time.After(5 * time.Second): + t.Fatal("blocked read did not return after CloseConns") + } + + // The tracking set must be empty again, so it does not grow without bound. + if got := numTrackedConns(mux); got != 0 { + t.Fatalf("expected 0 tracked connections after close, got %d", got) + } +} + +// Test_MuxConnDeregisterOnClose verifies that an accepted connection deregisters +// itself from the Mux's tracking set when closed, so the set does not leak over +// the lifetime of the node. Connecting is enough for the Mux to track the +// connection; no header byte is required. +func Test_MuxConnDeregisterOnClose(t *testing.T) { + tcpListener := mustTCPListener("127.0.0.1:0") + defer tcpListener.Close() + + mux, err := NewMux(tcpListener, nil) + if err != nil { + t.Fatalf("failed to create mux: %s", err.Error()) + } + if !testing.Verbose() { + mux.Logger = log.New(io.Discard, "", 0) + } + go mux.Serve() + + // Connecting is enough for the Mux to accept and track the connection. + client, err := net.Dial("tcp", tcpListener.Addr().String()) + if err != nil { + t.Fatalf("failed to dial mux: %s", err.Error()) + } + if !waitForTrackedConns(mux, 1, 5*time.Second) { + t.Fatalf("connection was not tracked after connecting, got %d", numTrackedConns(mux)) + } + + // Closing the connection must deregister it from the tracking set. + client.Close() + if !waitForTrackedConns(mux, 0, 5*time.Second) { + t.Fatalf("connection was not deregistered after close, got %d", numTrackedConns(mux)) + } +} + +// numTrackedConns returns the number of connections currently tracked by the +// Mux for force-close on shutdown. +func numTrackedConns(mux *Mux) int { + mux.connsMu.Lock() + defer mux.connsMu.Unlock() + return len(mux.conns) +} + +// waitForTrackedConns waits up to timeout for the Mux to be tracking exactly n +// connections, returning true if that count is reached. +func waitForTrackedConns(mux *Mux, n int, timeout time.Duration) bool { + deadline := time.Now().Add(timeout) + for { + if numTrackedConns(mux) == n { + return true + } + if time.Now().After(deadline) { + return false + } + time.Sleep(time.Millisecond) + } +} + // mustTCPListener returns a listener on bind, or panics. func mustTCPListener(bind string) net.Listener { l, err := net.Listen("tcp", bind) ++++++ vendor.tar.xz ++++++ ++++ 34673 lines of diff (skipped)
