From 824e92f2ad783e8530394a10de0b2d0af39a397f Mon Sep 17 00:00:00 2001 From: tmarshall Date: Mon, 15 Nov 2021 16:52:05 -0500 Subject: [PATCH 1/2] Make InstanceQueryOptions public to allow for custom predicates --- net.go | 28 ++++++++++++++-------------- net_test.go | 13 +++++++------ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/net.go b/net.go index 25205d7..53b245a 100644 --- a/net.go +++ b/net.go @@ -129,7 +129,7 @@ func instanceCount(apps []*Application) int { return count } -type instanceQueryOptions struct { +type InstanceQueryOptions struct { // predicate guides filtering, indicating whether to retain an instance when it returns true or // drop it when it returns false. predicate func(*Instance) bool @@ -140,9 +140,9 @@ type instanceQueryOptions struct { // InstanceQueryOption is a customization supplied to instance query functions like // GetInstancesByVIPAddress to tailor the set of instances returned. -type InstanceQueryOption func(*instanceQueryOptions) error +type InstanceQueryOption func(*InstanceQueryOptions) error -func retainIfStatusIs(status StatusType, o *instanceQueryOptions) { +func retainIfStatusIs(status StatusType, o *InstanceQueryOptions) { if prev := o.predicate; prev != nil { o.predicate = func(instance *Instance) bool { return prev(instance) || instance.Status == status @@ -158,7 +158,7 @@ func retainIfStatusIs(status StatusType, o *instanceQueryOptions) { // // Supplying multiple options produced by this function applies their logical disjunction. func WithStatus(status StatusType) InstanceQueryOption { - return func(o *instanceQueryOptions) error { + return func(o *InstanceQueryOptions) error { if len(status) == 0 { return errors.New("invalid instance status") } @@ -171,14 +171,14 @@ func WithStatus(status StatusType) InstanceQueryOption { // // Combining this function with the options produced by WithStatus applies their logical // disjunction. -func ThatAreUp(o *instanceQueryOptions) error { +func ThatAreUp(o *InstanceQueryOptions) error { retainIfStatusIs(UP, o) return nil } // Shuffled requests randomizing the order of the sequence of instances returned, using the default // shared rand.Source. -func Shuffled(o *instanceQueryOptions) error { +func Shuffled(o *InstanceQueryOptions) error { o.intn = rand.Intn return nil } @@ -186,7 +186,7 @@ func Shuffled(o *instanceQueryOptions) error { // ShuffledWith requests randomizing the order of the sequence of instances returned, using the // supplied source of random numbers. func ShuffledWith(r *rand.Rand) InstanceQueryOption { - return func(o *instanceQueryOptions) error { + return func(o *InstanceQueryOptions) error { o.intn = r.Intn return nil } @@ -315,7 +315,7 @@ func filterInstancesInApps(apps []*Application, pred func(*Instance) bool) []*In } } -func (e *EurekaConnection) getInstancesByVIPAddress(addr string, secure bool, opts instanceQueryOptions) ([]*Instance, error) { +func (e *EurekaConnection) getInstancesByVIPAddress(addr string, secure bool, opts InstanceQueryOptions) ([]*Instance, error) { var slug string if secure { slug = EurekaURLSlugs["InstancesBySecureVIPAddress"] @@ -365,19 +365,19 @@ func (e *EurekaConnection) getInstancesByVIPAddress(addr string, secure bool, op return instances, nil } -func mergeInstanceQueryOptions(defaults instanceQueryOptions, opts []InstanceQueryOption) (instanceQueryOptions, error) { +func mergeInstanceQueryOptions(defaults InstanceQueryOptions, opts []InstanceQueryOption) (InstanceQueryOptions, error) { for _, o := range opts { if o != nil { if err := o(&defaults); err != nil { - return instanceQueryOptions{}, err + return InstanceQueryOptions{}, err } } } return defaults, nil } -func collectInstanceQueryOptions(opts []InstanceQueryOption) (instanceQueryOptions, error) { - return mergeInstanceQueryOptions(instanceQueryOptions{}, opts) +func collectInstanceQueryOptions(opts []InstanceQueryOption) (InstanceQueryOptions, error) { + return mergeInstanceQueryOptions(InstanceQueryOptions{}, opts) } // GetInstancesByVIPAddress returns the set of instances registered with the given VIP address, @@ -435,7 +435,7 @@ func scheduleInstanceUpdates(d time.Duration, produce func() ([]*Instance, error return c } -func (e *EurekaConnection) scheduleVIPAddressUpdates(addr string, secure bool, await bool, done <-chan struct{}, opts instanceQueryOptions) <-chan InstanceSetUpdate { +func (e *EurekaConnection) scheduleVIPAddressUpdates(addr string, secure bool, await bool, done <-chan struct{}, opts InstanceQueryOptions) <-chan InstanceSetUpdate { produce := func() ([]*Instance, error) { return e.getInstancesByVIPAddress(addr, secure, opts) } @@ -549,7 +549,7 @@ func (e *EurekaConnection) newInstanceSetSourceFor(produce func() ([]*Instance, return s } -func (e *EurekaConnection) newInstanceSetSourceForVIPAddress(addr string, secure bool, await bool, opts instanceQueryOptions) *InstanceSetSource { +func (e *EurekaConnection) newInstanceSetSourceForVIPAddress(addr string, secure bool, await bool, opts InstanceQueryOptions) *InstanceSetSource { produce := func() ([]*Instance, error) { return e.getInstancesByVIPAddress(addr, secure, opts) } diff --git a/net_test.go b/net_test.go index 226e8c5..02946e3 100644 --- a/net_test.go +++ b/net_test.go @@ -10,7 +10,7 @@ import ( ) func instancePredicateFrom(t *testing.T, opts ...InstanceQueryOption) func(*Instance) bool { - var mergedOptions instanceQueryOptions + var mergedOptions InstanceQueryOptions for _, o := range opts { if err := o(&mergedOptions); err != nil { t.Fatal(err) @@ -44,7 +44,7 @@ func (s *countingSource) Reset() { func TestInstanceQueryOptions(t *testing.T) { Convey("A status predicate", t, func() { Convey("mandates a nonempty status", func() { - var opts instanceQueryOptions + var opts InstanceQueryOptions err := WithStatus("")(&opts) So(err, ShouldNotBeNil) So(opts.predicate, ShouldBeNil) @@ -53,7 +53,7 @@ func TestInstanceQueryOptions(t *testing.T) { return pred(&Instance{Status: status}) } Convey("matches a single status", func() { - var opts instanceQueryOptions + var opts InstanceQueryOptions desiredStatus := UNKNOWN err := WithStatus(desiredStatus)(&opts) So(err, ShouldBeNil) @@ -66,7 +66,7 @@ func TestInstanceQueryOptions(t *testing.T) { } }) Convey("matches a set of states", func() { - var opts instanceQueryOptions + var opts InstanceQueryOptions desiredStates := []StatusType{DOWN, OUTOFSERVICE} for _, status := range desiredStates { err := WithStatus(status)(&opts) @@ -85,7 +85,7 @@ func TestInstanceQueryOptions(t *testing.T) { }) Convey("A shuffling directive", t, func() { Convey("using the global Rand instance", func() { - var opts instanceQueryOptions + var opts InstanceQueryOptions err := Shuffled(&opts) So(err, ShouldBeNil) So(opts.intn, ShouldNotBeNil) @@ -93,7 +93,7 @@ func TestInstanceQueryOptions(t *testing.T) { }) Convey("using a specific Rand instance", func() { source := countingSource{} - var opts instanceQueryOptions + var opts InstanceQueryOptions err := ShuffledWith(rand.New(&source))(&opts) So(err, ShouldBeNil) So(opts.intn, ShouldNotBeNil) @@ -213,6 +213,7 @@ func TestFilterInstancesInApps(t *testing.T) { }) }) } + // Preclude compiler optimization eliding the filter procedure. var filterBenchmarkResult []*Instance From 2814244c0aaa35b0339db38b2d22c8392b56e51f Mon Sep 17 00:00:00 2001 From: tmarshall Date: Mon, 15 Nov 2021 17:14:07 -0500 Subject: [PATCH 2/2] Make InstanceQueryOptions.Predicate and Intn public. --- net.go | 34 +++++++++++++++++----------------- net_test.go | 22 +++++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/net.go b/net.go index 53b245a..778c2df 100644 --- a/net.go +++ b/net.go @@ -130,12 +130,12 @@ func instanceCount(apps []*Application) int { } type InstanceQueryOptions struct { - // predicate guides filtering, indicating whether to retain an instance when it returns true or + // Predicate guides filtering, indicating whether to retain an instance when it returns true or // drop it when it returns false. - predicate func(*Instance) bool - // intn behaves like the rand.Rand.Intn function, aiding in randomizing the order of the result + Predicate func(*Instance) bool + // Intn behaves like the rand.Rand.Intn function, aiding in randomizing the order of the result // sequence when non-nil. - intn func(int) int + Intn func(int) int } // InstanceQueryOption is a customization supplied to instance query functions like @@ -143,12 +143,12 @@ type InstanceQueryOptions struct { type InstanceQueryOption func(*InstanceQueryOptions) error func retainIfStatusIs(status StatusType, o *InstanceQueryOptions) { - if prev := o.predicate; prev != nil { - o.predicate = func(instance *Instance) bool { + if prev := o.Predicate; prev != nil { + o.Predicate = func(instance *Instance) bool { return prev(instance) || instance.Status == status } } else { - o.predicate = func(instance *Instance) bool { + o.Predicate = func(instance *Instance) bool { return instance.Status == status } } @@ -179,7 +179,7 @@ func ThatAreUp(o *InstanceQueryOptions) error { // Shuffled requests randomizing the order of the sequence of instances returned, using the default // shared rand.Source. func Shuffled(o *InstanceQueryOptions) error { - o.intn = rand.Intn + o.Intn = rand.Intn return nil } @@ -187,7 +187,7 @@ func Shuffled(o *InstanceQueryOptions) error { // supplied source of random numbers. func ShuffledWith(r *rand.Rand) InstanceQueryOption { return func(o *InstanceQueryOptions) error { - o.intn = r.Intn + o.Intn = r.Intn return nil } } @@ -208,14 +208,14 @@ func shuffleInstances(instances []*Instance, intn func(int) int) { } // filterInstances returns a filtered subset of the supplied sequence of instances, retaining only those -// instances for which the supplied predicate function returns true. It returns the retained instances in +// instances for which the supplied Predicate function returns true. It returns the retained instances in // the same order the occurred in the input sequence. Note that the returned slice may share storage with // the input sequence. // // The filtering algorithm is arguably baroque, in the interest of efficiency: namely, eliminating // allocation and copying when we can avoid it. We only need to allocate and copy elements of the // input sequence when the result sequence contains at least two nonadjacent subsequences of the -// input sequence. That is, if the predicate is, say, retaining only instances with status "UP", we +// input sequence. That is, if the Predicate is, say, retaining only instances with status "UP", we // can avoid copying elements and instead return a subsequence of the input sequence in the // following cases (where "U" indicates an instance with status "UP", "d" with status "DOWN"): // @@ -265,7 +265,7 @@ func shuffleInstances(instances []*Instance, intn func(int) int) { // Continue collecting any remaining retained elements into the array. // Return the populated subsequence of the array. // -// The algorithm evaluates the predicate exactly once for each element of the input sequence. +// The algorithm evaluates the Predicate exactly once for each element of the input sequence. func filterInstances(instances []*Instance, pred func(*Instance) bool) []*Instance { for firstBegin, instance := range instances { if !pred(instance) { @@ -344,7 +344,7 @@ func (e *EurekaConnection) getInstancesByVIPAddress(addr string, secure bool, op return nil, err } var instances []*Instance - if pred := opts.predicate; pred != nil { + if pred := opts.Predicate; pred != nil { instances = filterInstancesInApps(r.Applications, pred) } else { switch len(r.Applications) { @@ -359,7 +359,7 @@ func (e *EurekaConnection) getInstancesByVIPAddress(addr string, secure bool, op } } } - if intn := opts.intn; intn != nil { + if intn := opts.Intn; intn != nil { shuffleInstances(instances, intn) } return instances, nil @@ -467,8 +467,8 @@ func (e *EurekaConnection) makeInstanceProducerForApp(name string, opts []Instan if err != nil { return nil, err } - predicate := options.predicate - intn := options.intn + predicate := options.Predicate + intn := options.Intn return func() ([]*Instance, error) { app, err := e.GetApp(name) if err != nil { @@ -520,7 +520,7 @@ func (e *EurekaConnection) newInstanceSetSourceFor(produce func() ([]*Instance, } // NB: If an application contained no instances, such that it either lacked the "instance" field // entirely or had it present but with a "null" value, or none of the present instances - // satisfied the filtering predicate, then it's possible that the slice returned by + // satisfied the filtering Predicate, then it's possible that the slice returned by // getInstancesByVIPAddress (or similar) will be nil. Make it possible to discern when we've // received at least one update in Latest by never storing a nil value for a successful update. if await { diff --git a/net_test.go b/net_test.go index 02946e3..e08aafe 100644 --- a/net_test.go +++ b/net_test.go @@ -16,10 +16,10 @@ func instancePredicateFrom(t *testing.T, opts ...InstanceQueryOption) func(*Inst t.Fatal(err) } } - if pred := mergedOptions.predicate; pred != nil { + if pred := mergedOptions.Predicate; pred != nil { return pred } - t.Fatal("no predicate available") + t.Fatal("no Predicate available") panic("unreachable") } @@ -42,12 +42,12 @@ func (s *countingSource) Reset() { } func TestInstanceQueryOptions(t *testing.T) { - Convey("A status predicate", t, func() { + Convey("A status Predicate", t, func() { Convey("mandates a nonempty status", func() { var opts InstanceQueryOptions err := WithStatus("")(&opts) So(err, ShouldNotBeNil) - So(opts.predicate, ShouldBeNil) + So(opts.Predicate, ShouldBeNil) }) matchesStatus := func(pred func(*Instance) bool, status StatusType) bool { return pred(&Instance{Status: status}) @@ -57,7 +57,7 @@ func TestInstanceQueryOptions(t *testing.T) { desiredStatus := UNKNOWN err := WithStatus(desiredStatus)(&opts) So(err, ShouldBeNil) - pred := opts.predicate + pred := opts.Predicate So(pred, ShouldNotBeNil) So(matchesStatus(pred, desiredStatus), ShouldBeTrue) for _, status := range []StatusType{UP, DOWN, STARTING, OUTOFSERVICE} { @@ -72,7 +72,7 @@ func TestInstanceQueryOptions(t *testing.T) { err := WithStatus(status)(&opts) So(err, ShouldBeNil) } - pred := opts.predicate + pred := opts.Predicate So(pred, ShouldNotBeNil) for _, status := range desiredStates { So(matchesStatus(pred, status), ShouldBeTrue) @@ -88,24 +88,24 @@ func TestInstanceQueryOptions(t *testing.T) { var opts InstanceQueryOptions err := Shuffled(&opts) So(err, ShouldBeNil) - So(opts.intn, ShouldNotBeNil) - So(opts.intn(1), ShouldEqual, 0) + So(opts.Intn, ShouldNotBeNil) + So(opts.Intn(1), ShouldEqual, 0) }) Convey("using a specific Rand instance", func() { source := countingSource{} var opts InstanceQueryOptions err := ShuffledWith(rand.New(&source))(&opts) So(err, ShouldBeNil) - So(opts.intn, ShouldNotBeNil) + So(opts.Intn, ShouldNotBeNil) So(source.callCount, ShouldEqual, 0) - So(opts.intn(2), ShouldEqual, 0) + So(opts.Intn(2), ShouldEqual, 0) So(source.callCount, ShouldEqual, 1) }) }) } func TestFilterInstancesInApps(t *testing.T) { - Convey("A predicate should preserve only those instances", t, func() { + Convey("A Predicate should preserve only those instances", t, func() { Convey("with status UP", func() { areUp := instancePredicateFrom(t, ThatAreUp) Convey("from an empty set of applications", func() {