OSDN Git Service

add groupcache to vendor
authorpaladz <453256728@qq.com>
Wed, 1 Nov 2017 08:51:55 +0000 (16:51 +0800)
committerpaladz <453256728@qq.com>
Wed, 1 Nov 2017 08:51:55 +0000 (16:51 +0800)
21 files changed:
vendor/github.com/golang/groupcache/.gitignore [new file with mode: 0644]
vendor/github.com/golang/groupcache/LICENSE [new file with mode: 0644]
vendor/github.com/golang/groupcache/README.md [new file with mode: 0644]
vendor/github.com/golang/groupcache/byteview.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/byteview_test.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/consistenthash/consistenthash.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/consistenthash/consistenthash_test.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/groupcache.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/groupcache_test.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/groupcachepb/groupcache.pb.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/groupcachepb/groupcache.proto [new file with mode: 0644]
vendor/github.com/golang/groupcache/http.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/http_test.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/lru/lru.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/lru/lru_test.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/peers.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/singleflight/singleflight.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/singleflight/singleflight_test.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/sinks.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/testpb/test.pb.go [new file with mode: 0644]
vendor/github.com/golang/groupcache/testpb/test.proto [new file with mode: 0644]

diff --git a/vendor/github.com/golang/groupcache/.gitignore b/vendor/github.com/golang/groupcache/.gitignore
new file mode 100644 (file)
index 0000000..b25c15b
--- /dev/null
@@ -0,0 +1 @@
+*~
diff --git a/vendor/github.com/golang/groupcache/LICENSE b/vendor/github.com/golang/groupcache/LICENSE
new file mode 100644 (file)
index 0000000..37ec93a
--- /dev/null
@@ -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/vendor/github.com/golang/groupcache/README.md b/vendor/github.com/golang/groupcache/README.md
new file mode 100644 (file)
index 0000000..70c29da
--- /dev/null
@@ -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 suppression).
+
+## 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/vendor/github.com/golang/groupcache/byteview.go b/vendor/github.com/golang/groupcache/byteview.go
new file mode 100644 (file)
index 0000000..a2c2c49
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+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
+}
+
+// WriteTo implements io.WriterTo on the bytes in v.
+func (v ByteView) WriteTo(w io.Writer) (n int64, err error) {
+       var m int
+       if v.b != nil {
+               m, err = w.Write(v.b)
+       } else {
+               m, err = io.WriteString(w, v.s)
+       }
+       if err == nil && m < v.Len() {
+               err = io.ErrShortWrite
+       }
+       n = int64(m)
+       return
+}
diff --git a/vendor/github.com/golang/groupcache/byteview_test.go b/vendor/github.com/golang/groupcache/byteview_test.go
new file mode 100644 (file)
index 0000000..a09757a
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+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"
+       "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)
+                       }
+                       var dest bytes.Buffer
+                       if _, err := v.WriteTo(&dest); err != nil || !bytes.Equal(dest.Bytes(), []byte(s)) {
+                               t.Errorf("%s: WriteTo = %q, %v; want %q", name, dest.Bytes(), 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/vendor/github.com/golang/groupcache/consistenthash/consistenthash.go b/vendor/github.com/golang/groupcache/consistenthash/consistenthash.go
new file mode 100644 (file)
index 0000000..a9c56f0
--- /dev/null
@@ -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)))
+
+       // Binary search for appropriate replica.
+       idx := sort.Search(len(m.keys), func(i int) bool { return m.keys[i] >= hash })
+
+       // Means we have cycled back to the first replica.
+       if idx == len(m.keys) {
+               idx = 0
+       }
+
+       return m.hashMap[m.keys[idx]]
+}
diff --git a/vendor/github.com/golang/groupcache/consistenthash/consistenthash_test.go b/vendor/github.com/golang/groupcache/consistenthash/consistenthash_test.go
new file mode 100644 (file)
index 0000000..1a37fd7
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+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 (
+       "fmt"
+       "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")
+       }
+
+}
+
+func BenchmarkGet8(b *testing.B)   { benchmarkGet(b, 8) }
+func BenchmarkGet32(b *testing.B)  { benchmarkGet(b, 32) }
+func BenchmarkGet128(b *testing.B) { benchmarkGet(b, 128) }
+func BenchmarkGet512(b *testing.B) { benchmarkGet(b, 512) }
+
+func benchmarkGet(b *testing.B, shards int) {
+
+       hash := New(50, nil)
+
+       var buckets []string
+       for i := 0; i < shards; i++ {
+               buckets = append(buckets, fmt.Sprintf("shard-%d", i))
+       }
+
+       hash.Add(buckets...)
+
+       b.ResetTimer()
+
+       for i := 0; i < b.N; i++ {
+               hash.Get(buckets[i&(shards-1)])
+       }
+}
diff --git a/vendor/github.com/golang/groupcache/groupcache.go b/vendor/github.com/golang/groupcache/groupcache.go
new file mode 100644 (file)
index 0000000..316ca49
--- /dev/null
@@ -0,0 +1,491 @@
+/*
+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,
+               loadGroup:  &singleflight.Group{},
+       }
+       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 authoritative. 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
+       // authoritative (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 be 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 flightGroup
+
+       _ int32 // force Stats to be 8-byte aligned on 32-bit platforms
+
+       // Stats are statistics on the group.
+       Stats Stats
+}
+
+// flightGroup is defined as an interface which flightgroup.Group
+// satisfies.  We define this so that we may test with an alternate
+// implementation.
+type flightGroup interface {
+       // Done is called when Do is done.
+       Do(key string, fn func() (interface{}, error)) (interface{}, error)
+}
+
+// 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(g.name)
+       }
+}
+
+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) {
+               // Check the cache again because singleflight can only dedup calls
+               // that overlap concurrently.  It's possible for 2 concurrent
+               // requests to miss the cache, resulting in 2 load() calls.  An
+               // unfortunate goroutine scheduling would result in this callback
+               // being run twice, serially.  If we don't check the cache again,
+               // cache.nbytes would be incremented below even though there will
+               // be only one entry for this key.
+               //
+               // Consider the following serialized event ordering for two
+               // goroutines in which this callback gets called twice for hte
+               // same key:
+               // 1: Get("key")
+               // 2: Get("key")
+               // 1: lookupCache("key")
+               // 2: lookupCache("key")
+               // 1: load("key")
+               // 2: load("key")
+               // 1: loadGroup.Do("key", fn)
+               // 1: fn()
+               // 2: loadGroup.Do("key", fn)
+               // 2: fn()
+               if value, cacheHit := g.lookupCache(key); cacheHit {
+                       g.Stats.CacheHits.Add(1)
+                       return value, nil
+               }
+               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/vendor/github.com/golang/groupcache/groupcache_test.go b/vendor/github.com/golang/groupcache/groupcache_test.go
new file mode 100644 (file)
index 0000000..ea05cac
--- /dev/null
@@ -0,0 +1,456 @@
+/*
+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"
+       "unsafe"
+
+       "github.com/golang/protobuf/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")
+       }
+}
+
+// orderedFlightGroup allows the caller to force the schedule of when
+// orig.Do will be called.  This is useful to serialize calls such
+// that singleflight cannot dedup them.
+type orderedFlightGroup struct {
+       mu     sync.Mutex
+       stage1 chan bool
+       stage2 chan bool
+       orig   flightGroup
+}
+
+func (g *orderedFlightGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
+       <-g.stage1
+       <-g.stage2
+       g.mu.Lock()
+       defer g.mu.Unlock()
+       return g.orig.Do(key, fn)
+}
+
+// TestNoDedup tests invariants on the cache size when singleflight is
+// unable to dedup calls.
+func TestNoDedup(t *testing.T) {
+       const testkey = "testkey"
+       const testval = "testval"
+       g := newGroup("testgroup", 1024, GetterFunc(func(_ Context, key string, dest Sink) error {
+               return dest.SetString(testval)
+       }), nil)
+
+       orderedGroup := &orderedFlightGroup{
+               stage1: make(chan bool),
+               stage2: make(chan bool),
+               orig:   g.loadGroup,
+       }
+       // Replace loadGroup with our wrapper so we can control when
+       // loadGroup.Do is entered for each concurrent request.
+       g.loadGroup = orderedGroup
+
+       // Issue two idential requests concurrently.  Since the cache is
+       // empty, it will miss.  Both will enter load(), but we will only
+       // allow one at a time to enter singleflight.Do, so the callback
+       // function will be called twice.
+       resc := make(chan string, 2)
+       for i := 0; i < 2; i++ {
+               go func() {
+                       var s string
+                       if err := g.Get(dummyCtx, testkey, StringSink(&s)); err != nil {
+                               resc <- "ERROR:" + err.Error()
+                               return
+                       }
+                       resc <- s
+               }()
+       }
+
+       // Ensure both goroutines have entered the Do routine.  This implies
+       // both concurrent requests have checked the cache, found it empty,
+       // and called load().
+       orderedGroup.stage1 <- true
+       orderedGroup.stage1 <- true
+       orderedGroup.stage2 <- true
+       orderedGroup.stage2 <- true
+
+       for i := 0; i < 2; i++ {
+               if s := <-resc; s != testval {
+                       t.Errorf("result is %s want %s", s, testval)
+               }
+       }
+
+       const wantItems = 1
+       if g.mainCache.items() != wantItems {
+               t.Errorf("mainCache has %d items, want %d", g.mainCache.items(), wantItems)
+       }
+
+       // If the singleflight callback doesn't double-check the cache again
+       // upon entry, we would increment nbytes twice but the entry would
+       // only be in the cache once.
+       const wantBytes = int64(len(testkey) + len(testval))
+       if g.mainCache.nbytes != wantBytes {
+               t.Errorf("cache has %d bytes, want %d", g.mainCache.nbytes, wantBytes)
+       }
+}
+
+func TestGroupStatsAlignment(t *testing.T) {
+       var g Group
+       off := unsafe.Offsetof(g.Stats)
+       if off%8 != 0 {
+               t.Fatal("Stats structure is not 8-byte aligned.")
+       }
+}
+
+// TODO(bradfitz): port the Google-internal full integration test into here,
+// using HTTP requests instead of our RPC system.
diff --git a/vendor/github.com/golang/groupcache/groupcachepb/groupcache.pb.go b/vendor/github.com/golang/groupcache/groupcachepb/groupcache.pb.go
new file mode 100644 (file)
index 0000000..520d1ee
--- /dev/null
@@ -0,0 +1,65 @@
+// Code generated by protoc-gen-go.
+// source: groupcache.proto
+// DO NOT EDIT!
+
+package groupcachepb
+
+import proto "github.com/golang/protobuf/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/vendor/github.com/golang/groupcache/groupcachepb/groupcache.proto b/vendor/github.com/golang/groupcache/groupcachepb/groupcache.proto
new file mode 100644 (file)
index 0000000..b5bdff9
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+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.
+*/
+
+syntax = "proto2";
+
+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/vendor/github.com/golang/groupcache/http.go b/vendor/github.com/golang/groupcache/http.go
new file mode 100644 (file)
index 0000000..f37467a
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+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 groupcache
+
+import (
+       "bytes"
+       "fmt"
+       "io"
+       "net/http"
+       "net/url"
+       "strings"
+       "sync"
+
+       "github.com/golang/groupcache/consistenthash"
+       pb "github.com/golang/groupcache/groupcachepb"
+       "github.com/golang/protobuf/proto"
+)
+
+const defaultBasePath = "/_groupcache/"
+
+const defaultReplicas = 50
+
+// HTTPPool implements PeerPicker for a pool of HTTP peers.
+type HTTPPool struct {
+       // Context optionally specifies a context for the server to use when it
+       // receives a request.
+       // If nil, the server uses a nil Context.
+       Context func(*http.Request) Context
+
+       // Transport optionally specifies an http.RoundTripper for the client
+       // to use when it makes a request.
+       // If nil, the client uses http.DefaultTransport.
+       Transport func(Context) http.RoundTripper
+
+       // this peer's base URL, e.g. "https://example.net:8000"
+       self string
+
+       // opts specifies the options.
+       opts HTTPPoolOptions
+
+       mu          sync.Mutex // guards peers and httpGetters
+       peers       *consistenthash.Map
+       httpGetters map[string]*httpGetter // keyed by e.g. "http://10.0.0.2:8008"
+}
+
+// HTTPPoolOptions are the configurations of a HTTPPool.
+type HTTPPoolOptions struct {
+       // BasePath specifies the HTTP path that will serve groupcache requests.
+       // If blank, it defaults to "/_groupcache/".
+       BasePath string
+
+       // Replicas specifies the number of key replicas on the consistent hash.
+       // If blank, it defaults to 50.
+       Replicas int
+
+       // HashFn specifies the hash function of the consistent hash.
+       // If blank, it defaults to crc32.ChecksumIEEE.
+       HashFn consistenthash.Hash
+}
+
+// NewHTTPPool initializes an HTTP pool of peers, and registers itself as a PeerPicker.
+// For convenience, it also registers itself as an http.Handler with http.DefaultServeMux.
+// The self argument should be a valid base URL that points to the current server,
+// for example "http://example.net:8000".
+func NewHTTPPool(self string) *HTTPPool {
+       p := NewHTTPPoolOpts(self, nil)
+       http.Handle(p.opts.BasePath, p)
+       return p
+}
+
+var httpPoolMade bool
+
+// NewHTTPPoolOpts initializes an HTTP pool of peers with the given options.
+// Unlike NewHTTPPool, this function does not register the created pool as an HTTP handler.
+// The returned *HTTPPool implements http.Handler and must be registered using http.Handle.
+func NewHTTPPoolOpts(self string, o *HTTPPoolOptions) *HTTPPool {
+       if httpPoolMade {
+               panic("groupcache: NewHTTPPool must be called only once")
+       }
+       httpPoolMade = true
+
+       p := &HTTPPool{
+               self:        self,
+               httpGetters: make(map[string]*httpGetter),
+       }
+       if o != nil {
+               p.opts = *o
+       }
+       if p.opts.BasePath == "" {
+               p.opts.BasePath = defaultBasePath
+       }
+       if p.opts.Replicas == 0 {
+               p.opts.Replicas = defaultReplicas
+       }
+       p.peers = consistenthash.New(p.opts.Replicas, p.opts.HashFn)
+
+       RegisterPeerPicker(func() PeerPicker { return p })
+       return p
+}
+
+// Set updates the pool's list of peers.
+// Each peer value should be a valid base URL,
+// for example "http://example.net:8000".
+func (p *HTTPPool) Set(peers ...string) {
+       p.mu.Lock()
+       defer p.mu.Unlock()
+       p.peers = consistenthash.New(p.opts.Replicas, p.opts.HashFn)
+       p.peers.Add(peers...)
+       p.httpGetters = make(map[string]*httpGetter, len(peers))
+       for _, peer := range peers {
+               p.httpGetters[peer] = &httpGetter{transport: p.Transport, baseURL: peer + p.opts.BasePath}
+       }
+}
+
+func (p *HTTPPool) PickPeer(key string) (ProtoGetter, bool) {
+       p.mu.Lock()
+       defer p.mu.Unlock()
+       if p.peers.IsEmpty() {
+               return nil, false
+       }
+       if peer := p.peers.Get(key); peer != p.self {
+               return p.httpGetters[peer], true
+       }
+       return nil, false
+}
+
+func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+       // Parse request.
+       if !strings.HasPrefix(r.URL.Path, p.opts.BasePath) {
+               panic("HTTPPool serving unexpected path: " + r.URL.Path)
+       }
+       parts := strings.SplitN(r.URL.Path[len(p.opts.BasePath):], "/", 2)
+       if len(parts) != 2 {
+               http.Error(w, "bad request", http.StatusBadRequest)
+               return
+       }
+       groupName := parts[0]
+       key := parts[1]
+
+       // Fetch the value for this group/key.
+       group := GetGroup(groupName)
+       if group == nil {
+               http.Error(w, "no such group: "+groupName, http.StatusNotFound)
+               return
+       }
+       var ctx Context
+       if p.Context != nil {
+               ctx = p.Context(r)
+       }
+
+       group.Stats.ServerRequests.Add(1)
+       var value []byte
+       err := group.Get(ctx, key, AllocatingByteSliceSink(&value))
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+
+       // Write the value to the response body as a proto message.
+       body, err := proto.Marshal(&pb.GetResponse{Value: value})
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+       w.Header().Set("Content-Type", "application/x-protobuf")
+       w.Write(body)
+}
+
+type httpGetter struct {
+       transport func(Context) http.RoundTripper
+       baseURL   string
+}
+
+var bufferPool = sync.Pool{
+       New: func() interface{} { return new(bytes.Buffer) },
+}
+
+func (h *httpGetter) Get(context Context, in *pb.GetRequest, out *pb.GetResponse) error {
+       u := fmt.Sprintf(
+               "%v%v/%v",
+               h.baseURL,
+               url.QueryEscape(in.GetGroup()),
+               url.QueryEscape(in.GetKey()),
+       )
+       req, err := http.NewRequest("GET", u, nil)
+       if err != nil {
+               return err
+       }
+       tr := http.DefaultTransport
+       if h.transport != nil {
+               tr = h.transport(context)
+       }
+       res, err := tr.RoundTrip(req)
+       if err != nil {
+               return err
+       }
+       defer res.Body.Close()
+       if res.StatusCode != http.StatusOK {
+               return fmt.Errorf("server returned: %v", res.Status)
+       }
+       b := bufferPool.Get().(*bytes.Buffer)
+       b.Reset()
+       defer bufferPool.Put(b)
+       _, err = io.Copy(b, res.Body)
+       if err != nil {
+               return fmt.Errorf("reading response body: %v", err)
+       }
+       err = proto.Unmarshal(b.Bytes(), out)
+       if err != nil {
+               return fmt.Errorf("decoding response body: %v", err)
+       }
+       return nil
+}
diff --git a/vendor/github.com/golang/groupcache/http_test.go b/vendor/github.com/golang/groupcache/http_test.go
new file mode 100644 (file)
index 0000000..b42edd7
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+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 groupcache
+
+import (
+       "errors"
+       "flag"
+       "log"
+       "net"
+       "net/http"
+       "os"
+       "os/exec"
+       "strconv"
+       "strings"
+       "sync"
+       "testing"
+       "time"
+)
+
+var (
+       peerAddrs = flag.String("test_peer_addrs", "", "Comma-separated list of peer addresses; used by TestHTTPPool")
+       peerIndex = flag.Int("test_peer_index", -1, "Index of which peer this child is; used by TestHTTPPool")
+       peerChild = flag.Bool("test_peer_child", false, "True if running as a child process; used by TestHTTPPool")
+)
+
+func TestHTTPPool(t *testing.T) {
+       if *peerChild {
+               beChildForTestHTTPPool()
+               os.Exit(0)
+       }
+
+       const (
+               nChild = 4
+               nGets  = 100
+       )
+
+       var childAddr []string
+       for i := 0; i < nChild; i++ {
+               childAddr = append(childAddr, pickFreeAddr(t))
+       }
+
+       var cmds []*exec.Cmd
+       var wg sync.WaitGroup
+       for i := 0; i < nChild; i++ {
+               cmd := exec.Command(os.Args[0],
+                       "--test.run=TestHTTPPool",
+                       "--test_peer_child",
+                       "--test_peer_addrs="+strings.Join(childAddr, ","),
+                       "--test_peer_index="+strconv.Itoa(i),
+               )
+               cmds = append(cmds, cmd)
+               wg.Add(1)
+               if err := cmd.Start(); err != nil {
+                       t.Fatal("failed to start child process: ", err)
+               }
+               go awaitAddrReady(t, childAddr[i], &wg)
+       }
+       defer func() {
+               for i := 0; i < nChild; i++ {
+                       if cmds[i].Process != nil {
+                               cmds[i].Process.Kill()
+                       }
+               }
+       }()
+       wg.Wait()
+
+       // Use a dummy self address so that we don't handle gets in-process.
+       p := NewHTTPPool("should-be-ignored")
+       p.Set(addrToURL(childAddr)...)
+
+       // 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 some reason.
+       getter := GetterFunc(func(ctx Context, key string, dest Sink) error {
+               return errors.New("parent getter called; something's wrong")
+       })
+       g := NewGroup("httpPoolTest", 1<<20, getter)
+
+       for _, key := range testKeys(nGets) {
+               var value string
+               if err := g.Get(nil, key, StringSink(&value)); err != nil {
+                       t.Fatal(err)
+               }
+               if suffix := ":" + key; !strings.HasSuffix(value, suffix) {
+                       t.Errorf("Get(%q) = %q, want value ending in %q", key, value, suffix)
+               }
+               t.Logf("Get key=%q, value=%q (peer:key)", key, value)
+       }
+}
+
+func testKeys(n int) (keys []string) {
+       keys = make([]string, n)
+       for i := range keys {
+               keys[i] = strconv.Itoa(i)
+       }
+       return
+}
+
+func beChildForTestHTTPPool() {
+       addrs := strings.Split(*peerAddrs, ",")
+
+       p := NewHTTPPool("http://" + addrs[*peerIndex])
+       p.Set(addrToURL(addrs)...)
+
+       getter := GetterFunc(func(ctx Context, key string, dest Sink) error {
+               dest.SetString(strconv.Itoa(*peerIndex) + ":" + key)
+               return nil
+       })
+       NewGroup("httpPoolTest", 1<<20, getter)
+
+       log.Fatal(http.ListenAndServe(addrs[*peerIndex], p))
+}
+
+// This is racy. Another process could swoop in and steal the port between the
+// call to this function and the next listen call. Should be okay though.
+// The proper way would be to pass the l.File() as ExtraFiles to the child
+// process, and then close your copy once the child starts.
+func pickFreeAddr(t *testing.T) string {
+       l, err := net.Listen("tcp", "127.0.0.1:0")
+       if err != nil {
+               t.Fatal(err)
+       }
+       defer l.Close()
+       return l.Addr().String()
+}
+
+func addrToURL(addr []string) []string {
+       url := make([]string, len(addr))
+       for i := range addr {
+               url[i] = "http://" + addr[i]
+       }
+       return url
+}
+
+func awaitAddrReady(t *testing.T, addr string, wg *sync.WaitGroup) {
+       defer wg.Done()
+       const max = 1 * time.Second
+       tries := 0
+       for {
+               tries++
+               c, err := net.Dial("tcp", addr)
+               if err == nil {
+                       c.Close()
+                       return
+               }
+               delay := time.Duration(tries) * 25 * time.Millisecond
+               if delay > max {
+                       delay = max
+               }
+               time.Sleep(delay)
+       }
+}
diff --git a/vendor/github.com/golang/groupcache/lru/lru.go b/vendor/github.com/golang/groupcache/lru/lru.go
new file mode 100644 (file)
index 0000000..532cc45
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+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()
+}
+
+// Clear purges all stored items from the cache.
+func (c *Cache) Clear() {
+       if c.OnEvicted != nil {
+               for _, e := range c.cache {
+                       kv := e.Value.(*entry)
+                       c.OnEvicted(kv.key, kv.value)
+               }
+       }
+       c.ll = nil
+       c.cache = nil
+}
diff --git a/vendor/github.com/golang/groupcache/lru/lru_test.go b/vendor/github.com/golang/groupcache/lru/lru_test.go
new file mode 100644 (file)
index 0000000..98a2656
--- /dev/null
@@ -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/vendor/github.com/golang/groupcache/peers.go b/vendor/github.com/golang/groupcache/peers.go
new file mode 100644 (file)
index 0000000..1625ff0
--- /dev/null
@@ -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.
+*/
+
+// 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 returns the peer that owns the specific key
+       // and true to indicate that a remote peer was nominated.
+       // It returns nil, false if the key owner is the current peer.
+       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(groupName string) PeerPicker
+)
+
+// RegisterPeerPicker registers the peer initialization function.
+// It is called once, when the first group is created.
+// Either RegisterPeerPicker or RegisterPerGroupPeerPicker should be
+// called exactly once, but not both.
+func RegisterPeerPicker(fn func() PeerPicker) {
+       if portPicker != nil {
+               panic("RegisterPeerPicker called more than once")
+       }
+       portPicker = func(_ string) PeerPicker { return fn() }
+}
+
+// RegisterPerGroupPeerPicker registers the peer initialization function,
+// which takes the groupName, to be used in choosing a PeerPicker.
+// It is called once, when the first group is created.
+// Either RegisterPeerPicker or RegisterPerGroupPeerPicker should be
+// called exactly once, but not both.
+func RegisterPerGroupPeerPicker(fn func(groupName string) PeerPicker) {
+       if portPicker != nil {
+               panic("RegisterPeerPicker called more than once")
+       }
+       portPicker = fn
+}
+
+func getPeers(groupName string) PeerPicker {
+       if portPicker == nil {
+               return NoPeers{}
+       }
+       pk := portPicker(groupName)
+       if pk == nil {
+               pk = NoPeers{}
+       }
+       return pk
+}
diff --git a/vendor/github.com/golang/groupcache/singleflight/singleflight.go b/vendor/github.com/golang/groupcache/singleflight/singleflight.go
new file mode 100644 (file)
index 0000000..ff2c2ee
--- /dev/null
@@ -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/vendor/github.com/golang/groupcache/singleflight/singleflight_test.go b/vendor/github.com/golang/groupcache/singleflight/singleflight_test.go
new file mode 100644 (file)
index 0000000..47b4d3d
--- /dev/null
@@ -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)
+       }
+       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/vendor/github.com/golang/groupcache/sinks.go b/vendor/github.com/golang/groupcache/sinks.go
new file mode 100644 (file)
index 0000000..cb42b41
--- /dev/null
@@ -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"
+
+       "github.com/golang/protobuf/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/vendor/github.com/golang/groupcache/testpb/test.pb.go b/vendor/github.com/golang/groupcache/testpb/test.pb.go
new file mode 100644 (file)
index 0000000..038040d
--- /dev/null
@@ -0,0 +1,235 @@
+// Code generated by protoc-gen-go.
+// source: test.proto
+// DO NOT EDIT!
+
+package testpb
+
+import proto "github.com/golang/protobuf/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/vendor/github.com/golang/groupcache/testpb/test.proto b/vendor/github.com/golang/groupcache/testpb/test.proto
new file mode 100644 (file)
index 0000000..b9dc6c9
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+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.
+*/
+
+syntax = "proto2";
+
+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) {};
+}