Skip to content

Commit

Permalink
chore: fleshed out the Locator interface
Browse files Browse the repository at this point in the history
  • Loading branch information
johnabass committed Nov 11, 2024
1 parent f294986 commit c2ec12d
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 66 deletions.
6 changes: 3 additions & 3 deletions locator.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type Group string

type locatorNode struct {
group Group
names *Names
names Names
ring ring
}

Expand Down Expand Up @@ -194,7 +194,7 @@ func (l *locator) initialize() {
// newRing creates a hash ring using this locator's configuration
// along with the given service names. This method does not
// require execution under the lock.
func (l *locator) newRing(names *Names) ring {
func (l *locator) newRing(names Names) ring {
return newRing(l.vnodes, l.hash.New64(), names)

Check warning on line 198 in locator.go

View check run for this annotation

Codecov / codecov/patch

locator.go#L197-L198

Added lines #L197 - L198 were not covered by tests
}

Expand Down Expand Up @@ -240,7 +240,7 @@ func (l *locator) Remove(g Group) {
}

func (l *locator) Update(g Group, list ...string) {
var updatedNames *Names
var updatedNames Names
needsUpdate := true

l.lock.RLock()
Expand Down
171 changes: 109 additions & 62 deletions names.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package hashy

import (
"bytes"
"strings"
)

Expand All @@ -10,118 +11,164 @@ func normalizeName(v string) string {
return strings.ToLower(v)

Check warning on line 11 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L10-L11

Added lines #L10 - L11 were not covered by tests
}

// appendName adds a name to a Names set. This function
// returns true if the Names was updated.
func appendName(n *Names, v string) bool {
v = normalizeName(v)
var exists bool
if _, exists = n.m[v]; !exists {
n.l += len(v)
n.m[v] = true
}

Check warning on line 22 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L16-L22

Added lines #L16 - L22 were not covered by tests

return !exists

Check warning on line 24 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L24

Added line #L24 was not covered by tests
}

// appendNames adds several names to a Names set.
func appendNames(n *Names, list ...string) {
for _, v := range list {
appendName(n, v)
}

Check warning on line 31 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L28-L31

Added lines #L28 - L31 were not covered by tests
}

// Names is an immutable set of service names. Names are deduped
// and normalized. The zero value of this type is an empty set.
// Use NewNames to create a non-empty Names. A nil Names is valid,
// and is treated as empty by its methods.
// Use NewNames to create a non-empty Names.
type Names struct {
n map[string]bool
l int // the sum of the lengths of the names. used as a hint for marshaling.
m map[string]bool
}

// NewNames constructs an immutable Names set.
func NewNames(values ...string) *Names {
names := &Names{
n: make(map[string]bool, len(values)),
}

for _, v := range values {
names.n[normalizeName(v)] = true
}

return names
func NewNames(list ...string) (names Names) {
names.m = make(map[string]bool, len(list))
appendNames(&names, list...)
return

Check warning on line 46 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L43-L46

Added lines #L43 - L46 were not covered by tests
}

// Len returns the count of service names in this set.
// If this Names is nil, this method returns zero (0).
func (n *Names) Len() int {
if n == nil {
return 0
}

return len(n.n)
func (n Names) Len() int {
return len(n.m)

Check warning on line 51 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L50-L51

Added lines #L50 - L51 were not covered by tests
}

// Has tests if this set contains the normalized version of a given service name.
// If this Names is nil, this method always returns false.
func (n *Names) Has(v string) bool {
if n == nil {
return false
}

_, has := n.n[normalizeName(v)]
func (n Names) Has(v string) bool {
_, has := n.m[normalizeName(v)]
return has

Check warning on line 57 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L55-L57

Added lines #L55 - L57 were not covered by tests
}

// All provides iteration over each name in this set.
// If this Names is nil, this method does nothing.
func (n *Names) All(f func(string) bool) {
if n != nil {
for name := range n.n {
if !f(name) {
return
}
func (n Names) All(f func(string) bool) {
for name := range n.m {
if !f(name) {
return
}

Check warning on line 65 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L61-L65

Added lines #L61 - L65 were not covered by tests
}
}

// Merge returns a Names set that is the union of this Names
// with the given list. If list is empty, this method returns
// this Names as is. If this Names is nil or has zero length,
// NewNames is used to create a new Names with the given list.
func (n *Names) Merge(list ...string) *Names {
// Merge produces a new Names instance that is the union of this Names and
// the given list. The result is deduped and normalized.
func (n Names) Merge(list ...string) (merged Names) {
switch {
case len(list) == 0:
return n
case n.Len() == 0:
merged = NewNames(list...)

Check warning on line 74 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L71-L74

Added lines #L71 - L74 were not covered by tests

case n == nil || n.Len() == 0:
return NewNames(list...)
case len(list) == 0:
merged = n

Check warning on line 77 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L76-L77

Added lines #L76 - L77 were not covered by tests

default:
merged := make(map[string]bool, n.Len()+len(list))
for name := range n.n {
merged[name] = true
merged = Names{
l: n.l,
m: make(map[string]bool, n.Len()+len(list)),
}

for _, name := range list {
merged[normalizeName(name)] = true
for existing := range n.m {
merged.m[existing] = true
}

Check warning on line 87 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L79-L87

Added lines #L79 - L87 were not covered by tests

return &Names{n: merged}
appendNames(&merged, list...)

Check warning on line 89 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L89

Added line #L89 was not covered by tests
}

return

Check warning on line 92 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L92

Added line #L92 was not covered by tests
}

// Update tests if a list of names would represent an update to
// this set of names. If list represents a different set of names,
// accounting for duplicates, this method returns a distinct names
// instance and true. Otherwise, this method returns this names
// and false.
//
// Names are always immutable. This method does not modify the
// original Names set.
func (n *Names) Update(list ...string) (*Names, bool) {
func (n Names) Update(list ...string) (Names, bool) {
switch {
case len(list) == 0:
return n, false // no update

Check warning on line 103 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L100-L103

Added lines #L100 - L103 were not covered by tests

case n == nil || n.Len() == 0:
case n.Len() == 0:
return NewNames(list...), true // the entire list is the update

Check warning on line 106 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L105-L106

Added lines #L105 - L106 were not covered by tests

default:
// have to account for any duplicates in the updated list
updated := make(map[string]bool, len(list))
sameNames := true
updated := Names{
m: make(map[string]bool, len(list)),
}

subset := true // whether the updated list is a subset of this Names
for _, name := range list {
name = normalizeName(name)
updated[name] = true
if sameNames {
_, sameNames = n.n[name]
if appendName(&updated, name) {
if subset {
_, subset = n.m[name]
}

Check warning on line 119 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L108-L119

Added lines #L108 - L119 were not covered by tests
}
}

if !sameNames || n.Len() != len(updated) {
return &Names{n: updated}, true
if !subset || n.Len() != updated.Len() {
return updated, true
}

Check warning on line 125 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L123-L125

Added lines #L123 - L125 were not covered by tests

return n, false

Check warning on line 127 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L127

Added line #L127 was not covered by tests
}
}

// MarshalJSON marshals this set of names as a JSON array.
func (n *Names) MarshalJSON() ([]byte, error) {
if n == nil {
return []byte{'[', ']'}, nil
}

Check warning on line 135 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L132-L135

Added lines #L132 - L135 were not covered by tests

var b bytes.Buffer
b.Grow(
n.l + 3*len(n.m) + 1, // computes the space necessary for ["name1","name2",...]
)

b.WriteRune('[')
for name := range n.m {
if b.Len() > 1 {
b.WriteRune(',')
}

Check warning on line 146 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L137-L146

Added lines #L137 - L146 were not covered by tests

b.WriteRune('"')
b.WriteString(name)
b.WriteRune('"')

Check warning on line 150 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L148-L150

Added lines #L148 - L150 were not covered by tests
}

b.WriteRune(']')
return b.Bytes(), nil

Check warning on line 154 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L153-L154

Added lines #L153 - L154 were not covered by tests
}

// String returns a string representation of this names set.
func (n *Names) String() string {
if n == nil {
return ""
}

Check warning on line 161 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L158-L161

Added lines #L158 - L161 were not covered by tests

var o strings.Builder
o.Grow(n.l + len(n.m) - 1) // computes the space necessary for name1,name2,...
for name := range n.m {
if o.Len() > 0 {
o.WriteRune(',')
}

Check warning on line 168 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L163-L168

Added lines #L163 - L168 were not covered by tests

o.WriteString(name)

Check warning on line 170 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L170

Added line #L170 was not covered by tests
}

return o.String()

Check warning on line 173 in names.go

View check run for this annotation

Codecov / codecov/patch

names.go#L173

Added line #L173 was not covered by tests
}
2 changes: 1 addition & 1 deletion ring.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (r ring) Swap(i, j int) {
r[i], r[j] = r[j], r[i]

Check warning on line 26 in ring.go

View check run for this annotation

Codecov / codecov/patch

ring.go#L25-L26

Added lines #L25 - L26 were not covered by tests
}

func newRing(vnodes int, hash hash.Hash64, names *Names) (r ring) {
func newRing(vnodes int, hash hash.Hash64, names Names) (r ring) {
var (
nameBuffer bytes.Buffer
prefix = make([]byte, 0, 32) // grab enough that reallocations are unlikely
Expand Down

0 comments on commit c2ec12d

Please sign in to comment.