diff --git a/go.mod b/go.mod index 6ea32cd..98c3d70 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/go-redis/redis/v8 v8.11.4 github.com/gorilla/mux v1.8.0 github.com/prometheus/client_golang v1.12.1 + github.com/tevino/abool/v2 v2.0.1 ) require ( diff --git a/go.sum b/go.sum index 90f19ac..9102b79 100644 --- a/go.sum +++ b/go.sum @@ -208,6 +208,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tevino/abool/v2 v2.0.1 h1:OF7FC5V5z3yAWyixbc32ecEzrgAJCsPkVOsPM2qoZPI= +github.com/tevino/abool/v2 v2.0.1/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/main.go b/main.go index c38f3fd..d463bd2 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( "github.com/go-redis/redis/v8" "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/tevino/abool/v2" ) const ( @@ -220,6 +221,7 @@ type GlobalBackfeedManager struct { BackfeedRedis *redis.ClusterClient LegacyRedis *redis.Client Lock sync.RWMutex + Populated *abool.AtomicBool } func (that *GlobalBackfeedManager) RefreshFeeds() error { @@ -330,6 +332,9 @@ func (that *GlobalBackfeedManager) RefreshFeeds() error { <-projectBackfeedManager.Done log.Printf("removed project: %s", project) } + if !that.Populated.IsSet() { + that.Populated.Set() + } return nil } @@ -394,7 +399,7 @@ func (that *GlobalBackfeedManager) GetFeed(slug string) *ProjectBackfeedManager return projectBackfeedManager } -func (that *GlobalBackfeedManager) Handle(res http.ResponseWriter, req *http.Request) { +func (that *GlobalBackfeedManager) HandleLegacy(res http.ResponseWriter, req *http.Request) { defer req.Body.Close() vars := mux.Vars(req) @@ -454,7 +459,32 @@ func (that *GlobalBackfeedManager) Handle(res http.ResponseWriter, req *http.Req return } +func (that *GlobalBackfeedManager) HandleHealth(res http.ResponseWriter, req *http.Request) { + if that.Populated.IsNotSet() { + WriteResponse(res, http.StatusServiceUnavailable, fmt.Errorf("%s", "backfeed not populated")) + return + } + err := that.LegacyRedis.Ping(req.Context()).Err() + if err != nil { + WriteResponse(res, http.StatusInternalServerError, fmt.Errorf("failed to ping legacy redis: %s", err)) + return + } + err = that.BackfeedRedis.ForEachShard(req.Context(), func(ctx context.Context, client *redis.Client) error { + return client.Ping(ctx).Err() + }) + if err != nil { + WriteResponse(res, http.StatusInternalServerError, fmt.Errorf("failed to ping backfeed redis: %s", err)) + return + } + WriteResponse(res, http.StatusOK, "ok") +} + +func (that *GlobalBackfeedManager) HandlePing(res http.ResponseWriter, _ *http.Request) { + WriteResponse(res, http.StatusOK, "pong") +} + func (that *GlobalBackfeedManager) CancelAllFeeds() { + that.Populated.UnSet() that.Cancel() for project, projectBackfeedManager := range that.ActiveFeeds { log.Printf("waiting for %s channel to shut down...", project) @@ -513,7 +543,9 @@ func main() { log.Panicf("unable to set up backfeed projects: %s", err) } r := mux.NewRouter() - r.Methods(http.MethodPost).Path("/legacy/{slug}").HandlerFunc(globalBackfeedManager.Handle) + r.Methods(http.MethodPost).Path("/legacy/{slug}").HandlerFunc(globalBackfeedManager.HandleLegacy) + r.Methods(http.MethodGet).Path("/ping").HandlerFunc(globalBackfeedManager.HandlePing) + r.Methods(http.MethodGet).Path("/health").HandlerFunc(globalBackfeedManager.HandleHealth) rMetrics := mux.NewRouter() rMetrics.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux) rMetrics.Path("/metrics").Handler(promhttp.Handler()) diff --git a/vendor/github.com/tevino/abool/v2/LICENSE b/vendor/github.com/tevino/abool/v2/LICENSE new file mode 100644 index 0000000..f20dac8 --- /dev/null +++ b/vendor/github.com/tevino/abool/v2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Tevin Zhang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/tevino/abool/v2/bool.go b/vendor/github.com/tevino/abool/v2/bool.go new file mode 100644 index 0000000..bf1eb86 --- /dev/null +++ b/vendor/github.com/tevino/abool/v2/bool.go @@ -0,0 +1,87 @@ +// Package abool provides atomic Boolean type for cleaner code and +// better performance. +package abool + +import ( + "encoding/json" + "sync/atomic" +) + +// New creates an AtomicBool with default set to false. +func New() *AtomicBool { + return new(AtomicBool) +} + +// NewBool creates an AtomicBool with given default value. +func NewBool(ok bool) *AtomicBool { + ab := New() + if ok { + ab.Set() + } + return ab +} + +// AtomicBool is an atomic Boolean. +// Its methods are all atomic, thus safe to be called by multiple goroutines simultaneously. +// Note: When embedding into a struct one should always use *AtomicBool to avoid copy. +type AtomicBool int32 + +// Set sets the Boolean to true. +func (ab *AtomicBool) Set() { + atomic.StoreInt32((*int32)(ab), 1) +} + +// UnSet sets the Boolean to false. +func (ab *AtomicBool) UnSet() { + atomic.StoreInt32((*int32)(ab), 0) +} + +// IsSet returns whether the Boolean is true. +func (ab *AtomicBool) IsSet() bool { + return atomic.LoadInt32((*int32)(ab)) == 1 +} + +// IsNotSet returns whether the Boolean is false. +func (ab *AtomicBool) IsNotSet() bool { + return !ab.IsSet() +} + +// SetTo sets the boolean with given Boolean. +func (ab *AtomicBool) SetTo(yes bool) { + if yes { + atomic.StoreInt32((*int32)(ab), 1) + } else { + atomic.StoreInt32((*int32)(ab), 0) + } +} + +// SetToIf sets the Boolean to new only if the Boolean matches the old. +// Returns whether the set was done. +func (ab *AtomicBool) SetToIf(old, new bool) (set bool) { + var o, n int32 + if old { + o = 1 + } + if new { + n = 1 + } + return atomic.CompareAndSwapInt32((*int32)(ab), o, n) +} + +// MarshalJSON behaves the same as if the AtomicBool is a builtin.bool. +// NOTE: There's no lock during the process, usually it shouldn't be called with other methods in parallel. +func (ab *AtomicBool) MarshalJSON() ([]byte, error) { + return json.Marshal(ab.IsSet()) +} + +// UnmarshalJSON behaves the same as if the AtomicBool is a builtin.bool. +// NOTE: There's no lock during the process, usually it shouldn't be called with other methods in parallel. +func (ab *AtomicBool) UnmarshalJSON(b []byte) error { + var v bool + err := json.Unmarshal(b, &v) + + if err == nil { + ab.SetTo(v) + } + return err +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 9ecd660..876e095 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -48,6 +48,9 @@ github.com/prometheus/common/model github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util +# github.com/tevino/abool/v2 v2.0.1 +## explicit; go 1.17 +github.com/tevino/abool/v2 # golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 ## explicit; go 1.17 golang.org/x/sys/internal/unsafeheader