diff --git a/consistent.go b/consistent.go index 7bd648f..212af49 100644 --- a/consistent.go +++ b/consistent.go @@ -244,6 +244,9 @@ func DefaultReplicaKey(name string, idx int) []byte { } func (c *Consistent) add(member Member) { + if _, exists := c.members[member.String()]; exists { + return + } for i := 0; i < c.config.ReplicationFactor; i++ { key := c.config.ReplicaKey(member.String(), i) h := c.hasher.Sum64(key) @@ -300,6 +303,7 @@ func (c *Consistent) Remove(name string) { if len(c.members) == 0 { // consistent hash ring is empty now. Reset the partition table. c.partitions = make(map[int]Member) + c.loads = make(map[string]float64) return } c.distributePartitions() diff --git a/consistent_test.go b/consistent_test.go index cffc04f..35886cd 100644 --- a/consistent_test.go +++ b/consistent_test.go @@ -565,3 +565,31 @@ func TestConsistentConcurrentAccess(t *testing.T) { t.Fatalf("Expected seed.olric, Got: %s", members[0].String()) } } + +func TestRemoveDuplicateInitialMember(t *testing.T) { + c := New([]Member{testMember("a"), testMember("a"), testMember("b")}, newConfig()) + c.Remove("a") + if len(c.GetMembers()) != 1 { + t.Fatal("expected one remaining member") + } + for part := 0; part < newConfig().PartitionCount; part++ { + if owner := c.GetPartitionOwner(part); owner == nil || owner.String() != "b" { + t.Fatalf("partition %d owner = %v, want b", part, owner) + } + } +} + +func TestRemoveLastMemberClearsLoadDistribution(t *testing.T) { + c := New([]Member{testMember("a")}, newConfig()) + c.Remove("a") + if loads := c.LoadDistribution(); len(loads) != 0 { + t.Fatalf("removed member still has load: %v", loads) + } + if c.AverageLoad() != 0 { + t.Fatal("empty ring has nonzero average load") + } + c.Add(testMember("b")) + if loads := c.LoadDistribution(); len(loads) != 1 || loads["b"] != float64(newConfig().PartitionCount) { + t.Fatalf("unexpected load after repopulating ring: %v", loads) + } +}