initial commit.
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b25c15b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*~
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..37ec93a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..566857f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,73 @@
+# groupcache
+
+## Summary
+
+groupcache is a caching and cache-filling library, intended as a
+replacement for memcached in many cases.
+
+For API docs and examples, see http://godoc.org/github.com/golang/groupcache
+
+## Comparison to memcached
+
+### **Like memcached**, groupcache:
+
+ * shards by key to select which peer is responsible for that key
+
+### **Unlike memcached**, groupcache:
+
+ * does not require running a separate set of servers, thus massively
+   reducing deployment/configuration pain.  groupcache is a client
+   library as well as a server.  It connects to its own peers.
+
+ * comes with a cache filling mechanism.  Whereas memcached just says
+   "Sorry, cache miss", often resulting in a thundering herd of
+   database (or whatever) loads from an unbounded number of clients
+   (which has resulted in several fun outages), groupcache coordinates
+   cache fills such that only one load in one process of an entire
+   replicated set of processes populates the cache, then multiplexes
+   the loaded value to all callers.
+
+ * does not support versioned values.  If key "foo" is value "bar",
+   key "foo" must always be "bar".  There are neither cache expiration
+   times, nor explicit cache evictions.  Thus there is also no CAS,
+   nor Increment/Decrement.  This also means that groupcache....
+
+ * ... supports automatic mirroring of super-hot items to multiple
+   processes.  This prevents memcached hot spotting where a machine's
+   CPU and/or NIC are overloaded by very popular keys/values.
+
+ * is currently only available for Go.  It's very unlikely that I
+   (bradfitz@) will port the code to any other language.
+
+## Loading process
+
+In a nutshell, a groupcache lookup of **Get("foo")** looks like:
+
+(On machine #5 of a set of N machines running the same code)
+
+ 1. Is the value of "foo" in local memory because it's super hot?  If so, use it.
+
+ 2. Is the value of "foo" in local memory because peer #5 (the current
+    peer) is the owner of it?  If so, use it.
+
+ 3. Amongst all the peers in my set of N, am I the owner of the key
+    "foo"?  (e.g. does it consistent hash to 5?)  If so, load it.  If
+    other callers come in (via the same process or via RPC requests
+    from peers, they block waiting for the load to finish and get the
+    same answer).  If not, RPC to the peer that's the owner and get
+    the answer.  If the RPC fails, just load it locally (still with
+    local dup suppresion).
+
+## Users
+
+groupcache is in production use by dl.google.com (its original user),
+parts of Blogger, parts of Google Code, parts of Google Fiber, parts
+of Google production monitoring systems, etc.
+
+## Presentations
+
+See http://talks.golang.org/2013/oscon-dl.slide
+
+## Help
+
+Use the golang-nuts mailing list for any discussion or questions.
diff --git a/byteview.go b/byteview.go
new file mode 100644
index 0000000..035a9ee
--- /dev/null
+++ b/byteview.go
@@ -0,0 +1,160 @@
+/*
+Copyright 2012 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 groupcache
+
+import (
+	"bytes"
+	"errors"
+	"io"
+	"strings"
+)
+
+// A ByteView holds an immutable view of bytes.
+// Internally it wraps either a []byte or a string,
+// but that detail is invisible to callers.
+//
+// A ByteView is meant to be used as a value type, not
+// a pointer (like a time.Time).
+type ByteView struct {
+	// If b is non-nil, b is used, else s is used.
+	b []byte
+	s string
+}
+
+// Len returns the view's length.
+func (v ByteView) Len() int {
+	if v.b != nil {
+		return len(v.b)
+	}
+	return len(v.s)
+}
+
+// ByteSlice returns a copy of the data as a byte slice.
+func (v ByteView) ByteSlice() []byte {
+	if v.b != nil {
+		return cloneBytes(v.b)
+	}
+	return []byte(v.s)
+}
+
+// String returns the data as a string, making a copy if necessary.
+func (v ByteView) String() string {
+	if v.b != nil {
+		return string(v.b)
+	}
+	return v.s
+}
+
+// At returns the byte at index i.
+func (v ByteView) At(i int) byte {
+	if v.b != nil {
+		return v.b[i]
+	}
+	return v.s[i]
+}
+
+// Slice slices the view between the provided from and to indices.
+func (v ByteView) Slice(from, to int) ByteView {
+	if v.b != nil {
+		return ByteView{b: v.b[from:to]}
+	}
+	return ByteView{s: v.s[from:to]}
+}
+
+// SliceFrom slices the view from the provided index until the end.
+func (v ByteView) SliceFrom(from int) ByteView {
+	if v.b != nil {
+		return ByteView{b: v.b[from:]}
+	}
+	return ByteView{s: v.s[from:]}
+}
+
+// Copy copies b into dest and returns the number of bytes copied.
+func (v ByteView) Copy(dest []byte) int {
+	if v.b != nil {
+		return copy(dest, v.b)
+	}
+	return copy(dest, v.s)
+}
+
+// Equal returns whether the bytes in b are the same as the bytes in
+// b2.
+func (v ByteView) Equal(b2 ByteView) bool {
+	if b2.b == nil {
+		return v.EqualString(b2.s)
+	}
+	return v.EqualBytes(b2.b)
+}
+
+// EqualString returns whether the bytes in b are the same as the bytes
+// in s.
+func (v ByteView) EqualString(s string) bool {
+	if v.b == nil {
+		return v.s == s
+	}
+	l := v.Len()
+	if len(s) != l {
+		return false
+	}
+	for i, bi := range v.b {
+		if bi != s[i] {
+			return false
+		}
+	}
+	return true
+}
+
+// EqualBytes returns whether the bytes in b are the same as the bytes
+// in b2.
+func (v ByteView) EqualBytes(b2 []byte) bool {
+	if v.b != nil {
+		return bytes.Equal(v.b, b2)
+	}
+	l := v.Len()
+	if len(b2) != l {
+		return false
+	}
+	for i, bi := range b2 {
+		if bi != v.s[i] {
+			return false
+		}
+	}
+	return true
+}
+
+// Reader returns an io.ReadSeeker for the bytes in v.
+func (v ByteView) Reader() io.ReadSeeker {
+	if v.b != nil {
+		return bytes.NewReader(v.b)
+	}
+	return strings.NewReader(v.s)
+}
+
+// ReadAt implements io.ReaderAt on the bytes in v.
+func (v ByteView) ReadAt(p []byte, off int64) (n int, err error) {
+	if off < 0 {
+		return 0, errors.New("view: invalid offset")
+	}
+	if off >= int64(v.Len()) {
+		return 0, io.EOF
+	}
+	n = v.SliceFrom(int(off)).Copy(p)
+	if n < len(p) {
+		err = io.EOF
+	}
+	return
+}
diff --git a/byteview_test.go b/byteview_test.go
new file mode 100644
index 0000000..9ece00f
--- /dev/null
+++ b/byteview_test.go
@@ -0,0 +1,142 @@
+/*
+Copyright 2012 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 groupcache
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"testing"
+)
+
+func TestByteView(t *testing.T) {
+	for _, s := range []string{"", "x", "yy"} {
+		for _, v := range []ByteView{of([]byte(s)), of(s)} {
+			name := fmt.Sprintf("string %q, view %+v", s, v)
+			if v.Len() != len(s) {
+				t.Errorf("%s: Len = %d; want %d", name, v.Len(), len(s))
+			}
+			if v.String() != s {
+				t.Errorf("%s: String = %q; want %q", name, v.String(), s)
+			}
+			var longDest [3]byte
+			if n := v.Copy(longDest[:]); n != len(s) {
+				t.Errorf("%s: long Copy = %d; want %d", name, n, len(s))
+			}
+			var shortDest [1]byte
+			if n := v.Copy(shortDest[:]); n != min(len(s), 1) {
+				t.Errorf("%s: short Copy = %d; want %d", name, n, min(len(s), 1))
+			}
+			if got, err := ioutil.ReadAll(v.Reader()); err != nil || string(got) != s {
+				t.Errorf("%s: Reader = %q, %v; want %q", name, got, err, s)
+			}
+			if got, err := ioutil.ReadAll(io.NewSectionReader(v, 0, int64(len(s)))); err != nil || string(got) != s {
+				t.Errorf("%s: SectionReader of ReaderAt = %q, %v; want %q", name, got, err, s)
+			}
+		}
+	}
+}
+
+// of returns a byte view of the []byte or string in x.
+func of(x interface{}) ByteView {
+	if bytes, ok := x.([]byte); ok {
+		return ByteView{b: bytes}
+	}
+	return ByteView{s: x.(string)}
+}
+
+func TestByteViewEqual(t *testing.T) {
+	tests := []struct {
+		a    interface{} // string or []byte
+		b    interface{} // string or []byte
+		want bool
+	}{
+		{"x", "x", true},
+		{"x", "y", false},
+		{"x", "yy", false},
+		{[]byte("x"), []byte("x"), true},
+		{[]byte("x"), []byte("y"), false},
+		{[]byte("x"), []byte("yy"), false},
+		{[]byte("x"), "x", true},
+		{[]byte("x"), "y", false},
+		{[]byte("x"), "yy", false},
+		{"x", []byte("x"), true},
+		{"x", []byte("y"), false},
+		{"x", []byte("yy"), false},
+	}
+	for i, tt := range tests {
+		va := of(tt.a)
+		if bytes, ok := tt.b.([]byte); ok {
+			if got := va.EqualBytes(bytes); got != tt.want {
+				t.Errorf("%d. EqualBytes = %v; want %v", i, got, tt.want)
+			}
+		} else {
+			if got := va.EqualString(tt.b.(string)); got != tt.want {
+				t.Errorf("%d. EqualString = %v; want %v", i, got, tt.want)
+			}
+		}
+		if got := va.Equal(of(tt.b)); got != tt.want {
+			t.Errorf("%d. Equal = %v; want %v", i, got, tt.want)
+		}
+	}
+}
+
+func TestByteViewSlice(t *testing.T) {
+	tests := []struct {
+		in   string
+		from int
+		to   interface{} // nil to mean the end (SliceFrom); else int
+		want string
+	}{
+		{
+			in:   "abc",
+			from: 1,
+			to:   2,
+			want: "b",
+		},
+		{
+			in:   "abc",
+			from: 1,
+			want: "bc",
+		},
+		{
+			in:   "abc",
+			to:   2,
+			want: "ab",
+		},
+	}
+	for i, tt := range tests {
+		for _, v := range []ByteView{of([]byte(tt.in)), of(tt.in)} {
+			name := fmt.Sprintf("test %d, view %+v", i, v)
+			if tt.to != nil {
+				v = v.Slice(tt.from, tt.to.(int))
+			} else {
+				v = v.SliceFrom(tt.from)
+			}
+			if v.String() != tt.want {
+				t.Errorf("%s: got %q; want %q", name, v.String(), tt.want)
+			}
+		}
+	}
+}
+
+func min(a, b int) int {
+	if a < b {
+		return a
+	}
+	return b
+}
diff --git a/groupcache.go b/groupcache.go
new file mode 100644
index 0000000..ca25fe0
--- /dev/null
+++ b/groupcache.go
@@ -0,0 +1,455 @@
+/*
+Copyright 2012 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 groupcache provides a data loading mechanism with caching
+// and de-duplication that works across a set of peer processes.
+//
+// Each data Get first consults its local cache, otherwise delegates
+// to the requested key's canonical owner, which then checks its cache
+// or finally gets the data.  In the common case, many concurrent
+// cache misses across a set of peers for the same key result in just
+// one cache fill.
+package groupcache
+
+import (
+	"errors"
+	"math/rand"
+	"strconv"
+	"sync"
+	"sync/atomic"
+
+	pb "github.com/golang/groupcache/groupcachepb"
+	"github.com/golang/groupcache/lru"
+	"github.com/golang/groupcache/singleflight"
+)
+
+// A Getter loads data for a key.
+type Getter interface {
+	// Get returns the value identified by key, populating dest.
+	//
+	// The returned data must be unversioned. That is, key must
+	// uniquely describe the loaded data, without an implicit
+	// current time, and without relying on cache expiration
+	// mechanisms.
+	Get(ctx Context, key string, dest Sink) error
+}
+
+// A GetterFunc implements Getter with a function.
+type GetterFunc func(ctx Context, key string, dest Sink) error
+
+func (f GetterFunc) Get(ctx Context, key string, dest Sink) error {
+	return f(ctx, key, dest)
+}
+
+var (
+	mu     sync.RWMutex
+	groups = make(map[string]*Group)
+
+	initPeerServerOnce sync.Once
+	initPeerServer     func()
+)
+
+// GetGroup returns the named group previously created with NewGroup, or
+// nil if there's no such group.
+func GetGroup(name string) *Group {
+	mu.RLock()
+	g := groups[name]
+	mu.RUnlock()
+	return g
+}
+
+// NewGroup creates a coordinated group-aware Getter from a Getter.
+//
+// The returned Getter tries (but does not guarantee) to run only one
+// Get call at once for a given key across an entire set of peer
+// processes. Concurrent callers both in the local process and in
+// other processes receive copies of the answer once the original Get
+// completes.
+//
+// The group name must be unique for each getter.
+func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
+	return newGroup(name, cacheBytes, getter, nil)
+}
+
+// If peers is nil, the peerPicker is called via a sync.Once to initialize it.
+func newGroup(name string, cacheBytes int64, getter Getter, peers PeerPicker) *Group {
+	if getter == nil {
+		panic("nil Getter")
+	}
+	mu.Lock()
+	defer mu.Unlock()
+	initPeerServerOnce.Do(callInitPeerServer)
+	if _, dup := groups[name]; dup {
+		panic("duplicate registration of group " + name)
+	}
+	g := &Group{
+		name:       name,
+		getter:     getter,
+		peers:      peers,
+		cacheBytes: cacheBytes,
+	}
+	if fn := newGroupHook; fn != nil {
+		fn(g)
+	}
+	groups[name] = g
+	return g
+}
+
+// newGroupHook, if non-nil, is called right after a new group is created.
+var newGroupHook func(*Group)
+
+// RegisterNewGroupHook registers a hook that is run each time
+// a group is created.
+func RegisterNewGroupHook(fn func(*Group)) {
+	if newGroupHook != nil {
+		panic("RegisterNewGroupHook called more than once")
+	}
+	newGroupHook = fn
+}
+
+// RegisterServerStart registers a hook that is run when the first
+// group is created.
+func RegisterServerStart(fn func()) {
+	if initPeerServer != nil {
+		panic("RegisterServerStart called more than once")
+	}
+	initPeerServer = fn
+}
+
+func callInitPeerServer() {
+	if initPeerServer != nil {
+		initPeerServer()
+	}
+}
+
+// A Group is a cache namespace and associated data loaded spread over
+// a group of 1 or more machines.
+type Group struct {
+	name       string
+	getter     Getter
+	peersOnce  sync.Once
+	peers      PeerPicker
+	cacheBytes int64 // limit for sum of mainCache and hotCache size
+
+	// mainCache is a cache of the keys for which this process
+	// (amongst its peers) is authorative. That is, this cache
+	// contains keys which consistent hash on to this process's
+	// peer number.
+	mainCache cache
+
+	// hotCache contains keys/values for which this peer is not
+	// authorative (otherwise they would be in mainCache), but
+	// are popular enough to warrant mirroring in this process to
+	// avoid going over the network to fetch from a peer.  Having
+	// a hotCache avoids network hotspotting, where a peer's
+	// network card could become the bottleneck on a popular key.
+	// This cache is used sparingly to maximize the total number
+	// of key/value pairs that can stored globally.
+	hotCache cache
+
+	// loadGroup ensures that each key is only fetched once
+	// (either locally or remotely), regardless of the number of
+	// concurrent callers.
+	loadGroup singleflight.Group
+
+	// Stats are statistics on the group.
+	Stats Stats
+}
+
+// Stats are per-group statistics.
+type Stats struct {
+	Gets           AtomicInt // any Get request, including from peers
+	CacheHits      AtomicInt // either cache was good
+	PeerLoads      AtomicInt // either remote load or remote cache hit (not an error)
+	PeerErrors     AtomicInt
+	Loads          AtomicInt // (gets - cacheHits)
+	LoadsDeduped   AtomicInt // after singleflight
+	LocalLoads     AtomicInt // total good local loads
+	LocalLoadErrs  AtomicInt // total bad local loads
+	ServerRequests AtomicInt // gets that came over the network from peers
+}
+
+// Name returns the name of the group.
+func (g *Group) Name() string {
+	return g.name
+}
+
+func (g *Group) initPeers() {
+	if g.peers == nil {
+		g.peers = getPeers()
+	}
+}
+
+func (g *Group) Get(ctx Context, key string, dest Sink) error {
+	g.peersOnce.Do(g.initPeers)
+	g.Stats.Gets.Add(1)
+	if dest == nil {
+		return errors.New("groupcache: nil dest Sink")
+	}
+	value, cacheHit := g.lookupCache(key)
+
+	if cacheHit {
+		g.Stats.CacheHits.Add(1)
+		return setSinkView(dest, value)
+	}
+
+	// Optimization to avoid double unmarshalling or copying: keep
+	// track of whether the dest was already populated. One caller
+	// (if local) will set this; the losers will not. The common
+	// case will likely be one caller.
+	destPopulated := false
+	value, destPopulated, err := g.load(ctx, key, dest)
+	if err != nil {
+		return err
+	}
+	if destPopulated {
+		return nil
+	}
+	return setSinkView(dest, value)
+}
+
+// load loads key either by invoking the getter locally or by sending it to another machine.
+func (g *Group) load(ctx Context, key string, dest Sink) (value ByteView, destPopulated bool, err error) {
+	g.Stats.Loads.Add(1)
+	viewi, err := g.loadGroup.Do(key, func() (interface{}, error) {
+		g.Stats.LoadsDeduped.Add(1)
+		var value ByteView
+		var err error
+		if peer, ok := g.peers.PickPeer(key); ok {
+			value, err = g.getFromPeer(ctx, peer, key)
+			if err == nil {
+				g.Stats.PeerLoads.Add(1)
+				return value, nil
+			}
+			g.Stats.PeerErrors.Add(1)
+			// TODO(bradfitz): log the peer's error? keep
+			// log of the past few for /groupcachez?  It's
+			// probably boring (normal task movement), so not
+			// worth logging I imagine.
+		}
+		value, err = g.getLocally(ctx, key, dest)
+		if err != nil {
+			g.Stats.LocalLoadErrs.Add(1)
+			return nil, err
+		}
+		g.Stats.LocalLoads.Add(1)
+		destPopulated = true // only one caller of load gets this return value
+		g.populateCache(key, value, &g.mainCache)
+		return value, nil
+	})
+	if err == nil {
+		value = viewi.(ByteView)
+	}
+	return
+}
+
+func (g *Group) getLocally(ctx Context, key string, dest Sink) (ByteView, error) {
+	err := g.getter.Get(ctx, key, dest)
+	if err != nil {
+		return ByteView{}, err
+	}
+	return dest.view()
+}
+
+func (g *Group) getFromPeer(ctx Context, peer ProtoGetter, key string) (ByteView, error) {
+	req := &pb.GetRequest{
+		Group: &g.name,
+		Key:   &key,
+	}
+	res := &pb.GetResponse{}
+	err := peer.Get(ctx, req, res)
+	if err != nil {
+		return ByteView{}, err
+	}
+	value := ByteView{b: res.Value}
+	// TODO(bradfitz): use res.MinuteQps or something smart to
+	// conditionally populate hotCache.  For now just do it some
+	// percentage of the time.
+	if rand.Intn(10) == 0 {
+		g.populateCache(key, value, &g.hotCache)
+	}
+	return value, nil
+}
+
+func (g *Group) lookupCache(key string) (value ByteView, ok bool) {
+	if g.cacheBytes <= 0 {
+		return
+	}
+	value, ok = g.mainCache.get(key)
+	if ok {
+		return
+	}
+	value, ok = g.hotCache.get(key)
+	return
+}
+
+func (g *Group) populateCache(key string, value ByteView, cache *cache) {
+	if g.cacheBytes <= 0 {
+		return
+	}
+	cache.add(key, value)
+
+	// Evict items from cache(s) if necessary.
+	for {
+		mainBytes := g.mainCache.bytes()
+		hotBytes := g.hotCache.bytes()
+		if mainBytes+hotBytes <= g.cacheBytes {
+			return
+		}
+
+		// TODO(bradfitz): this is good-enough-for-now logic.
+		// It should be something based on measurements and/or
+		// respecting the costs of different resources.
+		victim := &g.mainCache
+		if hotBytes > mainBytes/8 {
+			victim = &g.hotCache
+		}
+		victim.removeOldest()
+	}
+}
+
+// CacheType represents a type of cache.
+type CacheType int
+
+const (
+	// The MainCache is the cache for items that this peer is the
+	// owner for.
+	MainCache CacheType = iota + 1
+
+	// The HotCache is the cache for items that seem popular
+	// enough to replicate to this node, even though it's not the
+	// owner.
+	HotCache
+)
+
+// CacheStats returns stats about the provided cache within the group.
+func (g *Group) CacheStats(which CacheType) CacheStats {
+	switch which {
+	case MainCache:
+		return g.mainCache.stats()
+	case HotCache:
+		return g.hotCache.stats()
+	default:
+		return CacheStats{}
+	}
+}
+
+// cache is a wrapper around an *lru.Cache that adds synchronization,
+// makes values always be ByteView, and counts the size of all keys and
+// values.
+type cache struct {
+	mu         sync.RWMutex
+	nbytes     int64 // of all keys and values
+	lru        *lru.Cache
+	nhit, nget int64
+	nevict     int64 // number of evictions
+}
+
+func (c *cache) stats() CacheStats {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	return CacheStats{
+		Bytes:     c.nbytes,
+		Items:     c.itemsLocked(),
+		Gets:      c.nget,
+		Hits:      c.nhit,
+		Evictions: c.nevict,
+	}
+}
+
+func (c *cache) add(key string, value ByteView) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	if c.lru == nil {
+		c.lru = &lru.Cache{
+			OnEvicted: func(key lru.Key, value interface{}) {
+				val := value.(ByteView)
+				c.nbytes -= int64(len(key.(string))) + int64(val.Len())
+				c.nevict++
+			},
+		}
+	}
+	c.lru.Add(key, value)
+	c.nbytes += int64(len(key)) + int64(value.Len())
+}
+
+func (c *cache) get(key string) (value ByteView, ok bool) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	c.nget++
+	if c.lru == nil {
+		return
+	}
+	vi, ok := c.lru.Get(key)
+	if !ok {
+		return
+	}
+	c.nhit++
+	return vi.(ByteView), true
+}
+
+func (c *cache) removeOldest() {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	if c.lru != nil {
+		c.lru.RemoveOldest()
+	}
+}
+
+func (c *cache) bytes() int64 {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	return c.nbytes
+}
+
+func (c *cache) items() int64 {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	return c.itemsLocked()
+}
+
+func (c *cache) itemsLocked() int64 {
+	if c.lru == nil {
+		return 0
+	}
+	return int64(c.lru.Len())
+}
+
+// An AtomicInt is an int64 to be accessed atomically.
+type AtomicInt int64
+
+// Add atomically adds n to i.
+func (i *AtomicInt) Add(n int64) {
+	atomic.AddInt64((*int64)(i), n)
+}
+
+// Get atomically gets the value of i.
+func (i *AtomicInt) Get() int64 {
+	return atomic.LoadInt64((*int64)(i))
+}
+
+func (i *AtomicInt) String() string {
+	return strconv.FormatInt(i.Get(), 10)
+}
+
+// CacheStats are returned by stats accessors on Group.
+type CacheStats struct {
+	Bytes     int64
+	Items     int64
+	Gets      int64
+	Hits      int64
+	Evictions int64
+}
diff --git a/groupcache.pb.go b/groupcache.pb.go
new file mode 100644
index 0000000..db12197
--- /dev/null
+++ b/groupcache.pb.go
@@ -0,0 +1,65 @@
+// Code generated by protoc-gen-go.
+// source: groupcache.proto
+// DO NOT EDIT!
+
+package groupcache
+
+import proto "code.google.com/p/goprotobuf/proto"
+import json "encoding/json"
+import math "math"
+
+// Reference proto, json, and math imports to suppress error if they are not otherwise used.
+var _ = proto.Marshal
+var _ = &json.SyntaxError{}
+var _ = math.Inf
+
+type GetRequest struct {
+	Group            *string `protobuf:"bytes,1,req,name=group" json:"group,omitempty"`
+	Key              *string `protobuf:"bytes,2,req,name=key" json:"key,omitempty"`
+	XXX_unrecognized []byte  `json:"-"`
+}
+
+func (m *GetRequest) Reset()         { *m = GetRequest{} }
+func (m *GetRequest) String() string { return proto.CompactTextString(m) }
+func (*GetRequest) ProtoMessage()    {}
+
+func (m *GetRequest) GetGroup() string {
+	if m != nil && m.Group != nil {
+		return *m.Group
+	}
+	return ""
+}
+
+func (m *GetRequest) GetKey() string {
+	if m != nil && m.Key != nil {
+		return *m.Key
+	}
+	return ""
+}
+
+type GetResponse struct {
+	Value            []byte   `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"`
+	MinuteQps        *float64 `protobuf:"fixed64,2,opt,name=minute_qps" json:"minute_qps,omitempty"`
+	XXX_unrecognized []byte   `json:"-"`
+}
+
+func (m *GetResponse) Reset()         { *m = GetResponse{} }
+func (m *GetResponse) String() string { return proto.CompactTextString(m) }
+func (*GetResponse) ProtoMessage()    {}
+
+func (m *GetResponse) GetValue() []byte {
+	if m != nil {
+		return m.Value
+	}
+	return nil
+}
+
+func (m *GetResponse) GetMinuteQps() float64 {
+	if m != nil && m.MinuteQps != nil {
+		return *m.MinuteQps
+	}
+	return 0
+}
+
+func init() {
+}
diff --git a/groupcache_test.go b/groupcache_test.go
new file mode 100644
index 0000000..2bfe986
--- /dev/null
+++ b/groupcache_test.go
@@ -0,0 +1,367 @@
+/*
+Copyright 2012 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.
+*/
+
+// Tests for groupcache.
+
+package groupcache
+
+import (
+	"errors"
+	"fmt"
+	"hash/crc32"
+	"math/rand"
+	"reflect"
+	"sync"
+	"testing"
+	"time"
+
+	"code.google.com/p/goprotobuf/proto"
+
+	pb "github.com/golang/groupcache/groupcachepb"
+	testpb "github.com/golang/groupcache/testpb"
+)
+
+var (
+	once                    sync.Once
+	stringGroup, protoGroup Getter
+
+	stringc = make(chan string)
+
+	dummyCtx Context
+
+	// cacheFills is the number of times stringGroup or
+	// protoGroup's Getter have been called. Read using the
+	// cacheFills function.
+	cacheFills AtomicInt
+)
+
+const (
+	stringGroupName = "string-group"
+	protoGroupName  = "proto-group"
+	testMessageType = "google3/net/groupcache/go/test_proto.TestMessage"
+	fromChan        = "from-chan"
+	cacheSize       = 1 << 20
+)
+
+func testSetup() {
+	stringGroup = NewGroup(stringGroupName, cacheSize, GetterFunc(func(_ Context, key string, dest Sink) error {
+		if key == fromChan {
+			key = <-stringc
+		}
+		cacheFills.Add(1)
+		return dest.SetString("ECHO:" + key)
+	}))
+
+	protoGroup = NewGroup(protoGroupName, cacheSize, GetterFunc(func(_ Context, key string, dest Sink) error {
+		if key == fromChan {
+			key = <-stringc
+		}
+		cacheFills.Add(1)
+		return dest.SetProto(&testpb.TestMessage{
+			Name: proto.String("ECHO:" + key),
+			City: proto.String("SOME-CITY"),
+		})
+	}))
+}
+
+// tests that a Getter's Get method is only called once with two
+// outstanding callers.  This is the string variant.
+func TestGetDupSuppressString(t *testing.T) {
+	once.Do(testSetup)
+	// Start two getters. The first should block (waiting reading
+	// from stringc) and the second should latch on to the first
+	// one.
+	resc := make(chan string, 2)
+	for i := 0; i < 2; i++ {
+		go func() {
+			var s string
+			if err := stringGroup.Get(dummyCtx, fromChan, StringSink(&s)); err != nil {
+				resc <- "ERROR:" + err.Error()
+				return
+			}
+			resc <- s
+		}()
+	}
+
+	// Wait a bit so both goroutines get merged together via
+	// singleflight.
+	// TODO(bradfitz): decide whether there are any non-offensive
+	// debug/test hooks that could be added to singleflight to
+	// make a sleep here unnecessary.
+	time.Sleep(250 * time.Millisecond)
+
+	// Unblock the first getter, which should unblock the second
+	// as well.
+	stringc <- "foo"
+
+	for i := 0; i < 2; i++ {
+		select {
+		case v := <-resc:
+			if v != "ECHO:foo" {
+				t.Errorf("got %q; want %q", v, "ECHO:foo")
+			}
+		case <-time.After(5 * time.Second):
+			t.Errorf("timeout waiting on getter #%d of 2", i+1)
+		}
+	}
+}
+
+// tests that a Getter's Get method is only called once with two
+// outstanding callers.  This is the proto variant.
+func TestGetDupSuppressProto(t *testing.T) {
+	once.Do(testSetup)
+	// Start two getters. The first should block (waiting reading
+	// from stringc) and the second should latch on to the first
+	// one.
+	resc := make(chan *testpb.TestMessage, 2)
+	for i := 0; i < 2; i++ {
+		go func() {
+			tm := new(testpb.TestMessage)
+			if err := protoGroup.Get(dummyCtx, fromChan, ProtoSink(tm)); err != nil {
+				tm.Name = proto.String("ERROR:" + err.Error())
+			}
+			resc <- tm
+		}()
+	}
+
+	// Wait a bit so both goroutines get merged together via
+	// singleflight.
+	// TODO(bradfitz): decide whether there are any non-offensive
+	// debug/test hooks that could be added to singleflight to
+	// make a sleep here unnecessary.
+	time.Sleep(250 * time.Millisecond)
+
+	// Unblock the first getter, which should unblock the second
+	// as well.
+	stringc <- "Fluffy"
+	want := &testpb.TestMessage{
+		Name: proto.String("ECHO:Fluffy"),
+		City: proto.String("SOME-CITY"),
+	}
+	for i := 0; i < 2; i++ {
+		select {
+		case v := <-resc:
+			if !reflect.DeepEqual(v, want) {
+				t.Errorf(" Got: %v\nWant: %v", proto.CompactTextString(v), proto.CompactTextString(want))
+			}
+		case <-time.After(5 * time.Second):
+			t.Errorf("timeout waiting on getter #%d of 2", i+1)
+		}
+	}
+}
+
+func countFills(f func()) int64 {
+	fills0 := cacheFills.Get()
+	f()
+	return cacheFills.Get() - fills0
+}
+
+func TestCaching(t *testing.T) {
+	once.Do(testSetup)
+	fills := countFills(func() {
+		for i := 0; i < 10; i++ {
+			var s string
+			if err := stringGroup.Get(dummyCtx, "TestCaching-key", StringSink(&s)); err != nil {
+				t.Fatal(err)
+			}
+		}
+	})
+	if fills != 1 {
+		t.Errorf("expected 1 cache fill; got %d", fills)
+	}
+}
+
+func TestCacheEviction(t *testing.T) {
+	once.Do(testSetup)
+	testKey := "TestCacheEviction-key"
+	getTestKey := func() {
+		var res string
+		for i := 0; i < 10; i++ {
+			if err := stringGroup.Get(dummyCtx, testKey, StringSink(&res)); err != nil {
+				t.Fatal(err)
+			}
+		}
+	}
+	fills := countFills(getTestKey)
+	if fills != 1 {
+		t.Fatalf("expected 1 cache fill; got %d", fills)
+	}
+
+	g := stringGroup.(*Group)
+	evict0 := g.mainCache.nevict
+
+	// Trash the cache with other keys.
+	var bytesFlooded int64
+	// cacheSize/len(testKey) is approximate
+	for bytesFlooded < cacheSize+1024 {
+		var res string
+		key := fmt.Sprintf("dummy-key-%d", bytesFlooded)
+		stringGroup.Get(dummyCtx, key, StringSink(&res))
+		bytesFlooded += int64(len(key) + len(res))
+	}
+	evicts := g.mainCache.nevict - evict0
+	if evicts <= 0 {
+		t.Errorf("evicts = %v; want more than 0", evicts)
+	}
+
+	// Test that the key is gone.
+	fills = countFills(getTestKey)
+	if fills != 1 {
+		t.Fatalf("expected 1 cache fill after cache trashing; got %d", fills)
+	}
+}
+
+type fakePeer struct {
+	hits int
+	fail bool
+}
+
+func (p *fakePeer) Get(_ Context, in *pb.GetRequest, out *pb.GetResponse) error {
+	p.hits++
+	if p.fail {
+		return errors.New("simulated error from peer")
+	}
+	out.Value = []byte("got:" + in.GetKey())
+	return nil
+}
+
+type fakePeers []ProtoGetter
+
+func (p fakePeers) PickPeer(key string) (peer ProtoGetter, ok bool) {
+	if len(p) == 0 {
+		return
+	}
+	n := crc32.Checksum([]byte(key), crc32.IEEETable) % uint32(len(p))
+	return p[n], p[n] != nil
+}
+
+// tests that peers (virtual, in-process) are hit, and how much.
+func TestPeers(t *testing.T) {
+	once.Do(testSetup)
+	rand.Seed(123)
+	peer0 := &fakePeer{}
+	peer1 := &fakePeer{}
+	peer2 := &fakePeer{}
+	peerList := fakePeers([]ProtoGetter{peer0, peer1, peer2, nil})
+	const cacheSize = 0 // disabled
+	localHits := 0
+	getter := func(_ Context, key string, dest Sink) error {
+		localHits++
+		return dest.SetString("got:" + key)
+	}
+	testGroup := newGroup("TestPeers-group", cacheSize, GetterFunc(getter), peerList)
+	run := func(name string, n int, wantSummary string) {
+		// Reset counters
+		localHits = 0
+		for _, p := range []*fakePeer{peer0, peer1, peer2} {
+			p.hits = 0
+		}
+
+		for i := 0; i < n; i++ {
+			key := fmt.Sprintf("key-%d", i)
+			want := "got:" + key
+			var got string
+			err := testGroup.Get(dummyCtx, key, StringSink(&got))
+			if err != nil {
+				t.Errorf("%s: error on key %q: %v", name, key, err)
+				continue
+			}
+			if got != want {
+				t.Errorf("%s: for key %q, got %q; want %q", name, key, got, want)
+			}
+		}
+		summary := func() string {
+			return fmt.Sprintf("localHits = %d, peers = %d %d %d", localHits, peer0.hits, peer1.hits, peer2.hits)
+		}
+		if got := summary(); got != wantSummary {
+			t.Errorf("%s: got %q; want %q", name, got, wantSummary)
+		}
+	}
+	resetCacheSize := func(maxBytes int64) {
+		g := testGroup
+		g.cacheBytes = maxBytes
+		g.mainCache = cache{}
+		g.hotCache = cache{}
+	}
+
+	// Base case; peers all up, with no problems.
+	resetCacheSize(1 << 20)
+	run("base", 200, "localHits = 49, peers = 51 49 51")
+
+	// Verify cache was hit.  All localHits are gone, and some of
+	// the peer hits (the ones randomly selected to be maybe hot)
+	run("cached_base", 200, "localHits = 0, peers = 49 47 48")
+	resetCacheSize(0)
+
+	// With one of the peers being down.
+	// TODO(bradfitz): on a peer number being unavailable, the
+	// consistent hashing should maybe keep trying others to
+	// spread the load out. Currently it fails back to local
+	// execution if the first consistent-hash slot is unavailable.
+	peerList[0] = nil
+	run("one_peer_down", 200, "localHits = 100, peers = 0 49 51")
+
+	// Failing peer
+	peerList[0] = peer0
+	peer0.fail = true
+	run("peer0_failing", 200, "localHits = 100, peers = 51 49 51")
+}
+
+func TestTruncatingByteSliceTarget(t *testing.T) {
+	var buf [100]byte
+	s := buf[:]
+	if err := stringGroup.Get(dummyCtx, "short", TruncatingByteSliceSink(&s)); err != nil {
+		t.Fatal(err)
+	}
+	if want := "ECHO:short"; string(s) != want {
+		t.Errorf("short key got %q; want %q", s, want)
+	}
+
+	s = buf[:6]
+	if err := stringGroup.Get(dummyCtx, "truncated", TruncatingByteSliceSink(&s)); err != nil {
+		t.Fatal(err)
+	}
+	if want := "ECHO:t"; string(s) != want {
+		t.Errorf("truncated key got %q; want %q", s, want)
+	}
+}
+
+func TestAllocatingByteSliceTarget(t *testing.T) {
+	var dst []byte
+	sink := AllocatingByteSliceSink(&dst)
+
+	inBytes := []byte("some bytes")
+	sink.SetBytes(inBytes)
+	if want := "some bytes"; string(dst) != want {
+		t.Errorf("SetBytes resulted in %q; want %q", dst, want)
+	}
+	v, err := sink.view()
+	if err != nil {
+		t.Fatalf("view after SetBytes failed: %v", err)
+	}
+	if &inBytes[0] == &dst[0] {
+		t.Error("inBytes and dst share memory")
+	}
+	if &inBytes[0] == &v.b[0] {
+		t.Error("inBytes and view share memory")
+	}
+	if &dst[0] == &v.b[0] {
+		t.Error("dst and view share memory")
+	}
+}
+
+// TODO(bradfitz): port the Google-internal full integration test into here,
+// using HTTP requests instead of our RPC system.
diff --git a/groupcachepb/groupcache.pb.go b/groupcachepb/groupcache.pb.go
new file mode 100644
index 0000000..9da3afc
--- /dev/null
+++ b/groupcachepb/groupcache.pb.go
@@ -0,0 +1,65 @@
+// Code generated by protoc-gen-go.
+// source: groupcache.proto
+// DO NOT EDIT!
+
+package groupcachepb
+
+import proto "code.google.com/p/goprotobuf/proto"
+import json "encoding/json"
+import math "math"
+
+// Reference proto, json, and math imports to suppress error if they are not otherwise used.
+var _ = proto.Marshal
+var _ = &json.SyntaxError{}
+var _ = math.Inf
+
+type GetRequest struct {
+	Group            *string `protobuf:"bytes,1,req,name=group" json:"group,omitempty"`
+	Key              *string `protobuf:"bytes,2,req,name=key" json:"key,omitempty"`
+	XXX_unrecognized []byte  `json:"-"`
+}
+
+func (m *GetRequest) Reset()         { *m = GetRequest{} }
+func (m *GetRequest) String() string { return proto.CompactTextString(m) }
+func (*GetRequest) ProtoMessage()    {}
+
+func (m *GetRequest) GetGroup() string {
+	if m != nil && m.Group != nil {
+		return *m.Group
+	}
+	return ""
+}
+
+func (m *GetRequest) GetKey() string {
+	if m != nil && m.Key != nil {
+		return *m.Key
+	}
+	return ""
+}
+
+type GetResponse struct {
+	Value            []byte   `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"`
+	MinuteQps        *float64 `protobuf:"fixed64,2,opt,name=minute_qps" json:"minute_qps,omitempty"`
+	XXX_unrecognized []byte   `json:"-"`
+}
+
+func (m *GetResponse) Reset()         { *m = GetResponse{} }
+func (m *GetResponse) String() string { return proto.CompactTextString(m) }
+func (*GetResponse) ProtoMessage()    {}
+
+func (m *GetResponse) GetValue() []byte {
+	if m != nil {
+		return m.Value
+	}
+	return nil
+}
+
+func (m *GetResponse) GetMinuteQps() float64 {
+	if m != nil && m.MinuteQps != nil {
+		return *m.MinuteQps
+	}
+	return 0
+}
+
+func init() {
+}
diff --git a/groupcachepb/groupcache.proto b/groupcachepb/groupcache.proto
new file mode 100644
index 0000000..153e851
--- /dev/null
+++ b/groupcachepb/groupcache.proto
@@ -0,0 +1,32 @@
+/*
+Copyright 2012 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 groupcachepb;
+
+message GetRequest {
+  required string group = 1;
+  required string key = 2; // not actually required/guaranteed to be UTF-8
+}
+
+message GetResponse {
+  optional bytes value = 1;
+  optional double minute_qps = 2;
+}
+
+service GroupCache {
+  rpc Get(GetRequest) returns (GetResponse) {
+  };
+}
diff --git a/lru/lru.go b/lru/lru.go
new file mode 100644
index 0000000..cdfe299
--- /dev/null
+++ b/lru/lru.go
@@ -0,0 +1,121 @@
+/*
+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 lru implements an LRU cache.
+package lru
+
+import "container/list"
+
+// Cache is an LRU cache. It is not safe for concurrent access.
+type Cache struct {
+	// MaxEntries is the maximum number of cache entries before
+	// an item is evicted. Zero means no limit.
+	MaxEntries int
+
+	// OnEvicted optionally specificies a callback function to be
+	// executed when an entry is purged from the cache.
+	OnEvicted func(key Key, value interface{})
+
+	ll    *list.List
+	cache map[interface{}]*list.Element
+}
+
+// A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators
+type Key interface{}
+
+type entry struct {
+	key   Key
+	value interface{}
+}
+
+// New creates a new Cache.
+// If maxEntries is zero, the cache has no limit and it's assumed
+// that eviction is done by the caller.
+func New(maxEntries int) *Cache {
+	return &Cache{
+		MaxEntries: maxEntries,
+		ll:         list.New(),
+		cache:      make(map[interface{}]*list.Element),
+	}
+}
+
+// Add adds a value to the cache.
+func (c *Cache) Add(key Key, value interface{}) {
+	if c.cache == nil {
+		c.cache = make(map[interface{}]*list.Element)
+		c.ll = list.New()
+	}
+	if ee, ok := c.cache[key]; ok {
+		c.ll.MoveToFront(ee)
+		ee.Value.(*entry).value = value
+		return
+	}
+	ele := c.ll.PushFront(&entry{key, value})
+	c.cache[key] = ele
+	if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
+		c.RemoveOldest()
+	}
+}
+
+// Get looks up a key's value from the cache.
+func (c *Cache) Get(key Key) (value interface{}, ok bool) {
+	if c.cache == nil {
+		return
+	}
+	if ele, hit := c.cache[key]; hit {
+		c.ll.MoveToFront(ele)
+		return ele.Value.(*entry).value, true
+	}
+	return
+}
+
+// Remove removes the provided key from the cache.
+func (c *Cache) Remove(key Key) {
+	if c.cache == nil {
+		return
+	}
+	if ele, hit := c.cache[key]; hit {
+		c.removeElement(ele)
+	}
+}
+
+// RemoveOldest removes the oldest item from the cache.
+func (c *Cache) RemoveOldest() {
+	if c.cache == nil {
+		return
+	}
+	ele := c.ll.Back()
+	if ele != nil {
+		c.removeElement(ele)
+	}
+}
+
+func (c *Cache) removeElement(e *list.Element) {
+	c.ll.Remove(e)
+	kv := e.Value.(*entry)
+	delete(c.cache, kv.key)
+	if c.OnEvicted != nil {
+		c.OnEvicted(kv.key, kv.value)
+	}
+}
+
+// Len returns the number of items in the cache.
+func (c *Cache) Len() int {
+	if c.cache == nil {
+		return 0
+	}
+	return c.ll.Len()
+}
diff --git a/lru/lru_test.go b/lru/lru_test.go
new file mode 100644
index 0000000..98a2656
--- /dev/null
+++ b/lru/lru_test.go
@@ -0,0 +1,73 @@
+/*
+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 lru
+
+import (
+	"testing"
+)
+
+type simpleStruct struct {
+	int
+	string
+}
+
+type complexStruct struct {
+	int
+	simpleStruct
+}
+
+var getTests = []struct {
+	name       string
+	keyToAdd   interface{}
+	keyToGet   interface{}
+	expectedOk bool
+}{
+	{"string_hit", "myKey", "myKey", true},
+	{"string_miss", "myKey", "nonsense", false},
+	{"simple_struct_hit", simpleStruct{1, "two"}, simpleStruct{1, "two"}, true},
+	{"simeple_struct_miss", simpleStruct{1, "two"}, simpleStruct{0, "noway"}, false},
+	{"complex_struct_hit", complexStruct{1, simpleStruct{2, "three"}},
+		complexStruct{1, simpleStruct{2, "three"}}, true},
+}
+
+func TestGet(t *testing.T) {
+	for _, tt := range getTests {
+		lru := New(0)
+		lru.Add(tt.keyToAdd, 1234)
+		val, ok := lru.Get(tt.keyToGet)
+		if ok != tt.expectedOk {
+			t.Fatalf("%s: cache hit = %v; want %v", tt.name, ok, !ok)
+		} else if ok && val != 1234 {
+			t.Fatalf("%s expected get to return 1234 but got %v", tt.name, val)
+		}
+	}
+}
+
+func TestRemove(t *testing.T) {
+	lru := New(0)
+	lru.Add("myKey", 1234)
+	if val, ok := lru.Get("myKey"); !ok {
+		t.Fatal("TestRemove returned no match")
+	} else if val != 1234 {
+		t.Fatalf("TestRemove failed.  Expected %d, got %v", 1234, val)
+	}
+
+	lru.Remove("myKey")
+	if _, ok := lru.Get("myKey"); ok {
+		t.Fatal("TestRemove returned a removed entry")
+	}
+}
diff --git a/peers.go b/peers.go
new file mode 100644
index 0000000..4644640
--- /dev/null
+++ b/peers.go
@@ -0,0 +1,68 @@
+/*
+Copyright 2012 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.
+*/
+
+// peers.go defines how processes find and communicate with their peers.
+
+package groupcache
+
+import (
+	pb "github.com/golang/groupcache/groupcachepb"
+)
+
+// Context is an opaque value passed through calls to the
+// ProtoGetter. It may be nil if your ProtoGetter implementation does
+// not require a context.
+type Context interface{}
+
+// ProtoGetter is the interface that must be implemented by a peer.
+type ProtoGetter interface {
+	Get(context Context, in *pb.GetRequest, out *pb.GetResponse) error
+}
+
+// PeerPicker is the interface that must be implemented to locate
+// the peer that owns a specific key.
+type PeerPicker interface {
+	PickPeer(key string) (peer ProtoGetter, ok bool)
+}
+
+// NoPeers is an implementation of PeerPicker that never finds a peer.
+type NoPeers struct{}
+
+func (NoPeers) PickPeer(key string) (peer ProtoGetter, ok bool) { return }
+
+var (
+	portPicker func() PeerPicker
+)
+
+// RegisterInitPeers registers the peer initialization function.
+// It is called once, when the first group is created.
+func RegisterPeerPicker(fn func() PeerPicker) {
+	if portPicker != nil {
+		panic("RegisterInitPeers called more than once")
+	}
+	portPicker = fn
+}
+
+func getPeers() PeerPicker {
+	if portPicker == nil {
+		return NoPeers{}
+	}
+	pk := portPicker()
+	if pk == nil {
+		pk = NoPeers{}
+	}
+	return pk
+}
diff --git a/singleflight/singleflight.go b/singleflight/singleflight.go
new file mode 100644
index 0000000..ff2c2ee
--- /dev/null
+++ b/singleflight/singleflight.go
@@ -0,0 +1,64 @@
+/*
+Copyright 2012 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 singleflight provides a duplicate function call suppression
+// mechanism.
+package singleflight
+
+import "sync"
+
+// call is an in-flight or completed Do call
+type call struct {
+	wg  sync.WaitGroup
+	val interface{}
+	err error
+}
+
+// Group represents a class of work and forms a namespace in which
+// units of work can be executed with duplicate suppression.
+type Group struct {
+	mu sync.Mutex       // protects m
+	m  map[string]*call // lazily initialized
+}
+
+// Do executes and returns the results of the given function, making
+// sure that only one execution is in-flight for a given key at a
+// time. If a duplicate comes in, the duplicate caller waits for the
+// original to complete and receives the same results.
+func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
+	g.mu.Lock()
+	if g.m == nil {
+		g.m = make(map[string]*call)
+	}
+	if c, ok := g.m[key]; ok {
+		g.mu.Unlock()
+		c.wg.Wait()
+		return c.val, c.err
+	}
+	c := new(call)
+	c.wg.Add(1)
+	g.m[key] = c
+	g.mu.Unlock()
+
+	c.val, c.err = fn()
+	c.wg.Done()
+
+	g.mu.Lock()
+	delete(g.m, key)
+	g.mu.Unlock()
+
+	return c.val, c.err
+}
diff --git a/singleflight/singleflight_test.go b/singleflight/singleflight_test.go
new file mode 100644
index 0000000..4e5881a
--- /dev/null
+++ b/singleflight/singleflight_test.go
@@ -0,0 +1,85 @@
+/*
+Copyright 2012 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 singleflight
+
+import (
+	"errors"
+	"fmt"
+	"sync"
+	"sync/atomic"
+	"testing"
+	"time"
+)
+
+func TestDo(t *testing.T) {
+	var g Group
+	v, err := g.Do("key", func() (interface{}, error) {
+		return "bar", nil
+	})
+	if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
+		t.Errorf("Do = %v; want %v", got, want)
+	}
+	if err != nil {
+		t.Errorf("Do error = %v", err)
+	}
+}
+
+func TestDoErr(t *testing.T) {
+	var g Group
+	someErr := errors.New("Some error")
+	v, err := g.Do("key", func() (interface{}, error) {
+		return nil, someErr
+	})
+	if err != someErr {
+		t.Errorf("Do error = %v; want someErr", err, someErr)
+	}
+	if v != nil {
+		t.Errorf("unexpected non-nil value %#v", v)
+	}
+}
+
+func TestDoDupSuppress(t *testing.T) {
+	var g Group
+	c := make(chan string)
+	var calls int32
+	fn := func() (interface{}, error) {
+		atomic.AddInt32(&calls, 1)
+		return <-c, nil
+	}
+
+	const n = 10
+	var wg sync.WaitGroup
+	for i := 0; i < n; i++ {
+		wg.Add(1)
+		go func() {
+			v, err := g.Do("key", fn)
+			if err != nil {
+				t.Errorf("Do error: %v", err)
+			}
+			if v.(string) != "bar" {
+				t.Errorf("got %q; want %q", v, "bar")
+			}
+			wg.Done()
+		}()
+	}
+	time.Sleep(100 * time.Millisecond) // let goroutines above block
+	c <- "bar"
+	wg.Wait()
+	if got := atomic.LoadInt32(&calls); got != 1 {
+		t.Errorf("number of calls = %d; want 1", got)
+	}
+}
diff --git a/sinks.go b/sinks.go
new file mode 100644
index 0000000..964d0cc
--- /dev/null
+++ b/sinks.go
@@ -0,0 +1,322 @@
+/*
+Copyright 2012 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 groupcache
+
+import (
+	"errors"
+
+	"code.google.com/p/goprotobuf/proto"
+)
+
+// A Sink receives data from a Get call.
+//
+// Implementation of Getter must call exactly one of the Set methods
+// on success.
+type Sink interface {
+	// SetString sets the value to s.
+	SetString(s string) error
+
+	// SetBytes sets the value to the contents of v.
+	// The caller retains ownership of v.
+	SetBytes(v []byte) error
+
+	// SetProto sets the value to the encoded version of m.
+	// The caller retains ownership of m.
+	SetProto(m proto.Message) error
+
+	// view returns a frozen view of the bytes for caching.
+	view() (ByteView, error)
+}
+
+func cloneBytes(b []byte) []byte {
+	c := make([]byte, len(b))
+	copy(c, b)
+	return c
+}
+
+func setSinkView(s Sink, v ByteView) error {
+	// A viewSetter is a Sink that can also receive its value from
+	// a ByteView. This is a fast path to minimize copies when the
+	// item was already cached locally in memory (where it's
+	// cached as a ByteView)
+	type viewSetter interface {
+		setView(v ByteView) error
+	}
+	if vs, ok := s.(viewSetter); ok {
+		return vs.setView(v)
+	}
+	if v.b != nil {
+		return s.SetBytes(v.b)
+	}
+	return s.SetString(v.s)
+}
+
+// StringSink returns a Sink that populates the provided string pointer.
+func StringSink(sp *string) Sink {
+	return &stringSink{sp: sp}
+}
+
+type stringSink struct {
+	sp *string
+	v  ByteView
+	// TODO(bradfitz): track whether any Sets were called.
+}
+
+func (s *stringSink) view() (ByteView, error) {
+	// TODO(bradfitz): return an error if no Set was called
+	return s.v, nil
+}
+
+func (s *stringSink) SetString(v string) error {
+	s.v.b = nil
+	s.v.s = v
+	*s.sp = v
+	return nil
+}
+
+func (s *stringSink) SetBytes(v []byte) error {
+	return s.SetString(string(v))
+}
+
+func (s *stringSink) SetProto(m proto.Message) error {
+	b, err := proto.Marshal(m)
+	if err != nil {
+		return err
+	}
+	s.v.b = b
+	*s.sp = string(b)
+	return nil
+}
+
+// ByteViewSink returns a Sink that populates a ByteView.
+func ByteViewSink(dst *ByteView) Sink {
+	if dst == nil {
+		panic("nil dst")
+	}
+	return &byteViewSink{dst: dst}
+}
+
+type byteViewSink struct {
+	dst *ByteView
+
+	// if this code ever ends up tracking that at least one set*
+	// method was called, don't make it an error to call set
+	// methods multiple times. Lorry's payload.go does that, and
+	// it makes sense. The comment at the top of this file about
+	// "exactly one of the Set methods" is overly strict. We
+	// really care about at least once (in a handler), but if
+	// multiple handlers fail (or multiple functions in a program
+	// using a Sink), it's okay to re-use the same one.
+}
+
+func (s *byteViewSink) setView(v ByteView) error {
+	*s.dst = v
+	return nil
+}
+
+func (s *byteViewSink) view() (ByteView, error) {
+	return *s.dst, nil
+}
+
+func (s *byteViewSink) SetProto(m proto.Message) error {
+	b, err := proto.Marshal(m)
+	if err != nil {
+		return err
+	}
+	*s.dst = ByteView{b: b}
+	return nil
+}
+
+func (s *byteViewSink) SetBytes(b []byte) error {
+	*s.dst = ByteView{b: cloneBytes(b)}
+	return nil
+}
+
+func (s *byteViewSink) SetString(v string) error {
+	*s.dst = ByteView{s: v}
+	return nil
+}
+
+// ProtoSink returns a sink that unmarshals binary proto values into m.
+func ProtoSink(m proto.Message) Sink {
+	return &protoSink{
+		dst: m,
+	}
+}
+
+type protoSink struct {
+	dst proto.Message // authorative value
+	typ string
+
+	v ByteView // encoded
+}
+
+func (s *protoSink) view() (ByteView, error) {
+	return s.v, nil
+}
+
+func (s *protoSink) SetBytes(b []byte) error {
+	err := proto.Unmarshal(b, s.dst)
+	if err != nil {
+		return err
+	}
+	s.v.b = cloneBytes(b)
+	s.v.s = ""
+	return nil
+}
+
+func (s *protoSink) SetString(v string) error {
+	b := []byte(v)
+	err := proto.Unmarshal(b, s.dst)
+	if err != nil {
+		return err
+	}
+	s.v.b = b
+	s.v.s = ""
+	return nil
+}
+
+func (s *protoSink) SetProto(m proto.Message) error {
+	b, err := proto.Marshal(m)
+	if err != nil {
+		return err
+	}
+	// TODO(bradfitz): optimize for same-task case more and write
+	// right through? would need to document ownership rules at
+	// the same time. but then we could just assign *dst = *m
+	// here. This works for now:
+	err = proto.Unmarshal(b, s.dst)
+	if err != nil {
+		return err
+	}
+	s.v.b = b
+	s.v.s = ""
+	return nil
+}
+
+// AllocatingByteSliceSink returns a Sink that allocates
+// a byte slice to hold the received value and assigns
+// it to *dst. The memory is not retained by groupcache.
+func AllocatingByteSliceSink(dst *[]byte) Sink {
+	return &allocBytesSink{dst: dst}
+}
+
+type allocBytesSink struct {
+	dst *[]byte
+	v   ByteView
+}
+
+func (s *allocBytesSink) view() (ByteView, error) {
+	return s.v, nil
+}
+
+func (s *allocBytesSink) setView(v ByteView) error {
+	if v.b != nil {
+		*s.dst = cloneBytes(v.b)
+	} else {
+		*s.dst = []byte(v.s)
+	}
+	s.v = v
+	return nil
+}
+
+func (s *allocBytesSink) SetProto(m proto.Message) error {
+	b, err := proto.Marshal(m)
+	if err != nil {
+		return err
+	}
+	return s.setBytesOwned(b)
+}
+
+func (s *allocBytesSink) SetBytes(b []byte) error {
+	return s.setBytesOwned(cloneBytes(b))
+}
+
+func (s *allocBytesSink) setBytesOwned(b []byte) error {
+	if s.dst == nil {
+		return errors.New("nil AllocatingByteSliceSink *[]byte dst")
+	}
+	*s.dst = cloneBytes(b) // another copy, protecting the read-only s.v.b view
+	s.v.b = b
+	s.v.s = ""
+	return nil
+}
+
+func (s *allocBytesSink) SetString(v string) error {
+	if s.dst == nil {
+		return errors.New("nil AllocatingByteSliceSink *[]byte dst")
+	}
+	*s.dst = []byte(v)
+	s.v.b = nil
+	s.v.s = v
+	return nil
+}
+
+// TruncatingByteSliceSink returns a Sink that writes up to len(*dst)
+// bytes to *dst. If more bytes are available, they're silently
+// truncated. If fewer bytes are available than len(*dst), *dst
+// is shrunk to fit the number of bytes available.
+func TruncatingByteSliceSink(dst *[]byte) Sink {
+	return &truncBytesSink{dst: dst}
+}
+
+type truncBytesSink struct {
+	dst *[]byte
+	v   ByteView
+}
+
+func (s *truncBytesSink) view() (ByteView, error) {
+	return s.v, nil
+}
+
+func (s *truncBytesSink) SetProto(m proto.Message) error {
+	b, err := proto.Marshal(m)
+	if err != nil {
+		return err
+	}
+	return s.setBytesOwned(b)
+}
+
+func (s *truncBytesSink) SetBytes(b []byte) error {
+	return s.setBytesOwned(cloneBytes(b))
+}
+
+func (s *truncBytesSink) setBytesOwned(b []byte) error {
+	if s.dst == nil {
+		return errors.New("nil TruncatingByteSliceSink *[]byte dst")
+	}
+	n := copy(*s.dst, b)
+	if n < len(*s.dst) {
+		*s.dst = (*s.dst)[:n]
+	}
+	s.v.b = b
+	s.v.s = ""
+	return nil
+}
+
+func (s *truncBytesSink) SetString(v string) error {
+	if s.dst == nil {
+		return errors.New("nil TruncatingByteSliceSink *[]byte dst")
+	}
+	n := copy(*s.dst, v)
+	if n < len(*s.dst) {
+		*s.dst = (*s.dst)[:n]
+	}
+	s.v.b = nil
+	s.v.s = v
+	return nil
+}
diff --git a/testpb/test.pb.go b/testpb/test.pb.go
new file mode 100644
index 0000000..ec666b0
--- /dev/null
+++ b/testpb/test.pb.go
@@ -0,0 +1,235 @@
+// Code generated by protoc-gen-go.
+// source: test.proto
+// DO NOT EDIT!
+
+package testpb
+
+import proto "code.google.com/p/goprotobuf/proto"
+import json "encoding/json"
+import math "math"
+
+// Reference proto, json, and math imports to suppress error if they are not otherwise used.
+var _ = proto.Marshal
+var _ = &json.SyntaxError{}
+var _ = math.Inf
+
+type TestMessage struct {
+	Name             *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
+	City             *string `protobuf:"bytes,2,opt,name=city" json:"city,omitempty"`
+	XXX_unrecognized []byte  `json:"-"`
+}
+
+func (m *TestMessage) Reset()         { *m = TestMessage{} }
+func (m *TestMessage) String() string { return proto.CompactTextString(m) }
+func (*TestMessage) ProtoMessage()    {}
+
+func (m *TestMessage) GetName() string {
+	if m != nil && m.Name != nil {
+		return *m.Name
+	}
+	return ""
+}
+
+func (m *TestMessage) GetCity() string {
+	if m != nil && m.City != nil {
+		return *m.City
+	}
+	return ""
+}
+
+type TestRequest struct {
+	Lower            *string `protobuf:"bytes,1,req,name=lower" json:"lower,omitempty"`
+	RepeatCount      *int32  `protobuf:"varint,2,opt,name=repeat_count,def=1" json:"repeat_count,omitempty"`
+	XXX_unrecognized []byte  `json:"-"`
+}
+
+func (m *TestRequest) Reset()         { *m = TestRequest{} }
+func (m *TestRequest) String() string { return proto.CompactTextString(m) }
+func (*TestRequest) ProtoMessage()    {}
+
+const Default_TestRequest_RepeatCount int32 = 1
+
+func (m *TestRequest) GetLower() string {
+	if m != nil && m.Lower != nil {
+		return *m.Lower
+	}
+	return ""
+}
+
+func (m *TestRequest) GetRepeatCount() int32 {
+	if m != nil && m.RepeatCount != nil {
+		return *m.RepeatCount
+	}
+	return Default_TestRequest_RepeatCount
+}
+
+type TestResponse struct {
+	Value            *string `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"`
+	XXX_unrecognized []byte  `json:"-"`
+}
+
+func (m *TestResponse) Reset()         { *m = TestResponse{} }
+func (m *TestResponse) String() string { return proto.CompactTextString(m) }
+func (*TestResponse) ProtoMessage()    {}
+
+func (m *TestResponse) GetValue() string {
+	if m != nil && m.Value != nil {
+		return *m.Value
+	}
+	return ""
+}
+
+type CacheStats struct {
+	Items            *int64 `protobuf:"varint,1,opt,name=items" json:"items,omitempty"`
+	Bytes            *int64 `protobuf:"varint,2,opt,name=bytes" json:"bytes,omitempty"`
+	Gets             *int64 `protobuf:"varint,3,opt,name=gets" json:"gets,omitempty"`
+	Hits             *int64 `protobuf:"varint,4,opt,name=hits" json:"hits,omitempty"`
+	Evicts           *int64 `protobuf:"varint,5,opt,name=evicts" json:"evicts,omitempty"`
+	XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *CacheStats) Reset()         { *m = CacheStats{} }
+func (m *CacheStats) String() string { return proto.CompactTextString(m) }
+func (*CacheStats) ProtoMessage()    {}
+
+func (m *CacheStats) GetItems() int64 {
+	if m != nil && m.Items != nil {
+		return *m.Items
+	}
+	return 0
+}
+
+func (m *CacheStats) GetBytes() int64 {
+	if m != nil && m.Bytes != nil {
+		return *m.Bytes
+	}
+	return 0
+}
+
+func (m *CacheStats) GetGets() int64 {
+	if m != nil && m.Gets != nil {
+		return *m.Gets
+	}
+	return 0
+}
+
+func (m *CacheStats) GetHits() int64 {
+	if m != nil && m.Hits != nil {
+		return *m.Hits
+	}
+	return 0
+}
+
+func (m *CacheStats) GetEvicts() int64 {
+	if m != nil && m.Evicts != nil {
+		return *m.Evicts
+	}
+	return 0
+}
+
+type StatsResponse struct {
+	Gets             *int64      `protobuf:"varint,1,opt,name=gets" json:"gets,omitempty"`
+	CacheHits        *int64      `protobuf:"varint,12,opt,name=cache_hits" json:"cache_hits,omitempty"`
+	Fills            *int64      `protobuf:"varint,2,opt,name=fills" json:"fills,omitempty"`
+	TotalAlloc       *uint64     `protobuf:"varint,3,opt,name=total_alloc" json:"total_alloc,omitempty"`
+	MainCache        *CacheStats `protobuf:"bytes,4,opt,name=main_cache" json:"main_cache,omitempty"`
+	HotCache         *CacheStats `protobuf:"bytes,5,opt,name=hot_cache" json:"hot_cache,omitempty"`
+	ServerIn         *int64      `protobuf:"varint,6,opt,name=server_in" json:"server_in,omitempty"`
+	Loads            *int64      `protobuf:"varint,8,opt,name=loads" json:"loads,omitempty"`
+	PeerLoads        *int64      `protobuf:"varint,9,opt,name=peer_loads" json:"peer_loads,omitempty"`
+	PeerErrors       *int64      `protobuf:"varint,10,opt,name=peer_errors" json:"peer_errors,omitempty"`
+	LocalLoads       *int64      `protobuf:"varint,11,opt,name=local_loads" json:"local_loads,omitempty"`
+	XXX_unrecognized []byte      `json:"-"`
+}
+
+func (m *StatsResponse) Reset()         { *m = StatsResponse{} }
+func (m *StatsResponse) String() string { return proto.CompactTextString(m) }
+func (*StatsResponse) ProtoMessage()    {}
+
+func (m *StatsResponse) GetGets() int64 {
+	if m != nil && m.Gets != nil {
+		return *m.Gets
+	}
+	return 0
+}
+
+func (m *StatsResponse) GetCacheHits() int64 {
+	if m != nil && m.CacheHits != nil {
+		return *m.CacheHits
+	}
+	return 0
+}
+
+func (m *StatsResponse) GetFills() int64 {
+	if m != nil && m.Fills != nil {
+		return *m.Fills
+	}
+	return 0
+}
+
+func (m *StatsResponse) GetTotalAlloc() uint64 {
+	if m != nil && m.TotalAlloc != nil {
+		return *m.TotalAlloc
+	}
+	return 0
+}
+
+func (m *StatsResponse) GetMainCache() *CacheStats {
+	if m != nil {
+		return m.MainCache
+	}
+	return nil
+}
+
+func (m *StatsResponse) GetHotCache() *CacheStats {
+	if m != nil {
+		return m.HotCache
+	}
+	return nil
+}
+
+func (m *StatsResponse) GetServerIn() int64 {
+	if m != nil && m.ServerIn != nil {
+		return *m.ServerIn
+	}
+	return 0
+}
+
+func (m *StatsResponse) GetLoads() int64 {
+	if m != nil && m.Loads != nil {
+		return *m.Loads
+	}
+	return 0
+}
+
+func (m *StatsResponse) GetPeerLoads() int64 {
+	if m != nil && m.PeerLoads != nil {
+		return *m.PeerLoads
+	}
+	return 0
+}
+
+func (m *StatsResponse) GetPeerErrors() int64 {
+	if m != nil && m.PeerErrors != nil {
+		return *m.PeerErrors
+	}
+	return 0
+}
+
+func (m *StatsResponse) GetLocalLoads() int64 {
+	if m != nil && m.LocalLoads != nil {
+		return *m.LocalLoads
+	}
+	return 0
+}
+
+type Empty struct {
+	XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *Empty) Reset()         { *m = Empty{} }
+func (m *Empty) String() string { return proto.CompactTextString(m) }
+func (*Empty) ProtoMessage()    {}
+
+func init() {
+}
diff --git a/testpb/test.proto b/testpb/test.proto
new file mode 100644
index 0000000..7292dce
--- /dev/null
+++ b/testpb/test.proto
@@ -0,0 +1,61 @@
+/*
+Copyright 2012 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 testpb;
+
+message TestMessage {
+  optional string name = 1;
+  optional string city = 2;
+}
+
+message TestRequest {
+  required string lower = 1; // to be returned upper case
+  optional int32 repeat_count = 2 [default = 1]; // .. this many times
+}
+
+message TestResponse {
+  optional string value = 1;
+}
+
+message CacheStats {
+  optional int64 items = 1;
+  optional int64 bytes = 2;
+  optional int64 gets = 3;
+  optional int64 hits = 4;
+  optional int64 evicts = 5;
+}
+
+message StatsResponse {
+  optional int64 gets = 1;
+  optional int64 cache_hits = 12;
+  optional int64 fills = 2;
+  optional uint64 total_alloc = 3;
+  optional CacheStats main_cache = 4;
+  optional CacheStats hot_cache = 5;
+  optional int64 server_in = 6;
+  optional int64 loads = 8;
+  optional int64 peer_loads = 9;
+  optional int64 peer_errors = 10;
+  optional int64 local_loads = 11;
+}
+
+message Empty {}
+
+service GroupCacheTest {
+  rpc InitPeers(Empty) returns (Empty) {};
+  rpc Get(TestRequest) returns (TestResponse) {};
+  rpc GetStats(Empty) returns (StatsResponse) {};
+}