├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── byteview.go ├── byteview_test.go ├── consistenthash ├── consistenthash.go └── consistenthash_test.go ├── go.mod ├── go.sum ├── groupcache.go ├── groupcache_test.go ├── groupcachepb ├── groupcache.pb.go └── groupcache.proto ├── http.go ├── http_test.go ├── lru ├── lru.go └── lru_test.go ├── peers.go ├── singleflight ├── singleflight.go └── singleflight_test.go ├── sinks.go ├── testCoverage.sh └── testpb ├── test.pb.go └── test.proto /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: ci 3 | env: 4 | GO111MODULE: on 5 | 6 | jobs: 7 | validate-changes: 8 | strategy: 9 | matrix: 10 | go-version: [1.13.8] 11 | platform: [ubuntu-latest] 12 | runs-on: ${{ matrix.platform }} 13 | 14 | steps: 15 | - uses: actions/setup-go@v2 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | - uses: actions/checkout@v2 19 | - name: Cache go modules 20 | uses: actions/cache@v2 21 | with: 22 | path: ~/go/pkg/mod 23 | key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} 24 | restore-keys: ${{ runner.os }}-go- 25 | - name: Run all tests 26 | run: make ci 27 | - name: Upload coverage to Codecov 28 | uses: codecov/codecov-action@v1 29 | timeout-minutes: 2 30 | with: 31 | token: ${{ secrets.CODECOV_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | coverage.txt 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME := groupcache 2 | DESC := caching library 3 | GO111MODULE := on 4 | export GO111MODULE 5 | 6 | SHELL := /bin/bash -o pipefail 7 | 8 | default: 9 | go build ./... 10 | 11 | format: 12 | go fmt ./... 13 | 14 | vet: 15 | go vet ./... 16 | 17 | test: 18 | go test -race ./... 19 | 20 | coverage: 21 | sh testCoverage.sh 22 | 23 | clean: 24 | go clean ./... 25 | 26 | ci: format vet test coverage 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # groupcache 2 | [![Build Status](https://github.com/twitter/groupcache/workflows/ci/badge.svg)](https://github.com/twitter/groupcache/actions) 3 | [![codecov.io](https://codecov.io/github/twitter/groupcache/coverage.svg?branch=master)](https://codecov.io/gh/twitter/groupcache?branch=master) 4 | [![GoDoc](https://godoc.org/github.com/twitter/groupcache?status.svg)](https://godoc.org/github.com/twitter/groupcache) 5 | 6 | ## Summary 7 | 8 | groupcache is a caching and cache-filling library, intended as a 9 | replacement for memcached in many cases. 10 | 11 | For API docs and examples, see http://godoc.org/github.com/golang/groupcache 12 | 13 | ## Comparison to memcached 14 | 15 | ### **Like memcached**, groupcache: 16 | 17 | * shards by key to select which peer is responsible for that key 18 | 19 | ### **Unlike memcached**, groupcache: 20 | 21 | * does not require running a separate set of servers, thus massively 22 | reducing deployment/configuration pain. groupcache is a client 23 | library as well as a server. It connects to its own peers. 24 | 25 | * comes with a cache filling mechanism. Whereas memcached just says 26 | "Sorry, cache miss", often resulting in a thundering herd of 27 | database (or whatever) loads from an unbounded number of clients 28 | (which has resulted in several fun outages), groupcache coordinates 29 | cache fills such that only one load in one process of an entire 30 | replicated set of processes populates the cache, then multiplexes 31 | the loaded value to all callers. 32 | 33 | * does not support versioned values. If key "foo" is value "bar", 34 | key "foo" must always be "bar". There are no explicit cache evictions. 35 | Thus there is also no CAS, nor Increment/Decrement. 36 | This also means that groupcache.... 37 | 38 | * ... supports automatic mirroring of super-hot items to multiple 39 | processes. This prevents memcached hot spotting where a machine's 40 | CPU and/or NIC are overloaded by very popular keys/values. 41 | 42 | * is currently only available for Go. It's very unlikely that I 43 | (bradfitz@) will port the code to any other language. 44 | 45 | ## Loading process 46 | 47 | In a nutshell, a groupcache lookup of **Get("foo")** looks like: 48 | 49 | (On machine #5 of a set of N machines running the same code) 50 | 51 | 1. Is the value of "foo" in local memory because it's super hot? If so, use it. 52 | 53 | 2. Is the value of "foo" in local memory because peer #5 (the current 54 | peer) is the owner of it? If so, use it. 55 | 56 | 3. Amongst all the peers in my set of N, am I the owner of the key 57 | "foo"? (e.g. does it consistent hash to 5?) If so, load it. If 58 | other callers come in, via the same process or via RPC requests 59 | from peers, they block waiting for the load to finish and get the 60 | same answer. If not, RPC to the peer that's the owner and get 61 | the answer. If the RPC fails, just load it locally (still with 62 | local dup suppression). 63 | 64 | ## Users 65 | 66 | groupcache is in production use by dl.google.com (its original user), 67 | parts of Blogger, parts of Google Code, parts of Google Fiber, parts 68 | of Google production monitoring systems, etc. 69 | 70 | ## Presentations 71 | 72 | See http://talks.golang.org/2013/oscon-dl.slide 73 | 74 | ## Help 75 | 76 | Use the golang-nuts mailing list for any discussion or questions. 77 | -------------------------------------------------------------------------------- /byteview.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package groupcache 18 | 19 | import ( 20 | "bytes" 21 | "errors" 22 | "io" 23 | "strings" 24 | ) 25 | 26 | // A ByteView holds an immutable view of bytes. 27 | // Internally it wraps either a []byte or a string, 28 | // but that detail is invisible to callers. 29 | // 30 | // A ByteView is meant to be used as a value type, not 31 | // a pointer (like a time.Time). 32 | type ByteView struct { 33 | // If b is non-nil, b is used, else s is used. 34 | b []byte 35 | s string 36 | } 37 | 38 | // Len returns the view's length. 39 | func (v ByteView) Len() int { 40 | if v.b != nil { 41 | return len(v.b) 42 | } 43 | return len(v.s) 44 | } 45 | 46 | // ByteSlice returns a copy of the data as a byte slice. 47 | func (v ByteView) ByteSlice() []byte { 48 | if v.b != nil { 49 | return cloneBytes(v.b) 50 | } 51 | return []byte(v.s) 52 | } 53 | 54 | // String returns the data as a string, making a copy if necessary. 55 | func (v ByteView) String() string { 56 | if v.b != nil { 57 | return string(v.b) 58 | } 59 | return v.s 60 | } 61 | 62 | // At returns the byte at index i. 63 | func (v ByteView) At(i int) byte { 64 | if v.b != nil { 65 | return v.b[i] 66 | } 67 | return v.s[i] 68 | } 69 | 70 | // Slice slices the view between the provided from and to indices. 71 | func (v ByteView) Slice(from, to int) ByteView { 72 | if v.b != nil { 73 | return ByteView{b: v.b[from:to]} 74 | } 75 | return ByteView{s: v.s[from:to]} 76 | } 77 | 78 | // SliceFrom slices the view from the provided index until the end. 79 | func (v ByteView) SliceFrom(from int) ByteView { 80 | if v.b != nil { 81 | return ByteView{b: v.b[from:]} 82 | } 83 | return ByteView{s: v.s[from:]} 84 | } 85 | 86 | // Copy copies b into dest and returns the number of bytes copied. 87 | func (v ByteView) Copy(dest []byte) int { 88 | if v.b != nil { 89 | return copy(dest, v.b) 90 | } 91 | return copy(dest, v.s) 92 | } 93 | 94 | // Equal returns whether the bytes in b are the same as the bytes in 95 | // b2. 96 | func (v ByteView) Equal(b2 ByteView) bool { 97 | if b2.b == nil { 98 | return v.EqualString(b2.s) 99 | } 100 | return v.EqualBytes(b2.b) 101 | } 102 | 103 | // EqualString returns whether the bytes in b are the same as the bytes 104 | // in s. 105 | func (v ByteView) EqualString(s string) bool { 106 | if v.b == nil { 107 | return v.s == s 108 | } 109 | l := v.Len() 110 | if len(s) != l { 111 | return false 112 | } 113 | for i, bi := range v.b { 114 | if bi != s[i] { 115 | return false 116 | } 117 | } 118 | return true 119 | } 120 | 121 | // EqualBytes returns whether the bytes in b are the same as the bytes 122 | // in b2. 123 | func (v ByteView) EqualBytes(b2 []byte) bool { 124 | if v.b != nil { 125 | return bytes.Equal(v.b, b2) 126 | } 127 | l := v.Len() 128 | if len(b2) != l { 129 | return false 130 | } 131 | for i, bi := range b2 { 132 | if bi != v.s[i] { 133 | return false 134 | } 135 | } 136 | return true 137 | } 138 | 139 | // Reader returns an io.ReadSeeker for the bytes in v. 140 | func (v ByteView) Reader() io.ReadSeeker { 141 | if v.b != nil { 142 | return bytes.NewReader(v.b) 143 | } 144 | return strings.NewReader(v.s) 145 | } 146 | 147 | // ReadAt implements io.ReaderAt on the bytes in v. 148 | func (v ByteView) ReadAt(p []byte, off int64) (n int, err error) { 149 | if off < 0 { 150 | return 0, errors.New("view: invalid offset") 151 | } 152 | if off >= int64(v.Len()) { 153 | return 0, io.EOF 154 | } 155 | n = v.SliceFrom(int(off)).Copy(p) 156 | if n < len(p) { 157 | err = io.EOF 158 | } 159 | return 160 | } 161 | 162 | // WriteTo implements io.WriterTo on the bytes in v. 163 | func (v ByteView) WriteTo(w io.Writer) (n int64, err error) { 164 | var m int 165 | if v.b != nil { 166 | m, err = w.Write(v.b) 167 | } else { 168 | m, err = io.WriteString(w, v.s) 169 | } 170 | if err == nil && m < v.Len() { 171 | err = io.ErrShortWrite 172 | } 173 | n = int64(m) 174 | return 175 | } 176 | -------------------------------------------------------------------------------- /byteview_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package groupcache 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "io" 23 | "io/ioutil" 24 | "testing" 25 | ) 26 | 27 | func TestByteView(t *testing.T) { 28 | for _, s := range []string{"", "x", "yy"} { 29 | for _, v := range []ByteView{of([]byte(s)), of(s)} { 30 | name := fmt.Sprintf("string %q, view %+v", s, v) 31 | if v.Len() != len(s) { 32 | t.Errorf("%s: Len = %d; want %d", name, v.Len(), len(s)) 33 | } 34 | if v.String() != s { 35 | t.Errorf("%s: String = %q; want %q", name, v.String(), s) 36 | } 37 | var longDest [3]byte 38 | if n := v.Copy(longDest[:]); n != len(s) { 39 | t.Errorf("%s: long Copy = %d; want %d", name, n, len(s)) 40 | } 41 | var shortDest [1]byte 42 | if n := v.Copy(shortDest[:]); n != min(len(s), 1) { 43 | t.Errorf("%s: short Copy = %d; want %d", name, n, min(len(s), 1)) 44 | } 45 | if got, err := ioutil.ReadAll(v.Reader()); err != nil || string(got) != s { 46 | t.Errorf("%s: Reader = %q, %v; want %q", name, got, err, s) 47 | } 48 | if got, err := ioutil.ReadAll(io.NewSectionReader(v, 0, int64(len(s)))); err != nil || string(got) != s { 49 | t.Errorf("%s: SectionReader of ReaderAt = %q, %v; want %q", name, got, err, s) 50 | } 51 | var dest bytes.Buffer 52 | if _, err := v.WriteTo(&dest); err != nil || !bytes.Equal(dest.Bytes(), []byte(s)) { 53 | t.Errorf("%s: WriteTo = %q, %v; want %q", name, dest.Bytes(), err, s) 54 | } 55 | } 56 | } 57 | } 58 | 59 | // of returns a byte view of the []byte or string in x. 60 | func of(x interface{}) ByteView { 61 | if bytes, ok := x.([]byte); ok { 62 | return ByteView{b: bytes} 63 | } 64 | return ByteView{s: x.(string)} 65 | } 66 | 67 | func TestByteViewEqual(t *testing.T) { 68 | tests := []struct { 69 | a interface{} // string or []byte 70 | b interface{} // string or []byte 71 | want bool 72 | }{ 73 | {"x", "x", true}, 74 | {"x", "y", false}, 75 | {"x", "yy", false}, 76 | {[]byte("x"), []byte("x"), true}, 77 | {[]byte("x"), []byte("y"), false}, 78 | {[]byte("x"), []byte("yy"), false}, 79 | {[]byte("x"), "x", true}, 80 | {[]byte("x"), "y", false}, 81 | {[]byte("x"), "yy", false}, 82 | {"x", []byte("x"), true}, 83 | {"x", []byte("y"), false}, 84 | {"x", []byte("yy"), false}, 85 | } 86 | for i, tt := range tests { 87 | va := of(tt.a) 88 | if bytes, ok := tt.b.([]byte); ok { 89 | if got := va.EqualBytes(bytes); got != tt.want { 90 | t.Errorf("%d. EqualBytes = %v; want %v", i, got, tt.want) 91 | } 92 | } else { 93 | if got := va.EqualString(tt.b.(string)); got != tt.want { 94 | t.Errorf("%d. EqualString = %v; want %v", i, got, tt.want) 95 | } 96 | } 97 | if got := va.Equal(of(tt.b)); got != tt.want { 98 | t.Errorf("%d. Equal = %v; want %v", i, got, tt.want) 99 | } 100 | } 101 | } 102 | 103 | func TestByteViewSlice(t *testing.T) { 104 | tests := []struct { 105 | in string 106 | from int 107 | to interface{} // nil to mean the end (SliceFrom); else int 108 | want string 109 | }{ 110 | { 111 | in: "abc", 112 | from: 1, 113 | to: 2, 114 | want: "b", 115 | }, 116 | { 117 | in: "abc", 118 | from: 1, 119 | want: "bc", 120 | }, 121 | { 122 | in: "abc", 123 | to: 2, 124 | want: "ab", 125 | }, 126 | } 127 | for i, tt := range tests { 128 | for _, v := range []ByteView{of([]byte(tt.in)), of(tt.in)} { 129 | name := fmt.Sprintf("test %d, view %+v", i, v) 130 | if tt.to != nil { 131 | v = v.Slice(tt.from, tt.to.(int)) 132 | } else { 133 | v = v.SliceFrom(tt.from) 134 | } 135 | if v.String() != tt.want { 136 | t.Errorf("%s: got %q; want %q", name, v.String(), tt.want) 137 | } 138 | } 139 | } 140 | } 141 | 142 | func min(a, b int) int { 143 | if a < b { 144 | return a 145 | } 146 | return b 147 | } 148 | -------------------------------------------------------------------------------- /consistenthash/consistenthash.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package consistenthash provides an implementation of a ring hash. 18 | package consistenthash 19 | 20 | import ( 21 | "hash/crc32" 22 | "sort" 23 | "strconv" 24 | ) 25 | 26 | type Hash func(data []byte) uint32 27 | 28 | type Map struct { 29 | hash Hash 30 | replicas int 31 | keys []int // Sorted 32 | hashMap map[int]string 33 | } 34 | 35 | func New(replicas int, fn Hash) *Map { 36 | m := &Map{ 37 | replicas: replicas, 38 | hash: fn, 39 | hashMap: make(map[int]string), 40 | } 41 | if m.hash == nil { 42 | m.hash = crc32.ChecksumIEEE 43 | } 44 | return m 45 | } 46 | 47 | // Returns true if there are no items available. 48 | func (m *Map) IsEmpty() bool { 49 | return len(m.keys) == 0 50 | } 51 | 52 | // Adds some keys to the hash. 53 | func (m *Map) Add(keys ...string) { 54 | for _, key := range keys { 55 | for i := 0; i < m.replicas; i++ { 56 | hash := int(m.hash([]byte(strconv.Itoa(i) + key))) 57 | m.keys = append(m.keys, hash) 58 | m.hashMap[hash] = key 59 | } 60 | } 61 | sort.Ints(m.keys) 62 | } 63 | 64 | // Gets the closest item in the hash to the provided key. 65 | func (m *Map) Get(key string) string { 66 | if m.IsEmpty() { 67 | return "" 68 | } 69 | 70 | hash := int(m.hash([]byte(key))) 71 | 72 | // Binary search for appropriate replica. 73 | idx := sort.Search(len(m.keys), func(i int) bool { return m.keys[i] >= hash }) 74 | 75 | // Means we have cycled back to the first replica. 76 | if idx == len(m.keys) { 77 | idx = 0 78 | } 79 | 80 | return m.hashMap[m.keys[idx]] 81 | } 82 | -------------------------------------------------------------------------------- /consistenthash/consistenthash_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package consistenthash 18 | 19 | import ( 20 | "fmt" 21 | "strconv" 22 | "testing" 23 | ) 24 | 25 | func TestHashing(t *testing.T) { 26 | 27 | // Override the hash function to return easier to reason about values. Assumes 28 | // the keys can be converted to an integer. 29 | hash := New(3, func(key []byte) uint32 { 30 | i, err := strconv.Atoi(string(key)) 31 | if err != nil { 32 | panic(err) 33 | } 34 | return uint32(i) 35 | }) 36 | 37 | // Given the above hash function, this will give replicas with "hashes": 38 | // 2, 4, 6, 12, 14, 16, 22, 24, 26 39 | hash.Add("6", "4", "2") 40 | 41 | testCases := map[string]string{ 42 | "2": "2", 43 | "11": "2", 44 | "23": "4", 45 | "27": "2", 46 | } 47 | 48 | for k, v := range testCases { 49 | if hash.Get(k) != v { 50 | t.Errorf("Asking for %s, should have yielded %s", k, v) 51 | } 52 | } 53 | 54 | // Adds 8, 18, 28 55 | hash.Add("8") 56 | 57 | // 27 should now map to 8. 58 | testCases["27"] = "8" 59 | 60 | for k, v := range testCases { 61 | if hash.Get(k) != v { 62 | t.Errorf("Asking for %s, should have yielded %s", k, v) 63 | } 64 | } 65 | 66 | } 67 | 68 | func TestConsistency(t *testing.T) { 69 | hash1 := New(1, nil) 70 | hash2 := New(1, nil) 71 | 72 | hash1.Add("Bill", "Bob", "Bonny") 73 | hash2.Add("Bob", "Bonny", "Bill") 74 | 75 | if hash1.Get("Ben") != hash2.Get("Ben") { 76 | t.Errorf("Fetching 'Ben' from both hashes should be the same") 77 | } 78 | 79 | hash2.Add("Becky", "Ben", "Bobby") 80 | 81 | if hash1.Get("Ben") != hash2.Get("Ben") || 82 | hash1.Get("Bob") != hash2.Get("Bob") || 83 | hash1.Get("Bonny") != hash2.Get("Bonny") { 84 | t.Errorf("Direct matches should always return the same entry") 85 | } 86 | 87 | } 88 | 89 | func BenchmarkGet8(b *testing.B) { benchmarkGet(b, 8) } 90 | func BenchmarkGet32(b *testing.B) { benchmarkGet(b, 32) } 91 | func BenchmarkGet128(b *testing.B) { benchmarkGet(b, 128) } 92 | func BenchmarkGet512(b *testing.B) { benchmarkGet(b, 512) } 93 | 94 | func benchmarkGet(b *testing.B, shards int) { 95 | 96 | hash := New(50, nil) 97 | 98 | var buckets []string 99 | for i := 0; i < shards; i++ { 100 | buckets = append(buckets, fmt.Sprintf("shard-%d", i)) 101 | } 102 | 103 | hash.Add(buckets...) 104 | 105 | b.ResetTimer() 106 | 107 | for i := 0; i < b.N; i++ { 108 | hash.Get(buckets[i&(shards-1)]) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/twitter/groupcache 2 | 3 | go 1.12 4 | 5 | require github.com/golang/protobuf v1.3.2 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 2 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 3 | -------------------------------------------------------------------------------- /groupcache.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package groupcache provides a data loading mechanism with caching 18 | // and de-duplication that works across a set of peer processes. 19 | // 20 | // Each data Get first consults its local cache, otherwise delegates 21 | // to the requested key's canonical owner, which then checks its cache 22 | // or finally gets the data. In the common case, many concurrent 23 | // cache misses across a set of peers for the same key result in just 24 | // one cache fill. 25 | // 26 | // Put supports manual loading of a data's canonical owner's cache. 27 | package groupcache 28 | 29 | import ( 30 | "errors" 31 | "math/rand" 32 | "strconv" 33 | "sync" 34 | "sync/atomic" 35 | "time" 36 | 37 | "github.com/golang/protobuf/ptypes" 38 | tspb "github.com/golang/protobuf/ptypes/timestamp" 39 | pb "github.com/twitter/groupcache/groupcachepb" 40 | "github.com/twitter/groupcache/lru" 41 | "github.com/twitter/groupcache/singleflight" 42 | ) 43 | 44 | // A Getter loads data for a key. 45 | type Getter interface { 46 | // Get returns the value identified by key, populating dest. 47 | // 48 | // The returned data must be unversioned. That is, key must 49 | // uniquely describe the loaded data, without relying on 50 | // cache expiration mechanisms. 51 | Get(ctx Context, key string, dest Sink) (*time.Time, error) 52 | } 53 | 54 | // A GetterFunc implements Getter with a function. 55 | type GetterFunc func(ctx Context, key string, dest Sink) (*time.Time, error) 56 | 57 | func (f GetterFunc) Get(ctx Context, key string, dest Sink) (*time.Time, error) { 58 | return f(ctx, key, dest) 59 | } 60 | 61 | // A Container checks if underlying data exists. 62 | type Container interface { 63 | // Contain returns metadata on the underlying data. 64 | Contain(ctx Context, key string) (bool, error) 65 | } 66 | 67 | // A ContainerFunc implements Container with a function. 68 | type ContainerFunc func(ctx Context, key string) (bool, error) 69 | 70 | func (f ContainerFunc) Contain(ctx Context, key string) (bool, error) { 71 | return f(ctx, key) 72 | } 73 | 74 | // A Putter stores data for a key. 75 | type Putter interface { 76 | // Put stores the data identified by key in the cache. 77 | // 78 | // Data to be stored must be unversioned as per Getters. 79 | // Data cannot be invalidated - it is assumed that 80 | // putting any data with a preexisting key can be 81 | // interpreted as a no-op. 82 | // TTL is a duration value that will be 83 | // passed through to any underlying PutterFunc. 84 | Put(ctx Context, key string, data []byte, ttl *time.Time) error 85 | } 86 | 87 | // A PutterFunc implements Putter with a function. 88 | type PutterFunc func(ctx Context, key string, data []byte, ttl *time.Time) error 89 | 90 | func (f PutterFunc) Put(ctx Context, key string, data []byte, ttl *time.Time) error { 91 | return f(ctx, key, data, ttl) 92 | } 93 | 94 | // A GetterPutter combines the Getter and Putter interfaces. 95 | type GetterPutter interface { 96 | Getter 97 | Putter 98 | } 99 | 100 | // A GetterContainerPutter combines the Getter, Container, and Putter interfaces. 101 | type GetterContainerPutter interface { 102 | Getter 103 | Container 104 | Putter 105 | } 106 | 107 | var ( 108 | mu sync.RWMutex 109 | groups = make(map[string]*Group) 110 | 111 | initPeerServerOnce sync.Once 112 | initPeerServer func() 113 | errResourceExpired = errors.New("resource is expired") 114 | ) 115 | 116 | const populateHotCacheOdds = 10 117 | 118 | // GetGroup returns the named group previously created with NewGroup, or 119 | // nil if there's no such group. 120 | func GetGroup(name string) *Group { 121 | mu.RLock() 122 | g := groups[name] 123 | mu.RUnlock() 124 | return g 125 | } 126 | 127 | // NewGroup creates a coordinated group-aware Getter/Container/Putter. 128 | // 129 | // The returned Getter/Container/Putter tries (but does not guarantee) to run 130 | // only one Get/Contain/Put call at once for a given key across an entire set 131 | // of peer processes. Concurrent callers both in the local process and in 132 | // other processes receive copies of the answer once the original 133 | // Get/Container/Put completes. 134 | // 135 | // The group name must be unique for each getter/container/putter. 136 | func NewGroup(name string, cacheBytes int64, getter Getter, container Container, putter Putter) *Group { 137 | return newGroup(name, cacheBytes, getter, container, putter, nil) 138 | } 139 | 140 | // If peers is nil, the peerPicker is called via a sync.Once to initialize it. 141 | func newGroup(name string, cacheBytes int64, getter Getter, container Container, putter Putter, peers PeerPicker) *Group { 142 | if getter == nil { 143 | panic("nil Getter") 144 | } 145 | if container == nil { 146 | panic("nil Container") 147 | } 148 | if putter == nil { 149 | panic("nil Putter") 150 | } 151 | mu.Lock() 152 | defer mu.Unlock() 153 | initPeerServerOnce.Do(callInitPeerServer) 154 | if _, dup := groups[name]; dup { 155 | panic("duplicate registration of group " + name) 156 | } 157 | g := &Group{ 158 | name: name, 159 | getter: getter, 160 | container: container, 161 | putter: putter, 162 | peers: peers, 163 | cacheBytes: cacheBytes, 164 | loadGroup: &singleflight.Group{}, 165 | } 166 | if fn := newGroupHook; fn != nil { 167 | fn(g) 168 | } 169 | groups[name] = g 170 | return g 171 | } 172 | 173 | // newGroupHook, if non-nil, is called right after a new group is created. 174 | var newGroupHook func(*Group) 175 | 176 | // RegisterNewGroupHook registers a hook that is run each time 177 | // a group is created. 178 | func RegisterNewGroupHook(fn func(*Group)) { 179 | if newGroupHook != nil { 180 | panic("RegisterNewGroupHook called more than once") 181 | } 182 | newGroupHook = fn 183 | } 184 | 185 | // RegisterServerStart registers a hook that is run when the first 186 | // group is created. 187 | func RegisterServerStart(fn func()) { 188 | if initPeerServer != nil { 189 | panic("RegisterServerStart called more than once") 190 | } 191 | initPeerServer = fn 192 | } 193 | 194 | func callInitPeerServer() { 195 | if initPeerServer != nil { 196 | initPeerServer() 197 | } 198 | } 199 | 200 | // A Group is a cache namespace and associated data loaded spread over 201 | // a group of 1 or more machines. 202 | type Group struct { 203 | name string 204 | getter Getter 205 | container Container 206 | putter Putter 207 | peersOnce sync.Once 208 | peers PeerPicker 209 | cacheBytes int64 // limit for sum of mainCache and hotCache size 210 | 211 | // mainCache is a cache of the keys for which this process 212 | // (amongst its peers) is authoritative. That is, this cache 213 | // contains keys which consistent hash on to this process's 214 | // peer number. 215 | mainCache cache 216 | 217 | // hotCache contains keys/values for which this peer is not 218 | // authoritative (otherwise they would be in mainCache), but 219 | // are popular enough to warrant mirroring in this process to 220 | // avoid going over the network to fetch from a peer. Having 221 | // a hotCache avoids network hotspotting, where a peer's 222 | // network card could become the bottleneck on a popular key. 223 | // This cache is used sparingly to maximize the total number 224 | // of key/value pairs that can be stored globally. 225 | hotCache cache 226 | 227 | // loadGroup ensures that each key is only fetched once 228 | // (either locally or remotely), regardless of the number of 229 | // concurrent callers. 230 | loadGroup flightGroup 231 | 232 | _ int32 // force Stats to be 8-byte aligned on 32-bit platforms 233 | 234 | // Stats are statistics on the group. 235 | Stats Stats 236 | } 237 | 238 | // flightGroup is defined as an interface which flightgroup.Group 239 | // satisfies. We define this so that we may test with an alternate 240 | // implementation. 241 | type flightGroup interface { 242 | // Done is called when Do is done. 243 | Do(key string, fn func() (interface{}, error)) (interface{}, error) 244 | } 245 | 246 | // Stats are per-group statistics. 247 | type Stats struct { 248 | Gets AtomicInt // any Get request, including from peers 249 | Puts AtomicInt // any Put request, including from peers 250 | Contains AtomicInt // any Contain request, including from peers 251 | CacheHits AtomicInt // either cache was good 252 | Loads AtomicInt // Gets not from the cache 253 | LoadsDeduped AtomicInt // after singleflight 254 | LocalLoads AtomicInt // total good local loads 255 | LocalLoadErrs AtomicInt // total bad local loads 256 | Checks AtomicInt // Checks not from the cache 257 | ChecksDeduped AtomicInt // after singleflight 258 | LocalChecks AtomicInt // total good local loads 259 | LocalCheckErrs AtomicInt // total bad local loads 260 | Stores AtomicInt // Puts that weren't in the cache 261 | StoresDeduped AtomicInt // after singleflight 262 | LocalStores AtomicInt // total good local stores 263 | LocalStoreErrs AtomicInt // total bad local stores 264 | PeerStores AtomicInt // either remote store or remote cache hit (not an error) 265 | PeerChecks AtomicInt // either remote check or remote cache hit (not an error) 266 | PeerLoads AtomicInt // either remote load or remote cache hit (not an error) 267 | PeerErrors AtomicInt 268 | ServerRequests AtomicInt // requests that came over the network from peers 269 | } 270 | 271 | // Name returns the name of the group. 272 | func (g *Group) Name() string { 273 | return g.name 274 | } 275 | 276 | func (g *Group) initPeers() { 277 | if g.peers == nil { 278 | g.peers = getPeers(g.name) 279 | } 280 | } 281 | 282 | // Get functions 283 | 284 | func (g *Group) Get(ctx Context, key string, dest Sink) (*time.Time, error) { 285 | g.peersOnce.Do(g.initPeers) 286 | g.Stats.Gets.Add(1) 287 | if dest == nil { 288 | return nil, errors.New("groupcache: nil dest Sink") 289 | } 290 | payload, cacheHit := g.lookupCache(key) 291 | 292 | if cacheHit { 293 | g.Stats.CacheHits.Add(1) 294 | return payload.ttl, setSinkView(dest, payload.value) 295 | } 296 | 297 | // Optimization to avoid double unmarshalling or copying: keep 298 | // track of whether the dest was already populated. One caller 299 | // (if local) will set this; the losers will not. The common 300 | // case will likely be one caller. 301 | destPopulated := false 302 | payload, destPopulated, err := g.load(ctx, key, dest) 303 | if err != nil { 304 | return nil, err 305 | } 306 | if destPopulated { 307 | return payload.ttl, nil 308 | } 309 | return payload.ttl, setSinkView(dest, payload.value) 310 | } 311 | 312 | // payload encapsulates the value cached and the ttl time for the value 313 | type payload struct { 314 | value ByteView 315 | length int64 316 | ttl *time.Time 317 | } 318 | 319 | func newPayload(value ByteView, ttl *time.Time) payload { 320 | return payload{ttl: ttl, length: int64(value.Len()), value: value} 321 | } 322 | 323 | // underlying Get logic - loads key either by invoking the getter locally or by sending it to another machine. 324 | func (g *Group) load(ctx Context, key string, dest Sink) (p payload, destPopulated bool, err error) { 325 | g.Stats.Loads.Add(1) 326 | viewi, err := g.loadGroup.Do(key, func() (interface{}, error) { 327 | // Check the cache again because singleflight can only dedup calls 328 | // that overlap concurrently. It's possible for 2 concurrent 329 | // requests to miss the cache, resulting in 2 load() calls. An 330 | // unfortunate goroutine scheduling would result in this callback 331 | // being run twice, serially. If we don't check the cache again, 332 | // cache.nbytes would be incremented below even though there will 333 | // be only one entry for this key. 334 | // 335 | // Consider the following serialized event ordering for two 336 | // goroutines in which this callback gets called twice for the 337 | // same key: 338 | // 1: Get("key") 339 | // 2: Get("key") 340 | // 1: lookupCache("key") 341 | // 2: lookupCache("key") 342 | // 1: load("key") 343 | // 2: load("key") 344 | // 1: loadGroup.Do("key", fn) 345 | // 1: fn() 346 | // 2: loadGroup.Do("key", fn) 347 | // 2: fn() 348 | if p, cacheHit := g.lookupCache(key); cacheHit { 349 | g.Stats.CacheHits.Add(1) 350 | return p, nil 351 | } 352 | g.Stats.LoadsDeduped.Add(1) 353 | var p payload 354 | var err error 355 | if peer, ok := g.peers.PickPeer(key); ok { 356 | p, err = g.getFromPeer(ctx, peer, key) 357 | if err == nil { 358 | g.Stats.PeerLoads.Add(1) 359 | return p, nil 360 | } 361 | g.Stats.PeerErrors.Add(1) 362 | // TODO(bradfitz): log the peer's error? keep 363 | // log of the past few for /groupcachez? It's 364 | // probably boring (normal task movement), so not 365 | // worth logging I imagine. 366 | } 367 | p, err = g.getLocally(ctx, key, dest) 368 | if err != nil { 369 | g.Stats.LocalLoadErrs.Add(1) 370 | return nil, err 371 | } 372 | g.Stats.LocalLoads.Add(1) 373 | destPopulated = true // only one caller of load gets this return value 374 | g.populateCache(key, p, &g.mainCache) 375 | return p, nil 376 | }) 377 | if err == nil { 378 | var ok bool 379 | p, ok = viewi.(payload) 380 | if !ok { 381 | err = errors.New("groupcache: failed interface conversion") 382 | } 383 | } 384 | return 385 | } 386 | 387 | func (g *Group) getLocally(ctx Context, key string, dest Sink) (payload, error) { 388 | ttl, err := g.getter.Get(ctx, key, dest) 389 | if err != nil { 390 | return payload{}, err 391 | } 392 | if ttl != nil && ttl.Before(time.Now().UTC()) { 393 | return payload{}, errResourceExpired 394 | } 395 | dv, err := dest.view() 396 | return newPayload(dv, ttl), err 397 | } 398 | 399 | func (g *Group) getFromPeer(ctx Context, peer ProtoPeer, key string) (payload, error) { 400 | req := &pb.GetRequest{ 401 | Group: g.name, 402 | Key: key, 403 | } 404 | res := &pb.GetResponse{} 405 | err := peer.Get(ctx, req, res) 406 | if err != nil { 407 | return payload{}, err 408 | } 409 | value := ByteView{b: res.GetValue()} 410 | var ttlp *time.Time = nil 411 | if res.GetTtl() != nil { 412 | ttl, err := ptypes.Timestamp(res.GetTtl()) 413 | if err != nil { 414 | return payload{}, err 415 | } 416 | ttl = ttl.UTC() 417 | if ttl.Before(time.Now().UTC()) { 418 | return payload{}, errResourceExpired 419 | } 420 | ttlp = &ttl 421 | } 422 | payload := newPayload(value, ttlp) 423 | // TODO(bradfitz): use res.MinuteQps or something smart to 424 | // conditionally populate hotCache. For now just do it some 425 | // percentage of the time. 426 | if rand.Intn(populateHotCacheOdds) == 0 { 427 | g.populateCache(key, payload, &g.hotCache) 428 | } 429 | return payload, nil 430 | } 431 | 432 | // Contain functions 433 | 434 | func (g *Group) Contain(ctx Context, key string) (bool, error) { 435 | g.peersOnce.Do(g.initPeers) 436 | g.Stats.Contains.Add(1) 437 | 438 | if _, cacheHit := g.lookupCache(key); cacheHit { 439 | g.Stats.CacheHits.Add(1) 440 | return true, nil 441 | } 442 | 443 | // Optimization to avoid double unmarshalling or copying: keep 444 | // track of whether the dest was already populated. One caller 445 | // (if local) will set this; the losers will not. The common 446 | // case will likely be one caller. 447 | ok, _, err := g.check(ctx, key) 448 | if err != nil { 449 | return true, err 450 | } 451 | return ok, nil 452 | } 453 | 454 | // underlying Contain logic - checks if key exists locally or on another machine. 455 | func (g *Group) check(ctx Context, key string) (ok bool, destPopulated bool, err error) { 456 | g.Stats.Checks.Add(1) 457 | viewi, err := g.loadGroup.Do(key, func() (interface{}, error) { 458 | // Deduplication checks - see explanation in load() 459 | if _, cacheHit := g.lookupCache(key); cacheHit { 460 | g.Stats.CacheHits.Add(1) 461 | return true, nil 462 | } 463 | g.Stats.ChecksDeduped.Add(1) 464 | var ok bool 465 | var err error 466 | if peer, ok := g.peers.PickPeer(key); ok { 467 | ok, err = g.checkFromPeer(ctx, peer, key) 468 | if err == nil { 469 | g.Stats.PeerChecks.Add(1) 470 | return ok, nil 471 | } 472 | g.Stats.PeerErrors.Add(1) 473 | // TODO(bradfitz): log the peer's error? keep 474 | // log of the past few for /groupcachez? It's 475 | // probably boring (normal task movement), so not 476 | // worth logging I imagine. 477 | } 478 | ok, err = g.checkLocally(ctx, key) 479 | if err != nil { 480 | g.Stats.LocalCheckErrs.Add(1) 481 | return nil, err 482 | } 483 | g.Stats.LocalChecks.Add(1) 484 | destPopulated = true // only one caller of load gets this return value 485 | return ok, nil 486 | }) 487 | if err == nil { 488 | var castOK bool 489 | ok, castOK = viewi.(bool) 490 | if !castOK { 491 | err = errors.New("groupcache: failed interface conversion") 492 | } 493 | } 494 | return 495 | } 496 | 497 | func (g *Group) checkLocally(ctx Context, key string) (bool, error) { 498 | ok, err := g.container.Contain(ctx, key) 499 | if err != nil { 500 | return false, err 501 | } 502 | return ok, nil 503 | } 504 | 505 | func (g *Group) checkFromPeer(ctx Context, peer ProtoPeer, key string) (bool, error) { 506 | req := &pb.ContainRequest{ 507 | Group: g.name, 508 | Key: key, 509 | } 510 | res := &pb.ContainResponse{} 511 | err := peer.Contain(ctx, req, res) 512 | if err != nil { 513 | return false, err 514 | } 515 | return res.Exists, nil 516 | } 517 | 518 | // Put functions 519 | 520 | func (g *Group) Put(ctx Context, key string, data []byte, ttl *time.Time) error { 521 | g.peersOnce.Do(g.initPeers) 522 | g.Stats.Puts.Add(1) 523 | if data == nil { 524 | return errors.New("groupcache: nil data") 525 | } 526 | _, cacheHit := g.lookupCache(key) 527 | 528 | if cacheHit { 529 | g.Stats.CacheHits.Add(1) 530 | return nil 531 | } 532 | 533 | err := g.store(ctx, key, data, ttl) 534 | if err != nil { 535 | return err 536 | } 537 | return nil 538 | } 539 | 540 | // underlying Put logic - stores data for key either by invoking the putter locally or by sending it to another machine. 541 | func (g *Group) store(ctx Context, key string, data []byte, ttl *time.Time) (err error) { 542 | g.Stats.Stores.Add(1) 543 | _, err = g.loadGroup.Do(key, func() (interface{}, error) { 544 | // Deduplication checks - see explanation in load() 545 | if _, cacheHit := g.lookupCache(key); cacheHit { 546 | g.Stats.CacheHits.Add(1) 547 | return nil, nil 548 | } 549 | g.Stats.StoresDeduped.Add(1) 550 | var err error 551 | if peer, ok := g.peers.PickPeer(key); ok { 552 | err = g.putFromPeer(ctx, peer, key, data, ttl) 553 | if err == nil { 554 | g.Stats.PeerStores.Add(1) 555 | return nil, nil 556 | } 557 | g.Stats.PeerErrors.Add(1) 558 | } 559 | err = g.putLocally(ctx, key, data, ttl) 560 | if err != nil { 561 | g.Stats.LocalStoreErrs.Add(1) 562 | return nil, err 563 | } 564 | g.Stats.LocalStores.Add(1) 565 | value := ByteView{b: data} 566 | g.populateCache(key, newPayload(value, ttl), &g.mainCache) 567 | return nil, nil 568 | }) 569 | return 570 | } 571 | 572 | func (g *Group) putLocally(ctx Context, key string, data []byte, ttl *time.Time) error { 573 | return g.putter.Put(ctx, key, data, ttl) 574 | } 575 | 576 | func (g *Group) putFromPeer(ctx Context, peer ProtoPeer, key string, data []byte, ttl *time.Time) error { 577 | var ttlProto *tspb.Timestamp = nil 578 | var err error 579 | if ttl != nil { 580 | ttlProto, err = ptypes.TimestampProto(*ttl) 581 | if err != nil { 582 | return err 583 | } 584 | } 585 | req := &pb.PutRequest{ 586 | Group: g.name, 587 | Key: key, 588 | Value: data, 589 | Ttl: ttlProto, 590 | } 591 | res := &pb.PutResponse{} 592 | err = peer.Put(ctx, req, res) 593 | if err != nil { 594 | return err 595 | } 596 | // TODO(bradfitz): use res.MinuteQps or something smart to 597 | // conditionally populate hotCache. For now just do it some 598 | // percentage of the time. 599 | if rand.Intn(populateHotCacheOdds) == 0 { 600 | payload := newPayload(ByteView{b: data}, ttl) 601 | g.populateCache(key, payload, &g.hotCache) 602 | } 603 | return nil 604 | } 605 | 606 | // Cache utils 607 | 608 | func (g *Group) lookupCache(key string) (payload payload, ok bool) { 609 | if g.cacheBytes <= 0 { 610 | return 611 | } 612 | payload, ok = g.mainCache.get(key) 613 | if ok { 614 | return 615 | } 616 | payload, ok = g.hotCache.get(key) 617 | return 618 | } 619 | 620 | func (g *Group) populateCache(key string, payload payload, cache *cache) { 621 | if g.cacheBytes <= 0 { 622 | return 623 | } 624 | cache.add(key, payload) 625 | 626 | // Evict items from cache(s) if necessary. 627 | for { 628 | mainBytes := g.mainCache.bytes() 629 | hotBytes := g.hotCache.bytes() 630 | if mainBytes+hotBytes <= g.cacheBytes { 631 | return 632 | } 633 | 634 | // TODO(bradfitz): this is good-enough-for-now logic. 635 | // It should be something based on measurements and/or 636 | // respecting the costs of different resources. 637 | victim := &g.mainCache 638 | if hotBytes > mainBytes/8 { 639 | victim = &g.hotCache 640 | } 641 | victim.removeOldest() 642 | } 643 | } 644 | 645 | // CacheType represents a type of cache. 646 | type CacheType int 647 | 648 | const ( 649 | // The MainCache is the cache for items that this peer is the 650 | // owner for. 651 | MainCache CacheType = iota + 1 652 | 653 | // The HotCache is the cache for items that seem popular 654 | // enough to replicate to this node, even though it's not the 655 | // owner. 656 | HotCache 657 | ) 658 | 659 | // CacheStats returns stats about the provided cache within the group. 660 | func (g *Group) CacheStats(which CacheType) CacheStats { 661 | switch which { 662 | case MainCache: 663 | return g.mainCache.stats() 664 | case HotCache: 665 | return g.hotCache.stats() 666 | default: 667 | return CacheStats{} 668 | } 669 | } 670 | 671 | // cache is a wrapper around an *lru.Cache that adds synchronization, 672 | // makes values always be ByteView, and counts the size of all keys and 673 | // values. 674 | type cache struct { 675 | mu sync.RWMutex 676 | nbytes int64 // of all keys and values 677 | lru *lru.Cache 678 | nhit, nget int64 679 | nevict int64 // number of evictions 680 | metadata map[string]*cacheValueMetadata 681 | } 682 | 683 | // cacheValueMetadata is metadata for values in the cache. 684 | // This structure was chosen so that it could hold additional 685 | // fields in the future. 686 | type cacheValueMetadata struct { 687 | ttl *time.Time 688 | length int64 689 | } 690 | 691 | func (c *cache) updateMetadata(key string, ttl *time.Time, length int64) *cacheValueMetadata { 692 | if c.metadata == nil { 693 | c.metadata = make(map[string]*cacheValueMetadata) 694 | } 695 | m := c.metadata[key] 696 | if m == nil { 697 | m = &cacheValueMetadata{} 698 | c.metadata[key] = m 699 | } 700 | m.setTTL(ttl) 701 | m.setLength(length) 702 | return m 703 | } 704 | 705 | func (c *cache) getMetadata(key string) *cacheValueMetadata { 706 | if c.metadata == nil { 707 | c.metadata = make(map[string]*cacheValueMetadata) 708 | } 709 | m := c.metadata[key] 710 | return m 711 | } 712 | 713 | func (c *cacheValueMetadata) setTTL(t *time.Time) { 714 | if t != nil { 715 | utc := (*t).UTC() 716 | c.ttl = &utc 717 | } 718 | } 719 | 720 | func (c *cacheValueMetadata) setLength(l int64) { 721 | c.length = l 722 | } 723 | 724 | func (c *cache) stats() CacheStats { 725 | c.mu.RLock() 726 | defer c.mu.RUnlock() 727 | return CacheStats{ 728 | Bytes: c.nbytes, 729 | Items: c.itemsLocked(), 730 | Gets: c.nget, 731 | Hits: c.nhit, 732 | Evictions: c.nevict, 733 | } 734 | } 735 | 736 | func (c *cache) add(key string, payload payload) { 737 | c.mu.Lock() 738 | defer c.mu.Unlock() 739 | c.updateMetadata(key, payload.ttl, int64(payload.value.Len())) 740 | if c.lru == nil { 741 | c.lru = &lru.Cache{ 742 | OnEvicted: func(key lru.Key, value interface{}) { 743 | val := value.(ByteView) 744 | delete(c.metadata, key.(string)) 745 | c.nbytes -= int64(len(key.(string))) + int64(val.Len()) 746 | c.nevict++ 747 | }, 748 | } 749 | } 750 | c.lru.Add(key, payload.value) 751 | c.nbytes += int64(len(key)) + int64(payload.value.Len()) 752 | } 753 | 754 | func (c *cache) get(key string) (p payload, ok bool) { 755 | c.mu.Lock() 756 | defer c.mu.Unlock() 757 | c.nget++ 758 | if c.lru == nil { 759 | return 760 | } 761 | md := c.getMetadata(key) 762 | if md == nil { 763 | return 764 | } 765 | var ttl *time.Time 766 | if ttl = md.ttl; ttl != nil && ttl.Before(time.Now().UTC()) { 767 | return 768 | } 769 | vi, ok := c.lru.Get(key) 770 | if !ok { 771 | return 772 | } 773 | c.nhit++ 774 | p = newPayload(vi.(ByteView), ttl) 775 | return p, true 776 | } 777 | 778 | func (c *cache) removeOldest() { 779 | c.mu.Lock() 780 | defer c.mu.Unlock() 781 | if c.lru != nil { 782 | c.lru.RemoveOldest() 783 | } 784 | } 785 | 786 | func (c *cache) bytes() int64 { 787 | c.mu.RLock() 788 | defer c.mu.RUnlock() 789 | return c.nbytes 790 | } 791 | 792 | func (c *cache) items() int64 { 793 | c.mu.RLock() 794 | defer c.mu.RUnlock() 795 | return c.itemsLocked() 796 | } 797 | 798 | func (c *cache) itemsLocked() int64 { 799 | if c.lru == nil { 800 | return 0 801 | } 802 | return int64(c.lru.Len()) 803 | } 804 | 805 | // An AtomicInt is an int64 to be accessed atomically. 806 | type AtomicInt int64 807 | 808 | // Add atomically adds n to i. 809 | func (i *AtomicInt) Add(n int64) { 810 | atomic.AddInt64((*int64)(i), n) 811 | } 812 | 813 | // Get atomically gets the value of i. 814 | func (i *AtomicInt) Get() int64 { 815 | return atomic.LoadInt64((*int64)(i)) 816 | } 817 | 818 | func (i *AtomicInt) String() string { 819 | return strconv.FormatInt(i.Get(), 10) 820 | } 821 | 822 | // CacheStats are returned by stats accessors on Group. 823 | type CacheStats struct { 824 | Bytes int64 825 | Items int64 826 | Gets int64 827 | Hits int64 828 | Evictions int64 829 | } 830 | -------------------------------------------------------------------------------- /groupcache_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Tests for groupcache. 18 | 19 | package groupcache 20 | 21 | import ( 22 | "bytes" 23 | "errors" 24 | "fmt" 25 | "hash/crc32" 26 | "math/rand" 27 | "reflect" 28 | "sync" 29 | "testing" 30 | "time" 31 | "unsafe" 32 | 33 | "github.com/golang/protobuf/proto" 34 | "github.com/golang/protobuf/ptypes" 35 | 36 | pb "github.com/twitter/groupcache/groupcachepb" 37 | testpb "github.com/twitter/groupcache/testpb" 38 | ) 39 | 40 | var ( 41 | once sync.Once 42 | stringGroup, protoGroup, byteGroup GetterContainerPutter 43 | 44 | stringc = make(chan string) 45 | 46 | dummyCtx Context 47 | 48 | // cacheFills is the number of times stringGroup or 49 | // protoGroup's Getter have been called. Read using the 50 | // cacheFills function. 51 | cacheFills AtomicInt 52 | // like cacheFills, but for the group's Container 53 | cacheMeta AtomicInt 54 | // like cacheFills, but for the group's Putter 55 | cachePuts AtomicInt 56 | ttl = time.Now().UTC().Add(time.Hour) 57 | ) 58 | 59 | const ( 60 | stringGroupName = "string-group" 61 | protoGroupName = "proto-group" 62 | byteGroupName = "byte-group" 63 | testMessageType = "google3/net/groupcache/go/test_proto.TestMessage" 64 | fromChan = "from-chan" 65 | cacheSize = 1 << 20 66 | ) 67 | 68 | // TODO(apratti): add better test facilities 69 | func newTestGroup(name, testval string) *Group { 70 | return newGroup( 71 | name, 72 | 1024, 73 | GetterFunc(func(_ Context, key string, dest Sink) (*time.Time, error) { 74 | 75 | return &ttl, dest.SetString(testval) 76 | }), 77 | ContainerFunc(func(_ Context, key string) (bool, error) { 78 | return true, nil 79 | }), 80 | PutterFunc(func(_ Context, key string, data []byte, ttl *time.Time) error { 81 | return nil 82 | }), 83 | nil, 84 | ) 85 | } 86 | 87 | func testSetup() { 88 | stringGroup = NewGroup( 89 | stringGroupName, 90 | cacheSize, 91 | GetterFunc(func(_ Context, key string, dest Sink) (*time.Time, error) { 92 | if key == fromChan { 93 | key = <-stringc 94 | } 95 | cacheFills.Add(1) 96 | return &ttl, dest.SetString("ECHO:" + key) 97 | }), 98 | ContainerFunc(func(_ Context, key string) (bool, error) { 99 | if key == fromChan { 100 | key = <-stringc 101 | } 102 | cacheMeta.Add(1) 103 | return true, nil 104 | }), 105 | PutterFunc(func(_ Context, key string, data []byte, ttl *time.Time) error { 106 | if key == fromChan { 107 | key = <-stringc 108 | } 109 | cachePuts.Add(1) 110 | return nil 111 | }), 112 | ) 113 | 114 | protoGroup = NewGroup( 115 | protoGroupName, 116 | cacheSize, 117 | GetterFunc(func(_ Context, key string, dest Sink) (*time.Time, error) { 118 | if key == fromChan { 119 | key = <-stringc 120 | } 121 | cacheFills.Add(1) 122 | return &ttl, dest.SetProto(&testpb.TestMessage{ 123 | Name: proto.String("ECHO:" + key), 124 | City: proto.String("SOME-CITY"), 125 | }) 126 | }), 127 | ContainerFunc(func(_ Context, key string) (bool, error) { 128 | if key == fromChan { 129 | key = <-stringc 130 | } 131 | cacheMeta.Add(1) 132 | return true, nil 133 | }), 134 | PutterFunc(func(_ Context, key string, data []byte, ttl *time.Time) error { 135 | if key == fromChan { 136 | key = <-stringc 137 | } 138 | cachePuts.Add(1) 139 | return nil 140 | }), 141 | ) 142 | 143 | byteGroup = NewGroup( 144 | byteGroupName, 145 | cacheSize, 146 | GetterFunc(func(_ Context, key string, dest Sink) (*time.Time, error) { 147 | if key == fromChan { 148 | key = <-stringc 149 | } 150 | cacheFills.Add(1) 151 | return &ttl, dest.SetBytes([]byte("ECHO:" + key)) 152 | }), 153 | ContainerFunc(func(_ Context, key string) (bool, error) { 154 | if key == fromChan { 155 | key = <-stringc 156 | } 157 | cacheMeta.Add(1) 158 | return true, nil 159 | }), 160 | PutterFunc(func(_ Context, key string, data []byte, ttl *time.Time) error { 161 | if key == fromChan { 162 | key = <-stringc 163 | } 164 | cachePuts.Add(1) 165 | return nil 166 | }), 167 | ) 168 | } 169 | 170 | // tests that a Getter's Get method is only called once with two 171 | // outstanding callers. This is the string variant. 172 | func TestGetDupSuppressString(t *testing.T) { 173 | once.Do(testSetup) 174 | // Start two getters. The first should block (waiting reading 175 | // from stringc) and the second should latch on to the first 176 | // one. 177 | resc := make(chan string, 2) 178 | for i := 0; i < 2; i++ { 179 | go func() { 180 | var s string 181 | if _, err := stringGroup.Get(dummyCtx, fromChan, StringSink(&s)); err != nil { 182 | resc <- "ERROR:" + err.Error() 183 | return 184 | } 185 | resc <- s 186 | }() 187 | } 188 | 189 | // Wait a bit so both goroutines get merged together via 190 | // singleflight. 191 | // TODO(bradfitz): decide whether there are any non-offensive 192 | // debug/test hooks that could be added to singleflight to 193 | // make a sleep here unnecessary. 194 | time.Sleep(250 * time.Millisecond) 195 | 196 | // Unblock the first getter, which should unblock the second 197 | // as well. 198 | stringc <- "foo" 199 | 200 | for i := 0; i < 2; i++ { 201 | select { 202 | case v := <-resc: 203 | if v != "ECHO:foo" { 204 | t.Errorf("got %q; want %q", v, "ECHO:foo") 205 | } 206 | case <-time.After(5 * time.Second): 207 | t.Errorf("timeout waiting on getter #%d of 2", i+1) 208 | } 209 | } 210 | } 211 | 212 | // tests that a Getter's Get method is only called once with two 213 | // outstanding callers. This is the proto variant. 214 | func TestGetDupSuppressProto(t *testing.T) { 215 | once.Do(testSetup) 216 | // Start two getters. The first should block (waiting reading 217 | // from stringc) and the second should latch on to the first 218 | // one. 219 | resc := make(chan *testpb.TestMessage, 2) 220 | for i := 0; i < 2; i++ { 221 | go func() { 222 | tm := new(testpb.TestMessage) 223 | if _, err := protoGroup.Get(dummyCtx, fromChan, ProtoSink(tm)); err != nil { 224 | tm.Name = proto.String("ERROR:" + err.Error()) 225 | } 226 | resc <- tm 227 | }() 228 | } 229 | 230 | // Wait a bit so both goroutines get merged together via 231 | // singleflight. 232 | // TODO(bradfitz): decide whether there are any non-offensive 233 | // debug/test hooks that could be added to singleflight to 234 | // make a sleep here unnecessary. 235 | time.Sleep(250 * time.Millisecond) 236 | 237 | // Unblock the first getter, which should unblock the second 238 | // as well. 239 | stringc <- "Fluffy" 240 | want := &testpb.TestMessage{ 241 | Name: proto.String("ECHO:Fluffy"), 242 | City: proto.String("SOME-CITY"), 243 | } 244 | for i := 0; i < 2; i++ { 245 | select { 246 | case v := <-resc: 247 | if !reflect.DeepEqual(v, want) { 248 | t.Errorf(" Got: %v\nWant: %v", proto.CompactTextString(v), proto.CompactTextString(want)) 249 | } 250 | case <-time.After(5 * time.Second): 251 | t.Errorf("timeout waiting on getter #%d of 2", i+1) 252 | } 253 | } 254 | } 255 | 256 | func countFills(f func()) int64 { 257 | fills0 := cacheFills.Get() 258 | f() 259 | return cacheFills.Get() - fills0 260 | } 261 | 262 | func countMeta(f func()) int64 { 263 | meta0 := cacheMeta.Get() 264 | f() 265 | return cacheMeta.Get() - meta0 266 | } 267 | 268 | func countPuts(f func()) int64 { 269 | puts0 := cachePuts.Get() 270 | f() 271 | return cachePuts.Get() - puts0 272 | } 273 | 274 | func TestCaching(t *testing.T) { 275 | once.Do(testSetup) 276 | meta := countMeta(func() { 277 | for i := 0; i < 10; i++ { 278 | if _, err := stringGroup.Contain(dummyCtx, "TestCaching-key1"); err != nil { 279 | t.Fatal(err) 280 | } 281 | } 282 | }) 283 | // Contain calls do not populate the cache, should call parent everytime. 284 | if meta != 10 { 285 | t.Errorf("expected 10 underlying contain; got %d", meta) 286 | } 287 | 288 | // gets 289 | fills := countFills(func() { 290 | for i := 0; i < 10; i++ { 291 | var s string 292 | if _, err := stringGroup.Get(dummyCtx, "TestCaching-key1", StringSink(&s)); err != nil { 293 | t.Fatal(err) 294 | } 295 | } 296 | }) 297 | if fills != 1 { 298 | t.Errorf("expected 1 cache fill; got %d", fills) 299 | } 300 | 301 | meta = countMeta(func() { 302 | for i := 0; i < 10; i++ { 303 | if _, err := stringGroup.Contain(dummyCtx, "TestCaching-key1"); err != nil { 304 | t.Fatal(err) 305 | } 306 | } 307 | }) 308 | // Now that the cache is populated, meta should use the cached results from Get. 309 | if meta != 0 { 310 | t.Errorf("expected 0 meta; got %d", meta) 311 | } 312 | 313 | // puts 314 | puts := countPuts(func() { 315 | for i := 0; i < 10; i++ { 316 | if err := stringGroup.Put(dummyCtx, "TestCaching-key2", []byte("TestCaching-value"), &ttl); err != nil { 317 | t.Fatal(err) 318 | } 319 | } 320 | }) 321 | if puts != 1 { 322 | t.Errorf("expected 1 cache put; got %d", puts) 323 | } 324 | } 325 | 326 | func TestResourceExpiration(t *testing.T) { 327 | expiredKey := "expiredKey" 328 | unexpiredKey := "unexpiredKey" 329 | expiredTTL := time.Now().Add(-1 * time.Hour) 330 | testval := "test" 331 | g := newGroup( 332 | "expiretestgroup", 333 | 1024, 334 | GetterFunc(func(_ Context, key string, dest Sink) (*time.Time, error) { 335 | if key == expiredKey { 336 | return &expiredTTL, nil 337 | } 338 | return &ttl, dest.SetString(testval) 339 | }), 340 | ContainerFunc(func(_ Context, key string) (bool, error) { 341 | if key == expiredKey { 342 | return false, nil 343 | } 344 | return true, nil 345 | }), 346 | PutterFunc(func(_ Context, key string, data []byte, ttl *time.Time) error { 347 | return nil 348 | }), 349 | nil, 350 | ) 351 | if err := g.Put(dummyCtx, expiredKey, []byte("TestCaching-value"), &expiredTTL); err != nil { 352 | t.Fatal(err) 353 | } 354 | if err := g.Put(dummyCtx, unexpiredKey, []byte("TestCaching-value"), &ttl); err != nil { 355 | t.Fatal(err) 356 | } 357 | 358 | fills1 := countFills(func() { 359 | var s string 360 | if _, err := g.Get(dummyCtx, expiredKey, StringSink(&s)); err == nil { 361 | t.Fatal("Expired Resources should return error") 362 | } 363 | }) 364 | if fills1 != 0 { 365 | t.Errorf("expected 0 cache fill; got %d", fills1) 366 | } 367 | 368 | fills2 := countFills(func() { 369 | var s string 370 | if _, err := g.Get(dummyCtx, unexpiredKey, StringSink(&s)); err != nil { 371 | t.Fatal(err) 372 | } 373 | }) 374 | if fills2 != 0 { 375 | t.Errorf("expected 0 cache fill; got %d", fills2) 376 | } 377 | } 378 | 379 | func TestCacheEviction(t *testing.T) { 380 | once.Do(testSetup) 381 | testKey := "TestCacheEviction-key" 382 | getTestKey := func() { 383 | var res string 384 | for i := 0; i < 10; i++ { 385 | if _, err := stringGroup.Get(dummyCtx, testKey, StringSink(&res)); err != nil { 386 | t.Fatal(err) 387 | } 388 | } 389 | } 390 | fills := countFills(getTestKey) 391 | if fills != 1 { 392 | t.Fatalf("expected 1 cache fill; got %d", fills) 393 | } 394 | 395 | g := stringGroup.(*Group) 396 | evict0 := g.mainCache.nevict 397 | 398 | // Trash the cache with other keys. 399 | var bytesFlooded int64 400 | // cacheSize/len(testKey) is approximate 401 | for bytesFlooded < cacheSize+1024 { 402 | var res string 403 | key := fmt.Sprintf("dummy-key-%d", bytesFlooded) 404 | stringGroup.Get(dummyCtx, key, StringSink(&res)) 405 | bytesFlooded += int64(len(key) + len(res)) 406 | } 407 | evicts := g.mainCache.nevict - evict0 408 | if evicts <= 0 { 409 | t.Errorf("evicts = %v; want more than 0", evicts) 410 | } 411 | 412 | // Test that the key is gone. 413 | fills = countFills(getTestKey) 414 | if fills != 1 { 415 | t.Fatalf("expected 1 cache fill after cache trashing; got %d", fills) 416 | } 417 | } 418 | 419 | type fakePeer struct { 420 | hits int 421 | fail bool 422 | } 423 | 424 | func (p *fakePeer) Get(_ Context, in *pb.GetRequest, out *pb.GetResponse) error { 425 | p.hits++ 426 | if p.fail { 427 | return errors.New("simulated error from peer") 428 | } 429 | out.Value = []byte("got:" + in.GetKey()) 430 | ttl, _ := ptypes.TimestampProto(ttl) 431 | out.Ttl = ttl 432 | return nil 433 | } 434 | 435 | func (p *fakePeer) Contain(_ Context, in *pb.ContainRequest, out *pb.ContainResponse) error { 436 | p.hits++ 437 | if p.fail { 438 | return errors.New("simulated error from peer") 439 | } 440 | return nil 441 | } 442 | 443 | func (p *fakePeer) Put(_ Context, in *pb.PutRequest, out *pb.PutResponse) error { 444 | p.hits++ 445 | if p.fail { 446 | return errors.New("simulated error from peer") 447 | } 448 | return nil 449 | } 450 | 451 | type fakePeers []ProtoPeer 452 | 453 | func (p fakePeers) PickPeer(key string) (peer ProtoPeer, ok bool) { 454 | if len(p) == 0 { 455 | return 456 | } 457 | n := crc32.Checksum([]byte(key), crc32.IEEETable) % uint32(len(p)) 458 | return p[n], p[n] != nil 459 | } 460 | 461 | // tests that peers (virtual, in-process) are hit, and how much. 462 | func TestPeers(t *testing.T) { 463 | once.Do(testSetup) 464 | rand.Seed(123) 465 | peer0 := &fakePeer{} 466 | peer1 := &fakePeer{} 467 | peer2 := &fakePeer{} 468 | peerList := fakePeers([]ProtoPeer{peer0, peer1, peer2, nil}) 469 | const cacheSize = 0 // disabled 470 | localHits := 0 471 | getter := func(_ Context, key string, dest Sink) (*time.Time, error) { 472 | localHits++ 473 | return &ttl, dest.SetString("got:" + key) 474 | } 475 | container := func(_ Context, key string) (bool, error) { 476 | localHits++ 477 | return true, nil 478 | } 479 | putter := func(_ Context, key string, data []byte, ttl *time.Time) error { 480 | localHits++ 481 | return nil 482 | } 483 | testGroup := newGroup("TestPeers-group", cacheSize, GetterFunc(getter), ContainerFunc(container), PutterFunc(putter), peerList) 484 | run := func(name string, n, m int, wantSummary string) { 485 | // Reset counters 486 | localHits = 0 487 | for _, p := range []*fakePeer{peer0, peer1, peer2} { 488 | p.hits = 0 489 | } 490 | 491 | // puts before gets - prepopulate to alter get hit rates 492 | for j := 0; j < m; j++ { 493 | key := fmt.Sprintf("key-%d", j) 494 | want := "got:" + key 495 | value := []byte(want) 496 | err := testGroup.Put(dummyCtx, key, value, &ttl) 497 | if err != nil { 498 | t.Errorf("%s: error on key %q: %v", name, key, err) 499 | continue 500 | } 501 | } 502 | 503 | for i := 0; i < n; i++ { 504 | key := fmt.Sprintf("key-%d", i) 505 | want := "got:" + key 506 | var got string 507 | _, err := testGroup.Get(dummyCtx, key, StringSink(&got)) 508 | if err != nil { 509 | t.Errorf("%s: error on key %q: %v", name, key, err) 510 | continue 511 | } 512 | if got != want { 513 | t.Errorf("%s: for key %q, got %q; want %q", name, key, got, want) 514 | } 515 | } 516 | summary := func() string { 517 | return fmt.Sprintf("localHits = %d, peers = %d %d %d", localHits, peer0.hits, peer1.hits, peer2.hits) 518 | } 519 | if got := summary(); got != wantSummary { 520 | t.Errorf("%s: got %q; want %q", name, got, wantSummary) 521 | } 522 | } 523 | resetCacheSize := func(maxBytes int64) { 524 | g := testGroup 525 | g.cacheBytes = maxBytes 526 | g.mainCache = cache{} 527 | g.hotCache = cache{} 528 | } 529 | 530 | // Base case; peers all up, with no problems. 531 | resetCacheSize(1 << 20) 532 | run("base", 200, 0, "localHits = 49, peers = 51 49 51") 533 | 534 | // Verify cache was hit. All localHits are gone, and some of 535 | // the peer hits (the ones randomly selected to be maybe hot) 536 | run("cached_base", 200, 0, "localHits = 0, peers = 49 47 48") 537 | resetCacheSize(0) 538 | 539 | // With one of the peers being down. 540 | // TODO(bradfitz): on a peer number being unavailable, the 541 | // consistent hashing should maybe keep trying others to 542 | // spread the load out. Currently it fails back to local 543 | // execution if the first consistent-hash slot is unavailable. 544 | peerList[0] = nil 545 | run("one_peer_down", 200, 0, "localHits = 100, peers = 0 49 51") 546 | 547 | // Failing peer 548 | peerList[0] = peer0 549 | peer0.fail = true 550 | run("peer0_failing", 200, 0, "localHits = 100, peers = 51 49 51") 551 | 552 | resetCacheSize(1 << 20) 553 | run("put", 1, 1, "localHits = 1, peers = 1 0 0") 554 | } 555 | 556 | func TestTruncatingByteSliceTarget(t *testing.T) { 557 | var buf [100]byte 558 | s := buf[:] 559 | if _, err := stringGroup.Get(dummyCtx, "short", TruncatingByteSliceSink(&s)); err != nil { 560 | t.Fatal(err) 561 | } 562 | if want := "ECHO:short"; string(s) != want { 563 | t.Errorf("short key got %q; want %q", s, want) 564 | } 565 | s = buf[:] 566 | if _, err := byteGroup.Get(dummyCtx, "short", TruncatingByteSliceSink(&s)); err != nil { 567 | t.Fatal(err) 568 | } 569 | if want := []byte("ECHO:short"); !bytes.Equal(s, want) { 570 | t.Errorf("short key got %q; want %q", s, want) 571 | } 572 | 573 | s = buf[:6] 574 | if _, err := stringGroup.Get(dummyCtx, "truncated", TruncatingByteSliceSink(&s)); err != nil { 575 | t.Fatal(err) 576 | } 577 | if want := "ECHO:t"; string(s) != want { 578 | t.Errorf("truncated key got %q; want %q", s, want) 579 | } 580 | s = buf[:6] 581 | if _, err := byteGroup.Get(dummyCtx, "truncated", TruncatingByteSliceSink(&s)); err != nil { 582 | t.Fatal(err) 583 | } 584 | if want := []byte("ECHO:t"); !bytes.Equal(s, want) { 585 | t.Errorf("short key got %q; want %q", s, want) 586 | } 587 | 588 | s = []byte{} 589 | dest := TruncatingByteSliceSink(&s) 590 | if _, err := byteGroup.Get(dummyCtx, "truncated", dest); err != nil { 591 | t.Fatal(err) 592 | } 593 | if want := []byte{}; !bytes.Equal(s, want) { 594 | t.Errorf("truncated key got %q; want %q", s, want) 595 | } 596 | _, err := dest.view() 597 | if err != nil { 598 | t.Errorf("error viewing trunceted sink: %s", err) 599 | } 600 | } 601 | 602 | func TestAllocatingByteSliceTarget(t *testing.T) { 603 | var dst []byte 604 | sink := AllocatingByteSliceSink(&dst) 605 | 606 | inBytes := []byte("some bytes") 607 | sink.SetBytes(inBytes) 608 | if want := "some bytes"; string(dst) != want { 609 | t.Errorf("SetBytes resulted in %q; want %q", dst, want) 610 | } 611 | v, err := sink.view() 612 | if err != nil { 613 | t.Fatalf("view after SetBytes failed: %v", err) 614 | } 615 | if &inBytes[0] == &dst[0] { 616 | t.Error("inBytes and dst share memory") 617 | } 618 | if &inBytes[0] == &v.b[0] { 619 | t.Error("inBytes and view share memory") 620 | } 621 | if &dst[0] == &v.b[0] { 622 | t.Error("dst and view share memory") 623 | } 624 | } 625 | 626 | // orderedFlightGroup allows the caller to force the schedule of when 627 | // orig.Do will be called. This is useful to serialize calls such 628 | // that singleflight cannot dedup them. 629 | type orderedFlightGroup struct { 630 | mu sync.Mutex 631 | stage1 chan bool 632 | stage2 chan bool 633 | orig flightGroup 634 | } 635 | 636 | func (g *orderedFlightGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) { 637 | <-g.stage1 638 | <-g.stage2 639 | g.mu.Lock() 640 | defer g.mu.Unlock() 641 | return g.orig.Do(key, fn) 642 | } 643 | 644 | // TestNoDedup tests invariants on the cache size when singleflight is 645 | // unable to dedup calls. 646 | func TestNoDedup(t *testing.T) { 647 | const testkey = "testkey" 648 | const testval = "testval" 649 | g := newGroup( 650 | "testgroup", 651 | 1024, 652 | GetterFunc(func(_ Context, key string, dest Sink) (*time.Time, error) { 653 | return &ttl, dest.SetString(testval) 654 | }), 655 | ContainerFunc(func(_ Context, key string) (bool, error) { 656 | return true, nil 657 | }), 658 | PutterFunc(func(_ Context, key string, data []byte, ttl *time.Time) error { 659 | return nil 660 | }), 661 | nil, 662 | ) 663 | 664 | orderedGroup := &orderedFlightGroup{ 665 | stage1: make(chan bool), 666 | stage2: make(chan bool), 667 | orig: g.loadGroup, 668 | } 669 | // Replace loadGroup with our wrapper so we can control when 670 | // loadGroup.Do is entered for each concurrent request. 671 | g.loadGroup = orderedGroup 672 | 673 | // Issue two idential requests concurrently. Since the cache is 674 | // empty, it will miss. Both will enter load(), but we will only 675 | // allow one at a time to enter singleflight.Do, so the callback 676 | // function will be called twice. 677 | resc := make(chan string, 2) 678 | for i := 0; i < 2; i++ { 679 | go func() { 680 | var s string 681 | if _, err := g.Get(dummyCtx, testkey, StringSink(&s)); err != nil { 682 | resc <- "ERROR:" + err.Error() 683 | return 684 | } 685 | resc <- s 686 | }() 687 | } 688 | 689 | // Ensure both goroutines have entered the Do routine. This implies 690 | // both concurrent requests have checked the cache, found it empty, 691 | // and called load(). 692 | orderedGroup.stage1 <- true 693 | orderedGroup.stage1 <- true 694 | orderedGroup.stage2 <- true 695 | orderedGroup.stage2 <- true 696 | 697 | for i := 0; i < 2; i++ { 698 | if s := <-resc; s != testval { 699 | t.Errorf("result is %s want %s", s, testval) 700 | } 701 | } 702 | 703 | const wantItems = 1 704 | if g.mainCache.items() != wantItems { 705 | t.Errorf("mainCache has %d items, want %d", g.mainCache.items(), wantItems) 706 | } 707 | 708 | // If the singleflight callback doesn't double-check the cache again 709 | // upon entry, we would increment nbytes twice but the entry would 710 | // only be in the cache once. 711 | const wantBytes = int64(len(testkey) + len(testval)) 712 | if g.mainCache.nbytes != wantBytes { 713 | t.Errorf("cache has %d bytes, want %d", g.mainCache.nbytes, wantBytes) 714 | } 715 | } 716 | 717 | func TestGroupStatsAlignment(t *testing.T) { 718 | var g Group 719 | off := unsafe.Offsetof(g.Stats) 720 | if off%8 != 0 { 721 | t.Fatal("Stats structure is not 8-byte aligned.") 722 | } 723 | } 724 | 725 | // TODO(bradfitz): port the Google-internal full integration test into here, 726 | // using HTTP requests instead of our RPC system. 727 | -------------------------------------------------------------------------------- /groupcachepb/groupcache.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: groupcache.proto 3 | 4 | package groupcachepb 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | timestamp "github.com/golang/protobuf/ptypes/timestamp" 10 | math "math" 11 | ) 12 | 13 | // Reference imports to suppress errors if they are not otherwise used. 14 | var _ = proto.Marshal 15 | var _ = fmt.Errorf 16 | var _ = math.Inf 17 | 18 | // This is a compile-time assertion to ensure that this generated file 19 | // is compatible with the proto package it is being compiled against. 20 | // A compilation error at this line likely means your copy of the 21 | // proto package needs to be updated. 22 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 23 | 24 | type GetRequest struct { 25 | Group string `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"` 26 | Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` 27 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 28 | XXX_unrecognized []byte `json:"-"` 29 | XXX_sizecache int32 `json:"-"` 30 | } 31 | 32 | func (m *GetRequest) Reset() { *m = GetRequest{} } 33 | func (m *GetRequest) String() string { return proto.CompactTextString(m) } 34 | func (*GetRequest) ProtoMessage() {} 35 | func (*GetRequest) Descriptor() ([]byte, []int) { 36 | return fileDescriptor_aba3e029bfa16198, []int{0} 37 | } 38 | 39 | func (m *GetRequest) XXX_Unmarshal(b []byte) error { 40 | return xxx_messageInfo_GetRequest.Unmarshal(m, b) 41 | } 42 | func (m *GetRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 43 | return xxx_messageInfo_GetRequest.Marshal(b, m, deterministic) 44 | } 45 | func (m *GetRequest) XXX_Merge(src proto.Message) { 46 | xxx_messageInfo_GetRequest.Merge(m, src) 47 | } 48 | func (m *GetRequest) XXX_Size() int { 49 | return xxx_messageInfo_GetRequest.Size(m) 50 | } 51 | func (m *GetRequest) XXX_DiscardUnknown() { 52 | xxx_messageInfo_GetRequest.DiscardUnknown(m) 53 | } 54 | 55 | var xxx_messageInfo_GetRequest proto.InternalMessageInfo 56 | 57 | func (m *GetRequest) GetGroup() string { 58 | if m != nil { 59 | return m.Group 60 | } 61 | return "" 62 | } 63 | 64 | func (m *GetRequest) GetKey() string { 65 | if m != nil { 66 | return m.Key 67 | } 68 | return "" 69 | } 70 | 71 | type GetResponse struct { 72 | Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` 73 | MinuteQps float64 `protobuf:"fixed64,2,opt,name=minute_qps,json=minuteQps,proto3" json:"minute_qps,omitempty"` 74 | Ttl *timestamp.Timestamp `protobuf:"bytes,3,opt,name=ttl,proto3" json:"ttl,omitempty"` 75 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 76 | XXX_unrecognized []byte `json:"-"` 77 | XXX_sizecache int32 `json:"-"` 78 | } 79 | 80 | func (m *GetResponse) Reset() { *m = GetResponse{} } 81 | func (m *GetResponse) String() string { return proto.CompactTextString(m) } 82 | func (*GetResponse) ProtoMessage() {} 83 | func (*GetResponse) Descriptor() ([]byte, []int) { 84 | return fileDescriptor_aba3e029bfa16198, []int{1} 85 | } 86 | 87 | func (m *GetResponse) XXX_Unmarshal(b []byte) error { 88 | return xxx_messageInfo_GetResponse.Unmarshal(m, b) 89 | } 90 | func (m *GetResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 91 | return xxx_messageInfo_GetResponse.Marshal(b, m, deterministic) 92 | } 93 | func (m *GetResponse) XXX_Merge(src proto.Message) { 94 | xxx_messageInfo_GetResponse.Merge(m, src) 95 | } 96 | func (m *GetResponse) XXX_Size() int { 97 | return xxx_messageInfo_GetResponse.Size(m) 98 | } 99 | func (m *GetResponse) XXX_DiscardUnknown() { 100 | xxx_messageInfo_GetResponse.DiscardUnknown(m) 101 | } 102 | 103 | var xxx_messageInfo_GetResponse proto.InternalMessageInfo 104 | 105 | func (m *GetResponse) GetValue() []byte { 106 | if m != nil { 107 | return m.Value 108 | } 109 | return nil 110 | } 111 | 112 | func (m *GetResponse) GetMinuteQps() float64 { 113 | if m != nil { 114 | return m.MinuteQps 115 | } 116 | return 0 117 | } 118 | 119 | func (m *GetResponse) GetTtl() *timestamp.Timestamp { 120 | if m != nil { 121 | return m.Ttl 122 | } 123 | return nil 124 | } 125 | 126 | type ContainRequest struct { 127 | Group string `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"` 128 | Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` 129 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 130 | XXX_unrecognized []byte `json:"-"` 131 | XXX_sizecache int32 `json:"-"` 132 | } 133 | 134 | func (m *ContainRequest) Reset() { *m = ContainRequest{} } 135 | func (m *ContainRequest) String() string { return proto.CompactTextString(m) } 136 | func (*ContainRequest) ProtoMessage() {} 137 | func (*ContainRequest) Descriptor() ([]byte, []int) { 138 | return fileDescriptor_aba3e029bfa16198, []int{2} 139 | } 140 | 141 | func (m *ContainRequest) XXX_Unmarshal(b []byte) error { 142 | return xxx_messageInfo_ContainRequest.Unmarshal(m, b) 143 | } 144 | func (m *ContainRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 145 | return xxx_messageInfo_ContainRequest.Marshal(b, m, deterministic) 146 | } 147 | func (m *ContainRequest) XXX_Merge(src proto.Message) { 148 | xxx_messageInfo_ContainRequest.Merge(m, src) 149 | } 150 | func (m *ContainRequest) XXX_Size() int { 151 | return xxx_messageInfo_ContainRequest.Size(m) 152 | } 153 | func (m *ContainRequest) XXX_DiscardUnknown() { 154 | xxx_messageInfo_ContainRequest.DiscardUnknown(m) 155 | } 156 | 157 | var xxx_messageInfo_ContainRequest proto.InternalMessageInfo 158 | 159 | func (m *ContainRequest) GetGroup() string { 160 | if m != nil { 161 | return m.Group 162 | } 163 | return "" 164 | } 165 | 166 | func (m *ContainRequest) GetKey() string { 167 | if m != nil { 168 | return m.Key 169 | } 170 | return "" 171 | } 172 | 173 | type ContainResponse struct { 174 | Exists bool `protobuf:"varint,1,opt,name=exists,proto3" json:"exists,omitempty"` 175 | MinuteQps float64 `protobuf:"fixed64,2,opt,name=minute_qps,json=minuteQps,proto3" json:"minute_qps,omitempty"` 176 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 177 | XXX_unrecognized []byte `json:"-"` 178 | XXX_sizecache int32 `json:"-"` 179 | } 180 | 181 | func (m *ContainResponse) Reset() { *m = ContainResponse{} } 182 | func (m *ContainResponse) String() string { return proto.CompactTextString(m) } 183 | func (*ContainResponse) ProtoMessage() {} 184 | func (*ContainResponse) Descriptor() ([]byte, []int) { 185 | return fileDescriptor_aba3e029bfa16198, []int{3} 186 | } 187 | 188 | func (m *ContainResponse) XXX_Unmarshal(b []byte) error { 189 | return xxx_messageInfo_ContainResponse.Unmarshal(m, b) 190 | } 191 | func (m *ContainResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 192 | return xxx_messageInfo_ContainResponse.Marshal(b, m, deterministic) 193 | } 194 | func (m *ContainResponse) XXX_Merge(src proto.Message) { 195 | xxx_messageInfo_ContainResponse.Merge(m, src) 196 | } 197 | func (m *ContainResponse) XXX_Size() int { 198 | return xxx_messageInfo_ContainResponse.Size(m) 199 | } 200 | func (m *ContainResponse) XXX_DiscardUnknown() { 201 | xxx_messageInfo_ContainResponse.DiscardUnknown(m) 202 | } 203 | 204 | var xxx_messageInfo_ContainResponse proto.InternalMessageInfo 205 | 206 | func (m *ContainResponse) GetExists() bool { 207 | if m != nil { 208 | return m.Exists 209 | } 210 | return false 211 | } 212 | 213 | func (m *ContainResponse) GetMinuteQps() float64 { 214 | if m != nil { 215 | return m.MinuteQps 216 | } 217 | return 0 218 | } 219 | 220 | type PutRequest struct { 221 | Group string `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"` 222 | Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` 223 | Value []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` 224 | Ttl *timestamp.Timestamp `protobuf:"bytes,4,opt,name=ttl,proto3" json:"ttl,omitempty"` 225 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 226 | XXX_unrecognized []byte `json:"-"` 227 | XXX_sizecache int32 `json:"-"` 228 | } 229 | 230 | func (m *PutRequest) Reset() { *m = PutRequest{} } 231 | func (m *PutRequest) String() string { return proto.CompactTextString(m) } 232 | func (*PutRequest) ProtoMessage() {} 233 | func (*PutRequest) Descriptor() ([]byte, []int) { 234 | return fileDescriptor_aba3e029bfa16198, []int{4} 235 | } 236 | 237 | func (m *PutRequest) XXX_Unmarshal(b []byte) error { 238 | return xxx_messageInfo_PutRequest.Unmarshal(m, b) 239 | } 240 | func (m *PutRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 241 | return xxx_messageInfo_PutRequest.Marshal(b, m, deterministic) 242 | } 243 | func (m *PutRequest) XXX_Merge(src proto.Message) { 244 | xxx_messageInfo_PutRequest.Merge(m, src) 245 | } 246 | func (m *PutRequest) XXX_Size() int { 247 | return xxx_messageInfo_PutRequest.Size(m) 248 | } 249 | func (m *PutRequest) XXX_DiscardUnknown() { 250 | xxx_messageInfo_PutRequest.DiscardUnknown(m) 251 | } 252 | 253 | var xxx_messageInfo_PutRequest proto.InternalMessageInfo 254 | 255 | func (m *PutRequest) GetGroup() string { 256 | if m != nil { 257 | return m.Group 258 | } 259 | return "" 260 | } 261 | 262 | func (m *PutRequest) GetKey() string { 263 | if m != nil { 264 | return m.Key 265 | } 266 | return "" 267 | } 268 | 269 | func (m *PutRequest) GetValue() []byte { 270 | if m != nil { 271 | return m.Value 272 | } 273 | return nil 274 | } 275 | 276 | func (m *PutRequest) GetTtl() *timestamp.Timestamp { 277 | if m != nil { 278 | return m.Ttl 279 | } 280 | return nil 281 | } 282 | 283 | type PutResponse struct { 284 | MinuteQps float64 `protobuf:"fixed64,1,opt,name=minute_qps,json=minuteQps,proto3" json:"minute_qps,omitempty"` 285 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 286 | XXX_unrecognized []byte `json:"-"` 287 | XXX_sizecache int32 `json:"-"` 288 | } 289 | 290 | func (m *PutResponse) Reset() { *m = PutResponse{} } 291 | func (m *PutResponse) String() string { return proto.CompactTextString(m) } 292 | func (*PutResponse) ProtoMessage() {} 293 | func (*PutResponse) Descriptor() ([]byte, []int) { 294 | return fileDescriptor_aba3e029bfa16198, []int{5} 295 | } 296 | 297 | func (m *PutResponse) XXX_Unmarshal(b []byte) error { 298 | return xxx_messageInfo_PutResponse.Unmarshal(m, b) 299 | } 300 | func (m *PutResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 301 | return xxx_messageInfo_PutResponse.Marshal(b, m, deterministic) 302 | } 303 | func (m *PutResponse) XXX_Merge(src proto.Message) { 304 | xxx_messageInfo_PutResponse.Merge(m, src) 305 | } 306 | func (m *PutResponse) XXX_Size() int { 307 | return xxx_messageInfo_PutResponse.Size(m) 308 | } 309 | func (m *PutResponse) XXX_DiscardUnknown() { 310 | xxx_messageInfo_PutResponse.DiscardUnknown(m) 311 | } 312 | 313 | var xxx_messageInfo_PutResponse proto.InternalMessageInfo 314 | 315 | func (m *PutResponse) GetMinuteQps() float64 { 316 | if m != nil { 317 | return m.MinuteQps 318 | } 319 | return 0 320 | } 321 | 322 | func init() { 323 | proto.RegisterType((*GetRequest)(nil), "groupcachepb.GetRequest") 324 | proto.RegisterType((*GetResponse)(nil), "groupcachepb.GetResponse") 325 | proto.RegisterType((*ContainRequest)(nil), "groupcachepb.ContainRequest") 326 | proto.RegisterType((*ContainResponse)(nil), "groupcachepb.ContainResponse") 327 | proto.RegisterType((*PutRequest)(nil), "groupcachepb.PutRequest") 328 | proto.RegisterType((*PutResponse)(nil), "groupcachepb.PutResponse") 329 | } 330 | 331 | func init() { proto.RegisterFile("groupcache.proto", fileDescriptor_aba3e029bfa16198) } 332 | 333 | var fileDescriptor_aba3e029bfa16198 = []byte{ 334 | // 327 bytes of a gzipped FileDescriptorProto 335 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xcf, 0x4e, 0x02, 0x31, 336 | 0x10, 0xc6, 0x59, 0xab, 0x28, 0x03, 0x51, 0xd2, 0x18, 0xb3, 0x6e, 0x24, 0x92, 0x9e, 0x38, 0x90, 337 | 0x92, 0xa0, 0x07, 0x0f, 0xdc, 0x38, 0xc0, 0x11, 0x1b, 0xef, 0x66, 0x21, 0x23, 0x6e, 0x84, 0x6d, 338 | 0xa1, 0xad, 0xc1, 0xd7, 0xf4, 0x89, 0xcc, 0xb6, 0x0b, 0x0b, 0xa8, 0x51, 0x6e, 0x3b, 0x7f, 0xbe, 339 | 0xed, 0x6f, 0xbe, 0x19, 0xa8, 0x4f, 0x97, 0xd2, 0xaa, 0x49, 0x3c, 0x79, 0x45, 0xae, 0x96, 0xd2, 340 | 0x48, 0x5a, 0x2b, 0x32, 0x6a, 0x1c, 0xdd, 0x4e, 0xa5, 0x9c, 0xce, 0xb0, 0xe3, 0x6a, 0x63, 0xfb, 341 | 0xd2, 0x31, 0xc9, 0x1c, 0xb5, 0x89, 0xe7, 0xca, 0xb7, 0xb3, 0x7b, 0x80, 0x01, 0x1a, 0x81, 0x0b, 342 | 0x8b, 0xda, 0xd0, 0x4b, 0x38, 0x71, 0xf2, 0x30, 0x68, 0x06, 0xad, 0x8a, 0xf0, 0x01, 0xad, 0x03, 343 | 0x79, 0xc3, 0x8f, 0xf0, 0xc8, 0xe5, 0xb2, 0x4f, 0xa6, 0xa0, 0xea, 0x54, 0x5a, 0xc9, 0x54, 0x63, 344 | 0x26, 0x7b, 0x8f, 0x67, 0x16, 0x9d, 0xac, 0x26, 0x7c, 0x40, 0x1b, 0x00, 0xf3, 0x24, 0xb5, 0x06, 345 | 0x9f, 0x17, 0x4a, 0x3b, 0x75, 0x20, 0x2a, 0x3e, 0xf3, 0xa8, 0x34, 0x6d, 0x03, 0x31, 0x66, 0x16, 346 | 0x92, 0x66, 0xd0, 0xaa, 0x76, 0x23, 0xee, 0x41, 0xf9, 0x1a, 0x94, 0x3f, 0xad, 0x41, 0x45, 0xd6, 347 | 0xc6, 0x1e, 0xe0, 0xbc, 0x2f, 0x53, 0x13, 0x27, 0xe9, 0xa1, 0xac, 0x43, 0xb8, 0xd8, 0x28, 0x73, 348 | 0xde, 0x2b, 0x28, 0xe3, 0x2a, 0xd1, 0x46, 0x3b, 0xed, 0x99, 0xc8, 0xa3, 0x3f, 0x88, 0xd9, 0x0a, 349 | 0x60, 0x64, 0x0f, 0xf5, 0xaa, 0x30, 0x87, 0x6c, 0x9b, 0x93, 0x4f, 0x7f, 0xfc, 0xbf, 0xe9, 0xdb, 350 | 0x50, 0x75, 0x2f, 0xe7, 0xfc, 0xbb, 0x9c, 0xc1, 0x1e, 0x67, 0xf7, 0x33, 0x00, 0x18, 0x64, 0x34, 351 | 0xfd, 0xec, 0x0a, 0x68, 0x0f, 0xc8, 0x00, 0x0d, 0x0d, 0xf9, 0xf6, 0x65, 0xf0, 0x62, 0xeb, 0xd1, 352 | 0xf5, 0x0f, 0x15, 0xff, 0x12, 0x2b, 0xd1, 0x21, 0x9c, 0xe6, 0xf6, 0xd1, 0x9b, 0xdd, 0xbe, 0xdd, 353 | 0x7d, 0x44, 0x8d, 0x5f, 0xaa, 0x9b, 0x3f, 0xf5, 0x80, 0x8c, 0xec, 0x37, 0x8e, 0xc2, 0xd1, 0x7d, 354 | 0x8e, 0xad, 0x89, 0x59, 0x69, 0x5c, 0x76, 0xde, 0xdc, 0x7d, 0x05, 0x00, 0x00, 0xff, 0xff, 0xd4, 355 | 0xd3, 0x2d, 0xec, 0xf2, 0x02, 0x00, 0x00, 356 | } 357 | -------------------------------------------------------------------------------- /groupcachepb/groupcache.proto: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | syntax = "proto3"; 18 | 19 | package groupcachepb; 20 | 21 | import "google/protobuf/timestamp.proto"; 22 | 23 | message GetRequest { 24 | string group = 1; 25 | string key = 2; // not actually required/guaranteed to be UTF-8 26 | } 27 | 28 | message GetResponse { 29 | bytes value = 1; 30 | double minute_qps = 2; 31 | google.protobuf.Timestamp ttl = 3; 32 | } 33 | 34 | message ContainRequest { 35 | string group = 1; 36 | string key = 2; // not actually required/guaranteed to be UTF-8 37 | } 38 | 39 | message ContainResponse { 40 | bool exists = 1; 41 | double minute_qps = 2; 42 | } 43 | 44 | message PutRequest { 45 | string group = 1; 46 | string key = 2; // not actually required/guaranteed to be UTF-8 47 | bytes value = 3; 48 | google.protobuf.Timestamp ttl = 4; 49 | } 50 | 51 | message PutResponse { 52 | double minute_qps = 1; 53 | } 54 | 55 | service GroupCache { 56 | rpc Get(GetRequest) returns (GetResponse) {}; 57 | rpc Contain(ContainRequest) returns (ContainResponse) {}; 58 | rpc Put(PutRequest) returns (PutResponse) {}; 59 | } 60 | -------------------------------------------------------------------------------- /http.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package groupcache 18 | 19 | import ( 20 | "bytes" 21 | "encoding/json" 22 | "fmt" 23 | "io" 24 | "net/http" 25 | "net/url" 26 | "strings" 27 | "sync" 28 | "time" 29 | 30 | "github.com/golang/protobuf/proto" 31 | "github.com/golang/protobuf/ptypes" 32 | tspb "github.com/golang/protobuf/ptypes/timestamp" 33 | "github.com/twitter/groupcache/consistenthash" 34 | pb "github.com/twitter/groupcache/groupcachepb" 35 | ) 36 | 37 | const defaultBasePath = "/_groupcache/" 38 | 39 | const defaultReplicas = 50 40 | 41 | // HTTPPool implements PeerPicker for a pool of HTTP peers. 42 | type HTTPPool struct { 43 | // Context optionally specifies a context for the server to use when it 44 | // receives a request. 45 | // If nil, the server uses a nil Context. 46 | Context func(*http.Request) Context 47 | 48 | // Transport optionally specifies an http.RoundTripper for the client 49 | // to use when it makes a request. 50 | // If nil, the client uses http.DefaultTransport. 51 | Transport func(Context) http.RoundTripper 52 | 53 | // this peer's base URL, e.g. "https://example.net:8000" 54 | self string 55 | 56 | // opts specifies the options. 57 | opts HTTPPoolOptions 58 | 59 | mu sync.Mutex // guards peers and httpPeers 60 | peers *consistenthash.Map 61 | httpPeers map[string]*httpPeer // keyed by e.g. "http://10.0.0.2:8008" 62 | } 63 | 64 | // HTTPPoolOptions are the configurations of a HTTPPool. 65 | type HTTPPoolOptions struct { 66 | // BasePath specifies the HTTP path that will serve groupcache requests. 67 | // If blank, it defaults to "/_groupcache/". 68 | BasePath string 69 | 70 | // Replicas specifies the number of key replicas on the consistent hash. 71 | // If blank, it defaults to 50. 72 | Replicas int 73 | 74 | // HashFn specifies the hash function of the consistent hash. 75 | // If blank, it defaults to crc32.ChecksumIEEE. 76 | HashFn consistenthash.Hash 77 | } 78 | 79 | // NewHTTPPool initializes an HTTP pool of peers, and registers itself as a PeerPicker. 80 | // For convenience, it also registers itself as an http.Handler with http.DefaultServeMux. 81 | // The self argument should be a valid base URL that points to the current server, 82 | // for example "http://example.net:8000". 83 | func NewHTTPPool(self string) *HTTPPool { 84 | p := NewHTTPPoolOpts(self, nil) 85 | http.Handle(p.opts.BasePath, p) 86 | return p 87 | } 88 | 89 | var httpPoolMade bool 90 | 91 | // NewHTTPPoolOpts initializes an HTTP pool of peers with the given options. 92 | // Unlike NewHTTPPool, this function does not register the created pool as an HTTP handler. 93 | // The returned *HTTPPool implements http.Handler and must be registered using http.Handle. 94 | func NewHTTPPoolOpts(self string, o *HTTPPoolOptions) *HTTPPool { 95 | if httpPoolMade { 96 | panic("groupcache: NewHTTPPool must be called only once") 97 | } 98 | httpPoolMade = true 99 | 100 | p := &HTTPPool{ 101 | self: self, 102 | httpPeers: make(map[string]*httpPeer), 103 | } 104 | if o != nil { 105 | p.opts = *o 106 | } 107 | if p.opts.BasePath == "" { 108 | p.opts.BasePath = defaultBasePath 109 | } 110 | if p.opts.Replicas == 0 { 111 | p.opts.Replicas = defaultReplicas 112 | } 113 | p.peers = consistenthash.New(p.opts.Replicas, p.opts.HashFn) 114 | 115 | RegisterPeerPicker(func() PeerPicker { return p }) 116 | return p 117 | } 118 | 119 | // Set updates the pool's list of peers. 120 | // Each peer value should be a valid base URL, 121 | // for example "http://example.net:8000". 122 | func (p *HTTPPool) Set(peers ...string) { 123 | p.mu.Lock() 124 | defer p.mu.Unlock() 125 | p.peers = consistenthash.New(p.opts.Replicas, p.opts.HashFn) 126 | p.peers.Add(peers...) 127 | p.httpPeers = make(map[string]*httpPeer, len(peers)) 128 | for _, peer := range peers { 129 | p.httpPeers[peer] = &httpPeer{transport: p.Transport, baseURL: peer + p.opts.BasePath} 130 | } 131 | } 132 | 133 | func (p *HTTPPool) PickPeer(key string) (ProtoPeer, bool) { 134 | p.mu.Lock() 135 | defer p.mu.Unlock() 136 | if p.peers.IsEmpty() { 137 | return nil, false 138 | } 139 | if peer := p.peers.Get(key); peer != p.self { 140 | return p.httpPeers[peer], true 141 | } 142 | return nil, false 143 | } 144 | 145 | func handleGet(ctx Context, w http.ResponseWriter, group *Group, key string) { 146 | var value []byte 147 | ttl, err := group.Get(ctx, key, AllocatingByteSliceSink(&value)) 148 | if err != nil { 149 | http.Error(w, err.Error(), http.StatusInternalServerError) 150 | return 151 | } 152 | 153 | var ttlTimestamp *tspb.Timestamp = nil 154 | if ttl != nil { 155 | ttlTimestamp, err = ptypes.TimestampProto(*ttl) 156 | if err != nil { 157 | http.Error(w, err.Error(), http.StatusInternalServerError) 158 | return 159 | } 160 | } 161 | 162 | // Write the value to the response body as a proto message. 163 | body, err := proto.Marshal(&pb.GetResponse{Value: value, Ttl: ttlTimestamp}) 164 | if err != nil { 165 | http.Error(w, err.Error(), http.StatusInternalServerError) 166 | return 167 | } 168 | w.Header().Set("Content-Type", "application/x-protobuf") 169 | w.Write(body) 170 | } 171 | 172 | func handleContain(ctx Context, w http.ResponseWriter, group *Group, key string) { 173 | ok, err := group.Contain(ctx, key) 174 | if err != nil { 175 | http.Error(w, err.Error(), http.StatusInternalServerError) 176 | return 177 | } 178 | resp := &pb.ContainResponse{Exists: ok} 179 | 180 | // Write the value to the response body as a proto message. 181 | body, err := proto.Marshal(resp) 182 | if err != nil { 183 | http.Error(w, err.Error(), http.StatusInternalServerError) 184 | return 185 | } 186 | w.Header().Set("Content-Type", "application/x-protobuf") 187 | w.Write(body) 188 | } 189 | 190 | func handlePut(ctx Context, w http.ResponseWriter, group *Group, key string, reqBody io.ReadCloser) { 191 | buf := new(bytes.Buffer) 192 | buf.ReadFrom(reqBody) 193 | var p putBody 194 | err := json.Unmarshal(buf.Bytes(), &p) 195 | if err != nil { 196 | http.Error(w, err.Error(), http.StatusInternalServerError) 197 | return 198 | } 199 | var ttl *time.Time = nil 200 | if p.HasTTL { 201 | ttl = &p.TTL 202 | } 203 | err = group.Put(ctx, key, p.Value, ttl) 204 | if err != nil { 205 | http.Error(w, err.Error(), http.StatusInternalServerError) 206 | return 207 | } 208 | 209 | // Write the value to the response body as a proto message. 210 | body, err := proto.Marshal(&pb.PutResponse{}) 211 | if err != nil { 212 | http.Error(w, err.Error(), http.StatusInternalServerError) 213 | return 214 | } 215 | w.Header().Set("Content-Type", "application/x-protobuf") 216 | w.Write(body) 217 | } 218 | 219 | func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) { 220 | // Parse request. 221 | if !strings.HasPrefix(r.URL.Path, p.opts.BasePath) { 222 | panic("HTTPPool serving unexpected path: " + r.URL.Path) 223 | } 224 | parts := strings.SplitN(r.URL.Path[len(p.opts.BasePath):], "/", 2) 225 | if len(parts) != 2 { 226 | http.Error(w, "bad request", http.StatusBadRequest) 227 | return 228 | } 229 | groupName := parts[0] 230 | key := parts[1] 231 | 232 | // Fetch the value for this group/key. 233 | group := GetGroup(groupName) 234 | if group == nil { 235 | http.Error(w, "no such group: "+groupName, http.StatusNotFound) 236 | return 237 | } 238 | var ctx Context 239 | if p.Context != nil { 240 | ctx = p.Context(r) 241 | } 242 | group.Stats.ServerRequests.Add(1) 243 | switch r.Method { 244 | case "GET": 245 | if _, ok := r.URL.Query()["exist"]; ok { 246 | handleContain(ctx, w, group, key) 247 | return 248 | } 249 | handleGet(ctx, w, group, key) 250 | case "POST": 251 | handlePut(ctx, w, group, key, r.Body) 252 | default: 253 | http.Error(w, fmt.Sprintf("unsuported method: %s", r.Method), http.StatusBadRequest) 254 | return 255 | } 256 | } 257 | 258 | // httpPeer implements the ProtoPeer interface 259 | type httpPeer struct { 260 | transport func(Context) http.RoundTripper 261 | baseURL string 262 | } 263 | 264 | var bufferPool = sync.Pool{ 265 | New: func() interface{} { return new(bytes.Buffer) }, 266 | } 267 | 268 | // putBody encapsulates groupcache data for peer to peer put messages. 269 | // The TTL field should only be used if HasTTL is true. 270 | type putBody struct { 271 | Value []byte 272 | HasTTL bool 273 | TTL time.Time 274 | } 275 | 276 | func (h *httpPeer) Get(context Context, in *pb.GetRequest, out *pb.GetResponse) error { 277 | u := fmt.Sprintf( 278 | "%v%v/%v", 279 | h.baseURL, 280 | url.QueryEscape(in.GetGroup()), 281 | url.QueryEscape(in.GetKey()), 282 | ) 283 | req, err := http.NewRequest("GET", u, nil) 284 | if err != nil { 285 | return err 286 | } 287 | tr := http.DefaultTransport 288 | if h.transport != nil { 289 | tr = h.transport(context) 290 | } 291 | res, err := tr.RoundTrip(req) 292 | if err != nil { 293 | return err 294 | } 295 | defer res.Body.Close() 296 | if res.StatusCode != http.StatusOK { 297 | return fmt.Errorf("server returned: %v", res.Status) 298 | } 299 | b := bufferPool.Get().(*bytes.Buffer) 300 | b.Reset() 301 | defer bufferPool.Put(b) 302 | _, err = io.Copy(b, res.Body) 303 | if err != nil { 304 | return fmt.Errorf("reading response body: %v", err) 305 | } 306 | err = proto.Unmarshal(b.Bytes(), out) 307 | if err != nil { 308 | return fmt.Errorf("decoding response body: %v", err) 309 | } 310 | return nil 311 | } 312 | 313 | func (h *httpPeer) Contain(context Context, in *pb.ContainRequest, out *pb.ContainResponse) error { 314 | u := fmt.Sprintf( 315 | "%v%v/%v?exist", 316 | h.baseURL, 317 | url.QueryEscape(in.GetGroup()), 318 | url.QueryEscape(in.GetKey()), 319 | ) 320 | req, err := http.NewRequest("GET", u, nil) 321 | if err != nil { 322 | return err 323 | } 324 | tr := http.DefaultTransport 325 | if h.transport != nil { 326 | tr = h.transport(context) 327 | } 328 | res, err := tr.RoundTrip(req) 329 | if err != nil { 330 | return err 331 | } 332 | defer res.Body.Close() 333 | if res.StatusCode != http.StatusOK { 334 | return fmt.Errorf("server returned: %v", res.Status) 335 | } 336 | b := bufferPool.Get().(*bytes.Buffer) 337 | b.Reset() 338 | defer bufferPool.Put(b) 339 | _, err = io.Copy(b, res.Body) 340 | if err != nil { 341 | return fmt.Errorf("reading response body: %v", err) 342 | } 343 | err = proto.Unmarshal(b.Bytes(), out) 344 | if err != nil { 345 | return fmt.Errorf("decoding response body: %v", err) 346 | } 347 | return nil 348 | } 349 | 350 | func (h *httpPeer) Put(context Context, in *pb.PutRequest, out *pb.PutResponse) error { 351 | u := fmt.Sprintf( 352 | "%v%v/%v", 353 | h.baseURL, 354 | url.QueryEscape(in.GetGroup()), 355 | url.QueryEscape(in.GetKey()), 356 | ) 357 | var ttl time.Time 358 | hasTTL := false 359 | var err error 360 | if in.GetTtl() != nil { 361 | ttl, err = ptypes.Timestamp(in.GetTtl()) 362 | if err != nil { 363 | return err 364 | } 365 | hasTTL = true 366 | } 367 | p := putBody{Value: in.GetValue(), HasTTL: hasTTL, TTL: ttl} 368 | jb, err := json.Marshal(p) 369 | if err != nil { 370 | return err 371 | } 372 | reader := bytes.NewReader(jb) 373 | req, err := http.NewRequest("POST", u, reader) 374 | if err != nil { 375 | return err 376 | } 377 | tr := http.DefaultTransport 378 | if h.transport != nil { 379 | tr = h.transport(context) 380 | } 381 | res, err := tr.RoundTrip(req) 382 | if err != nil { 383 | return err 384 | } 385 | defer res.Body.Close() 386 | if res.StatusCode != http.StatusOK { 387 | return fmt.Errorf("server returned: %v", res.Status) 388 | } 389 | b := bufferPool.Get().(*bytes.Buffer) 390 | b.Reset() 391 | defer bufferPool.Put(b) 392 | _, err = io.Copy(b, res.Body) 393 | if err != nil { 394 | return fmt.Errorf("reading response body: %v", err) 395 | } 396 | err = proto.Unmarshal(b.Bytes(), out) 397 | if err != nil { 398 | return fmt.Errorf("decoding response body: %v", err) 399 | } 400 | return nil 401 | } 402 | -------------------------------------------------------------------------------- /http_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package groupcache 18 | 19 | import ( 20 | "errors" 21 | "flag" 22 | "log" 23 | "net" 24 | "net/http" 25 | "os" 26 | "os/exec" 27 | "strconv" 28 | "strings" 29 | "sync" 30 | "testing" 31 | "time" 32 | ) 33 | 34 | var ( 35 | peerAddrs = flag.String("test_peer_addrs", "", "Comma-separated list of peer addresses; used by TestHTTPPool") 36 | peerIndex = flag.Int("test_peer_index", -1, "Index of which peer this child is; used by TestHTTPPool") 37 | peerChild = flag.Bool("test_peer_child", false, "True if running as a child process; used by TestHTTPPool") 38 | ) 39 | 40 | func TestHTTPPool(t *testing.T) { 41 | if *peerChild { 42 | beChildForTestHTTPPool() 43 | os.Exit(0) 44 | } 45 | 46 | const ( 47 | nChild = 4 48 | nGets = 100 49 | nContains = 100 50 | nPuts = 100 51 | ) 52 | 53 | var childAddr []string 54 | for i := 0; i < nChild; i++ { 55 | childAddr = append(childAddr, pickFreeAddr(t)) 56 | } 57 | 58 | var cmds []*exec.Cmd 59 | var wg sync.WaitGroup 60 | for i := 0; i < nChild; i++ { 61 | cmd := exec.Command(os.Args[0], 62 | "--test.run=TestHTTPPool", 63 | "--test_peer_child", 64 | "--test_peer_addrs="+strings.Join(childAddr, ","), 65 | "--test_peer_index="+strconv.Itoa(i), 66 | ) 67 | cmds = append(cmds, cmd) 68 | wg.Add(1) 69 | if err := cmd.Start(); err != nil { 70 | t.Fatal("failed to start child process: ", err) 71 | } 72 | go awaitAddrReady(t, childAddr[i], &wg) 73 | } 74 | defer func() { 75 | for i := 0; i < nChild; i++ { 76 | if cmds[i].Process != nil { 77 | cmds[i].Process.Kill() 78 | } 79 | } 80 | }() 81 | wg.Wait() 82 | 83 | // Use a dummy self address so that we don't handle gets in-process. 84 | p := NewHTTPPool("should-be-ignored") 85 | p.Set(addrToURL(childAddr)...) 86 | 87 | // Dummy getter function. Gets should go to children only. 88 | // The only time this process will handle a get is when the 89 | // children can't be contacted for some reason. 90 | getter := GetterFunc(func(ctx Context, key string, dest Sink) (*time.Time, error) { 91 | return &ttl, errors.New("parent getter called; something's wrong") 92 | }) 93 | // Dummy putter function 94 | container := ContainerFunc(func(ctx Context, key string) (bool, error) { 95 | return false, errors.New("parent container called; something's wrong") 96 | }) 97 | // Dummy putter function 98 | putter := PutterFunc(func(ctx Context, key string, data []byte, ttl *time.Time) error { 99 | return errors.New("parent putter called; something's wrong") 100 | }) 101 | g := NewGroup("httpPoolTest", cacheSize, getter, container, putter) 102 | 103 | for _, key := range testKeys(nGets) { 104 | var value string 105 | if _, err := g.Get(nil, key, StringSink(&value)); err != nil { 106 | t.Fatal(err) 107 | } 108 | if suffix := ":" + key; !strings.HasSuffix(value, suffix) { 109 | t.Errorf("Get(%q) = %q, want value ending in %q", key, value, suffix) 110 | } 111 | t.Logf("Get key=%q, value=%q (peer:key)", key, value) 112 | } 113 | 114 | for _, key := range testKeys(nContains) { 115 | ok, err := g.Contain(nil, key) 116 | if err != nil { 117 | t.Fatal(err) 118 | } 119 | t.Logf("Contain key=%q, ok=%t (peer:key)", key, ok) 120 | } 121 | 122 | // we can't verify the output from a child process easily, so just check for an error 123 | for _, key := range testKeys(nPuts) { 124 | value := []byte(key) 125 | if err := g.Put(nil, key, value, &ttl); err != nil { 126 | t.Fatal(err) 127 | } 128 | t.Logf("Put key=%q, value=%q (peer:key)", key, value) 129 | } 130 | } 131 | 132 | func testKeys(n int) (keys []string) { 133 | keys = make([]string, n) 134 | for i := range keys { 135 | keys[i] = strconv.Itoa(i) 136 | } 137 | return 138 | } 139 | 140 | func beChildForTestHTTPPool() { 141 | addrs := strings.Split(*peerAddrs, ",") 142 | 143 | p := NewHTTPPool("http://" + addrs[*peerIndex]) 144 | p.Set(addrToURL(addrs)...) 145 | 146 | getter := GetterFunc(func(ctx Context, key string, dest Sink) (*time.Time, error) { 147 | dest.SetString(strconv.Itoa(*peerIndex) + ":" + key) 148 | return &ttl, nil 149 | }) 150 | container := ContainerFunc(func(ctx Context, key string) (bool, error) { 151 | return true, nil 152 | }) 153 | putter := PutterFunc(func(ctx Context, key string, data []byte, ttl *time.Time) error { 154 | return nil 155 | }) 156 | NewGroup("httpPoolTest", cacheSize, getter, container, putter) 157 | 158 | log.Fatal(http.ListenAndServe(addrs[*peerIndex], p)) 159 | } 160 | 161 | // This is racy. Another process could swoop in and steal the port between the 162 | // call to this function and the next listen call. Should be okay though. 163 | // The proper way would be to pass the l.File() as ExtraFiles to the child 164 | // process, and then close your copy once the child starts. 165 | func pickFreeAddr(t *testing.T) string { 166 | l, err := net.Listen("tcp", "127.0.0.1:0") 167 | if err != nil { 168 | t.Fatal(err) 169 | } 170 | defer l.Close() 171 | return l.Addr().String() 172 | } 173 | 174 | func addrToURL(addr []string) []string { 175 | url := make([]string, len(addr)) 176 | for i := range addr { 177 | url[i] = "http://" + addr[i] 178 | } 179 | return url 180 | } 181 | 182 | func awaitAddrReady(t *testing.T, addr string, wg *sync.WaitGroup) { 183 | defer wg.Done() 184 | const max = 1 * time.Second 185 | tries := 0 186 | for { 187 | tries++ 188 | c, err := net.Dial("tcp", addr) 189 | if err == nil { 190 | c.Close() 191 | return 192 | } 193 | delay := time.Duration(tries) * 25 * time.Millisecond 194 | if delay > max { 195 | delay = max 196 | } 197 | time.Sleep(delay) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /lru/lru.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package lru implements an LRU cache. 18 | package lru 19 | 20 | import "container/list" 21 | 22 | // Cache is an LRU cache. It is not safe for concurrent access. 23 | type Cache struct { 24 | // MaxEntries is the maximum number of cache entries before 25 | // an item is evicted. Zero means no limit. 26 | MaxEntries int 27 | 28 | // OnEvicted optionally specificies a callback function to be 29 | // executed when an entry is purged from the cache. 30 | OnEvicted func(key Key, value interface{}) 31 | 32 | ll *list.List 33 | cache map[interface{}]*list.Element 34 | } 35 | 36 | // A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators 37 | type Key interface{} 38 | 39 | type entry struct { 40 | key Key 41 | value interface{} 42 | } 43 | 44 | // New creates a new Cache. 45 | // If maxEntries is zero, the cache has no limit and it's assumed 46 | // that eviction is done by the caller. 47 | func New(maxEntries int) *Cache { 48 | return &Cache{ 49 | MaxEntries: maxEntries, 50 | ll: list.New(), 51 | cache: make(map[interface{}]*list.Element), 52 | } 53 | } 54 | 55 | // Add adds a value to the cache. 56 | func (c *Cache) Add(key Key, value interface{}) { 57 | if c.cache == nil { 58 | c.cache = make(map[interface{}]*list.Element) 59 | c.ll = list.New() 60 | } 61 | if ee, ok := c.cache[key]; ok { 62 | c.ll.MoveToFront(ee) 63 | ee.Value.(*entry).value = value 64 | return 65 | } 66 | ele := c.ll.PushFront(&entry{key, value}) 67 | c.cache[key] = ele 68 | if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries { 69 | c.RemoveOldest() 70 | } 71 | } 72 | 73 | // Get looks up a key's value from the cache. 74 | func (c *Cache) Get(key Key) (value interface{}, ok bool) { 75 | if c.cache == nil { 76 | return 77 | } 78 | if ele, hit := c.cache[key]; hit { 79 | c.ll.MoveToFront(ele) 80 | return ele.Value.(*entry).value, true 81 | } 82 | return 83 | } 84 | 85 | // Remove removes the provided key from the cache. 86 | func (c *Cache) Remove(key Key) { 87 | if c.cache == nil { 88 | return 89 | } 90 | if ele, hit := c.cache[key]; hit { 91 | c.removeElement(ele) 92 | } 93 | } 94 | 95 | // RemoveOldest removes the oldest item from the cache. 96 | func (c *Cache) RemoveOldest() { 97 | if c.cache == nil { 98 | return 99 | } 100 | ele := c.ll.Back() 101 | if ele != nil { 102 | c.removeElement(ele) 103 | } 104 | } 105 | 106 | func (c *Cache) removeElement(e *list.Element) { 107 | c.ll.Remove(e) 108 | kv := e.Value.(*entry) 109 | delete(c.cache, kv.key) 110 | if c.OnEvicted != nil { 111 | c.OnEvicted(kv.key, kv.value) 112 | } 113 | } 114 | 115 | // Len returns the number of items in the cache. 116 | func (c *Cache) Len() int { 117 | if c.cache == nil { 118 | return 0 119 | } 120 | return c.ll.Len() 121 | } 122 | 123 | // Clear purges all stored items from the cache. 124 | func (c *Cache) Clear() { 125 | if c.OnEvicted != nil { 126 | for _, e := range c.cache { 127 | kv := e.Value.(*entry) 128 | c.OnEvicted(kv.key, kv.value) 129 | } 130 | } 131 | c.ll = nil 132 | c.cache = nil 133 | } 134 | -------------------------------------------------------------------------------- /lru/lru_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package lru 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | ) 23 | 24 | type simpleStruct struct { 25 | int 26 | string 27 | } 28 | 29 | type complexStruct struct { 30 | int 31 | simpleStruct 32 | } 33 | 34 | var getTests = []struct { 35 | name string 36 | keyToAdd interface{} 37 | keyToGet interface{} 38 | expectedOk bool 39 | }{ 40 | {"string_hit", "myKey", "myKey", true}, 41 | {"string_miss", "myKey", "nonsense", false}, 42 | {"simple_struct_hit", simpleStruct{1, "two"}, simpleStruct{1, "two"}, true}, 43 | {"simple_struct_miss", simpleStruct{1, "two"}, simpleStruct{0, "noway"}, false}, 44 | {"complex_struct_hit", complexStruct{1, simpleStruct{2, "three"}}, 45 | complexStruct{1, simpleStruct{2, "three"}}, true}, 46 | } 47 | 48 | func TestGet(t *testing.T) { 49 | for _, tt := range getTests { 50 | lru := New(0) 51 | lru.Add(tt.keyToAdd, 1234) 52 | val, ok := lru.Get(tt.keyToGet) 53 | if ok != tt.expectedOk { 54 | t.Fatalf("%s: cache hit = %v; want %v", tt.name, ok, !ok) 55 | } else if ok && val != 1234 { 56 | t.Fatalf("%s expected get to return 1234 but got %v", tt.name, val) 57 | } 58 | } 59 | } 60 | 61 | func TestRemove(t *testing.T) { 62 | lru := New(0) 63 | lru.Add("myKey", 1234) 64 | if val, ok := lru.Get("myKey"); !ok { 65 | t.Fatal("TestRemove returned no match") 66 | } else if val != 1234 { 67 | t.Fatalf("TestRemove failed. Expected %d, got %v", 1234, val) 68 | } 69 | 70 | lru.Remove("myKey") 71 | if _, ok := lru.Get("myKey"); ok { 72 | t.Fatal("TestRemove returned a removed entry") 73 | } 74 | } 75 | 76 | func TestEvict(t *testing.T) { 77 | evictedKeys := make([]Key, 0) 78 | onEvictedFun := func(key Key, value interface{}) { 79 | evictedKeys = append(evictedKeys, key) 80 | } 81 | 82 | lru := New(20) 83 | lru.OnEvicted = onEvictedFun 84 | for i := 0; i < 22; i++ { 85 | lru.Add(fmt.Sprintf("myKey%d", i), 1234) 86 | } 87 | 88 | if len(evictedKeys) != 2 { 89 | t.Fatalf("got %d evicted keys; want 2", len(evictedKeys)) 90 | } 91 | if evictedKeys[0] != Key("myKey0") { 92 | t.Fatalf("got %v in first evicted key; want %s", evictedKeys[0], "myKey0") 93 | } 94 | if evictedKeys[1] != Key("myKey1") { 95 | t.Fatalf("got %v in second evicted key; want %s", evictedKeys[1], "myKey1") 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /peers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // peers.go defines how processes find and communicate with their peers. 18 | 19 | package groupcache 20 | 21 | import ( 22 | pb "github.com/twitter/groupcache/groupcachepb" 23 | ) 24 | 25 | // Context is an opaque value passed through calls to the 26 | // ProtoPeer. It may be nil if your ProtoPeer implementation does 27 | // not require a context. 28 | type Context interface{} 29 | 30 | // ProtoPeer is the interface that must be implemented by a peer. 31 | type ProtoPeer interface { 32 | Get(context Context, in *pb.GetRequest, out *pb.GetResponse) error 33 | Contain(context Context, in *pb.ContainRequest, out *pb.ContainResponse) error 34 | Put(context Context, in *pb.PutRequest, out *pb.PutResponse) error 35 | } 36 | 37 | // PeerPicker is the interface that must be implemented to locate 38 | // the peer that owns a specific key. 39 | type PeerPicker interface { 40 | // PickPeer returns the peer that owns the specific key 41 | // and true to indicate that a remote peer was nominated. 42 | // It returns nil, false if the key owner is the current peer. 43 | PickPeer(key string) (peer ProtoPeer, ok bool) 44 | } 45 | 46 | // NoPeers is an implementation of PeerPicker that never finds a peer. 47 | type NoPeers struct{} 48 | 49 | func (NoPeers) PickPeer(key string) (peer ProtoPeer, ok bool) { return } 50 | 51 | var ( 52 | portPicker func(groupName string) PeerPicker 53 | ) 54 | 55 | // RegisterPeerPicker registers the peer initialization function. 56 | // It is called once, when the first group is created. 57 | // Either RegisterPeerPicker or RegisterPerGroupPeerPicker should be 58 | // called exactly once, but not both. 59 | func RegisterPeerPicker(fn func() PeerPicker) { 60 | if portPicker != nil { 61 | panic("RegisterPeerPicker called more than once") 62 | } 63 | portPicker = func(_ string) PeerPicker { return fn() } 64 | } 65 | 66 | // RegisterPerGroupPeerPicker registers the peer initialization function, 67 | // which takes the groupName, to be used in choosing a PeerPicker. 68 | // It is called once, when the first group is created. 69 | // Either RegisterPeerPicker or RegisterPerGroupPeerPicker should be 70 | // called exactly once, but not both. 71 | func RegisterPerGroupPeerPicker(fn func(groupName string) PeerPicker) { 72 | if portPicker != nil { 73 | panic("RegisterPeerPicker called more than once") 74 | } 75 | portPicker = fn 76 | } 77 | 78 | func getPeers(groupName string) PeerPicker { 79 | if portPicker == nil { 80 | return NoPeers{} 81 | } 82 | pk := portPicker(groupName) 83 | if pk == nil { 84 | pk = NoPeers{} 85 | } 86 | return pk 87 | } 88 | -------------------------------------------------------------------------------- /singleflight/singleflight.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package singleflight provides a duplicate function call suppression 18 | // mechanism. 19 | package singleflight 20 | 21 | import "sync" 22 | 23 | // call is an in-flight or completed Do call 24 | type call struct { 25 | wg sync.WaitGroup 26 | val interface{} 27 | err error 28 | } 29 | 30 | // Group represents a class of work and forms a namespace in which 31 | // units of work can be executed with duplicate suppression. 32 | type Group struct { 33 | mu sync.Mutex // protects m 34 | m map[string]*call // lazily initialized 35 | } 36 | 37 | // Do executes and returns the results of the given function, making 38 | // sure that only one execution is in-flight for a given key at a 39 | // time. If a duplicate comes in, the duplicate caller waits for the 40 | // original to complete and receives the same results. 41 | func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { 42 | g.mu.Lock() 43 | if g.m == nil { 44 | g.m = make(map[string]*call) 45 | } 46 | if c, ok := g.m[key]; ok { 47 | g.mu.Unlock() 48 | c.wg.Wait() 49 | return c.val, c.err 50 | } 51 | c := new(call) 52 | c.wg.Add(1) 53 | g.m[key] = c 54 | g.mu.Unlock() 55 | 56 | c.val, c.err = fn() 57 | c.wg.Done() 58 | 59 | g.mu.Lock() 60 | delete(g.m, key) 61 | g.mu.Unlock() 62 | 63 | return c.val, c.err 64 | } 65 | -------------------------------------------------------------------------------- /singleflight/singleflight_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package singleflight 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "sync" 23 | "sync/atomic" 24 | "testing" 25 | "time" 26 | ) 27 | 28 | func TestDo(t *testing.T) { 29 | var g Group 30 | v, err := g.Do("key", func() (interface{}, error) { 31 | return "bar", nil 32 | }) 33 | if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want { 34 | t.Errorf("Do = %v; want %v", got, want) 35 | } 36 | if err != nil { 37 | t.Errorf("Do error = %v", err) 38 | } 39 | } 40 | 41 | func TestDoErr(t *testing.T) { 42 | var g Group 43 | someErr := errors.New("Some error") 44 | v, err := g.Do("key", func() (interface{}, error) { 45 | return nil, someErr 46 | }) 47 | if err != someErr { 48 | t.Errorf("Do error = %v; want someErr", err) 49 | } 50 | if v != nil { 51 | t.Errorf("unexpected non-nil value %#v", v) 52 | } 53 | } 54 | 55 | func TestDoDupSuppress(t *testing.T) { 56 | var g Group 57 | c := make(chan string) 58 | var calls int32 59 | fn := func() (interface{}, error) { 60 | atomic.AddInt32(&calls, 1) 61 | return <-c, nil 62 | } 63 | 64 | const n = 10 65 | var wg sync.WaitGroup 66 | for i := 0; i < n; i++ { 67 | wg.Add(1) 68 | go func() { 69 | v, err := g.Do("key", fn) 70 | if err != nil { 71 | t.Errorf("Do error: %v", err) 72 | } 73 | if v.(string) != "bar" { 74 | t.Errorf("got %q; want %q", v, "bar") 75 | } 76 | wg.Done() 77 | }() 78 | } 79 | time.Sleep(100 * time.Millisecond) // let goroutines above block 80 | c <- "bar" 81 | wg.Wait() 82 | if got := atomic.LoadInt32(&calls); got != 1 { 83 | t.Errorf("number of calls = %d; want 1", got) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /sinks.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package groupcache 18 | 19 | import ( 20 | "errors" 21 | 22 | "github.com/golang/protobuf/proto" 23 | ) 24 | 25 | // A Sink receives data from a Get call. 26 | // 27 | // Implementation of Getter must call exactly one of the Set methods 28 | // on success. 29 | type Sink interface { 30 | // SetString sets the value to s. 31 | SetString(s string) error 32 | 33 | // SetBytes sets the value to the contents of v. 34 | // The caller retains ownership of v. 35 | SetBytes(v []byte) error 36 | 37 | // SetProto sets the value to the encoded version of m. 38 | // The caller retains ownership of m. 39 | SetProto(m proto.Message) error 40 | 41 | // view returns a frozen view of the bytes for caching. 42 | view() (ByteView, error) 43 | } 44 | 45 | func cloneBytes(b []byte) []byte { 46 | c := make([]byte, len(b)) 47 | copy(c, b) 48 | return c 49 | } 50 | 51 | func setSinkView(s Sink, v ByteView) error { 52 | // A viewSetter is a Sink that can also receive its value from 53 | // a ByteView. This is a fast path to minimize copies when the 54 | // item was already cached locally in memory (where it's 55 | // cached as a ByteView) 56 | type viewSetter interface { 57 | setView(v ByteView) error 58 | } 59 | if vs, ok := s.(viewSetter); ok { 60 | return vs.setView(v) 61 | } 62 | if v.b != nil { 63 | return s.SetBytes(v.b) 64 | } 65 | return s.SetString(v.s) 66 | } 67 | 68 | // StringSink returns a Sink that populates the provided string pointer. 69 | func StringSink(sp *string) Sink { 70 | return &stringSink{sp: sp} 71 | } 72 | 73 | type stringSink struct { 74 | sp *string 75 | v ByteView 76 | // TODO(bradfitz): track whether any Sets were called. 77 | } 78 | 79 | func (s *stringSink) view() (ByteView, error) { 80 | // TODO(bradfitz): return an error if no Set was called 81 | return s.v, nil 82 | } 83 | 84 | func (s *stringSink) SetString(v string) error { 85 | s.v.b = nil 86 | s.v.s = v 87 | *s.sp = v 88 | return nil 89 | } 90 | 91 | func (s *stringSink) SetBytes(v []byte) error { 92 | return s.SetString(string(v)) 93 | } 94 | 95 | func (s *stringSink) SetProto(m proto.Message) error { 96 | b, err := proto.Marshal(m) 97 | if err != nil { 98 | return err 99 | } 100 | s.v.b = b 101 | *s.sp = string(b) 102 | return nil 103 | } 104 | 105 | // ByteViewSink returns a Sink that populates a ByteView. 106 | func ByteViewSink(dst *ByteView) Sink { 107 | if dst == nil { 108 | panic("nil dst") 109 | } 110 | return &byteViewSink{dst: dst} 111 | } 112 | 113 | type byteViewSink struct { 114 | dst *ByteView 115 | 116 | // if this code ever ends up tracking that at least one set* 117 | // method was called, don't make it an error to call set 118 | // methods multiple times. Lorry's payload.go does that, and 119 | // it makes sense. The comment at the top of this file about 120 | // "exactly one of the Set methods" is overly strict. We 121 | // really care about at least once (in a handler), but if 122 | // multiple handlers fail (or multiple functions in a program 123 | // using a Sink), it's okay to re-use the same one. 124 | } 125 | 126 | func (s *byteViewSink) setView(v ByteView) error { 127 | *s.dst = v 128 | return nil 129 | } 130 | 131 | func (s *byteViewSink) view() (ByteView, error) { 132 | return *s.dst, nil 133 | } 134 | 135 | func (s *byteViewSink) SetProto(m proto.Message) error { 136 | b, err := proto.Marshal(m) 137 | if err != nil { 138 | return err 139 | } 140 | *s.dst = ByteView{b: b} 141 | return nil 142 | } 143 | 144 | func (s *byteViewSink) SetBytes(b []byte) error { 145 | *s.dst = ByteView{b: cloneBytes(b)} 146 | return nil 147 | } 148 | 149 | func (s *byteViewSink) SetString(v string) error { 150 | *s.dst = ByteView{s: v} 151 | return nil 152 | } 153 | 154 | // ProtoSink returns a sink that unmarshals binary proto values into m. 155 | func ProtoSink(m proto.Message) Sink { 156 | return &protoSink{ 157 | dst: m, 158 | } 159 | } 160 | 161 | type protoSink struct { 162 | dst proto.Message // authoritative value 163 | typ string 164 | 165 | v ByteView // encoded 166 | } 167 | 168 | func (s *protoSink) view() (ByteView, error) { 169 | return s.v, nil 170 | } 171 | 172 | func (s *protoSink) SetBytes(b []byte) error { 173 | err := proto.Unmarshal(b, s.dst) 174 | if err != nil { 175 | return err 176 | } 177 | s.v.b = cloneBytes(b) 178 | s.v.s = "" 179 | return nil 180 | } 181 | 182 | func (s *protoSink) SetString(v string) error { 183 | b := []byte(v) 184 | err := proto.Unmarshal(b, s.dst) 185 | if err != nil { 186 | return err 187 | } 188 | s.v.b = b 189 | s.v.s = "" 190 | return nil 191 | } 192 | 193 | func (s *protoSink) SetProto(m proto.Message) error { 194 | b, err := proto.Marshal(m) 195 | if err != nil { 196 | return err 197 | } 198 | // TODO(bradfitz): optimize for same-task case more and write 199 | // right through? would need to document ownership rules at 200 | // the same time. but then we could just assign *dst = *m 201 | // here. This works for now: 202 | err = proto.Unmarshal(b, s.dst) 203 | if err != nil { 204 | return err 205 | } 206 | s.v.b = b 207 | s.v.s = "" 208 | return nil 209 | } 210 | 211 | // AllocatingByteSliceSink returns a Sink that allocates 212 | // a byte slice to hold the received value and assigns 213 | // it to *dst. The memory is not retained by groupcache. 214 | func AllocatingByteSliceSink(dst *[]byte) Sink { 215 | return &allocBytesSink{dst: dst} 216 | } 217 | 218 | type allocBytesSink struct { 219 | dst *[]byte 220 | v ByteView 221 | } 222 | 223 | func (s *allocBytesSink) view() (ByteView, error) { 224 | return s.v, nil 225 | } 226 | 227 | func (s *allocBytesSink) setView(v ByteView) error { 228 | if v.b != nil { 229 | *s.dst = cloneBytes(v.b) 230 | } else { 231 | *s.dst = []byte(v.s) 232 | } 233 | s.v = v 234 | return nil 235 | } 236 | 237 | func (s *allocBytesSink) SetProto(m proto.Message) error { 238 | b, err := proto.Marshal(m) 239 | if err != nil { 240 | return err 241 | } 242 | return s.setBytesOwned(b) 243 | } 244 | 245 | func (s *allocBytesSink) SetBytes(b []byte) error { 246 | return s.setBytesOwned(cloneBytes(b)) 247 | } 248 | 249 | func (s *allocBytesSink) setBytesOwned(b []byte) error { 250 | if s.dst == nil { 251 | return errors.New("nil AllocatingByteSliceSink *[]byte dst") 252 | } 253 | *s.dst = cloneBytes(b) // another copy, protecting the read-only s.v.b view 254 | s.v.b = b 255 | s.v.s = "" 256 | return nil 257 | } 258 | 259 | func (s *allocBytesSink) SetString(v string) error { 260 | if s.dst == nil { 261 | return errors.New("nil AllocatingByteSliceSink *[]byte dst") 262 | } 263 | *s.dst = []byte(v) 264 | s.v.b = nil 265 | s.v.s = v 266 | return nil 267 | } 268 | 269 | // TruncatingByteSliceSink returns a Sink that writes up to len(*dst) 270 | // bytes to *dst. If more bytes are available, they're silently 271 | // truncated. If fewer bytes are available than len(*dst), *dst 272 | // is shrunk to fit the number of bytes available. 273 | func TruncatingByteSliceSink(dst *[]byte) Sink { 274 | return &truncBytesSink{dst: dst} 275 | } 276 | 277 | type truncBytesSink struct { 278 | dst *[]byte 279 | v ByteView 280 | } 281 | 282 | func (s *truncBytesSink) view() (ByteView, error) { 283 | return s.v, nil 284 | } 285 | 286 | func (s *truncBytesSink) SetProto(m proto.Message) error { 287 | b, err := proto.Marshal(m) 288 | if err != nil { 289 | return err 290 | } 291 | return s.setBytesOwned(b) 292 | } 293 | 294 | func (s *truncBytesSink) SetBytes(b []byte) error { 295 | return s.setBytesOwned(cloneBytes(b)) 296 | } 297 | 298 | func (s *truncBytesSink) setBytesOwned(b []byte) error { 299 | if s.dst == nil { 300 | return errors.New("nil TruncatingByteSliceSink *[]byte dst") 301 | } 302 | n := copy(*s.dst, b) 303 | if n < len(*s.dst) { 304 | *s.dst = (*s.dst)[:n] 305 | } 306 | s.v.b = b 307 | s.v.s = "" 308 | return nil 309 | } 310 | 311 | func (s *truncBytesSink) SetString(v string) error { 312 | if s.dst == nil { 313 | return errors.New("nil TruncatingByteSliceSink *[]byte dst") 314 | } 315 | n := copy(*s.dst, v) 316 | if n < len(*s.dst) { 317 | *s.dst = (*s.dst)[:n] 318 | } 319 | s.v.b = nil 320 | s.v.s = v 321 | return nil 322 | } 323 | -------------------------------------------------------------------------------- /testCoverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "" > coverage.txt 5 | 6 | for d in $(go list ./...); do 7 | go test -timeout 120s -race -coverprofile=profile.out -covermode=atomic $d 8 | if [ -f profile.out ]; then 9 | cat profile.out >> coverage.txt 10 | rm profile.out 11 | fi 12 | done 13 | -------------------------------------------------------------------------------- /testpb/test.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: test.proto 3 | // DO NOT EDIT! 4 | 5 | package testpb 6 | 7 | import proto "github.com/golang/protobuf/proto" 8 | import json "encoding/json" 9 | import math "math" 10 | 11 | // Reference proto, json, and math imports to suppress error if they are not otherwise used. 12 | var _ = proto.Marshal 13 | var _ = &json.SyntaxError{} 14 | var _ = math.Inf 15 | 16 | type TestMessage struct { 17 | Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` 18 | City *string `protobuf:"bytes,2,opt,name=city" json:"city,omitempty"` 19 | XXX_unrecognized []byte `json:"-"` 20 | } 21 | 22 | func (m *TestMessage) Reset() { *m = TestMessage{} } 23 | func (m *TestMessage) String() string { return proto.CompactTextString(m) } 24 | func (*TestMessage) ProtoMessage() {} 25 | 26 | func (m *TestMessage) GetName() string { 27 | if m != nil && m.Name != nil { 28 | return *m.Name 29 | } 30 | return "" 31 | } 32 | 33 | func (m *TestMessage) GetCity() string { 34 | if m != nil && m.City != nil { 35 | return *m.City 36 | } 37 | return "" 38 | } 39 | 40 | type TestRequest struct { 41 | Lower *string `protobuf:"bytes,1,req,name=lower" json:"lower,omitempty"` 42 | RepeatCount *int32 `protobuf:"varint,2,opt,name=repeat_count,def=1" json:"repeat_count,omitempty"` 43 | XXX_unrecognized []byte `json:"-"` 44 | } 45 | 46 | func (m *TestRequest) Reset() { *m = TestRequest{} } 47 | func (m *TestRequest) String() string { return proto.CompactTextString(m) } 48 | func (*TestRequest) ProtoMessage() {} 49 | 50 | const Default_TestRequest_RepeatCount int32 = 1 51 | 52 | func (m *TestRequest) GetLower() string { 53 | if m != nil && m.Lower != nil { 54 | return *m.Lower 55 | } 56 | return "" 57 | } 58 | 59 | func (m *TestRequest) GetRepeatCount() int32 { 60 | if m != nil && m.RepeatCount != nil { 61 | return *m.RepeatCount 62 | } 63 | return Default_TestRequest_RepeatCount 64 | } 65 | 66 | type TestResponse struct { 67 | Value *string `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"` 68 | XXX_unrecognized []byte `json:"-"` 69 | } 70 | 71 | func (m *TestResponse) Reset() { *m = TestResponse{} } 72 | func (m *TestResponse) String() string { return proto.CompactTextString(m) } 73 | func (*TestResponse) ProtoMessage() {} 74 | 75 | func (m *TestResponse) GetValue() string { 76 | if m != nil && m.Value != nil { 77 | return *m.Value 78 | } 79 | return "" 80 | } 81 | 82 | type CacheStats struct { 83 | Items *int64 `protobuf:"varint,1,opt,name=items" json:"items,omitempty"` 84 | Bytes *int64 `protobuf:"varint,2,opt,name=bytes" json:"bytes,omitempty"` 85 | Gets *int64 `protobuf:"varint,3,opt,name=gets" json:"gets,omitempty"` 86 | Hits *int64 `protobuf:"varint,4,opt,name=hits" json:"hits,omitempty"` 87 | Evicts *int64 `protobuf:"varint,5,opt,name=evicts" json:"evicts,omitempty"` 88 | XXX_unrecognized []byte `json:"-"` 89 | } 90 | 91 | func (m *CacheStats) Reset() { *m = CacheStats{} } 92 | func (m *CacheStats) String() string { return proto.CompactTextString(m) } 93 | func (*CacheStats) ProtoMessage() {} 94 | 95 | func (m *CacheStats) GetItems() int64 { 96 | if m != nil && m.Items != nil { 97 | return *m.Items 98 | } 99 | return 0 100 | } 101 | 102 | func (m *CacheStats) GetBytes() int64 { 103 | if m != nil && m.Bytes != nil { 104 | return *m.Bytes 105 | } 106 | return 0 107 | } 108 | 109 | func (m *CacheStats) GetGets() int64 { 110 | if m != nil && m.Gets != nil { 111 | return *m.Gets 112 | } 113 | return 0 114 | } 115 | 116 | func (m *CacheStats) GetHits() int64 { 117 | if m != nil && m.Hits != nil { 118 | return *m.Hits 119 | } 120 | return 0 121 | } 122 | 123 | func (m *CacheStats) GetEvicts() int64 { 124 | if m != nil && m.Evicts != nil { 125 | return *m.Evicts 126 | } 127 | return 0 128 | } 129 | 130 | type StatsResponse struct { 131 | Gets *int64 `protobuf:"varint,1,opt,name=gets" json:"gets,omitempty"` 132 | CacheHits *int64 `protobuf:"varint,12,opt,name=cache_hits" json:"cache_hits,omitempty"` 133 | Fills *int64 `protobuf:"varint,2,opt,name=fills" json:"fills,omitempty"` 134 | TotalAlloc *uint64 `protobuf:"varint,3,opt,name=total_alloc" json:"total_alloc,omitempty"` 135 | MainCache *CacheStats `protobuf:"bytes,4,opt,name=main_cache" json:"main_cache,omitempty"` 136 | HotCache *CacheStats `protobuf:"bytes,5,opt,name=hot_cache" json:"hot_cache,omitempty"` 137 | ServerIn *int64 `protobuf:"varint,6,opt,name=server_in" json:"server_in,omitempty"` 138 | Loads *int64 `protobuf:"varint,8,opt,name=loads" json:"loads,omitempty"` 139 | PeerLoads *int64 `protobuf:"varint,9,opt,name=peer_loads" json:"peer_loads,omitempty"` 140 | PeerErrors *int64 `protobuf:"varint,10,opt,name=peer_errors" json:"peer_errors,omitempty"` 141 | LocalLoads *int64 `protobuf:"varint,11,opt,name=local_loads" json:"local_loads,omitempty"` 142 | XXX_unrecognized []byte `json:"-"` 143 | } 144 | 145 | func (m *StatsResponse) Reset() { *m = StatsResponse{} } 146 | func (m *StatsResponse) String() string { return proto.CompactTextString(m) } 147 | func (*StatsResponse) ProtoMessage() {} 148 | 149 | func (m *StatsResponse) GetGets() int64 { 150 | if m != nil && m.Gets != nil { 151 | return *m.Gets 152 | } 153 | return 0 154 | } 155 | 156 | func (m *StatsResponse) GetCacheHits() int64 { 157 | if m != nil && m.CacheHits != nil { 158 | return *m.CacheHits 159 | } 160 | return 0 161 | } 162 | 163 | func (m *StatsResponse) GetFills() int64 { 164 | if m != nil && m.Fills != nil { 165 | return *m.Fills 166 | } 167 | return 0 168 | } 169 | 170 | func (m *StatsResponse) GetTotalAlloc() uint64 { 171 | if m != nil && m.TotalAlloc != nil { 172 | return *m.TotalAlloc 173 | } 174 | return 0 175 | } 176 | 177 | func (m *StatsResponse) GetMainCache() *CacheStats { 178 | if m != nil { 179 | return m.MainCache 180 | } 181 | return nil 182 | } 183 | 184 | func (m *StatsResponse) GetHotCache() *CacheStats { 185 | if m != nil { 186 | return m.HotCache 187 | } 188 | return nil 189 | } 190 | 191 | func (m *StatsResponse) GetServerIn() int64 { 192 | if m != nil && m.ServerIn != nil { 193 | return *m.ServerIn 194 | } 195 | return 0 196 | } 197 | 198 | func (m *StatsResponse) GetLoads() int64 { 199 | if m != nil && m.Loads != nil { 200 | return *m.Loads 201 | } 202 | return 0 203 | } 204 | 205 | func (m *StatsResponse) GetPeerLoads() int64 { 206 | if m != nil && m.PeerLoads != nil { 207 | return *m.PeerLoads 208 | } 209 | return 0 210 | } 211 | 212 | func (m *StatsResponse) GetPeerErrors() int64 { 213 | if m != nil && m.PeerErrors != nil { 214 | return *m.PeerErrors 215 | } 216 | return 0 217 | } 218 | 219 | func (m *StatsResponse) GetLocalLoads() int64 { 220 | if m != nil && m.LocalLoads != nil { 221 | return *m.LocalLoads 222 | } 223 | return 0 224 | } 225 | 226 | type Empty struct { 227 | XXX_unrecognized []byte `json:"-"` 228 | } 229 | 230 | func (m *Empty) Reset() { *m = Empty{} } 231 | func (m *Empty) String() string { return proto.CompactTextString(m) } 232 | func (*Empty) ProtoMessage() {} 233 | 234 | func init() { 235 | } 236 | -------------------------------------------------------------------------------- /testpb/test.proto: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | syntax = "proto2"; 18 | 19 | package testpb; 20 | 21 | message TestMessage { 22 | optional string name = 1; 23 | optional string city = 2; 24 | } 25 | 26 | message TestRequest { 27 | required string lower = 1; // to be returned upper case 28 | optional int32 repeat_count = 2 [default = 1]; // .. this many times 29 | } 30 | 31 | message TestResponse { 32 | optional string value = 1; 33 | } 34 | 35 | message CacheStats { 36 | optional int64 items = 1; 37 | optional int64 bytes = 2; 38 | optional int64 gets = 3; 39 | optional int64 hits = 4; 40 | optional int64 evicts = 5; 41 | } 42 | 43 | message StatsResponse { 44 | optional int64 gets = 1; 45 | optional int64 cache_hits = 12; 46 | optional int64 fills = 2; 47 | optional uint64 total_alloc = 3; 48 | optional CacheStats main_cache = 4; 49 | optional CacheStats hot_cache = 5; 50 | optional int64 server_in = 6; 51 | optional int64 loads = 8; 52 | optional int64 peer_loads = 9; 53 | optional int64 peer_errors = 10; 54 | optional int64 local_loads = 11; 55 | } 56 | 57 | message Empty {} 58 | 59 | service GroupCacheTest { 60 | rpc InitPeers(Empty) returns (Empty) {}; 61 | rpc Get(TestRequest) returns (TestResponse) {}; 62 | rpc GetStats(Empty) returns (StatsResponse) {}; 63 | } 64 | --------------------------------------------------------------------------------