Merge pull request #15 from Obvious/master

PeerPicker uses ring hash to more consistently choose peers
diff --git a/consistenthash/consistenthash.go b/consistenthash/consistenthash.go
new file mode 100644
index 0000000..455ffd5
--- /dev/null
+++ b/consistenthash/consistenthash.go
@@ -0,0 +1,81 @@
+/*
+Copyright 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package consistenthash provides an implementation of a ring hash.
+package consistenthash
+
+import (
+	"hash/crc32"
+	"sort"
+	"strconv"
+)
+
+type Hash func(data []byte) uint32
+
+type Map struct {
+	hash     Hash
+	replicas int
+	keys     []int // Sorted
+	hashMap  map[int]string
+}
+
+func New(replicas int, fn Hash) *Map {
+	m := &Map{
+		replicas: replicas,
+		hash:     fn,
+		hashMap:  make(map[int]string),
+	}
+	if m.hash == nil {
+		m.hash = crc32.ChecksumIEEE
+	}
+	return m
+}
+
+// Returns true if there are no items available.
+func (m *Map) IsEmpty() bool {
+	return len(m.keys) == 0
+}
+
+// Adds some keys to the hash.
+func (m *Map) Add(keys ...string) {
+	for _, key := range keys {
+		for i := 0; i < m.replicas; i++ {
+			hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
+			m.keys = append(m.keys, hash)
+			m.hashMap[hash] = key
+		}
+	}
+	sort.Ints(m.keys)
+}
+
+// Gets the closest item in the hash to the provided key.
+func (m *Map) Get(key string) string {
+	if m.IsEmpty() {
+		return ""
+	}
+
+	hash := int(m.hash([]byte(key)))
+
+	// Linear search for appropriate replica.
+	for _, v := range m.keys {
+		if v >= hash {
+			return m.hashMap[v]
+		}
+	}
+
+	// Means we have cycled back to the first replica.
+	return m.hashMap[m.keys[0]]
+}
diff --git a/consistenthash/consistenthash_test.go b/consistenthash/consistenthash_test.go
new file mode 100644
index 0000000..a1b96db
--- /dev/null
+++ b/consistenthash/consistenthash_test.go
@@ -0,0 +1,86 @@
+/*
+Copyright 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package consistenthash
+
+import (
+	"strconv"
+	"testing"
+)
+
+func TestHashing(t *testing.T) {
+
+	// Override the hash function to return easier to reason about values. Assumes
+	// the keys can be converted to an integer.
+	hash := New(3, func(key []byte) uint32 {
+		i, err := strconv.Atoi(string(key))
+		if err != nil {
+			panic(err)
+		}
+		return uint32(i)
+	})
+
+	// Given the above hash function, this will give replicas with "hashes":
+	// 2, 4, 6, 12, 14, 16, 22, 24, 26
+	hash.Add("6", "4", "2")
+
+	testCases := map[string]string{
+		"2":  "2",
+		"11": "2",
+		"23": "4",
+		"27": "2",
+	}
+
+	for k, v := range testCases {
+		if hash.Get(k) != v {
+			t.Errorf("Asking for %s, should have yielded %s", k, v)
+		}
+	}
+
+	// Adds 8, 18, 28
+	hash.Add("8")
+
+	// 27 should now map to 8.
+	testCases["27"] = "8"
+
+	for k, v := range testCases {
+		if hash.Get(k) != v {
+			t.Errorf("Asking for %s, should have yielded %s", k, v)
+		}
+	}
+
+}
+
+func TestConsistency(t *testing.T) {
+	hash1 := New(1, nil)
+	hash2 := New(1, nil)
+
+	hash1.Add("Bill", "Bob", "Bonny")
+	hash2.Add("Bob", "Bonny", "Bill")
+
+	if hash1.Get("Ben") != hash2.Get("Ben") {
+		t.Errorf("Fetching 'Ben' from both hashes should be the same")
+	}
+
+	hash2.Add("Becky", "Ben", "Bobby")
+
+	if hash1.Get("Ben") != hash2.Get("Ben") ||
+		hash1.Get("Bob") != hash2.Get("Bob") ||
+		hash1.Get("Bonny") != hash2.Get("Bonny") {
+		t.Errorf("Direct matches should always return the same entry")
+	}
+
+}
diff --git a/http.go b/http.go
index b131ed9..af329b5 100644
--- a/http.go
+++ b/http.go
@@ -18,7 +18,6 @@
 
 import (
 	"fmt"
-	"hash/crc32"
 	"io/ioutil"
 	"net/http"
 	"net/url"
@@ -26,13 +25,16 @@
 	"sync"
 
 	"code.google.com/p/goprotobuf/proto"
-
+	"github.com/golang/groupcache/consistenthash"
 	pb "github.com/golang/groupcache/groupcachepb"
 )
 
 // TODO: make this configurable?
 const defaultBasePath = "/_groupcache/"
 
+// TODO: make this configurable as well.
+const defaultReplicas = 3
+
 // HTTPPool implements PeerPicker for a pool of HTTP peers.
 type HTTPPool struct {
 	// Context optionally specifies a context for the server to use when it
@@ -52,7 +54,7 @@
 	self string
 
 	mu    sync.Mutex
-	peers []string
+	peers *consistenthash.Map
 }
 
 var httpPoolMade bool
@@ -67,7 +69,7 @@
 		panic("groupcache: NewHTTPPool must be called only once")
 	}
 	httpPoolMade = true
-	p := &HTTPPool{basePath: defaultBasePath, self: self}
+	p := &HTTPPool{basePath: defaultBasePath, self: self, peers: consistenthash.New(defaultReplicas, nil)}
 	RegisterPeerPicker(func() PeerPicker { return p })
 	http.Handle(defaultBasePath, p)
 	return p
@@ -79,22 +81,17 @@
 func (p *HTTPPool) Set(peers ...string) {
 	p.mu.Lock()
 	defer p.mu.Unlock()
-	p.peers = append([]string{}, peers...)
+	p.peers = consistenthash.New(defaultReplicas, nil)
+	p.peers.Add(peers...)
 }
 
 func (p *HTTPPool) PickPeer(key string) (ProtoGetter, bool) {
-	// TODO: make checksum implementation pluggable
-	h := crc32.Checksum([]byte(key), crc32.IEEETable)
 	p.mu.Lock()
 	defer p.mu.Unlock()
-	if len(p.peers) == 0 {
+	if p.peers.IsEmpty() {
 		return nil, false
 	}
-	n := int(h)
-	if n < 0 {
-		n *= -1
-	}
-	if peer := p.peers[n%len(p.peers)]; peer != p.self {
+	if peer := p.peers.Get(key); peer != p.self {
 		// TODO: pre-build a slice of *httpGetter when Set()
 		// is called to avoid these two allocations.
 		return &httpGetter{p.Transport, peer + p.basePath}, true
diff --git a/http_test.go b/http_test.go
index 279bcbf..b42edd7 100644
--- a/http_test.go
+++ b/http_test.go
@@ -84,7 +84,7 @@
 
 	// Dummy getter function. Gets should go to children only.
 	// The only time this process will handle a get is when the
-	// children can't be contacted for seome reason.
+	// children can't be contacted for some reason.
 	getter := GetterFunc(func(ctx Context, key string, dest Sink) error {
 		return errors.New("parent getter called; something's wrong")
 	})