From 4e7b01f11f22a3ddaf462d3d89a639b302c7275d Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 10 Jun 2022 18:47:12 +0200 Subject: [PATCH 01/21] tracing: Remove package --- tracing/noop/tracer.go | 32 --------------- tracing/statsd/tracer.go | 73 ----------------------------------- tracing/statsd/tracer_test.go | 31 --------------- tracing/tracer.go | 15 ------- 4 files changed, 151 deletions(-) delete mode 100644 tracing/noop/tracer.go delete mode 100644 tracing/statsd/tracer.go delete mode 100644 tracing/statsd/tracer_test.go delete mode 100644 tracing/tracer.go diff --git a/tracing/noop/tracer.go b/tracing/noop/tracer.go deleted file mode 100644 index 4c37f30..0000000 --- a/tracing/noop/tracer.go +++ /dev/null @@ -1,32 +0,0 @@ -package noop - -import ( - "sync" - "time" -) - -type Tracer struct{} - -func (t *Tracer) Trace(name string, fn func(), wg *sync.WaitGroup) error { - if wg == nil { - fn() - } else { - wg.Add(1) - - go func() { - defer wg.Done() - - fn() - }() - } - - return nil -} - -func (t *Tracer) Timing(name string, duration time.Duration) error { - return nil -} - -func (t *Tracer) Count(bucket string, value int) error { - return nil -} diff --git a/tracing/statsd/tracer.go b/tracing/statsd/tracer.go deleted file mode 100644 index 0fb75d8..0000000 --- a/tracing/statsd/tracer.go +++ /dev/null @@ -1,73 +0,0 @@ -package statsd - -import ( - "fmt" - "regexp" - "sync" - "time" - - statsdClient "github.com/cyberdelia/statsd" -) - -const ( - defaultRateTime = 0.1 - defaultRateIncrement = 1.0 -) - -type Tracer struct { - client *statsdClient.Client - namespace string - rateTime float64 - rateIncrement float64 -} - -func NewTracer(statsdUrl, namespace string) (*Tracer, error) { - c, err := statsdClient.Dial(statsdUrl) - - if err != nil { - return nil, err - } - - return &Tracer{c, namespace, defaultRateTime, defaultRateIncrement}, nil -} - -func statsdfyName(name string) string { - name = regexp.MustCompile("(-|@)").ReplaceAllString(name, "_") - name = regexp.MustCompile("[^a-zA-Z0-9_\\.]").ReplaceAllString(name, "") - return name -} - -func (t *Tracer) metricName(name string) string { - if t.namespace != "" { - name = fmt.Sprintf("%s.%s", t.namespace, name) - } - - return statsdfyName(name) -} - -func (t *Tracer) Timing(name string, duration time.Duration) error { - return t.client.Timing( - t.metricName(name), - int(duration/time.Millisecond), - t.rateTime, - ) -} - -func (t *Tracer) Trace(name string, fn func(), wg *sync.WaitGroup) error { - if wg == nil { - return t.client.Time(t.metricName(name), t.rateTime, fn) - } else { - wg.Add(1) - - go func() { - defer wg.Done() - t.client.Time(t.metricName(name), t.rateTime, fn) - }() - - return nil - } -} - -func (t *Tracer) Count(bucket string, value int) error { - return t.client.Increment(t.metricName(bucket), value, t.rateIncrement) -} diff --git a/tracing/statsd/tracer_test.go b/tracing/statsd/tracer_test.go deleted file mode 100644 index 1210dfd..0000000 --- a/tracing/statsd/tracer_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package statsd - -import "testing" - -type testCase struct { - in string - out string -} - -func TestStatsdfy(t *testing.T) { - for _, tCase := range []testCase{ - testCase{"*foo@bar", "foo_bar"}, - testCase{"bar123", "bar123"}, - testCase{"bar.123", "bar.123"}, - } { - if v := statsdfyName(tCase.in); tCase.out != v { - t.Errorf("Wrong statsdfy of %s: %s", tCase.in, v) - } - } -} - -func TestMetricName(t *testing.T) { - for _, tCase := range []testCase{ - testCase{"namespace", "namespace.foo"}, - testCase{"", "foo"}, - } { - if v := (&Tracer{namespace: tCase.in}).metricName("foo"); tCase.out != v { - t.Errorf("Wrong metricName of %s: %s", tCase.in, v) - } - } -} diff --git a/tracing/tracer.go b/tracing/tracer.go deleted file mode 100644 index d6fabfc..0000000 --- a/tracing/tracer.go +++ /dev/null @@ -1,15 +0,0 @@ -package tracing - -import ( - "sync" - "time" -) - -type Tracer interface { - // if the third parameter is nil the closure will be executed synchronous - // otherwise asynchronous - Trace(string, func(), *sync.WaitGroup) error - - Count(string, int) error - Timing(string, time.Duration) error -} From cc60050d678b091e906affc5a46137a7c1c3b4af Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 10 Jun 2022 18:47:59 +0200 Subject: [PATCH 02/21] testing: Remove outdated package --- testing/cassandra/gocql.go | 85 -------------------------------------- testing/postgres/gorm.go | 78 ---------------------------------- testing/sqlite3/gorm.go | 40 ------------------ testutil/helpers.go | 32 -------------- 4 files changed, 235 deletions(-) delete mode 100644 testing/cassandra/gocql.go delete mode 100644 testing/postgres/gorm.go delete mode 100644 testing/sqlite3/gorm.go delete mode 100644 testutil/helpers.go diff --git a/testing/cassandra/gocql.go b/testing/cassandra/gocql.go deleted file mode 100644 index d034416..0000000 --- a/testing/cassandra/gocql.go +++ /dev/null @@ -1,85 +0,0 @@ -package cassandra - -import ( - "fmt" - "testing" - "time" - - "github.com/gocql/gocql" - "github.com/mattes/migrate" - _ "github.com/mattes/migrate/database/cassandra" - "github.com/mattes/migrate/source" - - "github.com/upfluence/pkg/cfg" -) - -const ( - defaultTimeout = time.Minute - cassandraPort = 9042 - protocolVersion = 3 -) - -var ( - cassandraIP = cfg.FetchString("CASSANDRA_IP", "127.0.0.1") - keySpace = cfg.FetchString("CASSANDRA_KEY_SPACE", "test") -) - -func BuildKeySpace(t testing.TB, driver source.Driver, tables []string) *gocql.Session { - cluster := gocql.NewCluster(cassandraIP) - cluster.Consistency = gocql.All - cluster.ProtoVersion = protocolVersion - cluster.Timeout = defaultTimeout - - session, err := cluster.CreateSession() - - if err != nil { - t.Errorf("cant create cql session: %v", err) - } - - session.Query( - `CREATE KEYSPACE ` + keySpace + ` WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }`, - ).Exec() - - if driver != nil { - m, err := migrate.NewWithSourceInstance( - "testing_source", - driver, - fmt.Sprintf( - "cassandra://%s:%d/%s?protocol=%d", - cassandraIP, - cassandraPort, - keySpace, - protocolVersion, - ), - ) - - if err != nil { - t.Errorf("cant open migrate: %v", err) - } - - if err := m.Up(); err != nil && err != migrate.ErrNoChange { - t.Errorf("cant run migration: %v", err) - } - } - - session.Close() - - cluster.Keyspace = keySpace - - session, err = cluster.CreateSession() - - if err != nil { - t.Error(err) - t.FailNow() - } - - for _, table := range tables { - if err := session.Query( - fmt.Sprintf("TRUNCATE %s", table), - ).Exec(); err != nil { - t.Log(err) - } - } - - return session -} diff --git a/testing/postgres/gorm.go b/testing/postgres/gorm.go deleted file mode 100644 index 9e99c64..0000000 --- a/testing/postgres/gorm.go +++ /dev/null @@ -1,78 +0,0 @@ -package postgres - -import ( - "database/sql" - "fmt" - "net/url" - "testing" - - "github.com/jinzhu/gorm" - _ "github.com/lib/pq" - "github.com/mattes/migrate" - _ "github.com/mattes/migrate/database/postgres" - "github.com/mattes/migrate/source" - - "github.com/upfluence/pkg/cfg" -) - -var postgresURL = cfg.FetchString( - "POSTGRES_URL", - "postgres://localhost:5432/test_database?sslmode=disable", -) - -func parseURL(t testing.TB) (string, string) { - var ( - u, err = url.Parse(postgresURL) - db = "test_database" - ) - - if err != nil { - t.Errorf("Postgres URL not valid: %v", err) - } - - if len(u.Path) > 1 { - db = u.Path[1:] - } - - return postgresURL, db -} - -func BuildDatabase(t testing.TB, driver source.Driver) *gorm.DB { - var ( - url, database = parseURL(t) - plainDB, err = sql.Open("postgres", postgresURL) - ) - - if err != nil { - t.Errorf("cant open the DB: %v", err) - } - - plainDB.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", database)) - plainDB.Exec(fmt.Sprintf("CREATE DATABASE %s", database)) - - plainDB.Close() - - db, err := gorm.Open("postgres", postgresURL) - - if err != nil { - t.Errorf("cant open the DB: %v", err) - } - - if driver != nil { - m, err := migrate.NewWithSourceInstance( - "testing_source", - driver, - url, - ) - - if err != nil { - t.Errorf("cant open migrate: %v", err) - } - - if err := m.Up(); err != nil && err != migrate.ErrNoChange { - t.Errorf("cant run migration: %v", err) - } - } - - return db -} diff --git a/testing/sqlite3/gorm.go b/testing/sqlite3/gorm.go deleted file mode 100644 index 855e8b6..0000000 --- a/testing/sqlite3/gorm.go +++ /dev/null @@ -1,40 +0,0 @@ -package sqlite3 - -import ( - "fmt" - "io/ioutil" - "testing" - - "github.com/jinzhu/gorm" - "github.com/mattes/migrate" - _ "github.com/mattes/migrate/database/sqlite3" - "github.com/mattes/migrate/source" - _ "github.com/mattn/go-sqlite3" -) - -func BuildDatabase(t testing.TB, driver source.Driver) *gorm.DB { - f, _ := ioutil.TempFile("/tmp", "sqlite") - db, err := gorm.Open("sqlite3", f.Name()) - - if err != nil { - t.Errorf("cant open migrate: %v", err) - } - - if driver != nil { - m, err := migrate.NewWithSourceInstance( - "testing_source", - driver, - fmt.Sprintf("sqlite3://%s", f.Name()), - ) - - if err != nil { - t.Errorf("cant open migrate: %v", err) - } - - if err := m.Up(); err != nil && err != migrate.ErrNoChange { - t.Errorf("cant run migration: %v", err) - } - } - - return db -} diff --git a/testutil/helpers.go b/testutil/helpers.go deleted file mode 100644 index 2531ca5..0000000 --- a/testutil/helpers.go +++ /dev/null @@ -1,32 +0,0 @@ -package testutil - -import ( - "os" - "testing" - - "github.com/upfluence/errors/errtest" -) - -type ErrorAssertion func(testing.TB, error) - -func NoError(msgAndArgs ...interface{}) ErrorAssertion { - return errtest.NoError(msgAndArgs...).Assert -} - -func ErrorEqual(err error, msgAndArgs ...interface{}) ErrorAssertion { - return errtest.ErrorEqual(err, msgAndArgs...).Assert -} - -func ErrorCause(err error, msgAndArgs ...interface{}) ErrorAssertion { - return errtest.ErrorCause(err, msgAndArgs...).Assert -} - -func FetchEnvVariable(t testing.TB, ev string) string { - v := os.Getenv(ev) - - if v == "" { - t.Skipf("%q env var is not defined, skipping test", ev) - } - - return v -} From 77e63023a21800c1399ecbce3400ec20fc5093e8 Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 10 Jun 2022 18:48:16 +0200 Subject: [PATCH 03/21] stringcache: Remove outdated package --- stringcache/cache.go | 11 ------ stringcache/mock/cache.go | 45 ----------------------- stringcache/mock/cache_test.go | 11 ------ stringcache/redis/cache.go | 56 ----------------------------- stringcache/redis/cache_test.go | 26 -------------- stringcache/testutil/integration.go | 43 ---------------------- 6 files changed, 192 deletions(-) delete mode 100644 stringcache/cache.go delete mode 100644 stringcache/mock/cache.go delete mode 100644 stringcache/mock/cache_test.go delete mode 100644 stringcache/redis/cache.go delete mode 100644 stringcache/redis/cache_test.go delete mode 100644 stringcache/testutil/integration.go diff --git a/stringcache/cache.go b/stringcache/cache.go deleted file mode 100644 index 3528db4..0000000 --- a/stringcache/cache.go +++ /dev/null @@ -1,11 +0,0 @@ -package stringcache - -import "github.com/upfluence/errors" - -var ErrNotFound = errors.New("stringcache: Key not found") - -type Cache interface { - Has(string) (bool, error) - Add(string) error - Delete(string) error -} diff --git a/stringcache/mock/cache.go b/stringcache/mock/cache.go deleted file mode 100644 index 92ab02e..0000000 --- a/stringcache/mock/cache.go +++ /dev/null @@ -1,45 +0,0 @@ -package mock - -import "github.com/upfluence/pkg/stringcache" - -type Cache struct { - st map[string]bool -} - -func NewCache() *Cache { - return &Cache{st: make(map[string]bool)} -} - -func (c *Cache) Has(k string) (bool, error) { - _, e := c.st[k] - - return e, nil -} - -func (c *Cache) Add(k string) error { - c.st[k] = true - - return nil -} - -func (c *Cache) Delete(k string) error { - _, e := c.st[k] - - if !e { - return stringcache.ErrNotFound - } - - delete(c.st, k) - - return nil -} - -func (c *Cache) Keys() []string { - var r []string - - for k := range c.st { - r = append(r, k) - } - - return r -} diff --git a/stringcache/mock/cache_test.go b/stringcache/mock/cache_test.go deleted file mode 100644 index e10085d..0000000 --- a/stringcache/mock/cache_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package mock - -import ( - "testing" - - "github.com/upfluence/pkg/stringcache/testutil" -) - -func TestIntegration(t *testing.T) { - testutil.IntegrationScenario(t, NewCache()) -} diff --git a/stringcache/redis/cache.go b/stringcache/redis/cache.go deleted file mode 100644 index 667f272..0000000 --- a/stringcache/redis/cache.go +++ /dev/null @@ -1,56 +0,0 @@ -package redis - -import ( - "time" - - "github.com/garyburd/redigo/redis" - "github.com/upfluence/pkg/stringcache" -) - -const redisTimeout = 20 * time.Second - -type Cache struct { - conn redis.Conn - key string -} - -func NewCache(uri, key string) (*Cache, error) { - conn, err := redis.DialURL( - uri, - redis.DialConnectTimeout(redisTimeout), - redis.DialReadTimeout(redisTimeout), - redis.DialWriteTimeout(redisTimeout), - ) - - if err != nil { - return nil, err - } - - return &Cache{conn: conn, key: key}, nil -} - -func (c *Cache) Has(k string) (bool, error) { - return redis.Bool(c.conn.Do("SISMEMBER", c.key, k)) -} - -func (c *Cache) Add(k string) error { - _, err := c.conn.Do("SADD", c.key, k) - - return err -} - -func (c *Cache) Delete(k string) error { - e, err := redis.Bool(c.conn.Do("SISMEMBER", c.key, k)) - - if err != nil { - return err - } - - if !e { - return stringcache.ErrNotFound - } - - _, err = c.conn.Do("SREM", c.key, k) - - return err -} diff --git a/stringcache/redis/cache_test.go b/stringcache/redis/cache_test.go deleted file mode 100644 index 9493b51..0000000 --- a/stringcache/redis/cache_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package redis - -import ( - "os" - "testing" - - "github.com/upfluence/pkg/stringcache/testutil" -) - -func TestIntegration(t *testing.T) { - redisURL := os.Getenv("REDIS_URL") - - if redisURL == "" { - t.Skip("no REDIS_URL provided") - } - - c, err := NewCache(redisURL, "test-key") - - if err != nil { - t.Errorf("Error returned by the constructor %+v", err) - } - - c.conn.Do("SDEL", c.key) - - testutil.IntegrationScenario(t, c) -} diff --git a/stringcache/testutil/integration.go b/stringcache/testutil/integration.go deleted file mode 100644 index 5273f92..0000000 --- a/stringcache/testutil/integration.go +++ /dev/null @@ -1,43 +0,0 @@ -package testutil - -import ( - "testing" - - "github.com/upfluence/pkg/stringcache" -) - -const mockKey = "foo-bar" - -func IntegrationScenario(t *testing.T, cache stringcache.Cache) { - e, err := cache.Has(mockKey) - - if e { - t.Error("Key exist in empty cache") - } - - if err != nil { - t.Errorf("Has returned an error: %+v", err) - } - - if err2 := cache.Delete(mockKey); err2 != stringcache.ErrNotFound { - t.Errorf("Delete returned an unexpected error: %+v", err2) - } - - if err2 := cache.Add(mockKey); err2 != nil { - t.Errorf("Add returned an unexpected error: %+v", err2) - } - - e, err = cache.Has(mockKey) - - if !e { - t.Error("Key not exist in a warmed up cache") - } - - if err != nil { - t.Errorf("Has returned an error: %+v", err) - } - - if err := cache.Delete(mockKey); err != nil { - t.Errorf("Delete returned an unexpected error: %+v", err) - } -} From 34115448e27582b73704f5160ee7988c8db8347f Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 10 Jun 2022 18:53:26 +0200 Subject: [PATCH 04/21] syncutil: Make the KeyedSingleflight generic --- syncutil/keyed_singleflight.go | 12 ++++++------ syncutil/keyed_singleflight_test.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/syncutil/keyed_singleflight.go b/syncutil/keyed_singleflight.go index 724cb05..e73a957 100644 --- a/syncutil/keyed_singleflight.go +++ b/syncutil/keyed_singleflight.go @@ -5,23 +5,23 @@ import ( "sync" ) -type KeyedSingleflight struct { +type KeyedSingleflight[K comparable] struct { ctx context.Context cancel context.CancelFunc once sync.Once mu sync.Mutex - sfs map[string]*Singleflight + sfs map[K]*Singleflight } -func (ksf *KeyedSingleflight) init() { +func (ksf *KeyedSingleflight[K]) init() { ksf.once.Do(func() { ksf.ctx, ksf.cancel = context.WithCancel(context.Background()) - ksf.sfs = make(map[string]*Singleflight) + ksf.sfs = make(map[K]*Singleflight) }) } -func (ksf *KeyedSingleflight) Do(ctx context.Context, key string, fn func(context.Context) error) (bool, error) { +func (ksf *KeyedSingleflight[K]) Do(ctx context.Context, key K, fn func(context.Context) error) (bool, error) { ksf.init() ksf.mu.Lock() @@ -49,7 +49,7 @@ func (ksf *KeyedSingleflight) Do(ctx context.Context, key string, fn func(contex ) } -func (ksf *KeyedSingleflight) Close() error { +func (ksf *KeyedSingleflight[K]) Close() error { ksf.init() ksf.cancel() diff --git a/syncutil/keyed_singleflight_test.go b/syncutil/keyed_singleflight_test.go index 5c2014f..1909c39 100644 --- a/syncutil/keyed_singleflight_test.go +++ b/syncutil/keyed_singleflight_test.go @@ -11,7 +11,7 @@ import ( func TestKeyedSingleflight(t *testing.T) { var ( - ksf KeyedSingleflight + ksf KeyedSingleflight[string] wg sync.WaitGroup ctx = context.Background() From af5eeeb84c624312dea77a5f36bda77cb7c6f689 Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 22:41:40 -0700 Subject: [PATCH 05/21] prometheus: Drop prometheus pkg --- amqp/cluster_dialer.go | 101 ------------------- amqp/consumer/consumer_test.go | 57 ----------- amqp/consumer/options.go | 53 ---------- prometheus/exporter/exporter.go | 9 -- prometheus/exporter/intervalpush/exporter.go | 90 ----------------- prometheus/metricutil/result.go | 14 --- 6 files changed, 324 deletions(-) delete mode 100644 amqp/cluster_dialer.go delete mode 100644 amqp/consumer/consumer_test.go delete mode 100644 amqp/consumer/options.go delete mode 100644 prometheus/exporter/exporter.go delete mode 100644 prometheus/exporter/intervalpush/exporter.go delete mode 100644 prometheus/metricutil/result.go diff --git a/amqp/cluster_dialer.go b/amqp/cluster_dialer.go deleted file mode 100644 index 69433f8..0000000 --- a/amqp/cluster_dialer.go +++ /dev/null @@ -1,101 +0,0 @@ -package amqp - -import ( - "sync" - - "github.com/upfluence/errors" - - "github.com/upfluence/pkg/amqp/channelpool" - "github.com/upfluence/pkg/amqp/connectionpicker" - "github.com/upfluence/pkg/discovery/balancer" - "github.com/upfluence/pkg/discovery/balancer/roundrobin" - "github.com/upfluence/pkg/discovery/resolver" -) - -type Cluster struct { - Picker connectionpicker.Picker - Pool channelpool.Pool - - b balancer.Balancer -} - -func (c *Cluster) Close() error { - return errors.Combine( - c.Pool.Close(), - c.Picker.Close(), - c.b.Close(), - ) -} - -type ClusterDialer struct { - BalancerBuilder balancer.Builder - PickerOptions []connectionpicker.Option - PoolOptions []channelpool.Option - - mu sync.Mutex - cs map[string]*Cluster -} - -func NewClusterDialerFromResolver(rb resolver.Builder) *ClusterDialer { - return &ClusterDialer{ - BalancerBuilder: balancer.ResolverBuilder{ - Builder: rb, - BalancerFunc: roundrobin.BalancerFunc, - }, - } -} - -func (cd *ClusterDialer) Dial(n string) *Cluster { - cd.mu.Lock() - defer cd.mu.Unlock() - - if cd.cs == nil { - cd.cs = make(map[string]*Cluster, 1) - } - - c, ok := cd.cs[n] - - if ok { - return c - } - - b := cd.BalancerBuilder.Build(n) - - picker := connectionpicker.NewPicker( - append( - []connectionpicker.Option{connectionpicker.WithBalancer(b)}, - cd.PickerOptions..., - )..., - ) - - c = &Cluster{ - Picker: picker, - Pool: channelpool.NewPool( - append( - []channelpool.Option{channelpool.WithPicker(picker)}, - cd.PoolOptions..., - )..., - ), - } - - cd.cs[n] = c - - return c -} - -func (cd *ClusterDialer) Close() error { - cd.mu.Lock() - defer cd.mu.Unlock() - - var errs []error - - for _, c := range cd.cs { - if err := c.Close(); err != nil { - errs = append(errs, err) - } - } - - cd.cs = nil - - return errors.WrapErrors(errs) -} diff --git a/amqp/consumer/consumer_test.go b/amqp/consumer/consumer_test.go deleted file mode 100644 index ce1db41..0000000 --- a/amqp/consumer/consumer_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package consumer - -import ( - "context" - "testing" - - "github.com/streadway/amqp" - "github.com/stretchr/testify/assert" - - "github.com/upfluence/pkg/testutil" -) - -func TestIntegartion(t *testing.T) { - testutil.FetchEnvVariable(t, "RABBITMQ_URL") - c := NewConsumer() - ctx := context.Background() - - err := c.Open(ctx) - assert.Nil(t, err) - - q, err := c.QueueName(ctx) - assert.Nil(t, err) - - dc, ec, err := c.Consume() - assert.Nil(t, err) - - p := c.(*consumer).opts.pool - ch, err := p.Get(ctx) - assert.Nil(t, err) - - err = ch.Publish("", q, false, false, amqp.Publishing{Body: []byte("foo")}) - assert.Nil(t, err) - - assert.Nil(t, p.Put(ch)) - - select { - case <-ec: - t.Errorf("did not expect message on the error chan") - default: - } - - d := <-dc - assert.Equal(t, []byte("foo"), d.Body) - - assert.Nil(t, c.Close()) - - _, ok := <-dc - assert.False(t, ok) - - _, ok = <-ec - assert.False(t, ok) - - _, _, err = c.Consume() - assert.Equal(t, ErrCancelled, err) - - assert.Nil(t, c.Close()) -} diff --git a/amqp/consumer/options.go b/amqp/consumer/options.go deleted file mode 100644 index 0441cc4..0000000 --- a/amqp/consumer/options.go +++ /dev/null @@ -1,53 +0,0 @@ -package consumer - -import ( - "time" - - "github.com/upfluence/pkg/amqp/channelpool" - "github.com/upfluence/pkg/backoff" - "github.com/upfluence/pkg/backoff/static" - "github.com/upfluence/pkg/iopool" - "github.com/upfluence/pkg/peer" -) - -var defaultOptions = &options{ - pool: channelpool.NewPool( - channelpool.WithPoolOptions(iopool.WithSize(2)), - ), - handlePoolClosing: true, - shouldContinueFn: func(error) bool { return true }, - backoff: static.NewInfiniteBackoff(1 * time.Second), - consumerTag: peer.FromEnv().InstanceName, -} - -type Option func(*options) - -type options struct { - pool channelpool.Pool - handlePoolClosing bool - - queueName string - consumerTag string - - shouldContinueFn func(error) bool - backoff backoff.Strategy -} - -func WithPool(p channelpool.Pool) Option { - return func(o *options) { - o.pool = p - o.handlePoolClosing = false - } -} - -func WithQueueName(n string) Option { - return func(o *options) { o.queueName = n } -} - -func WithConsumerTag(t string) Option { - return func(o *options) { o.consumerTag = t } -} - -func WithBackoff(s backoff.Strategy) Option { - return func(o *options) { o.backoff = s } -} diff --git a/prometheus/exporter/exporter.go b/prometheus/exporter/exporter.go deleted file mode 100644 index 832c3c8..0000000 --- a/prometheus/exporter/exporter.go +++ /dev/null @@ -1,9 +0,0 @@ -package exporter - -import "io" - -type Exporter interface { - io.Closer - - Export(<-chan bool) -} diff --git a/prometheus/exporter/intervalpush/exporter.go b/prometheus/exporter/intervalpush/exporter.go deleted file mode 100644 index 3494329..0000000 --- a/prometheus/exporter/intervalpush/exporter.go +++ /dev/null @@ -1,90 +0,0 @@ -package intervalexporter - -import ( - "context" - "os" - "time" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/push" - - "github.com/upfluence/pkg/closer" - "github.com/upfluence/pkg/log" - exp "github.com/upfluence/pkg/prometheus/exporter" -) - -const defaultInterval = 15 * time.Second - -type exporter struct { - *closer.Monitor - - t *time.Ticker - p *push.Pusher -} - -func NewExporter(url string, interval time.Duration) exp.Exporter { - if interval == 0 { - interval = defaultInterval - } - - return NewExporterWithOptions( - url, - WithInterval(interval), - WithGrouping("instance", os.Getenv("UNIT_NAME")), - ) -} - -type Option func(exporter *exporter) - -func WithInterval(interval time.Duration) Option { - return func(e *exporter) { - e.t.Reset(interval) - } -} - -func WithGrouping(name, value string) Option { - return func(e *exporter) { - e.p = e.p.Grouping(name, value) - } -} - -func NewExporterWithOptions(url string, opts ...Option) exp.Exporter { - var e = exporter{ - Monitor: closer.NewMonitor(closer.WithClosingPolicy(closer.Wait)), - t: time.NewTicker(defaultInterval), - p: push.New( - url, - os.Getenv("APP_NAME"), - ).Gatherer( - prometheus.DefaultGatherer, - ), - } - - for _, opt := range opts { - opt(&e) - } - - return &e -} - -func (e *exporter) Export(exitChan <-chan bool) { - e.Run(func(ctx context.Context) { - for { - select { - case <-exitChan: - e.t.Stop() - return - case <-ctx.Done(): - e.t.Stop() - if err := e.p.Push(); err != nil { - log.Noticef("Push to gatherer failed: %s", err.Error()) - } - return - case <-e.t.C: - if err := e.p.Push(); err != nil { - log.Noticef("Push to gatherer failed: %s", err.Error()) - } - } - } - }) -} diff --git a/prometheus/metricutil/result.go b/prometheus/metricutil/result.go deleted file mode 100644 index c3589de..0000000 --- a/prometheus/metricutil/result.go +++ /dev/null @@ -1,14 +0,0 @@ -package metricutil - -import ( - "github.com/upfluence/errors" - "github.com/upfluence/errors/stats" -) - -func WrapStatus(err error, status string) error { - return errors.WithStatus(err, status) -} - -func ResultStatus(err error) string { - return stats.GetStatus(err) -} From 24d43aeefdde8a162105b601a0af9436c319776e Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 22:42:29 -0700 Subject: [PATCH 06/21] amqp: Drop cassandrautil pkg --- cassandrautil/parser.go | 115 ---------------------------------------- 1 file changed, 115 deletions(-) delete mode 100644 cassandrautil/parser.go diff --git a/cassandrautil/parser.go b/cassandrautil/parser.go deleted file mode 100644 index 1ac4182..0000000 --- a/cassandrautil/parser.go +++ /dev/null @@ -1,115 +0,0 @@ -package cassandrautil - -import ( - "fmt" - "strings" - "time" - - "github.com/gocql/gocql" - - "github.com/upfluence/pkg/cfg" - "github.com/upfluence/pkg/log" -) - -var defaultOptions = &options{ - keyspace: cfg.FetchString("CASSANDRA_KEYSPACE", "test"), - cassandraURL: cfg.FetchString("CASSANDRA_URL", "127.0.0.1"), - port: 9042, - protocolVersion: 3, - consistency: gocql.Quorum, - timeout: 15 * time.Second, - retryPolicy: &gocql.SimpleRetryPolicy{NumRetries: 3}, -} - -func Keyspace(k string) Option { - return func(o *options) { o.keyspace = k } -} - -func CassandraURL(url string) Option { - return func(o *options) { o.cassandraURL = url } -} - -func Consistency(c gocql.Consistency) Option { - return func(o *options) { o.consistency = c } -} - -func Timeout(t time.Duration) Option { - return func(o *options) { o.timeout = t } -} - -func Port(p int) Option { - return func(o *options) { o.port = p } -} - -func RetryPolicy(p gocql.RetryPolicy) Option { - return func(o *options) { o.retryPolicy = p } -} - -func WithRawOption(fn func(*gocql.ClusterConfig)) Option { - return func(o *options) { o.rawOptions = append(o.rawOptions, fn) } -} - -type options struct { - keyspace, cassandraURL string - port, protocolVersion int - - consistency gocql.Consistency - retryPolicy gocql.RetryPolicy - timeout time.Duration - - rawOptions []func(*gocql.ClusterConfig) -} - -func (o options) cassandraIPs() []string { - return strings.Split(o.cassandraURL, ",") -} - -type Option func(*options) - -func BuildSession(opts ...Option) (*gocql.Session, error) { - opt := *defaultOptions - - for _, optFn := range opts { - optFn(&opt) - } - - cluster := gocql.NewCluster(opt.cassandraIPs()...) - - cluster.Consistency = opt.consistency - cluster.ProtoVersion = opt.protocolVersion - cluster.Keyspace = opt.keyspace - cluster.Timeout = opt.timeout - cluster.RetryPolicy = opt.retryPolicy - - for _, o := range opt.rawOptions { - o(cluster) - } - - return cluster.CreateSession() -} - -func MustBuildSession(opts ...Option) *gocql.Session { - var sess, err = BuildSession(opts...) - - if err != nil { - log.Fatalf("cassandrautil: %v", err) - } - - return sess -} - -func CassandraURI(opts ...Option) string { - opt := *defaultOptions - - for _, optFn := range opts { - optFn(&opt) - } - - return fmt.Sprintf( - "cassandra://%s:%d/%s?protocol=%d", - strings.Split(opt.cassandraURL, ",")[0], - opt.port, - opt.keyspace, - opt.protocolVersion, - ) -} From 4332e80f5265a03d7db98d4d8b54b4639a252f9d Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 22:42:48 -0700 Subject: [PATCH 07/21] amqp: Drop container pkg --- container/lru/cache.go | 148 ------------------------------------ container/lru/cache_test.go | 49 ------------ 2 files changed, 197 deletions(-) delete mode 100644 container/lru/cache.go delete mode 100644 container/lru/cache_test.go diff --git a/container/lru/cache.go b/container/lru/cache.go deleted file mode 100644 index 77ca154..0000000 --- a/container/lru/cache.go +++ /dev/null @@ -1,148 +0,0 @@ -package lru - -import ( - "container/list" - "sync" -) - -type Cache struct { - mu *sync.RWMutex - entries int - ll *syncList - cache map[interface{}]*list.Element -} - -type Key interface{} - -type entry struct { - key Key - value interface{} -} - -func NewCache(maxEntries int) *Cache { - return &Cache{ - mu: &sync.RWMutex{}, - entries: maxEntries, - ll: &syncList{List: list.New()}, - cache: make(map[interface{}]*list.Element), - } -} - -func (c *Cache) cacheFetch(key Key) (*list.Element, bool) { - c.mu.RLock() - defer c.mu.RUnlock() - - v, ok := c.cache[key] - return v, ok -} - -func (c *Cache) Add(key Key, value interface{}) { - if ee, ok := c.cacheFetch(key); ok { - c.ll.MoveToFront(ee) - - c.mu.Lock() - defer c.mu.Unlock() - - ee.Value.(*entry).value = value - return - } - - c.mu.Lock() - ele := c.ll.PushFront(&entry{key, value}) - c.cache[key] = ele - c.mu.Unlock() - - if c.entries != 0 && c.ll.Len() > c.entries { - c.removeOldest() - } -} - -func (c *Cache) Get(key Key) (interface{}, bool) { - if ee, ok := c.cacheFetch(key); ok { - c.ll.MoveToFront(ee) - - c.mu.RLock() - defer c.mu.RUnlock() - - return ee.Value.(*entry).value, true - } - - return nil, false -} - -func (c *Cache) Remove(key Key) { - if ee, ok := c.cacheFetch(key); ok { - c.removeElement(ee) - } -} - -func (c *Cache) removeOldest() { - ele := c.ll.Back() - - if ele != nil { - c.removeElement(ele) - } -} - -func (c *Cache) removeElement(e *list.Element) { - c.mu.Lock() - defer c.mu.Unlock() - - c.ll.Remove(e) - kv := e.Value.(*entry) - - delete(c.cache, kv.key) -} - -func (c *Cache) Len() int { - return c.ll.Len() -} - -func (c *Cache) Clear() { - c.mu.Lock() - defer c.mu.Unlock() - - c.ll.Init() - c.cache = make(map[interface{}]*list.Element) -} - -type syncList struct { - *list.List - - mu sync.RWMutex -} - -func (l *syncList) MoveToFront(e *list.Element) { - l.mu.Lock() - defer l.mu.Unlock() - - l.List.MoveToFront(e) -} - -func (l *syncList) Remove(e *list.Element) interface{} { - l.mu.Lock() - defer l.mu.Unlock() - - return l.List.Remove(e) -} - -func (l *syncList) PushFront(v interface{}) *list.Element { - l.mu.Lock() - defer l.mu.Unlock() - - return l.List.PushFront(v) -} - -func (l *syncList) Back() *list.Element { - l.mu.RLock() - defer l.mu.RUnlock() - - return l.List.Back() -} - -func (l *syncList) Len() int { - l.mu.RLock() - defer l.mu.RUnlock() - - return l.List.Len() -} diff --git a/container/lru/cache_test.go b/container/lru/cache_test.go deleted file mode 100644 index 406d815..0000000 --- a/container/lru/cache_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package lru - -import ( - "fmt" - "sync" - "testing" - - "github.com/stretchr/testify/assert" -) - -func assertValue(t *testing.T, c *Cache, k Key, eV interface{}, eOk bool) { - v, ok := c.Get("foo") - - assert.Equal(t, eV, v) - assert.Equal(t, eOk, ok) -} - -func TestIntegration(t *testing.T) { - c := NewCache(2) - - assertValue(t, c, "foo", nil, false) - c.Add("foo", "bar") - assertValue(t, c, "foo", "bar", true) - c.Add("fiz", "bar") - assertValue(t, c, "foo", "bar", true) - c.Add("buz", "bar") - c.Add("bizz", "bar") - assertValue(t, c, "foo", nil, false) -} - -func TestRaceCondition(t *testing.T) { - c := NewCache(2) - wg := &sync.WaitGroup{} - - for i := 0; i < 200; i++ { - wg.Add(1) - - go func() { - defer wg.Done() - - for i := 0; i < 200; i++ { - c.Add(fmt.Sprintf("buz %d", i), "bar") - c.Get("foo") - } - }() - } - - wg.Wait() -} From bd6d88dad32aab28595be259292d94bb23d36bf0 Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 22:43:33 -0700 Subject: [PATCH 08/21] httputil: Remove pkg --- httputil/healthcheck.go | 7 ------- httputil/mux.go | 22 ---------------------- multierror/multierror.go | 12 ------------ noop.go | 7 ------- 4 files changed, 48 deletions(-) delete mode 100644 httputil/healthcheck.go delete mode 100644 httputil/mux.go delete mode 100644 multierror/multierror.go delete mode 100644 noop.go diff --git a/httputil/healthcheck.go b/httputil/healthcheck.go deleted file mode 100644 index 6a8468a..0000000 --- a/httputil/healthcheck.go +++ /dev/null @@ -1,7 +0,0 @@ -package httputil - -import "net/http" - -func HealthcheckHandler(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("ok")) -} diff --git a/httputil/mux.go b/httputil/mux.go deleted file mode 100644 index bd98075..0000000 --- a/httputil/mux.go +++ /dev/null @@ -1,22 +0,0 @@ -package httputil - -import ( - "net/http" - "net/http/pprof" - "os" -) - -func NewMux() *http.ServeMux { - mux := http.NewServeMux() - - mux.HandleFunc("/healthcheck", HealthcheckHandler) - - if os.Getenv("DEBUG") == "true" { - mux.HandleFunc("/debug/pprof/profile", pprof.Profile) - mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) - mux.HandleFunc("/debug/pprof/trace", pprof.Trace) - mux.HandleFunc("/debug/pprof/heap", pprof.Index) - } - - return mux -} diff --git a/multierror/multierror.go b/multierror/multierror.go deleted file mode 100644 index 2ecb1c2..0000000 --- a/multierror/multierror.go +++ /dev/null @@ -1,12 +0,0 @@ -package multierror - -import ( - "github.com/upfluence/errors" - "github.com/upfluence/errors/multi" -) - -func Wrap(errs []error) error { return errors.WrapErrors(errs) } - -func Combine(errs ...error) error { return errors.WrapErrors(errs) } - -type MultiError = multi.MultiError diff --git a/noop.go b/noop.go deleted file mode 100644 index 9330e26..0000000 --- a/noop.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/upfluence/pkg/log" - -func main() { - log.Notice("foo") -} From 1ded5309909d55da377e42c11e484d900623de0e Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 22:44:34 -0700 Subject: [PATCH 09/21] syncutil: Make the Singleflight generics --- syncutil/keyed_singleflight.go | 167 ++++++++++++++++++++++++---- syncutil/keyed_singleflight_test.go | 25 +++-- syncutil/singleflight.go | 29 +++-- syncutil/singleflight_test.go | 39 ++++--- 4 files changed, 204 insertions(+), 56 deletions(-) diff --git a/syncutil/keyed_singleflight.go b/syncutil/keyed_singleflight.go index e73a957..aa94217 100644 --- a/syncutil/keyed_singleflight.go +++ b/syncutil/keyed_singleflight.go @@ -3,53 +3,93 @@ package syncutil import ( "context" "sync" + + "github.com/upfluence/pkg/group" ) -type KeyedSingleflight[K comparable] struct { +type KeyedSingleflight[K comparable, V any] struct { ctx context.Context cancel context.CancelFunc once sync.Once mu sync.Mutex - sfs map[K]*Singleflight + sfs map[K]*Singleflight[map[K]V] } -func (ksf *KeyedSingleflight[K]) init() { +func (ksf *KeyedSingleflight[K, V]) init() { ksf.once.Do(func() { ksf.ctx, ksf.cancel = context.WithCancel(context.Background()) - ksf.sfs = make(map[K]*Singleflight) + ksf.sfs = make(map[K]*Singleflight[map[K]V]) }) } -func (ksf *KeyedSingleflight[K]) Do(ctx context.Context, key K, fn func(context.Context) error) (bool, error) { +func (ksf *KeyedSingleflight[K, V]) releaseKeys(keys []K) { + ksf.mu.Lock() + defer ksf.mu.Unlock() + + for _, key := range keys { + delete(ksf.sfs, key) + } +} + +func (ksf *KeyedSingleflight[K, V]) prepare(keys []K) executors[K, V] { + var ( + newSingleflight *Singleflight[map[K]V] + keysToExecute []K + existingSingleflights = make(map[*Singleflight[map[K]V]][]K) + ) + ksf.init() ksf.mu.Lock() - sf, ok := ksf.sfs[key] + for _, key := range keys { + if sf, ok := ksf.sfs[key]; ok { + existingSingleflights[sf] = append(existingSingleflights[sf], key) + continue + } + + keysToExecute = append(keysToExecute, key) + } + + if len(keysToExecute) > 0 { + newSingleflight = &Singleflight[map[K]V]{ctx: ksf.ctx, cancel: ksf.cancel} - if !ok { - sf = &Singleflight{ctx: ksf.ctx, cancel: ksf.cancel} - ksf.sfs[key] = sf + for _, k := range keysToExecute { + ksf.sfs[k] = newSingleflight + } } ksf.mu.Unlock() - return sf.Do( - ctx, - func(ctx context.Context) error { - err := fn(ctx) + executors := make(executors[K, V], 0, len(existingSingleflights)+1) - ksf.mu.Lock() - delete(ksf.sfs, key) - ksf.mu.Unlock() + for sf, ks := range existingSingleflights { + executors = append( + executors, + executor[K, V]{singleflight: sf, keys: ks, ksf: ksf}, + ) + } - return err - }, - ) + if newSingleflight != nil { + executors = append( + executors, + executor[K, V]{ + singleflight: newSingleflight, + keys: keysToExecute, + ksf: ksf, + }, + ) + } + + return executors } -func (ksf *KeyedSingleflight[K]) Close() error { +func (ksf *KeyedSingleflight[K, V]) Do(ctx context.Context, keys []K, fn func(context.Context, []K) (map[K]V, error)) (map[K]V, error) { + return ksf.prepare(keys).execute(ctx, fn) +} + +func (ksf *KeyedSingleflight[K, V]) Close() error { ksf.init() ksf.cancel() @@ -59,3 +99,90 @@ func (ksf *KeyedSingleflight[K]) Close() error { return nil } + +type executor[K comparable, V any] struct { + keys []K + singleflight *Singleflight[map[K]V] + + ksf *KeyedSingleflight[K, V] +} + +func (e executor[K, V]) execute(ctx context.Context, fn func(context.Context, []K) (map[K]V, error)) (bool, map[K]V, error) { + ok, vs, err := e.singleflight.Do( + ctx, + func(ctx context.Context) (map[K]V, error) { + res, err := fn(ctx, e.keys) + e.ksf.releaseKeys(e.keys) + return res, err + }, + ) + + res := make(map[K]V, len(e.keys)) + + for _, key := range e.keys { + if v, ok := vs[key]; ok { + res[key] = v + } + } + + return ok, res, err +} + +type executors[K comparable, V any] []executor[K, V] + +func (es executors[K, V]) execute(ctx context.Context, fn func(context.Context, []K) (map[K]V, error)) (map[K]V, error) { + switch len(es) { + case 0: + return nil, nil + case 1: + _, vs, err := es[0].execute(ctx, fn) + return vs, err + } + + tg := group.TypedGroup[map[K]V]{ + Group: group.ErrorGroup(ctx), + Value: make(map[K]V), + } + + for _, e := range es { + e := e + + tg.Do(func(ctx context.Context) (func(*map[K]V), error) { + _, vs, err := e.execute(ctx, fn) + + if err != nil { + return nil, err + } + + return func(res *map[K]V) { + for k, v := range vs { + (*res)[k] = v + } + }, nil + }) + } + + return tg.Wait() +} + +func DoOne[K comparable, V any](ctx context.Context, ksf *KeyedSingleflight[K, V], key K, fn func(context.Context) (V, error)) (bool, V, error) { + ok, vs, err := ksf.prepare([]K{key})[0].execute( + ctx, + func(ctx context.Context, _ []K) (map[K]V, error) { + v, err := fn(ctx) + + if err != nil { + return nil, err + } + + return map[K]V{key: v}, nil + }, + ) + + if err != nil { + var zero V + return ok, zero, err + } + + return ok, vs[key], nil +} diff --git a/syncutil/keyed_singleflight_test.go b/syncutil/keyed_singleflight_test.go index 1909c39..7e35b83 100644 --- a/syncutil/keyed_singleflight_test.go +++ b/syncutil/keyed_singleflight_test.go @@ -3,6 +3,7 @@ package syncutil import ( "context" "sync" + "sync/atomic" "testing" "time" @@ -11,8 +12,9 @@ import ( func TestKeyedSingleflight(t *testing.T) { var ( - ksf KeyedSingleflight[string] + ksf KeyedSingleflight[string, int32] wg sync.WaitGroup + ctr int32 ctx = context.Background() donec = make(chan struct{}) @@ -21,36 +23,45 @@ func TestKeyedSingleflight(t *testing.T) { wg.Add(3) go func() { - ok, _ := ksf.Do(ctx, "foo", func(context.Context) error { + ok, res, err := DoOne(ctx, &ksf, "foo", func(context.Context) (int32, error) { + res := atomic.AddInt32(&ctr, 1) <-donec - return nil + return res, nil }) + assert.Equal(t, res, int32(1)) assert.True(t, ok) + assert.NoError(t, err) wg.Done() }() time.Sleep(10 * time.Millisecond) go func() { - ok, _ := ksf.Do(ctx, "bar", func(context.Context) error { + ok, res, err := DoOne(ctx, &ksf, "bar", func(context.Context) (int32, error) { + res := atomic.AddInt32(&ctr, 1) <-donec - return nil + return res, nil }) + assert.Equal(t, res, int32(2)) assert.True(t, ok) + assert.NoError(t, err) wg.Done() }() time.Sleep(10 * time.Millisecond) go func() { - ok, _ := ksf.Do(ctx, "foo", func(context.Context) error { + ok, res, err := DoOne(ctx, &ksf, "foo", func(context.Context) (int32, error) { + res := atomic.AddInt32(&ctr, 1) <-donec - return nil + return res, nil }) + assert.Equal(t, res, int32(1)) assert.False(t, ok) + assert.NoError(t, err) wg.Done() }() diff --git a/syncutil/singleflight.go b/syncutil/singleflight.go index 6d67ea2..496f77d 100644 --- a/syncutil/singleflight.go +++ b/syncutil/singleflight.go @@ -5,26 +5,31 @@ import ( "sync" ) -type Singleflight struct { +type result[T any] struct { + res T + err error +} + +type Singleflight[T any] struct { ctx context.Context cancel context.CancelFunc once sync.Once wg sync.WaitGroup mu sync.Mutex - chs []chan<- error + chs []chan<- result[T] } -func (sf *Singleflight) init() { +func (sf *Singleflight[T]) init() { sf.once.Do(func() { sf.ctx, sf.cancel = context.WithCancel(context.Background()) }) } -func (sf *Singleflight) Do(ctx context.Context, fn func(context.Context) error) (bool, error) { +func (sf *Singleflight[T]) Do(ctx context.Context, fn func(context.Context) (T, error)) (bool, T, error) { sf.init() - ch := make(chan error, 1) + ch := make(chan result[T], 1) executor := false sf.mu.Lock() @@ -35,12 +40,13 @@ func (sf *Singleflight) Do(ctx context.Context, fn func(context.Context) error) sf.wg.Add(1) go func() { - err := fn(sf.ctx) + val, err := fn(sf.ctx) + res := result[T]{res: val, err: err} sf.mu.Lock() for _, ch := range sf.chs { - ch <- err + ch <- res close(ch) } @@ -56,13 +62,14 @@ func (sf *Singleflight) Do(ctx context.Context, fn func(context.Context) error) select { case <-ctx.Done(): - return executor, ctx.Err() - case err := <-ch: - return executor, err + var zero T + return executor, zero, ctx.Err() + case res := <-ch: + return executor, res.res, res.err } } -func (sf *Singleflight) Close() error { +func (sf *Singleflight[T]) Close() error { sf.init() sf.cancel() sf.wg.Wait() diff --git a/syncutil/singleflight_test.go b/syncutil/singleflight_test.go index 514ffd3..932ff4b 100644 --- a/syncutil/singleflight_test.go +++ b/syncutil/singleflight_test.go @@ -12,7 +12,7 @@ import ( func TestSingleflightDedup(t *testing.T) { var ( - sf Singleflight + sf Singleflight[int32] ctr int32 wg sync.WaitGroup @@ -20,17 +20,18 @@ func TestSingleflightDedup(t *testing.T) { donec = make(chan struct{}) ) - fn := func(context.Context) error { - atomic.AddInt32(&ctr, 1) + fn := func(context.Context) (int32, error) { + res := atomic.AddInt32(&ctr, 1) <-donec - return nil + return res, nil } wg.Add(2) go func() { - ok, err := sf.Do(ctx, fn) + ok, res, err := sf.Do(ctx, fn) + assert.Equal(t, res, int32(1)) assert.True(t, ok) assert.Nil(t, err) @@ -40,8 +41,9 @@ func TestSingleflightDedup(t *testing.T) { time.Sleep(10 * time.Millisecond) go func() { - ok, err := sf.Do(ctx, fn) + ok, res, err := sf.Do(ctx, fn) + assert.Equal(t, res, int32(1)) assert.False(t, ok) assert.Nil(t, err) @@ -61,7 +63,7 @@ func TestSingleflightDedup(t *testing.T) { func TestSingleflightDedupEarlyCancel(t *testing.T) { var ( - sf Singleflight + sf Singleflight[int32] ctr int32 wg sync.WaitGroup @@ -69,10 +71,10 @@ func TestSingleflightDedupEarlyCancel(t *testing.T) { donec = make(chan struct{}) ) - fn := func(context.Context) error { - atomic.AddInt32(&ctr, 1) + fn := func(context.Context) (int32, error) { + res := atomic.AddInt32(&ctr, 1) <-donec - return nil + return res, nil } wg.Add(2) @@ -81,7 +83,7 @@ func TestSingleflightDedupEarlyCancel(t *testing.T) { cctx, cancel := context.WithCancel(ctx) cancel() - ok, err := sf.Do(cctx, fn) + ok, _, err := sf.Do(cctx, fn) assert.True(t, ok) assert.Equal(t, context.Canceled, err) @@ -92,8 +94,9 @@ func TestSingleflightDedupEarlyCancel(t *testing.T) { time.Sleep(10 * time.Millisecond) go func() { - ok, err := sf.Do(ctx, fn) + ok, res, err := sf.Do(ctx, fn) + assert.Equal(t, res, int32(1)) assert.False(t, ok) assert.Nil(t, err) @@ -112,7 +115,7 @@ func TestSingleflightDedupEarlyCancel(t *testing.T) { func TestSingleflightStopOnClose(t *testing.T) { var ( - sf Singleflight + sf Singleflight[int32] ctr int32 wg sync.WaitGroup @@ -120,21 +123,21 @@ func TestSingleflightStopOnClose(t *testing.T) { donec = make(chan struct{}) ) - fn := func(ctx context.Context) error { + fn := func(ctx context.Context) (int32, error) { select { case <-ctx.Done(): - return ctx.Err() + return 0, ctx.Err() case <-donec: } - atomic.AddInt32(&ctr, 1) - return nil + res := atomic.AddInt32(&ctr, 1) + return res, nil } wg.Add(1) go func() { - ok, err := sf.Do(ctx, fn) + ok, _, err := sf.Do(ctx, fn) assert.True(t, ok) assert.Equal(t, context.Canceled, err) From 3d2a0995e3e49f5d66cf05f1d5eb0af0f89c4cfc Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 22:45:32 -0700 Subject: [PATCH 10/21] iopool,pool: Make the Pool s generic --- iopool/options.go | 8 +-- iopool/pool.go | 117 ++++++++++++++++++--------------- iopool/pool_test.go | 24 +++---- pool/bounded/pool.go | 102 ---------------------------- pool/middleware/logger/pool.go | 92 -------------------------- pool/pool.go | 39 ++++++----- 6 files changed, 105 insertions(+), 277 deletions(-) delete mode 100644 pool/bounded/pool.go delete mode 100644 pool/middleware/logger/pool.go diff --git a/iopool/options.go b/iopool/options.go index 79c2d19..b6f9054 100644 --- a/iopool/options.go +++ b/iopool/options.go @@ -19,7 +19,7 @@ type options struct { size int idleSize int - eps []policy.EvictionPolicy + eps []policy.EvictionPolicy[uint64] sc stats.Scope scfn func(stats.Scope) stats.Scope @@ -37,15 +37,15 @@ func newOptions(opts ...Option) *options { func (o *options) scope() stats.Scope { return o.scfn(o.sc) } -func (o *options) evictionPolicy() policy.EvictionPolicy { - return policy.CombinePolicies(o.eps...) +func (o *options) evictionPolicy() policy.EvictionPolicy[uint64] { + return policy.CombinePolicies[uint64](o.eps...) } type Option func(*options) func WithIdleTimeout(d time.Duration) Option { return func(o *options) { - o.eps = append(o.eps, ptime.NewIdlePolicy(d)) + o.eps = append(o.eps, ptime.NewIdlePolicy[uint64](d)) } } diff --git a/iopool/pool.go b/iopool/pool.go index 25c3b90..8b38dc3 100644 --- a/iopool/pool.go +++ b/iopool/pool.go @@ -3,7 +3,6 @@ package iopool import ( "context" "io" - "strconv" "sync" "sync/atomic" "time" @@ -17,25 +16,28 @@ import ( var ( ErrNotCheckout = errors.New("iopool: the entity does not belong to the pool") ErrClosed = errors.New("iopool: the pool is closed") + + errEmpty = errors.New("pool empty") ) -type Factory func(context.Context) (Entity, error) +type Factory[E Entity] func(context.Context) (E, error) type Entity interface { + comparable io.Closer Open(context.Context) error IsOpen() bool } -type entityWrapper struct { - e Entity - n string +type entityWrapper[E Entity] struct { + e E + n uint64 closed bool } -type Pool struct { +type Pool[E Entity] struct { *closer.Monitor size int @@ -43,33 +45,33 @@ type Pool struct { closeOnce sync.Once - factory Factory + factory Factory[E] createc chan struct{} - poolc chan *entityWrapper + poolc chan *entityWrapper[E] mu sync.Mutex - checkedout map[Entity]*entityWrapper + checkedout map[E]*entityWrapper[E] - checkout map[string]*entityWrapper - checkin map[string]*entityWrapper + checkout map[uint64]*entityWrapper[E] + checkin map[uint64]*entityWrapper[E] - ep policy.EvictionPolicy + ep policy.EvictionPolicy[uint64] cnt uint64 metrics metrics } -func NewPool(f Factory, opts ...Option) *Pool { +func NewPool[E Entity](f Factory[E], opts ...Option) *Pool[E] { o := newOptions(opts...) - p := Pool{ + p := Pool[E]{ size: o.size, idleSize: o.idleSize, Monitor: closer.NewMonitor(), factory: f, createc: make(chan struct{}, o.size), - poolc: make(chan *entityWrapper, o.idleSize), - checkedout: make(map[Entity]*entityWrapper), - checkout: make(map[string]*entityWrapper), - checkin: make(map[string]*entityWrapper), + poolc: make(chan *entityWrapper[E], o.idleSize), + checkedout: make(map[E]*entityWrapper[E]), + checkout: make(map[uint64]*entityWrapper[E]), + checkin: make(map[uint64]*entityWrapper[E]), ep: o.evictionPolicy(), } @@ -85,7 +87,7 @@ func NewPool(f Factory, opts ...Option) *Pool { return &p } -func (p *Pool) closer(ctx context.Context) { +func (p *Pool[E]) closer(ctx context.Context) { for { ch := p.ep.C() @@ -113,13 +115,15 @@ func (p *Pool) closer(ctx context.Context) { delete(p.checkin, ew.n) p.mu.Unlock() ew.e.Close() - ew.e = nil + + var zero E + ew.e = zero } } } } -func (p *Pool) markOut(ew *entityWrapper) { +func (p *Pool[E]) markOut(ew *entityWrapper[E]) { p.mu.Lock() p.checkedout[ew.e] = ew p.checkout[ew.n] = ew @@ -127,33 +131,37 @@ func (p *Pool) markOut(ew *entityWrapper) { p.mu.Unlock() } -func (p *Pool) getNoWait() (Entity, error) { +func (p *Pool[E]) getNoWait() (E, error) { + var zero E + for { select { case ew, ok := <-p.poolc: if !ok { - return nil, ErrClosed + return zero, ErrClosed } if p.checkoutWrapper(ew) { return ew.e, nil } default: - return nil, nil + return zero, errEmpty } } } -func (p *Pool) Get(ctx context.Context) (Entity, error) { +func (p *Pool[E]) Get(ctx context.Context) (E, error) { + var zero E + p.metrics.get.Inc() if !p.IsOpen() { - return nil, ErrClosed + return zero, ErrClosed } e, err := p.getNoWait() - if err != nil || e != nil { + if err != errEmpty { return e, err } @@ -166,12 +174,12 @@ func (p *Pool) Get(ctx context.Context) (Entity, error) { for { select { case <-p.Context().Done(): - return nil, ErrClosed + return zero, ErrClosed case <-ctx.Done(): - return nil, ctx.Err() + return zero, ctx.Err() case ew, ok := <-p.poolc: if !ok { - return nil, ErrClosed + return zero, ErrClosed } if p.checkoutWrapper(ew) { @@ -180,7 +188,7 @@ func (p *Pool) Get(ctx context.Context) (Entity, error) { } case _, ok := <-p.createc: if !ok { - return nil, ErrClosed + return zero, ErrClosed } ew, err := p.dial(ctx) @@ -191,7 +199,7 @@ func (p *Pool) Get(ctx context.Context) (Entity, error) { default: } - return nil, err + return zero, err } p.markOut(ew) @@ -202,14 +210,16 @@ func (p *Pool) Get(ctx context.Context) (Entity, error) { } } -func (p *Pool) checkoutWrapper(ew *entityWrapper) bool { - if ew.closed || ew.e == nil || !ew.e.IsOpen() { - if ew.e != nil { +func (p *Pool[E]) checkoutWrapper(ew *entityWrapper[E]) bool { + var zero E + + if ew.closed || ew.e == zero || !ew.e.IsOpen() { + if ew.e != zero { ew.e.Close() } p.mu.Lock() - if ew.e != nil { + if ew.e != zero { delete(p.checkedout, ew.e) } @@ -229,13 +239,13 @@ func (p *Pool) checkoutWrapper(ew *entityWrapper) bool { p.mu.Unlock() p.markOut(ew) - p.ep.Op(ew.n, policy.Evict) + p.ep.Op(policy.Evict, ew.n) p.metrics.idle.Update(int64(len(p.poolc))) return true } -func (p *Pool) dial(ctx context.Context) (*entityWrapper, error) { +func (p *Pool[E]) dial(ctx context.Context) (*entityWrapper[E], error) { e, err := p.factory(ctx) if err != nil { @@ -246,13 +256,13 @@ func (p *Pool) dial(ctx context.Context) (*entityWrapper, error) { return nil, err } - return &entityWrapper{ + return &entityWrapper[E]{ e: e, - n: strconv.Itoa(int(atomic.AddUint64(&p.cnt, 1))), + n: atomic.AddUint64(&p.cnt, 1), }, nil } -func (p *Pool) requeue(ew *entityWrapper) error { +func (p *Pool[E]) requeue(ew *entityWrapper[E]) error { select { case p.poolc <- ew: p.metrics.idle.Update(int64(len(p.poolc))) @@ -262,7 +272,7 @@ func (p *Pool) requeue(ew *entityWrapper) error { default: } - p.ep.Op(ew.n, policy.Evict) + p.ep.Op(policy.Evict, ew.n) p.mu.Lock() delete(p.checkin, ew.n) p.mu.Unlock() @@ -272,7 +282,7 @@ func (p *Pool) requeue(ew *entityWrapper) error { return nil } -func (p *Pool) Put(e Entity) error { +func (p *Pool[E]) Put(e E) error { if !e.IsOpen() { return p.Discard(e) } @@ -297,13 +307,13 @@ func (p *Pool) Put(e Entity) error { delete(p.checkout, ew.n) p.mu.Unlock() - p.ep.Op(ew.n, policy.Set) + p.ep.Op(policy.Set, ew.n) p.metrics.checkout.Update(int64(len(p.checkout))) return p.requeue(ew) } -func (p *Pool) Discard(e Entity) error { +func (p *Pool[E]) Discard(e E) error { p.metrics.discard.Inc() p.mu.Lock() ew, ok := p.checkedout[e] @@ -317,7 +327,7 @@ func (p *Pool) Discard(e Entity) error { delete(p.checkout, ew.n) p.mu.Unlock() - p.ep.Op(ew.n, policy.Evict) + p.ep.Op(policy.Evict, ew.n) p.metrics.checkout.Update(int64(len(p.checkout))) err := e.Close() @@ -330,14 +340,14 @@ func (p *Pool) Discard(e Entity) error { return err } -func (p *Pool) drainPoolChannel() []error { +func (p *Pool[E]) drainPoolChannel() []error { var errs []error for { select { case ew := <-p.poolc: p.metrics.idle.Update(int64(len(p.poolc))) - p.ep.Op(ew.n, policy.Evict) + p.ep.Op(policy.Evict, ew.n) if err := ew.e.Close(); err != nil { errs = append(errs, err) @@ -348,7 +358,7 @@ func (p *Pool) drainPoolChannel() []error { } } -func (p *Pool) Shutdown(ctx context.Context) error { +func (p *Pool[E]) Shutdown(ctx context.Context) error { if err := p.Monitor.Shutdown(ctx); err != nil { return err } @@ -372,8 +382,11 @@ func (p *Pool) Shutdown(ctx context.Context) error { return errors.WrapErrors(errs) } -func (p *Pool) Close() error { - var err error +func (p *Pool[E]) Close() error { + var ( + err error + zero E + ) p.closeOnce.Do(func() { p.Monitor.Close() @@ -391,7 +404,7 @@ func (p *Pool) Close() error { case ew := <-p.poolc: p.metrics.idle.Update(int64(len(p.poolc))) - if !ew.closed && ew.e != nil { + if !ew.closed && ew.e != zero { if err := ew.e.Close(); err != nil { errs = append(errs, err) } diff --git a/iopool/pool_test.go b/iopool/pool_test.go index 9ad57c4..7a72f63 100644 --- a/iopool/pool_test.go +++ b/iopool/pool_test.go @@ -35,8 +35,8 @@ func (e *entity) IsOpen() bool { func TestGarbageIdleConnections(t *testing.T) { e := &entity{isOpen: true} - p := NewPool( - func(context.Context) (Entity, error) { return e, nil }, + p := NewPool[*entity]( + func(context.Context) (*entity, error) { return e, nil }, WithIdleTimeout(100*time.Millisecond), ) @@ -68,8 +68,8 @@ func TestGarbageIdleConnections(t *testing.T) { func TestReuseConnections(t *testing.T) { e := &entity{isOpen: true} - p := NewPool( - func(context.Context) (Entity, error) { return e, nil }, + p := NewPool[*entity]( + func(context.Context) (*entity, error) { return e, nil }, WithIdleTimeout(100*time.Millisecond), ) @@ -89,8 +89,8 @@ func TestReuseConnections(t *testing.T) { func TestCloseSync(t *testing.T) { e := &entity{isOpen: true} - p := NewPool( - func(context.Context) (Entity, error) { return e, nil }, + p := NewPool[*entity]( + func(context.Context) (*entity, error) { return e, nil }, WithIdleTimeout(100*time.Millisecond), ) @@ -121,8 +121,8 @@ func TestLimitedIdleSize(t *testing.T) { &entity{isOpen: true}, } - p := NewPool( - func(context.Context) (Entity, error) { + p := NewPool[*entity]( + func(context.Context) (*entity, error) { i++ return es[i-1], nil }, @@ -152,8 +152,8 @@ func TestConcurrentPut(t *testing.T) { &entity{isOpen: true}, } - p := NewPool( - func(context.Context) (Entity, error) { + p := NewPool[*entity]( + func(context.Context) (*entity, error) { i++ return es[i-1], nil }, @@ -192,8 +192,8 @@ func TestConcurrentDiscard(t *testing.T) { &entity{isOpen: true}, } - p := NewPool( - func(context.Context) (Entity, error) { + p := NewPool[*entity]( + func(context.Context) (*entity, error) { i++ return es[i-1], nil }, diff --git a/pool/bounded/pool.go b/pool/bounded/pool.go deleted file mode 100644 index 785b37f..0000000 --- a/pool/bounded/pool.go +++ /dev/null @@ -1,102 +0,0 @@ -package bounded - -import ( - "context" - - "github.com/upfluence/pkg/iopool" - "github.com/upfluence/pkg/pool" - "github.com/upfluence/stats" -) - -type PoolFactory struct { - size int - - opts []iopool.Option -} - -func NewPoolFactory(size int, opts ...iopool.Option) *PoolFactory { - return &PoolFactory{size: size} -} - -func (f *PoolFactory) GetPool(factory pool.Factory) pool.Pool { - return f.GetIntrospectablePool(factory) -} - -func (f *PoolFactory) GetIntrospectablePool(factory pool.Factory) pool.IntrospectablePool { - return NewPool(f.size, factory, f.opts...) -} - -type entityWrapper struct { - v interface{} -} - -func (e entityWrapper) Close() error { return nil } -func (e entityWrapper) Open(context.Context) error { return nil } -func (e entityWrapper) IsOpen() bool { return true } - -func NewPool(limit int, factory pool.Factory, opts ...iopool.Option) pool.IntrospectablePool { - c := stats.NewStaticCollector() - - return &poolWrapper{ - Pool: iopool.NewPool( - func(ctx context.Context) (iopool.Entity, error) { - v, err := factory(ctx) - if err != nil { - return nil, err - } - - return entityWrapper{v}, nil - }, - append( - []iopool.Option{ - iopool.WithSize(limit), - iopool.WithMaxIdle(limit), - iopool.WithScope(stats.RootScope(c)), - }, - opts..., - )..., - ), - c: c, - } -} - -type poolWrapper struct { - *iopool.Pool - - c *stats.StaticCollector -} - -func (p *poolWrapper) Get(ctx context.Context) (interface{}, error) { - v, err := p.Pool.Get(ctx) - - if err != nil { - return nil, err - } - - return v.(entityWrapper).v, nil -} - -func (p *poolWrapper) Put(v interface{}) error { - return p.Pool.Put(entityWrapper{v: v}) -} - -func (p *poolWrapper) Discard(v interface{}) error { - return p.Pool.Discard(entityWrapper{v: v}) -} - -func (p *poolWrapper) GetStats() (int, int) { - snap := p.c.Get() - - var idle, checkout int - - for _, is := range snap.Gauges { - switch is.Name { - case "idle": - idle = int(is.Value) - case "checkout": - checkout = int(is.Value) - } - } - - return idle, checkout -} diff --git a/pool/middleware/logger/pool.go b/pool/middleware/logger/pool.go deleted file mode 100644 index 588533e..0000000 --- a/pool/middleware/logger/pool.go +++ /dev/null @@ -1,92 +0,0 @@ -package logger - -import ( - "context" - "time" - - "github.com/upfluence/pkg/log" - "github.com/upfluence/pkg/pool" -) - -type PoolFactory struct { - name string - log func(string, ...interface{}) - next pool.IntrospectablePoolFactory -} - -func NewDebugPoolFactory(name string, next pool.IntrospectablePoolFactory) *PoolFactory { - return &PoolFactory{ - name: name, - log: func(fmt string, vs ...interface{}) { log.Debugf(fmt, vs...) }, - next: next, - } -} - -func (f *PoolFactory) GetPool(factory pool.Factory) pool.Pool { - return &Pool{ - name: f.name, - log: f.log, - next: f.next.GetIntrospectablePool( - func(ctx context.Context) (interface{}, error) { - var t0 = time.Now() - - defer func() { - f.log("%s: Factory called: %v", f.name, time.Since(t0)) - }() - - return factory(ctx) - }, - ), - } -} - -type Pool struct { - name string - log func(string, ...interface{}) - next pool.IntrospectablePool -} - -func (p *Pool) logAction(method string, t0 time.Time) { - var in, out = p.next.GetStats() - - p.log( - "%s: %s: [ checked in: %d, checked out: %d ]: %v", - p.name, - method, - in, - out, - time.Since(t0), - ) -} - -func (p *Pool) Get(ctx context.Context) (interface{}, error) { - var t0 = time.Now() - - defer p.logAction("Get", t0) - - return p.next.Get(ctx) -} - -func (p *Pool) Put(e interface{}) error { - var t0 = time.Now() - - defer p.logAction("Put", t0) - - return p.next.Put(e) -} - -func (p *Pool) Discard(e interface{}) error { - var t0 = time.Now() - - defer p.logAction("Discard", t0) - - return p.next.Discard(e) -} - -func (p *Pool) Close() error { - var t0 = time.Now() - - defer p.logAction("Close", t0) - - return p.next.Close() -} diff --git a/pool/pool.go b/pool/pool.go index d835df9..7ecf240 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -2,29 +2,38 @@ package pool import ( "context" - "io" + + "github.com/upfluence/pkg/iopool" ) -type Factory func(context.Context) (interface{}, error) +type entity[T comparable] struct { + Value T +} -type Pool interface { - io.Closer +func (e entity[T]) Close() error { return nil } +func (e entity[T]) Open(context.Context) error { return nil } +func (e entity[T]) IsOpen() bool { return true } - Get(context.Context) (interface{}, error) - Put(interface{}) error - Discard(interface{}) error +type Pool[T comparable] struct { + p *iopool.Pool[entity[T]] } -type PoolFactory interface { - GetPool(Factory) Pool +func NewPool[T comparable](newfn func() T, opts ...iopool.Option) *Pool[T] { + return &Pool[T]{ + p: iopool.NewPool[entity[T]]( + func(context.Context) (entity[T], error) { + return entity[T]{Value: newfn()}, nil + }, + opts..., + ), + } } -type IntrospectablePool interface { - Pool +func (p *Pool[T]) Get(ctx context.Context) (T, error) { + var v, err = p.p.Get(ctx) - GetStats() (int, int) + return v.Value, err } -type IntrospectablePoolFactory interface { - GetIntrospectablePool(Factory) IntrospectablePool -} +func (p *Pool[T]) Put(v T) error { return p.p.Put(entity[T]{Value: v}) } +func (p *Pool[T]) Discard(v T) error { return p.p.Discard(entity[T]{Value: v}) } From e49ec8c6dc0555d64abaac7c783c92aa23ad89ae Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 22:47:23 -0700 Subject: [PATCH 11/21] cfg => envutil: rename pkg --- cfg/env.go => envutil/helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename cfg/env.go => envutil/helpers.go (94%) diff --git a/cfg/env.go b/envutil/helpers.go similarity index 94% rename from cfg/env.go rename to envutil/helpers.go index f3bc255..5ed1653 100644 --- a/cfg/env.go +++ b/envutil/helpers.go @@ -43,7 +43,7 @@ func FetchInt(variable string, defaultValue int) int { return v1 } - fmt.Printf("cfg: fetchInt: %s\n", err.Error()) + fmt.Printf("envutil: fetchInt: %s\n", err.Error()) } return defaultValue From 14864704ec32e9f0e566f9396903bc1cd536930c Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 22:48:56 -0700 Subject: [PATCH 12/21] thrift/serializer: Use errtest --- thrift/serializer/deserializer_test.go | 14 ++++++------- .../multi_type_deserializer_test.go | 20 +++++++++---------- thrift/serializer/serializer_test.go | 16 +++++++-------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/thrift/serializer/deserializer_test.go b/thrift/serializer/deserializer_test.go index 0f50c8e..d57f16a 100644 --- a/thrift/serializer/deserializer_test.go +++ b/thrift/serializer/deserializer_test.go @@ -4,10 +4,10 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/upfluence/errors/errtest" "github.com/upfluence/thrift/lib/go/thrift" "github.com/upfluence/pkg/encoding" - "github.com/upfluence/pkg/testutil" ) func TestDeserializerReadString(t *testing.T) { @@ -18,7 +18,7 @@ func TestDeserializerReadString(t *testing.T) { in string msgfn func() TStruct out TStruct - errfn testutil.ErrorAssertion + errfn errtest.ErrorAssertion }{ { name: "regular encoding", @@ -26,7 +26,7 @@ func TestDeserializerReadString(t *testing.T) { in: "\"foobar\"", msgfn: func() TStruct { return &stringTStruct{} }, out: &stringTStruct{"foobar"}, - errfn: testutil.NoError(), + errfn: errtest.NoError(), }, { name: "base64", @@ -35,7 +35,7 @@ func TestDeserializerReadString(t *testing.T) { in: "ImZvb2JhciI=", msgfn: func() TStruct { return &stringTStruct{} }, out: &stringTStruct{"foobar"}, - errfn: testutil.NoError(), + errfn: errtest.NoError(), }, { name: "snappy+base64", @@ -47,7 +47,7 @@ func TestDeserializerReadString(t *testing.T) { in: "/wYAAHNOYVBwWQEIAABlyOH6AAAABgEKAACWBYFbZm9vYmFy", msgfn: func() TStruct { return &stringTStruct{} }, out: &stringTStruct{"foobar"}, - errfn: testutil.NoError(), + errfn: errtest.NoError(), }, { name: "snappy", @@ -57,7 +57,7 @@ func TestDeserializerReadString(t *testing.T) { }, msgfn: func() TStruct { return &stringTStruct{} }, out: &stringTStruct{"foobar"}, - errfn: testutil.NoError(), + errfn: errtest.NoError(), in: "\xff\x06\x00\x00sNaPpY\x01\f\x00\x00\xff\x12\xfd\\\"foobar\"", }, } { @@ -68,7 +68,7 @@ func TestDeserializerReadString(t *testing.T) { err := s.ReadString(msg, tt.in) - tt.errfn(t, err) + tt.errfn.Assert(t, err) assert.Equal(t, tt.out, msg) }) } diff --git a/thrift/serializer/multi_type_deserializer_test.go b/thrift/serializer/multi_type_deserializer_test.go index 56cb170..bfb4e0a 100644 --- a/thrift/serializer/multi_type_deserializer_test.go +++ b/thrift/serializer/multi_type_deserializer_test.go @@ -4,52 +4,52 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/upfluence/pkg/testutil" + "github.com/upfluence/errors/errtest" ) func TestMTDReadString(t *testing.T) { for _, tt := range []struct { ct, p string - errfn testutil.ErrorAssertion + errfn errtest.ErrorAssertion out string }{ { p: "\"foobar\"", - errfn: testutil.NoError(), + errfn: errtest.NoError(), out: "foobar", }, { p: "\"foobar\"", - errfn: testutil.ErrorEqual(ErrProtocolNotProvided), + errfn: errtest.ErrorEqual(ErrProtocolNotProvided), ct: "application/not-provided", }, { p: "\"foobar\"", - errfn: testutil.ErrorEqual(ErrEncodingNotProvided), + errfn: errtest.ErrorEqual(ErrEncodingNotProvided), ct: "application/json+not-provided", }, { p: "\"foobar\"", - errfn: testutil.NoError(), + errfn: errtest.NoError(), out: "foobar", ct: "application/json", }, { p: "\x00\x00\x00\x06foobar", - errfn: testutil.NoError(), + errfn: errtest.NoError(), out: "foobar", ct: "application/binary", }, { p: "\xff\x06\x00\x00sNaPpY\x01\f\x00\x00\xff\x12\xfd\\\"foobar\"", - errfn: testutil.NoError(), + errfn: errtest.NoError(), out: "foobar", ct: "application/json+snappy", }, { p: "/wYAAHNOYVBwWQEIAABlyOH6AAAABgEKAACWBYFbZm9vYmFy", - errfn: testutil.NoError(), + errfn: errtest.NoError(), out: "foobar", ct: "application/binary+snappy+base64", }, @@ -60,7 +60,7 @@ func TestMTDReadString(t *testing.T) { err = NewDefaultTMultiTypeDeserializer().ReadString(&sts, tt.p, tt.ct) ) - tt.errfn(t, err) + tt.errfn.Assert(t, err) assert.Equal(t, tt.out, sts.string) } } diff --git a/thrift/serializer/serializer_test.go b/thrift/serializer/serializer_test.go index 452ab59..9b03960 100644 --- a/thrift/serializer/serializer_test.go +++ b/thrift/serializer/serializer_test.go @@ -6,8 +6,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/upfluence/thrift/lib/go/thrift" + "github.com/upfluence/errors/errtest" "github.com/upfluence/pkg/encoding" - "github.com/upfluence/pkg/testutil" ) func TestContentType(t *testing.T) { @@ -69,21 +69,21 @@ func TestSerializerWriteString(t *testing.T) { es []encoding.Encoding in TStruct out string - errfn testutil.ErrorAssertion + errfn errtest.ErrorAssertion }{ { name: "regular binary", pf: thrift.NewTBinaryProtocolFactoryDefault(), in: &stringTStruct{"foobar"}, out: "\x00\x00\x00\x06foobar", - errfn: testutil.NoError(), + errfn: errtest.NoError(), }, { name: "regular encoding", pf: thrift.NewTJSONProtocolFactory(), in: &stringTStruct{"foobar"}, out: "\"foobar\"", - errfn: testutil.NoError(), + errfn: errtest.NoError(), }, { name: "base64", @@ -91,7 +91,7 @@ func TestSerializerWriteString(t *testing.T) { es: []encoding.Encoding{encoding.Base64Encoding}, in: &stringTStruct{"foobar"}, out: "ImZvb2JhciI=", - errfn: testutil.NoError(), + errfn: errtest.NoError(), }, { name: "snappy", @@ -101,7 +101,7 @@ func TestSerializerWriteString(t *testing.T) { }, in: &stringTStruct{"foobar"}, out: "\xff\x06\x00\x00sNaPpY\x01\f\x00\x00\xff\x12\xfd\\\"foobar\"", - errfn: testutil.NoError(), + errfn: errtest.NoError(), }, { name: "snappy+base64", @@ -112,7 +112,7 @@ func TestSerializerWriteString(t *testing.T) { }, in: &stringTStruct{"foobar"}, out: "/wYAAHNOYVBwWQEIAABlyOH6AAAABgEKAACWBYFbZm9vYmFy", - errfn: testutil.NoError(), + errfn: errtest.NoError(), }, } { t.Run(tt.name, func(t *testing.T) { @@ -120,7 +120,7 @@ func TestSerializerWriteString(t *testing.T) { out, err := s.WriteString(tt.in) - tt.errfn(t, err) + tt.errfn.Assert(t, err) assert.Equal(t, tt.out, out) }) } From f72e00eb926776bc1f036db7209f0584396bd9d1 Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 23:00:26 -0700 Subject: [PATCH 13/21] encoding: Drop support for snappy --- encoding/encoding.go | 8 -------- thrift/serializer/deserializer_test.go | 17 +++-------------- thrift/serializer/multi_type_deserializer.go | 1 - .../serializer/multi_type_deserializer_test.go | 8 ++++---- thrift/serializer/serializer_test.go | 16 ++++++++-------- 5 files changed, 15 insertions(+), 35 deletions(-) diff --git a/encoding/encoding.go b/encoding/encoding.go index 957ddcf..a7c56f3 100644 --- a/encoding/encoding.go +++ b/encoding/encoding.go @@ -5,8 +5,6 @@ import ( "encoding/base64" "io" "strings" - - "github.com/golang/snappy" ) var ( @@ -16,12 +14,6 @@ var ( ReaderFunc: func(r io.Reader) (io.Reader, error) { return gzip.NewReader(r) }, } - SnappyEncoding = &StaticEncoding{ - Type: "snappy", - WriterFunc: func(w io.Writer) (io.Writer, error) { return snappy.NewWriter(w), nil }, - ReaderFunc: func(r io.Reader) (io.Reader, error) { return snappy.NewReader(r), nil }, - } - Base64Encoding = &StaticEncoding{ Type: "base64", WriterFunc: func(w io.Writer) (io.Writer, error) { diff --git a/thrift/serializer/deserializer_test.go b/thrift/serializer/deserializer_test.go index d57f16a..e32816e 100644 --- a/thrift/serializer/deserializer_test.go +++ b/thrift/serializer/deserializer_test.go @@ -38,28 +38,17 @@ func TestDeserializerReadString(t *testing.T) { errfn: errtest.NoError(), }, { - name: "snappy+base64", + name: "gzip+base64", pf: thrift.NewTBinaryProtocolFactoryDefault(), es: []encoding.Encoding{ - encoding.SnappyEncoding, + encoding.GZipEncoding, encoding.Base64Encoding, }, - in: "/wYAAHNOYVBwWQEIAABlyOH6AAAABgEKAACWBYFbZm9vYmFy", + in: "H4sIAAAAAAAA/2JgYGBLy89PSiwCAAAA//8AAAD//wEAAP//euNurwoA", msgfn: func() TStruct { return &stringTStruct{} }, out: &stringTStruct{"foobar"}, errfn: errtest.NoError(), }, - { - name: "snappy", - pf: thrift.NewTJSONProtocolFactory(), - es: []encoding.Encoding{ - encoding.SnappyEncoding, - }, - msgfn: func() TStruct { return &stringTStruct{} }, - out: &stringTStruct{"foobar"}, - errfn: errtest.NoError(), - in: "\xff\x06\x00\x00sNaPpY\x01\f\x00\x00\xff\x12\xfd\\\"foobar\"", - }, } { t.Run(tt.name, func(t *testing.T) { s := NewTDeserializer(tt.pf, tt.es...) diff --git a/thrift/serializer/multi_type_deserializer.go b/thrift/serializer/multi_type_deserializer.go index 7c51387..8f849bb 100644 --- a/thrift/serializer/multi_type_deserializer.go +++ b/thrift/serializer/multi_type_deserializer.go @@ -27,7 +27,6 @@ func NewDefaultTMultiTypeDeserializer() *TMultiTypeDeserializer { thriftutil.JSONProtocolFactory, []thriftutil.TTypedProtocolFactory{thriftutil.BinaryProtocolFactory}, []encoding.Encoding{ - encoding.SnappyEncoding, encoding.GZipEncoding, encoding.Base64Encoding, }, diff --git a/thrift/serializer/multi_type_deserializer_test.go b/thrift/serializer/multi_type_deserializer_test.go index bfb4e0a..dcd47f4 100644 --- a/thrift/serializer/multi_type_deserializer_test.go +++ b/thrift/serializer/multi_type_deserializer_test.go @@ -42,16 +42,16 @@ func TestMTDReadString(t *testing.T) { ct: "application/binary", }, { - p: "\xff\x06\x00\x00sNaPpY\x01\f\x00\x00\xff\x12\xfd\\\"foobar\"", + p: "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xffRJ\xcb\xcfOJ,R\x02\x00\x00\x00\xff\xff\x00\x00\x00\xff\xff\x01\x00\x00\xff\xff\x81Z\x84\xc4\b\x00\x00\x00", errfn: errtest.NoError(), out: "foobar", - ct: "application/json+snappy", + ct: "application/json+gzip", }, { - p: "/wYAAHNOYVBwWQEIAABlyOH6AAAABgEKAACWBYFbZm9vYmFy", + p: "H4sIAAAAAAAA/2JgYGBLy89PSiwCAAAA//8AAAD//wEAAP//euNurwoA", errfn: errtest.NoError(), out: "foobar", - ct: "application/binary+snappy+base64", + ct: "application/binary+gzip+base64", }, } { var ( diff --git a/thrift/serializer/serializer_test.go b/thrift/serializer/serializer_test.go index 9b03960..cf8838e 100644 --- a/thrift/serializer/serializer_test.go +++ b/thrift/serializer/serializer_test.go @@ -28,10 +28,10 @@ func TestContentType(t *testing.T) { { pf: thrift.NewTBinaryProtocolFactoryDefault(), es: []encoding.Encoding{ - encoding.SnappyEncoding, + encoding.GZipEncoding, encoding.Base64Encoding, }, - out: "application/binary+snappy+base64", + out: "application/binary+gzip+base64", }, } { s := NewTSerializer(tt.pf, tt.es...) @@ -94,24 +94,24 @@ func TestSerializerWriteString(t *testing.T) { errfn: errtest.NoError(), }, { - name: "snappy", + name: "gzip", pf: thrift.NewTJSONProtocolFactory(), es: []encoding.Encoding{ - encoding.SnappyEncoding, + encoding.GZipEncoding, }, in: &stringTStruct{"foobar"}, - out: "\xff\x06\x00\x00sNaPpY\x01\f\x00\x00\xff\x12\xfd\\\"foobar\"", + out: "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xffRJ\xcb\xcfOJ,R\x02\x00\x00\x00\xff\xff\x00\x00\x00\xff\xff\x01\x00\x00\xff\xff\x81Z\x84\xc4\b\x00\x00\x00", errfn: errtest.NoError(), }, { - name: "snappy+base64", + name: "gzip+base64", pf: thrift.NewTBinaryProtocolFactoryDefault(), es: []encoding.Encoding{ - encoding.SnappyEncoding, + encoding.GZipEncoding, encoding.Base64Encoding, }, in: &stringTStruct{"foobar"}, - out: "/wYAAHNOYVBwWQEIAABlyOH6AAAABgEKAACWBYFbZm9vYmFy", + out: "H4sIAAAAAAAA/2JgYGBLy89PSiwCAAAA//8AAAD//wEAAP//euNurwoA", errfn: errtest.NoError(), }, } { From cbae2cca08da81f0bd88828bf38c77854f015d51 Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 23:02:31 -0700 Subject: [PATCH 14/21] envutil: Use the new envutil path --- envutil/helpers.go | 2 +- log/logger.go | 4 ++-- peer/peer.go | 21 +++++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/envutil/helpers.go b/envutil/helpers.go index 5ed1653..9a0fc15 100644 --- a/envutil/helpers.go +++ b/envutil/helpers.go @@ -1,4 +1,4 @@ -package cfg +package envutil import ( "fmt" diff --git a/log/logger.go b/log/logger.go index c3aa685..1f98ed5 100644 --- a/log/logger.go +++ b/log/logger.go @@ -2,6 +2,7 @@ package log import ( "context" + "os" "strings" "sync" @@ -12,7 +13,6 @@ import ( "github.com/upfluence/log/sink/multi" "github.com/upfluence/log/sink/writer" - "github.com/upfluence/pkg/cfg" "github.com/upfluence/pkg/error_logger" ) @@ -50,7 +50,7 @@ func RegisterContextExtractor(ce log.ContextExtractor) { } func FetchLevel() record.Level { - switch strings.ToUpper(cfg.FetchString("LOGGER_LEVEL", "")) { + switch strings.ToUpper(os.Getenv("LOGGER_LEVEL")) { case "DEBUG": return record.Debug case "INFO": diff --git a/peer/peer.go b/peer/peer.go index 1641e3f..065452c 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -3,9 +3,10 @@ package peer import ( "fmt" "net/url" + "os" "strings" - "github.com/upfluence/pkg/cfg" + "github.com/upfluence/pkg/envutil" "github.com/upfluence/pkg/peer/version" ) @@ -18,7 +19,7 @@ var ( ) func buildGitVersion() version.GitVersion { - commit := cfg.FetchString("GIT_COMMIT", GitCommit) + commit := envutil.FetchString("GIT_COMMIT", GitCommit) if commit == "" { return version.GitVersion{} @@ -26,8 +27,8 @@ func buildGitVersion() version.GitVersion { return version.GitVersion{ Commit: commit, - Remote: cfg.FetchString("GIT_REMOTE", GitRemote), - Branch: cfg.FetchString("GIT_BRANCH", GitBranch), + Remote: envutil.FetchString("GIT_REMOTE", GitRemote), + Branch: envutil.FetchString("GIT_BRANCH", GitBranch), } } @@ -140,14 +141,14 @@ func (p *Peer) URL() *url.URL { } func FromEnv() *Peer { - sv := version.ParseSemanticVersion(cfg.FetchString("VERSION", Version)) + sv := version.ParseSemanticVersion(envutil.FetchString("VERSION", Version)) return &Peer{ - Authority: cfg.FetchString("AUTHORITY", "local"), - InstanceName: cfg.FetchString("UNIT_NAME", "unknow-service"), - AppName: cfg.FetchString("APP_NAME", "unknown-app"), - ProjectName: cfg.FetchString("PROJECT_NAME", "unknown-app"), - Environment: cfg.FetchString("ENV", "development"), + Authority: envutil.FetchString("AUTHORITY", "local"), + InstanceName: envutil.FetchString("UNIT_NAME", "unknow-service"), + AppName: envutil.FetchString("APP_NAME", "unknown-app"), + ProjectName: envutil.FetchString("PROJECT_NAME", "unknown-app"), + Environment: envutil.FetchString("ENV", "development"), Version: version.Version{Git: buildGitVersion(), Semantic: sv}, } } From 2abf73e1136092ca36064531731d577787f56f16 Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 23:04:11 -0700 Subject: [PATCH 15/21] *: Use new syncutil API --- currency/eucentralbank/rate_fetcher.go | 51 +++++++------------------- discovery/balancer/dialer.go | 19 +++------- fixtures/0001_test_migration.up.sql | 1 - 3 files changed, 19 insertions(+), 52 deletions(-) delete mode 100644 fixtures/0001_test_migration.up.sql diff --git a/currency/eucentralbank/rate_fetcher.go b/currency/eucentralbank/rate_fetcher.go index fefba1e..a7104d1 100644 --- a/currency/eucentralbank/rate_fetcher.go +++ b/currency/eucentralbank/rate_fetcher.go @@ -4,19 +4,16 @@ import ( "context" "encoding/xml" "net/http" - "sync" "time" - "golang.org/x/sync/singleflight" - "github.com/upfluence/pkg/currency" + "github.com/upfluence/pkg/syncutil" ) const apiURL = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml" type RateFetcher struct { - mu sync.RWMutex - g singleflight.Group + sf syncutil.Singleflight[map[currency.Currency]float64] cl *http.Client req *http.Request @@ -40,6 +37,7 @@ type ratePayload struct { func NewRateFetcher() *RateFetcher { req, _ := http.NewRequest("GET", apiURL, http.NoBody) + return &RateFetcher{ cl: http.DefaultClient, req: req, @@ -55,22 +53,11 @@ func (rf *RateFetcher) Rate(ctx context.Context, c currency.Currency) (float64, return 1., nil } - rf.mu.RLock() - - if rf.expiresAt.After(time.Now()) && rf.rates != nil { - r, ok := rf.rates[c] - rf.mu.RUnlock() - - if ok { - return r, nil + _, rates, err := rf.sf.Do(ctx, func(ctx context.Context) (map[currency.Currency]float64, error) { + if rf.expiresAt.After(time.Now()) && rf.rates != nil { + return rf.rates, nil } - return .0, currency.ErrCurrencyNotHandled - } - - rf.mu.RUnlock() - - resc := rf.g.DoChan("", func() (interface{}, error) { resp, err := rf.cl.Do(rf.req) if err != nil { @@ -91,31 +78,19 @@ func (rf *RateFetcher) Rate(ctx context.Context, c currency.Currency) (float64, res[currency.Currency(cube.Currency)] = cube.Rate } - rf.mu.Lock() - rf.rates = res rf.expiresAt = time.Now().Add(rf.duration) - rf.mu.Unlock() - return res, nil }) - select { - case <-ctx.Done(): - return .0, ctx.Err() - case res := <-resc: - if res.Err != nil { - return .0, res.Err - } - - rates := res.Val.(map[currency.Currency]float64) - r, ok := rates[c] - - if ok { - return r, nil - } + if err != nil { + return .0, err + } - return .0, currency.ErrCurrencyNotHandled + if r, ok := rates[c]; ok { + return r, nil } + + return .0, currency.ErrCurrencyNotHandled } diff --git a/discovery/balancer/dialer.go b/discovery/balancer/dialer.go index d413317..6ce58c6 100644 --- a/discovery/balancer/dialer.go +++ b/discovery/balancer/dialer.go @@ -70,32 +70,25 @@ type localDialer struct { d *Dialer b Balancer - mu sync.RWMutex opened bool - sf syncutil.Singleflight + sf syncutil.Singleflight[struct{}] } -func (ld *localDialer) open(ctx context.Context) error { +func (ld *localDialer) open(ctx context.Context) (struct{}, error) { if !ld.b.IsOpen() { if err := ld.b.Open(ctx); err != nil { - return err + return struct{}{}, err } } - ld.mu.Lock() ld.opened = true - ld.mu.Unlock() - return nil + return struct{}{}, nil } func (ld *localDialer) dial(ctx context.Context, network string) (net.Conn, error) { - ld.mu.RLock() - opened := ld.opened - ld.mu.RUnlock() - - if !opened { - if _, err := ld.sf.Do(ctx, ld.open); err != nil { + if !ld.opened { + if _, _, err := ld.sf.Do(ctx, ld.open); err != nil { return nil, err } } diff --git a/fixtures/0001_test_migration.up.sql b/fixtures/0001_test_migration.up.sql deleted file mode 100644 index 2e19d22..0000000 --- a/fixtures/0001_test_migration.up.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE TABLE z(x INTEGER, y INTEGER, z INTEGER); From f587e8892743d71ff1de40475faeefcbb151d72b Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 23:18:50 -0700 Subject: [PATCH 16/21] group: Add some helpers --- group/map.go | 36 ++++++++++++++++++++++++++++++++++++ group/map_test.go | 26 ++++++++++++++++++++++++++ group/typed_group.go | 35 +++++++++++++++++++++++++++++++++++ group/typed_group_test.go | 30 ++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 group/map.go create mode 100644 group/map_test.go create mode 100644 group/typed_group.go create mode 100644 group/typed_group_test.go diff --git a/group/map.go b/group/map.go new file mode 100644 index 0000000..ccca831 --- /dev/null +++ b/group/map.go @@ -0,0 +1,36 @@ +package group + +import ( + "context" + "sync" +) + +type MapRunner[K comparable, V any] func(context.Context, K) (V, error) + +func ExecuteMap[K comparable, V any](g Group, ks []K, fn MapRunner[K, V]) (map[K]V, error) { + var ( + mu sync.Mutex + + res = make(map[K]V, len(ks)) + ) + + for _, k := range ks { + k := k + + g.Do(func(ctx context.Context) error { + var v, err = fn(ctx, k) + + if err != nil { + return err + } + + mu.Lock() + res[k] = v + mu.Unlock() + + return nil + }) + } + + return res, g.Wait() +} diff --git a/group/map_test.go b/group/map_test.go new file mode 100644 index 0000000..50c55ac --- /dev/null +++ b/group/map_test.go @@ -0,0 +1,26 @@ +package group + +import ( + "context" + "reflect" + "strconv" + "testing" +) + +func TestExecuteMap(t *testing.T) { + res, err := ExecuteMap[int, string]( + ErrorGroup(context.Background()), + []int{1, 2, 3}, + func(_ context.Context, v int) (string, error) { + return strconv.Itoa(v), nil + }, + ) + + if err != nil { + t.Errorf("ExecuteMap(...) = (_, %v), wanted nil", err) + } + + if !reflect.DeepEqual(res, map[int]string{1: "1", 2: "2", 3: "3"}) { + t.Errorf("ExecuteMap(...) = (%v, nil), wanted [1, 2, 3]", res) + } +} diff --git a/group/typed_group.go b/group/typed_group.go new file mode 100644 index 0000000..60b5227 --- /dev/null +++ b/group/typed_group.go @@ -0,0 +1,35 @@ +package group + +import ( + "context" + "sync" +) + +type TypedRunner[T any] func(context.Context) (func(*T), error) + +type TypedGroup[T any] struct { + Group Group + Value T + + mu sync.Mutex +} + +func (tg *TypedGroup[T]) Do(fn TypedRunner[T]) { + tg.Group.Do(func(ctx context.Context) error { + fn, err := fn(ctx) + + if err != nil { + return err + } + + tg.mu.Lock() + fn(&tg.Value) + tg.mu.Unlock() + + return nil + }) +} + +func (tg *TypedGroup[T]) Wait() (T, error) { + return tg.Value, tg.Group.Wait() +} diff --git a/group/typed_group_test.go b/group/typed_group_test.go new file mode 100644 index 0000000..40a59b6 --- /dev/null +++ b/group/typed_group_test.go @@ -0,0 +1,30 @@ +package group + +import ( + "context" + "testing" +) + +func TestTypedGroup(t *testing.T) { + readyc := make(chan struct{}) + g := TypedGroup[int]{Group: ErrorGroup(context.Background())} + + for i := 0; i < 5; i++ { + g.Do(func(context.Context) (func(*int), error) { + <-readyc + return func(v *int) { *v++ }, nil + }) + } + + close(readyc) + + res, err := g.Wait() + + if res != 5 { + t.Errorf("Wait() = (%v, _), wanted 4", res) + } + + if err != nil { + t.Errorf("Wait() = (_, %v), wanted nil", err) + } +} From 95882da6209da27512dfadf2b01a909459a5cb16 Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 19 Aug 2022 23:48:54 -0700 Subject: [PATCH 17/21] cache: Make the cache generic --- cache/cache.go | 12 ++++--- cache/lock_cache.go | 28 +++++++++++----- cache/policy/eviction_policy.go | 10 +++--- cache/policy/multi_policy.go | 38 ++++++++++----------- cache/policy/multi_policy_test.go | 6 ++-- cache/policy/nop_policy.go | 12 +++---- cache/policy/size/policy.go | 30 ++++++++--------- cache/policy/size/policy_test.go | 2 +- cache/policy/time/policy.go | 56 +++++++++++++++---------------- cache/policy/time/policy_test.go | 4 +-- cache/policy_cache.go | 32 +++++++++++------- cache/policy_cache_test.go | 2 +- cache/sharded_cache.go | 54 +++++++++++++++++++---------- iopool/pool.go | 10 +++--- 14 files changed, 168 insertions(+), 128 deletions(-) diff --git a/cache/cache.go b/cache/cache.go index 1069c77..c8a5b65 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -1,11 +1,13 @@ package cache -import "io" +import ( + "io" +) -type Cache interface { - Get(string) (interface{}, bool, error) - Set(string, interface{}) error - Evict(string) error +type Cache[K comparable, V any] interface { + Get(K) (V, bool, error) + Set(K, V) error + Evict(K) error io.Closer } diff --git a/cache/lock_cache.go b/cache/lock_cache.go index 888f2b6..a642422 100644 --- a/cache/lock_cache.go +++ b/cache/lock_cache.go @@ -2,16 +2,28 @@ package cache import "sync" -type lockCache struct { +type lockCache[K comparable, V any] struct { mu sync.RWMutex - items map[string]interface{} + items map[K]V } -func newLockCache() Cache { - return &lockCache{items: make(map[string]interface{})} +func newLockCache[K comparable, V any]() *lockCache[K, V] { + return &lockCache[K, V]{items: make(map[K]V)} } -func (lc *lockCache) Get(k string) (interface{}, bool, error) { +func (lc *lockCache[K, V]) get(res map[K]V, ks []K) { + lc.mu.RLock() + + for _, k := range ks { + if v, ok := lc.items[k]; ok { + res[k] = v + } + } + + lc.mu.RUnlock() +} + +func (lc *lockCache[K, V]) Get(k K) (V, bool, error) { lc.mu.RLock() v, ok := lc.items[k] lc.mu.RUnlock() @@ -19,7 +31,7 @@ func (lc *lockCache) Get(k string) (interface{}, bool, error) { return v, ok, nil } -func (lc *lockCache) Set(k string, v interface{}) error { +func (lc *lockCache[K, V]) Set(k K, v V) error { lc.mu.Lock() lc.items[k] = v lc.mu.Unlock() @@ -27,7 +39,7 @@ func (lc *lockCache) Set(k string, v interface{}) error { return nil } -func (lc *lockCache) Evict(k string) error { +func (lc *lockCache[K, V]) Evict(k K) error { lc.mu.Lock() delete(lc.items, k) lc.mu.Unlock() @@ -35,4 +47,4 @@ func (lc *lockCache) Evict(k string) error { return nil } -func (lc *lockCache) Close() error { return nil } +func (lc *lockCache[K, V]) Close() error { return nil } diff --git a/cache/policy/eviction_policy.go b/cache/policy/eviction_policy.go index 1950e36..9fc7a45 100644 --- a/cache/policy/eviction_policy.go +++ b/cache/policy/eviction_policy.go @@ -6,6 +6,8 @@ import ( "github.com/upfluence/errors" ) +var ErrClosed = errors.New("cache/policy: eviction policy closed") + type OpType uint8 const ( @@ -14,12 +16,10 @@ const ( Evict ) -type EvictionPolicy interface { - C() <-chan string +type EvictionPolicy[K comparable] interface { + C() <-chan K - Op(string, OpType) error + Op(K, OpType) error io.Closer } - -var ErrClosed = errors.New("cache/policy: eviction policy closed") diff --git a/cache/policy/multi_policy.go b/cache/policy/multi_policy.go index ca2393b..1083783 100644 --- a/cache/policy/multi_policy.go +++ b/cache/policy/multi_policy.go @@ -7,10 +7,10 @@ import ( "github.com/upfluence/errors" ) -func CombinePolicies(ps ...EvictionPolicy) EvictionPolicy { +func CombinePolicies[K comparable](ps ...EvictionPolicy[K]) EvictionPolicy[K] { switch len(ps) { case 0: - return &NopPolicy{} + return &NopPolicy[K]{} case 1: return ps[0] } @@ -18,17 +18,26 @@ func CombinePolicies(ps ...EvictionPolicy) EvictionPolicy { l := ps[0] for i := 1; i < len(ps); i++ { - l = newMultiPolicty(l, ps[i]) + l = newMultiPolicty[K](l, ps[i]) } return l } -func newMultiPolicty(l, r EvictionPolicy) *multiPolicy { - mp := multiPolicy{ +type multiPolicy[K comparable] struct { + wg sync.WaitGroup + ctx context.Context + cancel context.CancelFunc + ch chan K + + l, r EvictionPolicy[K] +} + +func newMultiPolicty[K comparable](l, r EvictionPolicy[K]) *multiPolicy[K] { + mp := multiPolicy[K]{ l: l, r: r, - ch: make(chan string), + ch: make(chan K), } mp.wg.Add(1) @@ -39,16 +48,7 @@ func newMultiPolicty(l, r EvictionPolicy) *multiPolicy { return &mp } -type multiPolicy struct { - wg sync.WaitGroup - ctx context.Context - cancel context.CancelFunc - ch chan string - - l, r EvictionPolicy -} - -func (mp *multiPolicy) pull() { +func (mp *multiPolicy[K]) pull() { defer mp.wg.Done() for { @@ -65,15 +65,15 @@ func (mp *multiPolicy) pull() { } } -func (mp *multiPolicy) C() <-chan string { +func (mp *multiPolicy[K]) C() <-chan K { return mp.ch } -func (mp *multiPolicy) Op(k string, op OpType) error { +func (mp *multiPolicy[K]) Op(k K, op OpType) error { return errors.Combine(mp.l.Op(k, op), mp.r.Op(k, op)) } -func (mp *multiPolicy) Close() error { +func (mp *multiPolicy[K]) Close() error { mp.cancel() mp.wg.Wait() close(mp.ch) diff --git a/cache/policy/multi_policy_test.go b/cache/policy/multi_policy_test.go index bc8bdc1..63c3b3b 100644 --- a/cache/policy/multi_policy_test.go +++ b/cache/policy/multi_policy_test.go @@ -8,7 +8,7 @@ import ( ) func TestNopPolicy(t *testing.T) { - p := CombinePolicies() + p := CombinePolicies[string]() select { case <-p.C(): @@ -46,7 +46,7 @@ func (mp *mockPolicy) C() <-chan string { func (mp *mockPolicy) Op(k string, ot OpType) error { mp.Lock() - mp.ops = append(mp.ops, op{k, ot}) + mp.ops = append(mp.ops, op{k: k, op: ot}) mp.Unlock() return mp.oerr @@ -67,7 +67,7 @@ func TestMultiPolicy(t *testing.T) { wg sync.WaitGroup ) - p := CombinePolicies(m1, m2, m3) + p := CombinePolicies[string](m1, m2, m3) wg.Add(1) diff --git a/cache/policy/nop_policy.go b/cache/policy/nop_policy.go index 92af526..55cdca6 100644 --- a/cache/policy/nop_policy.go +++ b/cache/policy/nop_policy.go @@ -5,21 +5,21 @@ import ( "sync/atomic" ) -type NopPolicy struct { +type NopPolicy[K comparable] struct { sync.Once sync.Mutex closed int32 - ch chan string + ch chan K } -func (np *NopPolicy) C() <-chan string { +func (np *NopPolicy[K]) C() <-chan K { np.Do(func() { np.Lock() defer np.Unlock() if np.ch == nil { - np.ch = make(chan string) + np.ch = make(chan K) if atomic.LoadInt32(&np.closed) == 1 { close(np.ch) @@ -30,7 +30,7 @@ func (np *NopPolicy) C() <-chan string { return np.ch } -func (np *NopPolicy) Op(string, OpType) error { +func (np *NopPolicy[K]) Op(K, OpType) error { if atomic.LoadInt32(&np.closed) == 1 { return ErrClosed } @@ -38,7 +38,7 @@ func (np *NopPolicy) Op(string, OpType) error { return nil } -func (np *NopPolicy) Close() error { +func (np *NopPolicy[K]) Close() error { if atomic.CompareAndSwapInt32(&np.closed, 0, 1) { np.Lock() if np.ch != nil { diff --git a/cache/policy/size/policy.go b/cache/policy/size/policy.go index 8cd1202..a93faf4 100644 --- a/cache/policy/size/policy.go +++ b/cache/policy/size/policy.go @@ -8,29 +8,29 @@ import ( "github.com/upfluence/pkg/cache/policy" ) -type Policy struct { +type Policy[K comparable] struct { sync.Mutex size int l *list.List - ks map[string]*list.Element + ks map[K]*list.Element ctx context.Context cancel context.CancelFunc - fn func(string) + fn func(K) closeOnce sync.Once - ch chan string + ch chan K } -func NewLRUPolicy(size int) *Policy { - p := Policy{ +func NewLRUPolicy[K comparable](size int) *Policy[K] { + p := Policy[K]{ l: list.New(), size: size, - ks: make(map[string]*list.Element), - ch: make(chan string, 1), + ks: make(map[K]*list.Element), + ch: make(chan K, 1), } p.ctx, p.cancel = context.WithCancel(context.Background()) @@ -39,11 +39,11 @@ func NewLRUPolicy(size int) *Policy { return &p } -func (p *Policy) C() <-chan string { +func (p *Policy[K]) C() <-chan K { return p.ch } -func (p *Policy) Op(k string, op policy.OpType) error { +func (p *Policy[K]) Op(k K, op policy.OpType) error { if p.ctx.Err() != nil { return policy.ErrClosed } @@ -64,7 +64,7 @@ func (p *Policy) Op(k string, op policy.OpType) error { return nil } -func (p *Policy) move(k string) { +func (p *Policy[K]) move(k K) { e, ok := p.ks[k] if !ok { @@ -74,7 +74,7 @@ func (p *Policy) move(k string) { p.l.MoveToBack(e) } -func (p *Policy) insert(k string) { +func (p *Policy[K]) insert(k K) { if _, ok := p.ks[k]; ok { return } @@ -90,7 +90,7 @@ func (p *Policy) insert(k string) { p.ks[k] = e if p.l.Len() > p.size { - v := p.l.Remove(p.l.Front()).(string) + v := p.l.Remove(p.l.Front()).(K) delete(p.ks, v) select { @@ -100,7 +100,7 @@ func (p *Policy) insert(k string) { } } -func (p *Policy) evict(k string) { +func (p *Policy[K]) evict(k K) { e, ok := p.ks[k] if !ok { @@ -111,7 +111,7 @@ func (p *Policy) evict(k string) { delete(p.ks, k) } -func (p *Policy) Close() error { +func (p *Policy[K]) Close() error { p.cancel() p.closeOnce.Do(func() { close(p.ch) }) return nil diff --git a/cache/policy/size/policy_test.go b/cache/policy/size/policy_test.go index 4ff6d60..56d8f85 100644 --- a/cache/policy/size/policy_test.go +++ b/cache/policy/size/policy_test.go @@ -8,7 +8,7 @@ import ( ) func TestLRUPolicy(t *testing.T) { - p := NewLRUPolicy(2) + p := NewLRUPolicy[string](2) p.Op("foo", policy.Set) p.Op("bar", policy.Set) diff --git a/cache/policy/time/policy.go b/cache/policy/time/policy.go index 1eb9fcc..82fa8a4 100644 --- a/cache/policy/time/policy.go +++ b/cache/policy/time/policy.go @@ -9,12 +9,12 @@ import ( "github.com/upfluence/pkg/cache/policy" ) -type element struct { - key string +type element[K comparable] struct { + key K t int64 } -type Policy struct { +type Policy[K comparable] struct { sync.Mutex ttl int64 @@ -24,29 +24,29 @@ type Policy struct { ctx context.Context cancel context.CancelFunc - ks map[string]*list.Element + ks map[K]*list.Element l *list.List - fn func(string) + fn func(K) closeOnce sync.Once - ch chan string + ch chan K } -func NewIdlePolicy(ttl time.Duration) *Policy { - return newPolicy(ttl, func(p *Policy) func(string) { return p.move }) +func NewIdlePolicy[K comparable](ttl time.Duration) *Policy[K] { + return newPolicy[K](ttl, func(p *Policy[K]) func(K) { return p.move }) } -func NewLifetimePolicy(ttl time.Duration) *Policy { - return newPolicy(ttl, func(p *Policy) func(string) { return func(string) {} }) +func NewLifetimePolicy[K comparable](ttl time.Duration) *Policy[K] { + return newPolicy[K](ttl, func(p *Policy[K]) func(K) { return func(K) {} }) } -func newPolicy(ttl time.Duration, fn func(*Policy) func(string)) *Policy { - p := Policy{ +func newPolicy[K comparable](ttl time.Duration, fn func(*Policy[K]) func(K)) *Policy[K] { + p := Policy[K]{ ttl: int64(ttl), - ks: make(map[string]*list.Element), + ks: make(map[K]*list.Element), l: list.New(), - ch: make(chan string), + ch: make(chan K), } p.fn = fn(&p) @@ -58,15 +58,15 @@ func newPolicy(ttl time.Duration, fn func(*Policy) func(string)) *Policy { return &p } -func (p *Policy) now() int64 { return time.Now().UnixNano() } +func (p *Policy[K]) now() int64 { return time.Now().UnixNano() } -func (p *Policy) C() <-chan string { +func (p *Policy[K]) C() <-chan K { return p.ch } -func (p *Policy) insert(k string) { +func (p *Policy[K]) insert(k K) { if p.l.Len() == 0 { - p.ks[k] = p.l.PushFront(element{key: k, t: p.now()}) + p.ks[k] = p.l.PushFront(element[K]{key: k, t: p.now()}) } _, ok := p.ks[k] @@ -75,23 +75,23 @@ func (p *Policy) insert(k string) { return } - p.ks[k] = p.l.InsertAfter(element{key: k, t: p.now()}, p.l.Back()) + p.ks[k] = p.l.InsertAfter(element[K]{key: k, t: p.now()}, p.l.Back()) } -func (p *Policy) move(k string) { +func (p *Policy[K]) move(k K) { e, ok := p.ks[k] if !ok { return } - ee := e.Value.(element) + ee := e.Value.(element[K]) ee.t = p.now() p.l.MoveToBack(e) } -func (p *Policy) evict(k string) { +func (p *Policy[K]) evict(k K) { e, ok := p.ks[k] if !ok { @@ -102,7 +102,7 @@ func (p *Policy) evict(k string) { delete(p.ks, k) } -func (p *Policy) Op(k string, op policy.OpType) error { +func (p *Policy[K]) Op(k K, op policy.OpType) error { if p.ctx.Err() != nil { return policy.ErrClosed } @@ -123,7 +123,7 @@ func (p *Policy) Op(k string, op policy.OpType) error { return nil } -func (p *Policy) pump() { +func (p *Policy[K]) pump() { defer p.wg.Done() for { @@ -138,7 +138,7 @@ func (p *Policy) pump() { } } -func (p *Policy) cleanup(now int64) { +func (p *Policy[K]) cleanup(now int64) { p.Lock() defer p.Unlock() @@ -148,7 +148,7 @@ func (p *Policy) cleanup(now int64) { return } - ee := e.Value.(element) + ee := e.Value.(element[K]) for ee.t+p.ttl < now { next := e.Next() @@ -166,11 +166,11 @@ func (p *Policy) cleanup(now int64) { if e == nil { return } - ee = e.Value.(element) + ee = e.Value.(element[K]) } } -func (p *Policy) Close() error { +func (p *Policy[K]) Close() error { p.cancel() p.wg.Wait() diff --git a/cache/policy/time/policy_test.go b/cache/policy/time/policy_test.go index 2440dd0..d543677 100644 --- a/cache/policy/time/policy_test.go +++ b/cache/policy/time/policy_test.go @@ -9,7 +9,7 @@ import ( ) func TestIdlePolicy(t *testing.T) { - p := NewIdlePolicy(10 * time.Millisecond) + p := NewIdlePolicy[string](10 * time.Millisecond) p.Op("foo", policy.Set) p.Op("bar", policy.Set) @@ -26,7 +26,7 @@ func TestIdlePolicy(t *testing.T) { } func TestLifetimePolicy(t *testing.T) { - p := NewLifetimePolicy(10 * time.Millisecond) + p := NewLifetimePolicy[string](10 * time.Millisecond) p.Op("foo", policy.Set) p.Op("bar", policy.Set) diff --git a/cache/policy_cache.go b/cache/policy_cache.go index 7240952..ef38a7a 100644 --- a/cache/policy_cache.go +++ b/cache/policy_cache.go @@ -10,19 +10,19 @@ func nop() {} var testHookEviction = nop -type policyCache struct { - c Cache - ep policy.EvictionPolicy +type policyCache[K comparable, V any] struct { + c Cache[K, V] + ep policy.EvictionPolicy[K] wg sync.WaitGroup } -func WithEvictionPolicy(c Cache, ep policy.EvictionPolicy) Cache { +func WithEvictionPolicy[K comparable, V any](c Cache[K, V], ep policy.EvictionPolicy[K]) Cache[K, V] { return newPolicyCache(c, ep) } -func newPolicyCache(c Cache, ep policy.EvictionPolicy) *policyCache { - pc := policyCache{c: c, ep: ep} +func newPolicyCache[K comparable, V any](c Cache[K, V], ep policy.EvictionPolicy[K]) *policyCache[K, V] { + pc := policyCache[K, V]{c: c, ep: ep} pc.wg.Add(1) @@ -31,7 +31,7 @@ func newPolicyCache(c Cache, ep policy.EvictionPolicy) *policyCache { return &pc } -func (pc *policyCache) watch() { +func (pc *policyCache[K, V]) watch() { defer pc.wg.Done() ch := pc.ep.C() @@ -48,17 +48,23 @@ func (pc *policyCache) watch() { } } -func (pc *policyCache) Get(k string) (interface{}, bool, error) { +func (pc *policyCache[K, V]) Get(k K) (V, bool, error) { v, ok, err := pc.c.Get(k) if err != nil { - return v, ok, err + return v, false, err } - return v, ok, pc.ep.Op(k, policy.Get) + if ok { + if err := pc.ep.Op(k, policy.Get); err != nil { + return v, false, err + } + } + + return v, ok, nil } -func (pc *policyCache) Set(k string, v interface{}) error { +func (pc *policyCache[K, V]) Set(k K, v V) error { if err := pc.c.Set(k, v); err != nil { return err } @@ -66,7 +72,7 @@ func (pc *policyCache) Set(k string, v interface{}) error { return pc.ep.Op(k, policy.Set) } -func (pc *policyCache) Evict(k string) error { +func (pc *policyCache[K, V]) Evict(k K) error { if err := pc.c.Evict(k); err != nil { return err } @@ -74,7 +80,7 @@ func (pc *policyCache) Evict(k string) error { return pc.ep.Op(k, policy.Evict) } -func (pc *policyCache) Close() error { +func (pc *policyCache[K, V]) Close() error { err := pc.ep.Close() pc.wg.Wait() diff --git a/cache/policy_cache_test.go b/cache/policy_cache_test.go index 891be88..fd78f83 100644 --- a/cache/policy_cache_test.go +++ b/cache/policy_cache_test.go @@ -9,7 +9,7 @@ import ( ) func TestPolicyCache(t *testing.T) { - c := WithEvictionPolicy(NewCache(), size.NewLRUPolicy(1)) + c := WithEvictionPolicy[string](NewStringCache[string](), size.NewLRUPolicy[string](1)) done := make(chan struct{}) diff --git a/cache/sharded_cache.go b/cache/sharded_cache.go index d1c1e82..56a773e 100644 --- a/cache/sharded_cache.go +++ b/cache/sharded_cache.go @@ -1,6 +1,11 @@ package cache -import "github.com/upfluence/errors" +import ( + "github.com/upfluence/errors" + "github.com/upfluence/pkg/maputil" + "github.com/upfluence/pkg/sliceutil" + "golang.org/x/exp/constraints" +) const ( defaultSharding = 256 @@ -20,40 +25,55 @@ func fnv64a(s string) uint64 { return h } -type shardedCache struct { - cs []Cache +type shardedCache[K comparable, V any] struct { + cs []*lockCache[K, V] - kfn func(string) uint64 + kfn func(K) uint64 size uint64 + + kp sliceutil.Pool[K] + mp maputil.Pool[*lockCache[K, V], []K] +} + +func NewStringCache[V any]() Cache[string, V] { + return NewCache[string, V](fnv64a) } -func NewCache() Cache { - return newShardedCache(newLockCache, defaultSharding) +func NewIntegerCache[K constraints.Integer, V any]() Cache[K, V] { + return NewCache[K, V](func(k K) uint64 { return uint64(k) }) } -func newShardedCache(cfn func() Cache, size int) Cache { - cs := make([]Cache, size) +func NewCache[K comparable, V any](kfn func(K) uint64) Cache[K, V] { + return newShardedCache[K, V](defaultSharding, kfn) +} + +func newShardedCache[K comparable, V any](size int, kfn func(K) uint64) Cache[K, V] { + cs := make([]*lockCache[K, V], size) for i := 0; i < size; i++ { - cs[i] = cfn() + cs[i] = newLockCache[K, V]() } - return &shardedCache{cs: cs, kfn: fnv64a, size: uint64(size)} + return &shardedCache[K, V]{cs: cs, kfn: kfn, size: uint64(size)} +} + +func (sc *shardedCache[K, V]) shard(k K) *lockCache[K, V] { + return sc.cs[sc.kfn(k)%sc.size] } -func (sc *shardedCache) Get(k string) (interface{}, bool, error) { - return sc.cs[sc.kfn(k)%sc.size].Get(k) +func (sc *shardedCache[K, V]) Get(k K) (V, bool, error) { + return sc.shard(k).Get(k) } -func (sc *shardedCache) Set(k string, v interface{}) error { - return sc.cs[sc.kfn(k)%sc.size].Set(k, v) +func (sc *shardedCache[K, V]) Set(k K, v V) error { + return sc.shard(k).Set(k, v) } -func (sc *shardedCache) Evict(k string) error { - return sc.cs[sc.kfn(k)%sc.size].Evict(k) +func (sc *shardedCache[K, V]) Evict(k K) error { + return sc.shard(k).Evict(k) } -func (sc *shardedCache) Close() error { +func (sc *shardedCache[K, V]) Close() error { var errs []error for _, c := range sc.cs { diff --git a/iopool/pool.go b/iopool/pool.go index 8b38dc3..fdba377 100644 --- a/iopool/pool.go +++ b/iopool/pool.go @@ -239,7 +239,7 @@ func (p *Pool[E]) checkoutWrapper(ew *entityWrapper[E]) bool { p.mu.Unlock() p.markOut(ew) - p.ep.Op(policy.Evict, ew.n) + p.ep.Op(ew.n, policy.Evict) p.metrics.idle.Update(int64(len(p.poolc))) return true @@ -272,7 +272,7 @@ func (p *Pool[E]) requeue(ew *entityWrapper[E]) error { default: } - p.ep.Op(policy.Evict, ew.n) + p.ep.Op(ew.n, policy.Evict) p.mu.Lock() delete(p.checkin, ew.n) p.mu.Unlock() @@ -307,7 +307,7 @@ func (p *Pool[E]) Put(e E) error { delete(p.checkout, ew.n) p.mu.Unlock() - p.ep.Op(policy.Set, ew.n) + p.ep.Op(ew.n, policy.Set) p.metrics.checkout.Update(int64(len(p.checkout))) return p.requeue(ew) @@ -327,7 +327,7 @@ func (p *Pool[E]) Discard(e E) error { delete(p.checkout, ew.n) p.mu.Unlock() - p.ep.Op(policy.Evict, ew.n) + p.ep.Op(ew.n, policy.Evict) p.metrics.checkout.Update(int64(len(p.checkout))) err := e.Close() @@ -347,7 +347,7 @@ func (p *Pool[E]) drainPoolChannel() []error { select { case ew := <-p.poolc: p.metrics.idle.Update(int64(len(p.poolc))) - p.ep.Op(policy.Evict, ew.n) + p.ep.Op(ew.n, policy.Evict) if err := ew.e.Close(); err != nil { errs = append(errs, err) From a4e7b29bddddc25cfd35db37c02bdf41647e8ed9 Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Fri, 23 Feb 2024 07:39:47 -0800 Subject: [PATCH 18/21] *: remove deprecated libs & funcs --- amqp/amqputil/uri.go | 60 ------- amqp/channelpool/pool.go | 179 --------------------- amqp/connectionpicker/options.go | 58 ------- amqp/connectionpicker/picker.go | 125 -------------- amqp/consumer/consumer.go | 268 ------------------------------- cache/sharded_cache.go | 5 - generics/batch.go | 25 --- generics/batch_test.go | 34 ---- generics/ptr.go | 42 ----- generics/ptr_test.go | 51 ------ generics/unique.go | 34 ---- generics/unique_test.go | 29 ---- go.mod | 37 +---- go.sum | 107 +----------- peer/peer.go | 1 - stringutil/batch.go | 22 --- stringutil/batch_test.go | 34 ---- stringutil/decoder.go | 1 - stringutil/ptrutil.go | 10 -- stringutil/ptrutil_test.go | 24 --- 20 files changed, 7 insertions(+), 1139 deletions(-) delete mode 100644 amqp/amqputil/uri.go delete mode 100644 amqp/channelpool/pool.go delete mode 100644 amqp/connectionpicker/options.go delete mode 100644 amqp/connectionpicker/picker.go delete mode 100644 amqp/consumer/consumer.go delete mode 100644 generics/batch.go delete mode 100644 generics/batch_test.go delete mode 100644 generics/ptr.go delete mode 100644 generics/ptr_test.go delete mode 100644 generics/unique.go delete mode 100644 generics/unique_test.go delete mode 100644 stringutil/batch.go delete mode 100644 stringutil/batch_test.go delete mode 100644 stringutil/ptrutil.go delete mode 100644 stringutil/ptrutil_test.go diff --git a/amqp/amqputil/uri.go b/amqp/amqputil/uri.go deleted file mode 100644 index 4ba9e0f..0000000 --- a/amqp/amqputil/uri.go +++ /dev/null @@ -1,60 +0,0 @@ -package amqputil - -import ( - "context" - "fmt" - "net" - "strings" - - "github.com/streadway/amqp" - - dpeer "github.com/upfluence/pkg/discovery/peer" - lpeer "github.com/upfluence/pkg/peer" -) - -func peerTable(p *lpeer.Peer) amqp.Table { - if p == nil { - return amqp.Table{} - } - - return amqp.Table{ - "upfluence-unit-name": p.InstanceName, - "upfluence-app-name": p.AppName, - "upfluence-project-name": p.ProjectName, - "upfluence-env": p.Environment, - "upfluence-version": p.Version.String(), - } -} - -func peerURI(p dpeer.Peer) string { - addr := p.Addr() - - if strings.HasPrefix(addr, "amqp://") { - return addr - } - - return fmt.Sprintf("amqp://guest:guest@%s/%%2f", addr) -} - -func Dial(ctx context.Context, p dpeer.Peer, l *lpeer.Peer, name string) (*amqp.Connection, error) { - var ( - d net.Dialer - - table = peerTable(l) - ) - - if name != "" { - table["upfluence-connection-name"] = name - table["connection_name"] = name - } - - return amqp.DialConfig( - peerURI(p), - amqp.Config{ - Dial: func(network, addr string) (net.Conn, error) { - return d.DialContext(ctx, network, addr) - }, - Properties: table, - }, - ) -} diff --git a/amqp/channelpool/pool.go b/amqp/channelpool/pool.go deleted file mode 100644 index c0ee8fc..0000000 --- a/amqp/channelpool/pool.go +++ /dev/null @@ -1,179 +0,0 @@ -package channelpool - -import ( - "context" - "sync" - - "github.com/streadway/amqp" - "github.com/upfluence/errors" - - "github.com/upfluence/pkg/amqp/connectionpicker" - "github.com/upfluence/pkg/closer" - "github.com/upfluence/pkg/iopool" - "github.com/upfluence/pkg/log" -) - -type Pool interface { - closer.Shutdowner - - Open(context.Context) error - IsOpen() bool - - Get(context.Context) (*amqp.Channel, error) - Put(*amqp.Channel) error - Discard(*amqp.Channel) error -} - -type pool struct { - *closer.Monitor - connectionpicker.Picker - - pool *iopool.Pool - st sync.Map -} - -type Option func(*options) - -func WithPoolOptions(opts ...iopool.Option) Option { - return func(o *options) { o.poopts = append(o.poopts, opts...) } -} - -func WithPickerOptions(opts ...connectionpicker.Option) Option { - return func(o *options) { o.piopts = append(o.piopts, opts...) } -} - -func WithPicker(p connectionpicker.Picker) Option { - return func(o *options) { o.p = p } -} - -type options struct { - poopts []iopool.Option - piopts []connectionpicker.Option - - p connectionpicker.Picker -} - -func NewPool(opts ...Option) Pool { - var o options - - for _, opt := range opts { - opt(&o) - } - - picker := o.p - - if picker == nil { - picker = connectionpicker.NewPicker(o.piopts...) - } - - p := pool{Monitor: closer.NewMonitor(), Picker: picker} - - p.pool = iopool.NewPool(p.factory, o.poopts...) - - return &p -} - -func (p *pool) IsOpen() bool { - return p.Picker.IsOpen() && p.Monitor.IsOpen() && p.pool.IsOpen() -} - -func (p *pool) Shutdown(ctx context.Context) error { - return errors.Combine( - p.pool.Shutdown(ctx), - p.Monitor.Shutdown(ctx), - p.Picker.Shutdown(ctx), - ) -} - -func (p *pool) Close() error { - return errors.Combine( - p.pool.Close(), - p.Monitor.Close(), - p.Picker.Close(), - ) -} - -type poolEntity struct { - *amqp.Channel - err error - closed bool -} - -func (p *poolEntity) Open(context.Context) error { - return nil -} - -func (p *poolEntity) IsOpen() bool { - return !p.closed -} - -func (p *poolEntity) supervise(ctx context.Context) { - var ch = make(chan *amqp.Error, 1) - - p.NotifyClose(ch) - select { - case err, ok := <-ch: - if ok { - if err != nil { - log.WithError(err).Warning("AMQPChannelPool channel closed") - } - - p.err = err - } - p.closed = true - case <-ctx.Done(): - p.err = p.Close() - p.closed = true - } -} - -func (p *pool) factory(ctx context.Context) (iopool.Entity, error) { - var conn, err = p.Picker.Pick(ctx) - - if err != nil { - return nil, err - } - - ch, err := conn.Channel() - - if err != nil { - return nil, err - } - - entity := &poolEntity{Channel: ch} - p.Run(func(ctx context.Context) { entity.supervise(ctx) }) - - return entity, nil -} - -func (p *pool) Get(ctx context.Context) (*amqp.Channel, error) { - var e, err = p.pool.Get(ctx) - - if err != nil { - return nil, err - } - - pe := e.(*poolEntity) - ch := pe.Channel - p.st.Store(ch, pe) - - return ch, nil -} - -func (p *pool) Put(ch *amqp.Channel) error { - if e, ok := p.st.Load(ch); ok { - p.st.Delete(ch) - return p.pool.Put(e.(*poolEntity)) - } - - return nil -} - -func (p *pool) Discard(ch *amqp.Channel) error { - if e, ok := p.st.Load(ch); ok { - p.st.Delete(ch) - return p.pool.Discard(e.(*poolEntity)) - } - - return nil -} diff --git a/amqp/connectionpicker/options.go b/amqp/connectionpicker/options.go deleted file mode 100644 index 062ce23..0000000 --- a/amqp/connectionpicker/options.go +++ /dev/null @@ -1,58 +0,0 @@ -package connectionpicker - -import ( - "fmt" - - "github.com/upfluence/pkg/cfg" - "github.com/upfluence/pkg/discovery/balancer" - "github.com/upfluence/pkg/discovery/balancer/random" - "github.com/upfluence/pkg/discovery/resolver/static" - "github.com/upfluence/pkg/peer" -) - -var ( - defaultOptions = &options{ - Balancer: defaultBalancer(), - peer: peer.FromEnv(), - targetOpenedConn: 1, - connectionNamer: func(p *peer.Peer, d int) string { - return fmt.Sprintf("%s-%d", p.URL().String(), d) - }, - } -) - -func defaultBalancer() balancer.Balancer { - var uris = cfg.FetchStrings( - "RABBITMQ_URLS", - []string{cfg.FetchString("RABBITMQ_URL", "localhost:5672")}, - ) - - return random.NewBalancer(static.NewResolverFromStrings(uris)) -} - -type Option func(*options) - -func WithBalancer(b balancer.Balancer) Option { - return func(o *options) { o.Balancer = b } -} - -func WithPeer(p *peer.Peer) Option { return func(o *options) { o.peer = p } } - -func WithConnectionNameTemplate(tmpl string) Option { - return func(o *options) { - o.connectionNamer = func(p *peer.Peer, v int) string { return fmt.Sprintf(tmpl, p, v) } - } -} - -func MaxOpenedConnections(v int) Option { - return func(o *options) { o.targetOpenedConn = v } -} - -type options struct { - balancer.Balancer - - targetOpenedConn int - - peer *peer.Peer - connectionNamer func(*peer.Peer, int) string -} diff --git a/amqp/connectionpicker/picker.go b/amqp/connectionpicker/picker.go deleted file mode 100644 index 50ee8d9..0000000 --- a/amqp/connectionpicker/picker.go +++ /dev/null @@ -1,125 +0,0 @@ -package connectionpicker - -import ( - "context" - "sync" - - "github.com/streadway/amqp" - "github.com/upfluence/errors" - - "github.com/upfluence/pkg/amqp/amqputil" - "github.com/upfluence/pkg/closer" - "github.com/upfluence/pkg/discovery/balancer" - "github.com/upfluence/pkg/group" - "github.com/upfluence/pkg/log" -) - -type Picker interface { - closer.Shutdowner - - Open(context.Context) error - IsOpen() bool - - Pick(context.Context) (*amqp.Connection, error) -} - -type picker struct { - *options - *closer.Monitor - - sync.Mutex - cs []*amqp.Connection - - lastUsedConn int -} - -func NewPicker(opts ...Option) Picker { - options := *defaultOptions - - for _, opt := range opts { - opt(&options) - } - - return &picker{options: &options, Monitor: closer.NewMonitor()} -} - -func (p *picker) IsOpen() bool { - return p.Balancer.IsOpen() && p.Monitor.IsOpen() -} - -func (p *picker) Shutdown(ctx context.Context) error { - g := group.WaitGroup(ctx) - - if sb, ok := p.Balancer.(closer.Shutdowner); ok { - g.Do(sb.Shutdown) - } - - g.Do(p.Monitor.Shutdown) - - return g.Wait() -} - -func (p *picker) Close() error { - return errors.Combine( - p.Balancer.Close(), - p.Monitor.Close(), - ) -} - -func (p *picker) Pick(ctx context.Context) (*amqp.Connection, error) { - p.Lock() - defer p.Unlock() - - if len(p.cs) < p.targetOpenedConn { - peer, err := p.Balancer.Get(ctx, balancer.GetOptions{}) - - if err != nil { - return nil, err - } - - conn, err := amqputil.Dial( - ctx, - peer, - p.peer, - p.connectionNamer(p.peer, len(p.cs)), - ) - - if conn != nil { - p.Run(func(ctx context.Context) { p.supervise(ctx, conn) }) - p.cs = append(p.cs, conn) - } - - return conn, err - } - - p.lastUsedConn = (p.lastUsedConn + 1) % p.targetOpenedConn - - return p.cs[p.lastUsedConn], nil -} - -func (p *picker) supervise(ctx context.Context, conn *amqp.Connection) { - var ch = make(chan *amqp.Error, 1) - - conn.NotifyClose(ch) - - select { - case err := <-ch: - if err != nil { - log.WithError(err).Warning("Connection closed") - } - case <-ctx.Done(): - conn.Close() - } - - p.Lock() - var cs []*amqp.Connection - - for _, wConn := range p.cs { - if wConn != conn { - cs = append(cs, wConn) - } - } - - p.cs = cs - p.Unlock() -} diff --git a/amqp/consumer/consumer.go b/amqp/consumer/consumer.go deleted file mode 100644 index ea97214..0000000 --- a/amqp/consumer/consumer.go +++ /dev/null @@ -1,268 +0,0 @@ -package consumer - -import ( - "context" - "sync" - "time" - - "github.com/streadway/amqp" - "github.com/upfluence/errors" - - "github.com/upfluence/pkg/closer" - "github.com/upfluence/pkg/log" -) - -var ErrCancelled = errors.New("amqp/consumer: Consumer is cancelled") - -type Consumer interface { - Open(context.Context) error - IsOpen() bool - - Consume() (<-chan amqp.Delivery, <-chan *amqp.Error, error) - QueueName(context.Context) (string, error) - - closer.Closer -} - -type consumer struct { - *closer.Monitor - - opts *options - - openOnce sync.Once - - consumersM sync.Mutex - consumers []chan amqp.Delivery - - errForwardersM sync.Mutex - errForwarders []chan *amqp.Error - - queueCond *sync.Cond - queueM sync.Mutex - queue string -} - -func NewConsumer(opts ...Option) Consumer { - var options = *defaultOptions - - for _, opt := range opts { - opt(&options) - } - - c := consumer{Monitor: closer.NewMonitor(), opts: &options} - c.queueCond = sync.NewCond(&c.queueM) - - return &c -} - -func (c *consumer) QueueName(ctx context.Context) (string, error) { - if !c.IsOpen() { - return "", ErrCancelled - } - - c.queueM.Lock() - q := c.queue - c.queueM.Unlock() - - if q != "" { - return q, nil - } - - done := make(chan struct{}) - cancelled := false - - go func() { - c.queueM.Lock() - defer c.queueM.Unlock() - - for { - if cancelled || c.queue != "" { - q = c.queue - close(done) - return - } - - c.queueCond.Wait() - } - }() - - select { - case <-ctx.Done(): - c.queueM.Lock() - cancelled = true - c.queueCond.Broadcast() - c.queueM.Unlock() - - return "", ctx.Err() - case <-done: - return q, nil - } -} - -func (c *consumer) loop(ctx context.Context) { - var i int - - for { - var ok, err = c.consume(ctx) - - if ok || !c.opts.shouldContinueFn(err) || ctx.Err() != nil { - return - } - - log.WithError(err).Warning("cant consume") - - t, _ := c.opts.backoff.Backoff(i) - i++ - - time.Sleep(t) - } -} - -func (c *consumer) consume(ctx context.Context) (bool, error) { - ch, err := c.opts.pool.Get(ctx) - - if err != nil { - return false, errors.Wrap(err, "pool.Get") - } - - if qName := c.opts.queueName; qName == "" { - q, errQ := ch.QueueDeclare("", false, true, true, false, nil) - - if errQ != nil { - c.opts.pool.Discard(ch) - return false, errors.Wrap(errQ, "channel.QueueDeclare") - } - - log.Noticef("Queue declared: %s", q.Name) - c.queueM.Lock() - c.queue = q.Name - c.queueM.Unlock() - } else { - c.queueM.Lock() - c.queue = qName - c.queueM.Unlock() - } - - ds, err := ch.Consume( - c.queue, - c.opts.consumerTag, - false, - false, - false, - false, - nil, - ) - - if err != nil { - c.opts.pool.Discard(ch) - return false, errors.Wrap(err, "channel.Consume") - } - - c.queueCond.Broadcast() - - closeCh := make(chan *amqp.Error, 1) - ch.NotifyClose(closeCh) - - for { - select { - case <-ctx.Done(): - c.queueM.Lock() - c.queue = "" - c.queueM.Unlock() - - if err := ch.Cancel(c.opts.consumerTag, false); err != nil { - c.opts.pool.Discard(ch) - } else { - c.opts.pool.Put(ch) - } - - return true, ctx.Err() - case err := <-closeCh: - c.queueM.Lock() - c.queue = "" - c.queueM.Unlock() - - for _, f := range c.errForwarders { - select { - case <-ctx.Done(): - case f <- err: - } - } - - c.opts.pool.Discard(ch) - return false, nil - case d := <-ds: - for _, c := range c.consumers { - select { - case <-ctx.Done(): - case c <- d: - } - } - - d.Ack(false) - } - } -} - -func (c *consumer) open(ctx context.Context) error { - if err := c.opts.pool.Open(ctx); err != nil { - return err - } - - c.Run(c.loop) - - return nil -} - -func (c *consumer) Open(ctx context.Context) error { - var err error - - c.openOnce.Do(func() { err = c.open(ctx) }) - - return err -} - -func (c *consumer) Consume() (<-chan amqp.Delivery, <-chan *amqp.Error, error) { - if !c.IsOpen() { - return nil, nil, ErrCancelled - } - - var ( - ch = make(chan amqp.Delivery) - errF = make(chan *amqp.Error) - ) - - c.consumersM.Lock() - c.consumers = append(c.consumers, ch) - c.consumersM.Unlock() - - c.errForwardersM.Lock() - c.errForwarders = append(c.errForwarders, errF) - c.errForwardersM.Unlock() - - return ch, errF, nil -} - -func (c *consumer) Close() error { - c.Monitor.Close() - - c.consumersM.Lock() - for _, c := range c.consumers { - close(c) - } - c.consumers = nil - c.consumersM.Unlock() - - c.errForwardersM.Lock() - for _, f := range c.errForwarders { - close(f) - } - c.errForwarders = nil - c.errForwardersM.Unlock() - - if c.opts.handlePoolClosing { - return c.opts.pool.Close() - } - - return nil -} diff --git a/cache/sharded_cache.go b/cache/sharded_cache.go index 56a773e..ab8e5ce 100644 --- a/cache/sharded_cache.go +++ b/cache/sharded_cache.go @@ -2,8 +2,6 @@ package cache import ( "github.com/upfluence/errors" - "github.com/upfluence/pkg/maputil" - "github.com/upfluence/pkg/sliceutil" "golang.org/x/exp/constraints" ) @@ -30,9 +28,6 @@ type shardedCache[K comparable, V any] struct { kfn func(K) uint64 size uint64 - - kp sliceutil.Pool[K] - mp maputil.Pool[*lockCache[K, V], []K] } func NewStringCache[V any]() Cache[string, V] { diff --git a/generics/batch.go b/generics/batch.go deleted file mode 100644 index 182265f..0000000 --- a/generics/batch.go +++ /dev/null @@ -1,25 +0,0 @@ -package generics - -// Batch -// -// Deprecated: use slices.Batch. -func Batch[T any](slice []T, size int) [][]T { - if len(slice) == 0 { - return nil - } - - if size < 1 { - panic("generics: illegal batch size") - } - - batches := make([][]T, 0, (len(slice)+size-1)/size) - - for size < len(slice) { - batches = append(batches, slice[:size:size]) - slice = slice[size:] - } - - batches = append(batches, slice) - - return batches -} diff --git a/generics/batch_test.go b/generics/batch_test.go deleted file mode 100644 index 3ea70b3..0000000 --- a/generics/batch_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package generics - -import ( - "reflect" - "testing" -) - -func TestBatch(t *testing.T) { - for _, tt := range []struct { - slice []string - size int - want [][]string - }{ - {size: 5}, - {slice: []string{}, size: 5}, - {slice: []string{"foo"}, size: 2, want: [][]string{{"foo"}}}, - {slice: []string{"foo", "bar"}, size: 2, want: [][]string{{"foo", "bar"}}}, - { - slice: []string{"foo", "bar", "biz"}, - size: 2, - want: [][]string{{"foo", "bar"}, {"biz"}}, - }, - } { - if out := Batch(tt.slice, tt.size); !reflect.DeepEqual(tt.want, out) { - t.Errorf( - "Batch(%v, %d) = %+v [ want: %+v ]", - tt.slice, - tt.size, - out, - tt.want, - ) - } - } -} diff --git a/generics/ptr.go b/generics/ptr.go deleted file mode 100644 index c5bc5e1..0000000 --- a/generics/ptr.go +++ /dev/null @@ -1,42 +0,0 @@ -//go:build go1.18 - -// Package generics -// Deprecated -package generics - -import "github.com/upfluence/pkg/slices" - -// Pointer pointers -// -// Deprecated: Use pointers.Ptr. -func Pointer[T comparable](v T) *T { - return &v -} - -// NullablePtr return nil if the value is zero -// -// Deprecated: Use pointers.NullablePtr. -func NullablePtr[T comparable](v T) *T { - var zero T - - if v == zero { - return nil - } - - return &v -} - -// ReferenceSlice returns slice with the value of the slice in params as -// pointers. -// -// Deprecated: use slices.References instead. -func ReferenceSlice[T comparable](s []T) []*T { - return slices.References(s) -} - -// IndirectSlice -// -// Deprecated: use slices.Indirect instead. -func IndirectSlice[T comparable](s []*T) []T { - return slices.Indirect(s) -} diff --git a/generics/ptr_test.go b/generics/ptr_test.go deleted file mode 100644 index 785753d..0000000 --- a/generics/ptr_test.go +++ /dev/null @@ -1,51 +0,0 @@ -//go:build go1.18 - -package generics - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPointer(t *testing.T) { - assert.Equal( - t, - "test", - *(Pointer("test")), - ) -} - -func TestNullablePtr(t *testing.T) { - assert.Equal(t, -5, *NullablePtr(-5)) - assert.Nil(t, NullablePtr(0)) - - assert.Equal(t, *NullablePtr("string"), "string") - assert.Nil(t, NullablePtr("")) -} - -func TestReferenceSlice(t *testing.T) { - var ( - i = 1 - j = 2 - ) - - assert.Equal( - t, - []*int{&i, &j}, - ReferenceSlice([]int{i, j}), - ) -} - -func TestIndirectSlice(t *testing.T) { - var ( - i = 1 - j = 2 - ) - - assert.Equal( - t, - []int{i, j}, - IndirectSlice([]*int{&i, &j}), - ) -} diff --git a/generics/unique.go b/generics/unique.go deleted file mode 100644 index 66e9b52..0000000 --- a/generics/unique.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build go1.18 - -package generics - -// Unique creates a map with keys being the values of a slice -// -// Deprecated: use slices.Unique. -func Unique[T comparable](s []T) map[T]struct{} { - var m = make(map[T]struct{}, len(s)) - - for _, v := range s { - m[v] = struct{}{} - } - - return m -} - -// UniquePtr creates a map with keys being the de-referenced value of a pointer -// contained in a slice. Nil ptr are ignored. -// -// Deprecated: use slices.UniquePtr. -func UniquePtr[T comparable](s []*T) map[T]struct{} { - var m = make(map[T]struct{}, len(s)) - - for _, v := range s { - if v == nil { - continue - } - - m[*v] = struct{}{} - } - - return m -} diff --git a/generics/unique_test.go b/generics/unique_test.go deleted file mode 100644 index 2aa1ce9..0000000 --- a/generics/unique_test.go +++ /dev/null @@ -1,29 +0,0 @@ -//go:build go1.18 - -package generics - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestUnique(t *testing.T) { - var s = []int64{1, 3, 23} - - assert.Equal( - t, - map[int64]struct{}{1: {}, 3: {}, 23: {}}, - Unique(s), - ) -} - -func TestUniquePtr(t *testing.T) { - var s = ReferenceSlice([]int64{1, 3, 23}) - - assert.Equal( - t, - map[int64]struct{}{1: {}, 3: {}, 23: {}}, - UniquePtr(s), - ) -} diff --git a/go.mod b/go.mod index fc4697a..8779c58 100644 --- a/go.mod +++ b/go.mod @@ -5,27 +5,6 @@ go 1.23.0 toolchain go1.23.1 require ( - github.com/Microsoft/go-winio v0.4.14 // indirect - github.com/cyberdelia/statsd v1.0.0 - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/docker/docker v1.13.1 // indirect - github.com/docker/go-connections v0.4.0 // indirect - github.com/docker/go-units v0.4.0 // indirect - github.com/garyburd/redigo v1.6.4 - github.com/gocql/gocql v1.6.0 - github.com/golang/snappy v0.0.4 - github.com/jinzhu/gorm v1.9.16 - github.com/jinzhu/inflection v1.0.1-0.20231016084002-bbe0a3e7399f // indirect - github.com/lib/pq v1.10.9 - github.com/mattes/migrate v3.0.2-0.20180508041624-4768a648fbd9+incompatible - github.com/mattn/go-sqlite3 v1.14.17 - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.18.0 - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - github.com/streadway/amqp v1.1.0 github.com/stretchr/testify v1.8.4 github.com/upfluence/cfg v0.3.5 github.com/upfluence/errors v0.2.9 @@ -34,26 +13,16 @@ require ( github.com/upfluence/thrift v2.6.8+incompatible golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f golang.org/x/oauth2 v0.19.0 - golang.org/x/sync v0.9.0 golang.org/x/term v0.26.0 golang.org/x/text v0.20.0 golang.org/x/time v0.3.0 ) require ( - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/denisenkom/go-mssqldb v0.11.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/getsentry/sentry-go v0.25.0 // indirect - github.com/go-sql-driver/mysql v1.6.0 // indirect - github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect - github.com/jinzhu/now v1.1.2 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - golang.org/x/crypto v0.29.0 // indirect - golang.org/x/net v0.31.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.27.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index fe4d9be..4321f71 100644 --- a/go.sum +++ b/go.sum @@ -3,57 +3,27 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cyberdelia/statsd v1.0.0 h1:DwSQkjPoS2pKOtyUYYvmYnb5hKrQ1tuH56+v9Iuw2Gk= -github.com/cyberdelia/statsd v1.0.0/go.mod h1:m6jLfyiM8BKUtgPe7szXEwZ0TBZ8KenicIeRAMkiB74= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/denisenkom/go-mssqldb v0.11.0 h1:9rHa233rhdOyrz2GcP9NM+gi2psgJZ4GWDpL/7ND8HI= -github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= -github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/garyburd/redigo v1.6.4 h1:LFu2R3+ZOPgSMWMOL+saa/zXRjw0ID2G8FepO53BGlg= -github.com/garyburd/redigo v1.6.4/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/getsentry/sentry-go v0.9.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws= github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= @@ -65,33 +35,19 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/gocql/gocql v1.6.0 h1:IdFdOTbnpbd0pDhl4REKQDM+Q0SzKXQ1Yh+YZZ8T/qU= -github.com/gocql/gocql v1.6.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -102,14 +58,6 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= -github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/inflection v1.0.1-0.20231016084002-bbe0a3e7399f h1:CaL7/sMHOaAqSeJN80BJ5RH7oZSjJIpYg7jC+QcEsCA= -github.com/jinzhu/inflection v1.0.1-0.20231016084002-bbe0a3e7399f/go.mod h1:lcrn0jrToZnZxiaoiYlmxfr1DnDQn86NkcbmyrP7m5w= -github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= -github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -122,32 +70,21 @@ github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubc github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattes/migrate v3.0.2-0.20180508041624-4768a648fbd9+incompatible h1:FAIQGZlq18kmVrnwmje0nAOsHtZaIJjJNliv4yk38rk= -github.com/mattes/migrate v3.0.2-0.20180508041624-4768a648fbd9+incompatible/go.mod h1:LJcqgpj1jQoxv3m2VXd3drv0suK5CbN/RCX7MXwgnVI= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 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-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= -github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= -github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -164,33 +101,19 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -202,10 +125,7 @@ github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tL github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= -github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -242,15 +162,10 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDf github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -260,27 +175,18 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= @@ -297,17 +203,12 @@ golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 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= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/peer/peer.go b/peer/peer.go index 065452c..02a349b 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -3,7 +3,6 @@ package peer import ( "fmt" "net/url" - "os" "strings" "github.com/upfluence/pkg/envutil" diff --git a/stringutil/batch.go b/stringutil/batch.go deleted file mode 100644 index 91cd1b5..0000000 --- a/stringutil/batch.go +++ /dev/null @@ -1,22 +0,0 @@ -package stringutil - -// Deprecated: Use slices.Batch. -func Batch(slice []string, size int) [][]string { - if len(slice) == 0 { - return nil - } - - if size < 1 { - panic("stringutil: illegal batch size") - } - - batches := make([][]string, 0, (len(slice)+size-1)/size) - - for size < len(slice) { - slice, batches = slice[size:], append(batches, slice[0:size:size]) - } - - batches = append(batches, slice) - - return batches -} diff --git a/stringutil/batch_test.go b/stringutil/batch_test.go deleted file mode 100644 index 6efd814..0000000 --- a/stringutil/batch_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package stringutil - -import ( - "reflect" - "testing" -) - -func TestBatch(t *testing.T) { - for _, tt := range []struct { - slice []string - size int - want [][]string - }{ - {size: 5}, - {slice: []string{}, size: 5}, - {slice: []string{"foo"}, size: 2, want: [][]string{{"foo"}}}, - {slice: []string{"foo", "bar"}, size: 2, want: [][]string{{"foo", "bar"}}}, - { - slice: []string{"foo", "bar", "biz"}, - size: 2, - want: [][]string{{"foo", "bar"}, {"biz"}}, - }, - } { - if out := Batch(tt.slice, tt.size); !reflect.DeepEqual(tt.want, out) { - t.Errorf( - "Batch(%v, %d) = %+v [ want: %+v ]", - tt.slice, - tt.size, - out, - tt.want, - ) - } - } -} diff --git a/stringutil/decoder.go b/stringutil/decoder.go index eb875c4..34930f4 100644 --- a/stringutil/decoder.go +++ b/stringutil/decoder.go @@ -14,7 +14,6 @@ import ( "github.com/upfluence/pkg/log" ) -var cmap = charmap.ISO8859_1 var defaultDecoder = charmap.ISO8859_1.NewDecoder() type ASCIIDecodeOption func(*asciiDecodeOptions) diff --git a/stringutil/ptrutil.go b/stringutil/ptrutil.go deleted file mode 100644 index 2391e40..0000000 --- a/stringutil/ptrutil.go +++ /dev/null @@ -1,10 +0,0 @@ -package stringutil - -// Deprecated: Use pointers.NullablePtr. -func NullablePtr(s string) *string { - if s == "" { - return nil - } - - return &s -} diff --git a/stringutil/ptrutil_test.go b/stringutil/ptrutil_test.go deleted file mode 100644 index d251234..0000000 --- a/stringutil/ptrutil_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package stringutil - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNullablePtr(t *testing.T) { - var str = "string" - - for _, tt := range []struct { - s string - want *string - }{ - {}, - {s: str, want: &str}, - } { - t.Run(fmt.Sprint("ptr value of ", tt.s), func(t *testing.T) { - assert.Equal(t, tt.want, NullablePtr(tt.s)) - }) - } -} From e1112673443b00b127f8e27b77440625a2b82927 Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Mon, 20 May 2024 16:11:44 -0700 Subject: [PATCH 19/21] discovery/*: Make the resolver & balancer generic --- discovery/balancer/balancer.go | 16 +++--- discovery/balancer/dialer.go | 55 +++++++++++++------ discovery/balancer/dialer_test.go | 6 +- discovery/balancer/random/balancer.go | 32 ++++++----- discovery/balancer/roundrobin/balancer.go | 33 +++++------ .../balancer/roundrobin/balancer_test.go | 28 ++++++---- discovery/peer/peer.go | 4 +- discovery/resolver/puller.go | 25 +++++---- discovery/resolver/resolver.go | 22 ++++---- discovery/resolver/static/resolver.go | 54 +++++++++--------- discovery/resolver/static/resolver_test.go | 12 ++-- discovery/resolver/sync_resolver.go | 40 +++++++------- discovery/resolver/sync_resolver_test.go | 6 +- 13 files changed, 183 insertions(+), 150 deletions(-) diff --git a/discovery/balancer/balancer.go b/discovery/balancer/balancer.go index 23e11c8..26eae43 100644 --- a/discovery/balancer/balancer.go +++ b/discovery/balancer/balancer.go @@ -15,23 +15,23 @@ type GetOptions struct { NoWait bool } -type Builder interface { - Build(string) Balancer +type Builder[T peer.Peer] interface { + Build(string) Balancer[T] } -type ResolverBuilder struct { - Builder resolver.Builder - BalancerFunc func(resolver.Resolver) Balancer +type ResolverBuilder[T peer.Peer] struct { + Builder resolver.Builder[T] + BalancerFunc func(resolver.Resolver[T]) Balancer[T] } -func (rb ResolverBuilder) Build(k string) Balancer { +func (rb ResolverBuilder[T]) Build(k string) Balancer[T] { return rb.BalancerFunc(rb.Builder.Build(k)) } -type Balancer interface { +type Balancer[T peer.Peer] interface { Open(context.Context) error IsOpen() bool Close() error - Get(context.Context, GetOptions) (peer.Peer, error) + Get(context.Context, GetOptions) (T, func(error), error) } diff --git a/discovery/balancer/dialer.go b/discovery/balancer/dialer.go index 6ce58c6..1c5a56f 100644 --- a/discovery/balancer/dialer.go +++ b/discovery/balancer/dialer.go @@ -7,19 +7,20 @@ import ( "github.com/upfluence/errors" + "github.com/upfluence/pkg/discovery/peer" "github.com/upfluence/pkg/syncutil" ) -type Dialer struct { - Builder Builder +type Dialer[T peer.Peer] struct { + Builder Builder[T] Dialer *net.Dialer Options GetOptions mu sync.Mutex - lds map[string]*localDialer + lds map[string]*localDialer[T] } -func (d *Dialer) dialer() *net.Dialer { +func (d *Dialer[T]) dialer() *net.Dialer { if d.Dialer == nil { d.Dialer = &net.Dialer{} } @@ -27,21 +28,21 @@ func (d *Dialer) dialer() *net.Dialer { return d.Dialer } -func (d *Dialer) Dial(network, addr string) (net.Conn, error) { +func (d *Dialer[T]) Dial(network, addr string) (net.Conn, error) { return d.DialContext(context.Background(), network, addr) } -func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { +func (d *Dialer[T]) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { d.mu.Lock() if d.lds == nil { - d.lds = make(map[string]*localDialer) + d.lds = make(map[string]*localDialer[T]) } ld, ok := d.lds[addr] if !ok { - ld = &localDialer{d: d, b: d.Builder.Build(addr)} + ld = &localDialer[T]{d: d, b: d.Builder.Build(addr)} d.lds[addr] = ld } d.mu.Unlock() @@ -49,7 +50,7 @@ func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Con return ld.dial(ctx, network) } -func (d *Dialer) Close() error { +func (d *Dialer[T]) Close() error { var errs []error d.mu.Lock() @@ -66,15 +67,15 @@ func (d *Dialer) Close() error { return errors.WrapErrors(errs) } -type localDialer struct { - d *Dialer - b Balancer +type localDialer[T peer.Peer] struct { + d *Dialer[T] + b Balancer[T] opened bool sf syncutil.Singleflight[struct{}] } -func (ld *localDialer) open(ctx context.Context) (struct{}, error) { +func (ld *localDialer[T]) open(ctx context.Context) (struct{}, error) { if !ld.b.IsOpen() { if err := ld.b.Open(ctx); err != nil { return struct{}{}, err @@ -86,22 +87,42 @@ func (ld *localDialer) open(ctx context.Context) (struct{}, error) { return struct{}{}, nil } -func (ld *localDialer) dial(ctx context.Context, network string) (net.Conn, error) { +func (ld *localDialer[T]) dial(ctx context.Context, network string) (net.Conn, error) { if !ld.opened { if _, _, err := ld.sf.Do(ctx, ld.open); err != nil { return nil, err } } - p, err := ld.b.Get(ctx, ld.d.Options) + p, done, err := ld.b.Get(ctx, ld.d.Options) if err != nil { return nil, err } - return ld.d.dialer().DialContext(ctx, network, p.Addr()) + conn, err := ld.d.dialer().DialContext(ctx, network, p.Addr()) + + if err != nil { + return nil, err + } + + return &doneCloserConn{Conn: conn, done: done}, nil } -func (ld *localDialer) close() error { +func (ld *localDialer[T]) close() error { return errors.Combine(ld.sf.Close(), ld.b.Close()) } + +type doneCloserConn struct { + net.Conn + + done func(error) +} + +func (dcc *doneCloserConn) Close() error { + err := dcc.Conn.Close() + + dcc.done(err) + + return err +} diff --git a/discovery/balancer/dialer_test.go b/discovery/balancer/dialer_test.go index f963c3c..ede977b 100644 --- a/discovery/balancer/dialer_test.go +++ b/discovery/balancer/dialer_test.go @@ -30,10 +30,10 @@ func TestDialer(t *testing.T) { []string{s1.Listener.Addr().String(), s2.Listener.Addr().String()}, ) - d := balancer.Dialer{ - Builder: balancer.ResolverBuilder{ + d := balancer.Dialer[static.Peer]{ + Builder: balancer.ResolverBuilder[static.Peer]{ Builder: r, - BalancerFunc: roundrobin.BalancerFunc, + BalancerFunc: roundrobin.BalancerFunc[static.Peer], }, } diff --git a/discovery/balancer/random/balancer.go b/discovery/balancer/random/balancer.go index 840528d..7bd8672 100644 --- a/discovery/balancer/random/balancer.go +++ b/discovery/balancer/random/balancer.go @@ -16,10 +16,10 @@ type Rand interface { Intn(int) int } -type Balancer struct { - *resolver.Puller +type Balancer[T peer.Peer] struct { + *resolver.Puller[T] - peers []peer.Peer + peers []T peersMu *sync.RWMutex rand Rand @@ -27,8 +27,8 @@ type Balancer struct { closeFn func() } -func NewBalancer(r resolver.Resolver) *Balancer { - var b = &Balancer{ +func NewBalancer[T peer.Peer](r resolver.Resolver[T]) *Balancer[T] { + var b = &Balancer[T]{ rand: rand.New(rand.NewSource(time.Now().UnixNano())), peersMu: &sync.RWMutex{}, notifier: make(chan interface{}), @@ -39,15 +39,15 @@ func NewBalancer(r resolver.Resolver) *Balancer { return b } -func (b *Balancer) String() string { +func (b *Balancer[T]) String() string { return fmt.Sprintf("loadbalancer/random [resolver: %v]", b.Puller) } -func (b *Balancer) updatePeers(u resolver.Update) { +func (b *Balancer[T]) updatePeers(u resolver.Update[T]) { b.peersMu.Lock() defer b.peersMu.Unlock() - var newPeers = make(map[peer.Peer]interface{}) + var newPeers = make(map[T]interface{}) for _, p := range b.peers { var found bool @@ -82,7 +82,7 @@ func (b *Balancer) updatePeers(u resolver.Update) { empty = len(b.peers) == 0 ) - b.peers = make([]peer.Peer, len(newPeers)) + b.peers = make([]T, len(newPeers)) for p, _ := range newPeers { b.peers[i] = p @@ -100,32 +100,34 @@ func (b *Balancer) updatePeers(u resolver.Update) { } } -func (b *Balancer) hasPeers() bool { +func (b *Balancer[T]) hasPeers() bool { b.peersMu.RLock() defer b.peersMu.RUnlock() return len(b.peers) > 0 } -func (b *Balancer) Get(ctx context.Context, opts balancer.GetOptions) (peer.Peer, error) { +func (b *Balancer[T]) Get(ctx context.Context, opts balancer.GetOptions) (T, func(error), error) { + var zero T + if !b.hasPeers() { if opts.NoWait { - return nil, balancer.ErrNoPeerAvailable + return zero, nil, balancer.ErrNoPeerAvailable } select { case b.notifier <- true: case <-ctx.Done(): - return nil, ctx.Err() + return zero, nil, ctx.Err() } } b.peersMu.RLock() defer b.peersMu.RUnlock() - return b.peers[b.rand.Intn(len(b.peers))], nil + return b.peers[b.rand.Intn(len(b.peers))], func(error) {}, nil } -func (b *Balancer) Close() error { +func (b *Balancer[T]) Close() error { b.closeFn() return nil } diff --git a/discovery/balancer/roundrobin/balancer.go b/discovery/balancer/roundrobin/balancer.go index 0ab532f..d30ded2 100644 --- a/discovery/balancer/roundrobin/balancer.go +++ b/discovery/balancer/roundrobin/balancer.go @@ -11,8 +11,8 @@ import ( "github.com/upfluence/pkg/discovery/resolver" ) -type Balancer struct { - resolver.Puller +type Balancer[T peer.Peer] struct { + resolver.Puller[T] addrs map[string]*ring.Ring ring *ring.Ring @@ -21,26 +21,26 @@ type Balancer struct { notifier chan struct{} } -func BalancerFunc(r resolver.Resolver) balancer.Balancer { - return NewBalancer(r) +func BalancerFunc[T peer.Peer](r resolver.Resolver[T]) balancer.Balancer[T] { + return NewBalancer[T](r) } -func NewBalancer(r resolver.Resolver) *Balancer { - var b = Balancer{ +func NewBalancer[T peer.Peer](r resolver.Resolver[T]) *Balancer[T] { + var b = Balancer[T]{ addrs: make(map[string]*ring.Ring), notifier: make(chan struct{}), } - b.Puller = resolver.Puller{Resolver: r, UpdateFunc: b.updateRing} + b.Puller = resolver.Puller[T]{Resolver: r, UpdateFunc: b.updateRing} return &b } -func (b *Balancer) String() string { +func (b *Balancer[T]) String() string { return fmt.Sprintf("loadbalancer/roundrobin [resolver: %v]", &b.Puller) } -func (b *Balancer) updateRing(update resolver.Update) { +func (b *Balancer[T]) updateRing(update resolver.Update[T]) { b.ringMu.Lock() defer b.ringMu.Unlock() @@ -85,7 +85,9 @@ func (b *Balancer) updateRing(update resolver.Update) { } } -func (b *Balancer) Get(ctx context.Context, opts balancer.GetOptions) (peer.Peer, error) { +func (b *Balancer[T]) Get(ctx context.Context, opts balancer.GetOptions) (T, func(error), error) { + var zero T + b.ringMu.RLock() r := b.ring n := b.notifier @@ -93,7 +95,7 @@ func (b *Balancer) Get(ctx context.Context, opts balancer.GetOptions) (peer.Peer if r == nil { if opts.NoWait { - return nil, balancer.ErrNoPeerAvailable + return zero, nil, balancer.ErrNoPeerAvailable } pctx := b.Puller.Monitor.Context() @@ -101,9 +103,9 @@ func (b *Balancer) Get(ctx context.Context, opts balancer.GetOptions) (peer.Peer select { case <-n: case <-ctx.Done(): - return nil, ctx.Err() + return zero, nil, ctx.Err() case <-pctx.Done(): - return nil, pctx.Err() + return zero, nil, pctx.Err() } } @@ -113,9 +115,8 @@ func (b *Balancer) Get(ctx context.Context, opts balancer.GetOptions) (peer.Peer if v := b.ring.Value; v != nil { b.ring = b.ring.Next() - p := v.(peer.Peer) - return p, nil + return v.(T), func(error) {}, nil } - return nil, balancer.ErrNoPeerAvailable + return zero, nil, balancer.ErrNoPeerAvailable } diff --git a/discovery/balancer/roundrobin/balancer_test.go b/discovery/balancer/roundrobin/balancer_test.go index cfe375e..2889832 100644 --- a/discovery/balancer/roundrobin/balancer_test.go +++ b/discovery/balancer/roundrobin/balancer_test.go @@ -12,27 +12,30 @@ import ( func TestBalanceEmpty(t *testing.T) { ctx := context.Background() - b := NewBalancer(&static.Resolver{}) + b := NewBalancer(&static.Resolver[static.Peer]{}) - p, err := b.Get(ctx, balancer.GetOptions{NoWait: true}) + p, done, err := b.Get(ctx, balancer.GetOptions{NoWait: true}) - assert.Nil(t, p) + assert.Empty(t, p.Addr()) + assert.Nil(t, done) assert.Equal(t, balancer.ErrNoPeerAvailable, err) cctx, cancel := context.WithCancel(ctx) cancel() - p, err = b.Get(cctx, balancer.GetOptions{}) + p, done, err = b.Get(cctx, balancer.GetOptions{}) - assert.Nil(t, p) + assert.Empty(t, p.Addr()) + assert.Nil(t, done) assert.Equal(t, err, context.Canceled) err = b.Close() - assert.Nil(t, p) + assert.NoError(t, err) - p, err = b.Get(ctx, balancer.GetOptions{}) + p, done, err = b.Get(ctx, balancer.GetOptions{}) - assert.Nil(t, p) + assert.Empty(t, p.Addr()) + assert.Nil(t, done) assert.Equal(t, err, context.Canceled) } @@ -45,17 +48,20 @@ func TestBalanceWithPerrs(t *testing.T) { err := b.Open(ctx) assert.Nil(t, err) - p, err := b.Get(ctx, balancer.GetOptions{}) + p, done, err := b.Get(ctx, balancer.GetOptions{}) + done(nil) assert.Nil(t, err) assert.Equal(t, "localhost:0", p.Addr()) - p, err = b.Get(ctx, balancer.GetOptions{}) + p, done, err = b.Get(ctx, balancer.GetOptions{}) + done(nil) assert.Nil(t, err) assert.Equal(t, "localhost:1", p.Addr()) - p, err = b.Get(ctx, balancer.GetOptions{}) + p, done, err = b.Get(ctx, balancer.GetOptions{}) + done(nil) assert.Nil(t, err) assert.Equal(t, "localhost:0", p.Addr()) diff --git a/discovery/peer/peer.go b/discovery/peer/peer.go index 2c4b959..7e51fcf 100644 --- a/discovery/peer/peer.go +++ b/discovery/peer/peer.go @@ -1,8 +1,6 @@ package peer -import "github.com/upfluence/pkg/metadata" - type Peer interface { + comparable Addr() string - Metadata() metadata.Metadata } diff --git a/discovery/resolver/puller.go b/discovery/resolver/puller.go index 2e00988..fc9953e 100644 --- a/discovery/resolver/puller.go +++ b/discovery/resolver/puller.go @@ -8,12 +8,13 @@ import ( "github.com/upfluence/errors" "github.com/upfluence/pkg/closer" + "github.com/upfluence/pkg/discovery/peer" "github.com/upfluence/pkg/log" ) -type Puller struct { - Resolver Resolver - UpdateFunc func(Update) +type Puller[T peer.Peer] struct { + Resolver Resolver[T] + UpdateFunc func(Update[T]) Monitor closer.Monitor NoWait bool @@ -21,8 +22,8 @@ type Puller struct { openOnce sync.Once } -func NewPuller(r Resolver, fn func(Update)) (*Puller, func()) { - var p = &Puller{ +func NewPuller[T peer.Peer](r Resolver[T], fn func(Update[T])) (*Puller[T], func()) { + var p = &Puller[T]{ Resolver: r, UpdateFunc: fn, } @@ -30,19 +31,19 @@ func NewPuller(r Resolver, fn func(Update)) (*Puller, func()) { return p, func() { p.Close() } } -func (p *Puller) Close() error { +func (p *Puller[T]) Close() error { return errors.Combine(p.Monitor.Close(), p.Resolver.Close()) } -func (p *Puller) IsOpen() bool { +func (p *Puller[T]) IsOpen() bool { return p.openErr == nil && p.openOnce != sync.Once{} } -func (p *Puller) String() string { +func (p *Puller[T]) String() string { return fmt.Sprintf("%v", p.Resolver) } -func (p *Puller) Open(ctx context.Context) error { +func (p *Puller[T]) Open(ctx context.Context) error { p.openOnce.Do(func() { p.openErr = p.Resolver.Open(ctx) @@ -54,11 +55,11 @@ func (p *Puller) Open(ctx context.Context) error { return p.openErr } -func (p *Puller) pull(ctx context.Context) { +func (p *Puller[T]) pull(ctx context.Context) { var ( - u Update + u Update[T] err error - w Watcher + w Watcher[T] noWait = p.NoWait ) diff --git a/discovery/resolver/resolver.go b/discovery/resolver/resolver.go index 8ba88dc..a61b910 100644 --- a/discovery/resolver/resolver.go +++ b/discovery/resolver/resolver.go @@ -9,18 +9,18 @@ import ( "github.com/upfluence/pkg/discovery/peer" ) -type Update struct { - Additions []peer.Peer - Deletions []peer.Peer +type Update[T peer.Peer] struct { + Additions []T + Deletions []T } -type Builder interface { - Build(string) Resolver +type Builder[T peer.Peer] interface { + Build(string) Resolver[T] } -type BuilderFunc func(string) Resolver +type BuilderFunc[T peer.Peer] func(string) Resolver[T] -func (fn BuilderFunc) Build(k string) Resolver { return fn(k) } +func (fn BuilderFunc[T]) Build(k string) Resolver[T] { return fn(k) } var ( ErrNoUpdates = errors.New("discovery/resolver: No update available") @@ -31,16 +31,16 @@ type ResolveOptions struct { NoWait bool } -type Resolver interface { +type Resolver[T peer.Peer] interface { io.Closer Open(context.Context) error - Resolve() Watcher + Resolve() Watcher[T] } -type Watcher interface { +type Watcher[T peer.Peer] interface { io.Closer - Next(context.Context, ResolveOptions) (Update, error) + Next(context.Context, ResolveOptions) (Update[T], error) } diff --git a/discovery/resolver/static/resolver.go b/discovery/resolver/static/resolver.go index 9942417..72948c5 100644 --- a/discovery/resolver/static/resolver.go +++ b/discovery/resolver/static/resolver.go @@ -11,42 +11,42 @@ import ( "github.com/upfluence/pkg/metadata" ) -type Builder map[string][]peer.Peer +type Builder[T peer.Peer] map[string][]T -func (b Builder) Build(n string) resolver.Resolver { - return &Resolver{Peers: b[n]} +func (b Builder[T]) Build(n string) resolver.Resolver[T] { + return &Resolver[T]{Peers: b[n]} } -func PeersFromStrings(addrs ...string) []peer.Peer { - var peers = make([]peer.Peer, len(addrs)) +func PeersFromStrings(addrs ...string) []Peer { + var peers = make([]Peer, len(addrs)) for i, addr := range addrs { - peers[i] = staticPeer(addr) + peers[i] = Peer(addr) } return peers } -type Resolver struct { +type Resolver[T peer.Peer] struct { closer.Monitor - Peers []peer.Peer + Peers []T } -type staticPeer string +type Peer string -func (sp staticPeer) Addr() string { return string(sp) } -func (sp staticPeer) Metadata() metadata.Metadata { return nil } +func (sp Peer) Addr() string { return string(sp) } +func (sp Peer) Metadata() metadata.Metadata { return nil } -func NewResolverFromStrings(addrs []string) *Resolver { +func NewResolverFromStrings(addrs []string) *Resolver[Peer] { return NewResolver(PeersFromStrings(addrs...)) } -func NewResolver(peers []peer.Peer) *Resolver { - return &Resolver{Peers: peers} +func NewResolver[T peer.Peer](peers []T) *Resolver[T] { + return &Resolver[T]{Peers: peers} } -func (r *Resolver) String() string { +func (r *Resolver[T]) String() string { var addrs = make([]string, len(r.Peers)) for i, peer := range r.Peers { @@ -56,30 +56,30 @@ func (r *Resolver) String() string { return fmt.Sprintf("resolver/static: [seeds: %v]", addrs) } -func (r *Resolver) Build(string) resolver.Resolver { return r } +func (r *Resolver[T]) Build(string) resolver.Resolver[T] { return r } -func (r *Resolver) Open(_ context.Context) error { return nil } +func (r *Resolver[T]) Open(_ context.Context) error { return nil } -func (r *Resolver) Resolve() resolver.Watcher { - return &watcher{r: r} +func (r *Resolver[T]) Resolve() resolver.Watcher[T] { + return &watcher[T]{r: r} } -type watcher struct { +type watcher[T peer.Peer] struct { closer.Monitor - r *Resolver + r *Resolver[T] initial int32 } -func (w *watcher) Next(ctx context.Context, opts resolver.ResolveOptions) (resolver.Update, error) { +func (w *watcher[T]) Next(ctx context.Context, opts resolver.ResolveOptions) (resolver.Update[T], error) { ok := atomic.CompareAndSwapInt32(&w.initial, 0, 1) if opts.NoWait && (!ok || len(w.r.Peers) == 0) { - return resolver.Update{}, resolver.ErrNoUpdates + return resolver.Update[T]{}, resolver.ErrNoUpdates } if ok && len(w.r.Peers) > 0 { - return resolver.Update{Additions: w.r.Peers}, nil + return resolver.Update[T]{Additions: w.r.Peers}, nil } wctx := w.Context() @@ -87,11 +87,11 @@ func (w *watcher) Next(ctx context.Context, opts resolver.ResolveOptions) (resol select { case <-ctx.Done(): - return resolver.Update{}, ctx.Err() + return resolver.Update[T]{}, ctx.Err() case <-wctx.Done(): - return resolver.Update{}, wctx.Err() + return resolver.Update[T]{}, wctx.Err() case <-rctx.Done(): w.Close() - return resolver.Update{}, wctx.Err() + return resolver.Update[T]{}, wctx.Err() } } diff --git a/discovery/resolver/static/resolver_test.go b/discovery/resolver/static/resolver_test.go index 10f58e9..a6b9ce5 100644 --- a/discovery/resolver/static/resolver_test.go +++ b/discovery/resolver/static/resolver_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/upfluence/pkg/discovery/peer" "github.com/upfluence/pkg/discovery/resolver" ) @@ -20,14 +19,19 @@ func TestResolve(t *testing.T) { assert.Nil(t, err) assert.Equal( t, - resolver.Update{Additions: []peer.Peer{staticPeer("localhost:1"), staticPeer("localhost:2")}}, + resolver.Update[Peer]{ + Additions: []Peer{ + Peer("localhost:1"), + Peer("localhost:2"), + }, + }, u, ) u, err = w.Next(ctx, resolver.ResolveOptions{NoWait: true}) assert.Equal(t, err, resolver.ErrNoUpdates) - assert.Equal(t, resolver.Update{}, u) + assert.Equal(t, resolver.Update[Peer]{}, u) err = w.Close() assert.Nil(t, err) @@ -35,5 +39,5 @@ func TestResolve(t *testing.T) { u, err = w.Next(ctx, resolver.ResolveOptions{}) assert.Equal(t, err, context.Canceled) - assert.Equal(t, resolver.Update{}, u) + assert.Equal(t, resolver.Update[Peer]{}, u) } diff --git a/discovery/resolver/sync_resolver.go b/discovery/resolver/sync_resolver.go index 2682496..d6ee9e3 100644 --- a/discovery/resolver/sync_resolver.go +++ b/discovery/resolver/sync_resolver.go @@ -9,36 +9,36 @@ import ( "github.com/upfluence/pkg/discovery/peer" ) -type SyncResolver interface { - ResolveSync(context.Context, string) ([]peer.Peer, error) +type SyncResolver[T peer.Peer] interface { + ResolveSync(context.Context, string) ([]T, error) Close() error } -func SyncResolverFromBuilder(b Builder, noWait bool) SyncResolver { - return &syncResolver{ +func SyncResolverFromBuilder[T peer.Peer](b Builder[T], noWait bool) SyncResolver[T] { + return &syncResolver[T]{ builder: b, noWait: noWait, - lrs: make(map[string]*localResolver), + lrs: make(map[string]*localResolver[T]), } } -type syncResolver struct { - builder Builder +type syncResolver[T peer.Peer] struct { + builder Builder[T] noWait bool mu sync.Mutex - lrs map[string]*localResolver + lrs map[string]*localResolver[T] } -func (sr *syncResolver) ResolveSync(ctx context.Context, n string) ([]peer.Peer, error) { +func (sr *syncResolver[T]) ResolveSync(ctx context.Context, n string) ([]T, error) { sr.mu.Lock() lr, ok := sr.lrs[n] if !ok { - lr = &localResolver{readyc: make(chan struct{})} + lr = &localResolver[T]{readyc: make(chan struct{})} - lr.p = &Puller{ + lr.p = &Puller[T]{ Resolver: sr.builder.Build(n), UpdateFunc: lr.update, NoWait: sr.noWait, @@ -58,7 +58,7 @@ func (sr *syncResolver) ResolveSync(ctx context.Context, n string) ([]peer.Peer, return lr.resolve(ctx) } -func (sr *syncResolver) Close() error { +func (sr *syncResolver[T]) Close() error { var errs []error sr.mu.Lock() @@ -75,21 +75,21 @@ func (sr *syncResolver) Close() error { return errors.WrapErrors(errs) } -type localResolver struct { - p *Puller +type localResolver[T peer.Peer] struct { + p *Puller[T] readyOnce sync.Once readyc chan struct{} mu sync.RWMutex - ps map[string]peer.Peer + ps map[string]T } -func (lr *localResolver) update(u Update) { +func (lr *localResolver[T]) update(u Update[T]) { lr.mu.Lock() if lr.ps == nil { - lr.ps = make(map[string]peer.Peer) + lr.ps = make(map[string]T) } for _, p := range u.Deletions { @@ -109,11 +109,11 @@ func (lr *localResolver) update(u Update) { lr.readyOnce.Do(func() { close(lr.readyc) }) } -func (lr *localResolver) close() error { +func (lr *localResolver[T]) close() error { return errors.Combine(lr.p.Close()) } -func (lr *localResolver) resolve(ctx context.Context) ([]peer.Peer, error) { +func (lr *localResolver[T]) resolve(ctx context.Context) ([]T, error) { if !lr.p.IsOpen() { return nil, ErrClose } @@ -126,7 +126,7 @@ func (lr *localResolver) resolve(ctx context.Context) ([]peer.Peer, error) { lr.mu.RLock() - ps := make([]peer.Peer, 0, len(lr.ps)) + ps := make([]T, 0, len(lr.ps)) for _, p := range lr.ps { ps = append(ps, p) diff --git a/discovery/resolver/sync_resolver_test.go b/discovery/resolver/sync_resolver_test.go index 85f34ca..d077682 100644 --- a/discovery/resolver/sync_resolver_test.go +++ b/discovery/resolver/sync_resolver_test.go @@ -14,7 +14,7 @@ import ( func TestNameResolverWithPeers(t *testing.T) { ctx := context.Background() nr := resolver.SyncResolverFromBuilder( - static.Builder{ + static.Builder[static.Peer]{ "n1": static.PeersFromStrings("foo", "bar"), "n2": static.PeersFromStrings("biz", "buz"), }, @@ -37,7 +37,7 @@ func TestNameResolverWithPeers(t *testing.T) { func TestNameResolverNoPeerNoWait(t *testing.T) { ctx := context.Background() - nr := resolver.SyncResolverFromBuilder(static.Builder{}, true) + nr := resolver.SyncResolverFromBuilder(static.Builder[static.Peer]{}, true) ps, err := nr.ResolveSync(ctx, "n1") @@ -50,7 +50,7 @@ func TestNameResolverNoPeerNoWait(t *testing.T) { func TestNameResolverNoPeerWait(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) - nr := resolver.SyncResolverFromBuilder(static.Builder{}, false) + nr := resolver.SyncResolverFromBuilder(static.Builder[static.Peer]{}, false) defer cancel() From 4fec6245555ef07cfe49163d48658620c1b4fb74 Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Thu, 18 Sep 2025 11:48:01 -0700 Subject: [PATCH 20/21] discovery: Correct forgotten error handling --- discovery/balancer/dialer.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discovery/balancer/dialer.go b/discovery/balancer/dialer.go index 1c5a56f..735d027 100644 --- a/discovery/balancer/dialer.go +++ b/discovery/balancer/dialer.go @@ -57,7 +57,7 @@ func (d *Dialer[T]) Close() error { for _, ld := range d.lds { if err := ld.close(); err != nil { - errs = append(errs) + errs = append(errs, err) } } @@ -103,6 +103,8 @@ func (ld *localDialer[T]) dial(ctx context.Context, network string) (net.Conn, e conn, err := ld.d.dialer().DialContext(ctx, network, p.Addr()) if err != nil { + done(err) + return nil, err } From 94489cc9b427678afe52e60c7ed63ada55aa21f1 Mon Sep 17 00:00:00 2001 From: Alexis Montagne Date: Mon, 22 Sep 2025 10:30:59 -0700 Subject: [PATCH 21/21] go.mod: Update deps and toolchain version --- go.mod | 30 ++--- go.sum | 238 +++++---------------------------- group/map.go | 6 +- group/typed_group.go | 4 +- group/typed_group_test.go | 2 +- syncutil/keyed_singleflight.go | 2 - 6 files changed, 56 insertions(+), 226 deletions(-) diff --git a/go.mod b/go.mod index 8779c58..ecf85a4 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,26 @@ module github.com/upfluence/pkg -go 1.23.0 - -toolchain go1.23.1 +go 1.24.0 require ( - github.com/stretchr/testify v1.8.4 - github.com/upfluence/cfg v0.3.5 - github.com/upfluence/errors v0.2.9 - github.com/upfluence/log v0.0.5 + github.com/stretchr/testify v1.11.1 + github.com/upfluence/cfg v0.3.6 + github.com/upfluence/errors v0.2.15 + github.com/upfluence/log v0.0.6 github.com/upfluence/stats v0.1.9 - github.com/upfluence/thrift v2.6.8+incompatible - golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f - golang.org/x/oauth2 v0.19.0 - golang.org/x/term v0.26.0 - golang.org/x/text v0.20.0 - golang.org/x/time v0.3.0 + github.com/upfluence/thrift v2.6.10+incompatible + golang.org/x/exp v0.0.0-20250911091902-df9299821621 + golang.org/x/oauth2 v0.31.0 + golang.org/x/term v0.35.0 + golang.org/x/text v0.29.0 + golang.org/x/time v0.13.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/getsentry/sentry-go v0.25.0 // indirect + github.com/getsentry/sentry-go v0.35.3 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.27.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + golang.org/x/sys v0.36.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4321f71..7a723b6 100644 --- a/go.sum +++ b/go.sum @@ -1,221 +1,55 @@ -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= -github.com/getsentry/sentry-go v0.9.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws= -github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= -github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/getsentry/sentry-go v0.35.3 h1:u5IJaEqZyPdWqe/hKlBKBBnMTSxB/HenCqF3QLabeds= +github.com/getsentry/sentry-go v0.35.3/go.mod h1:mdL49ixwT2yi57k5eh7mpnDyPybixPzlzEJFu0Z76QA= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= -github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= -github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= -github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= -github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= -github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= -github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= -github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -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/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/upfluence/cfg v0.3.5 h1:NoA7vrzPM2gOso58x0H8kEDdDd9TnBJtds6zXCaieI0= -github.com/upfluence/cfg v0.3.5/go.mod h1:0TzWsboPheOr4WtyZ370j6KuFAkXAS2IqpdVnTYSBSc= -github.com/upfluence/errors v0.0.0-20210413181856-d161314a8a8c/go.mod h1:WWK5iZiYpEHoLYZV+MZSFsHrnYueCUMwsaEEko7aiTU= -github.com/upfluence/errors v0.2.9 h1:aX3Ij5yF7r9c6WdbjxhNiyG2ydrqC2+x3mNCU91poGM= -github.com/upfluence/errors v0.2.9/go.mod h1:XmrnFoB1O343aOni1tqU1wZNQuRhWNDHXa/cqnFzDzE= -github.com/upfluence/log v0.0.5 h1:OR7KY6NKfrqvWfXz1nWHEGLtwP+tw1a5x/6hfPBatHU= -github.com/upfluence/log v0.0.5/go.mod h1:1uMfWKPiN8wDCUtENVcYC0bRrTqMeSyAP5R3P5yWXsc= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/upfluence/cfg v0.3.6 h1:CL5ph7Avn8B5oR6vCoOcQdOMGImNqeXQifmtcQJdUPA= +github.com/upfluence/cfg v0.3.6/go.mod h1:0TzWsboPheOr4WtyZ370j6KuFAkXAS2IqpdVnTYSBSc= +github.com/upfluence/errors v0.2.15 h1:RtX6cPPB/DRRF2/lWIkjht3qA7fPj+9XfDusCQPMvc0= +github.com/upfluence/errors v0.2.15/go.mod h1:F20G28/uHKxrcXwrufkVl7iy/SVozr+6w9Xyg5BEx0k= +github.com/upfluence/log v0.0.6 h1:cG+dq0CXTZTXGknzwZbQEW2QQH6KRjUu9rysdhmhBcA= +github.com/upfluence/log v0.0.6/go.mod h1:pCbKPbgnoF5zqR9JsTgParTIEBvqXsIuaa8eoEVhdPA= github.com/upfluence/stats v0.1.9 h1:lCALzQXll2oMSj04hWCZu15vGVj140yR1V9tN/SclWU= github.com/upfluence/stats v0.1.9/go.mod h1:68ZOq0AWeoZlgdp9M50Vxbtxl6+HCtsMlAcSEQmSr4Y= -github.com/upfluence/thrift v2.6.8+incompatible h1:y8wb0oxdKgPfWW+p62lYZkbks3rIvh/IWGS73RwAQ8Y= -github.com/upfluence/thrift v2.6.8+incompatible/go.mod h1:3uGdmLrgG2WjEd1pvDN5Rnxlui3lAE1cZHtv+7O337E= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= -golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= -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.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -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= +github.com/upfluence/thrift v2.6.10+incompatible h1:AYDe+brIhUkVsySFlRmc9nqPenjUsA2PGANBtSlPJpk= +github.com/upfluence/thrift v2.6.10+incompatible/go.mod h1:3uGdmLrgG2WjEd1pvDN5Rnxlui3lAE1cZHtv+7O337E= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= +golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= +golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= +golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/group/map.go b/group/map.go index ccca831..eaaaa07 100644 --- a/group/map.go +++ b/group/map.go @@ -3,6 +3,8 @@ package group import ( "context" "sync" + + "github.com/upfluence/errors" ) type MapRunner[K comparable, V any] func(context.Context, K) (V, error) @@ -15,13 +17,11 @@ func ExecuteMap[K comparable, V any](g Group, ks []K, fn MapRunner[K, V]) (map[K ) for _, k := range ks { - k := k - g.Do(func(ctx context.Context) error { var v, err = fn(ctx, k) if err != nil { - return err + return errors.WithStack(err) } mu.Lock() diff --git a/group/typed_group.go b/group/typed_group.go index 60b5227..99e1665 100644 --- a/group/typed_group.go +++ b/group/typed_group.go @@ -14,9 +14,9 @@ type TypedGroup[T any] struct { mu sync.Mutex } -func (tg *TypedGroup[T]) Do(fn TypedRunner[T]) { +func (tg *TypedGroup[T]) Do(runner TypedRunner[T]) { tg.Group.Do(func(ctx context.Context) error { - fn, err := fn(ctx) + fn, err := runner(ctx) if err != nil { return err diff --git a/group/typed_group_test.go b/group/typed_group_test.go index 40a59b6..1ce3baf 100644 --- a/group/typed_group_test.go +++ b/group/typed_group_test.go @@ -21,7 +21,7 @@ func TestTypedGroup(t *testing.T) { res, err := g.Wait() if res != 5 { - t.Errorf("Wait() = (%v, _), wanted 4", res) + t.Errorf("Wait() = (%v, _), wanted 5", res) } if err != nil { diff --git a/syncutil/keyed_singleflight.go b/syncutil/keyed_singleflight.go index aa94217..b096231 100644 --- a/syncutil/keyed_singleflight.go +++ b/syncutil/keyed_singleflight.go @@ -145,8 +145,6 @@ func (es executors[K, V]) execute(ctx context.Context, fn func(context.Context, } for _, e := range es { - e := e - tg.Do(func(ctx context.Context) (func(*map[K]V), error) { _, vs, err := e.execute(ctx, fn)