├── .gitignore ├── LICENSE ├── README.md ├── go.mod └── memcache ├── cluster_config_parser.go ├── cluster_config_parser_test.go ├── config_poller.go ├── fake_discovery_memcache_server.go ├── memcache.go ├── memcache_discovery_test.go ├── memcache_test.go ├── selector.go └── selector_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | _* 2 | *.out 3 | *~ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | This is a memcache client library for the Go programming language 4 | (http://golang.org/). 5 | 6 | It is a fork of https://github.com/bradfitz/gomemcache/memcache, 7 | with support for Autodiscovery client based on https://cloud.google.com/memorystore/docs/memcached/auto-discovery-overview 8 | 9 | 10 | ## Installing 11 | 12 | ### Using *go get* 13 | 14 | $ go get github.com/google/gomemcache/memcache 15 | 16 | After this command *gomemcache* is ready to use. Its source will be in: 17 | 18 | $GOPATH/src/github.com/google/gomemcache/memcache 19 | 20 | ## Example without Autodiscovery 21 | 22 | import ( 23 | "fmt" 24 | "github.com/google/gomemcache/memcache" 25 | ) 26 | 27 | func main() { 28 | // Create a client by providing the list of memcached servers. 29 | mc := memcache.New("10.0.0.1:11211", "10.0.0.2:11211", "10.0.0.3:11212") 30 | 31 | // For data queries (e.g., SET/GET), the client will first select a 32 | // memcached server based on computed hash of the key and then send 33 | // the query to that server. 34 | mc.Set(&memcache.Item{Key: "foo", Value: []byte("my value")}) 35 | 36 | it, _ := mc.Get("foo") 37 | fmt.Println(string(it.Value)) // Expecting "my value" to be printed 38 | } 39 | 40 | ## Example with Autodiscovery 41 | 42 | import ( 43 | "fmt" 44 | "time" 45 | "github.com/google/gomemcache/memcache" 46 | ) 47 | 48 | func main() { 49 | // Create a client by providing the autodiscovery endpoint. Behind the 50 | // scene, the client queries the autodiscovery endpoint to fetch the 51 | // list of memcached servers. 52 | mcDiscovery, _ := memcache.NewDiscoveryClient("10.0.0.1:11211", 30 * time.Second) 53 | 54 | // For data queries (e.g., SET/GET), the client will first select a 55 | // memcached server based on computed hash of the key and then send 56 | // the query to that server. 57 | mcDiscovery.Set(&memcache.Item{Key: "foo", Value: []byte("my value")}) 58 | 59 | it, _ := mcDiscovery.Get("foo") 60 | fmt.Println(string(it.Value)) // Expecting "my value" to be printed 61 | 62 | mcDiscovery.StopPolling() 63 | } 64 | 65 | ### Note 66 | Remember to call StopPolling() on the discovery enabled client to stop the polling, else this can leak "go" methods. 67 | 68 | ## Full docs, see: 69 | 70 | See https://godoc.org/github.com/google/gomemcache/memcache 71 | 72 | Or run: 73 | 74 | $ godoc github.com/google/gomemcache/memcache 75 | 76 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/google/gomemcache 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /memcache/cluster_config_parser.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 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 memcache 18 | 19 | import ( 20 | "bufio" 21 | "fmt" 22 | "strconv" 23 | "strings" 24 | ) 25 | 26 | var ( 27 | configKeyword = "CONFIG" 28 | ) 29 | 30 | // ClusterNode represents address of a memcached node. 31 | type ClusterNode struct { 32 | Host string 33 | Port int64 34 | } 35 | 36 | // ClusterConfig represents cluster configuration which contains nodes and version. 37 | type ClusterConfig struct { 38 | // ConfigId is the monotonically increasing identifier for the config information 39 | ConfigID int64 40 | 41 | // NodeAddresses are array of ClusterNode which contain address of a memcache node. 42 | NodeAddresses []ClusterNode 43 | } 44 | 45 | // parseConfigGetResponse reads a CONFIG GET response from r and calls cb for each 46 | // read and allocates ClusterConfig 47 | func parseConfigGetResponse(r *bufio.Reader, cb func(*ClusterConfig)) error { 48 | scanner := bufio.NewScanner(r) 49 | clusterConfig := new(ClusterConfig) 50 | // TODO-GO: Replace below document with Feature on github describing the change. 51 | // Response from config service is here: 52 | // https://docs.google.com/document/d/15V9tKuffWrcCVwDZRmRBOV1SDcuo6P8u05dddwZYxCo/edit 53 | for scanner.Scan() { 54 | line := scanner.Text() 55 | line = strings.TrimSpace(line) 56 | // Skip empty line 57 | if line == "" { 58 | continue 59 | } 60 | // CONFIG keyword line is expected as follows: 61 | // CONFIG cluster 0 \r\n 62 | if strings.Contains(line, configKeyword) { 63 | // After config keyword we expect next line to contain config id in the form 64 | // \n 65 | scanner.Scan() 66 | configIDLine := strings.TrimSpace(scanner.Text()) 67 | configID, parseError := strconv.ParseInt(configIDLine, 10, 64) 68 | if parseError != nil { 69 | return parseError 70 | } 71 | clusterConfig.ConfigID = configID 72 | 73 | // Read the third line of the response which contains host 74 | // hostname1|ip-address1|port1hostname2|ip-address2|port2\n\r\n 75 | scanner.Scan() 76 | nodeHostPortAdds := strings.TrimSpace(scanner.Text()) 77 | // tokenize on space and then pipe 78 | nodes := strings.Split(nodeHostPortAdds, " ") 79 | for _, node := range nodes { 80 | nodeHostPort := strings.Split(node, "|") 81 | if len(nodeHostPort) < 3 { 82 | return fmt.Errorf("host address (%s) not in expected format", nodeHostPort) 83 | } 84 | nodePort, intParseError := strconv.ParseInt(nodeHostPort[2], 10, 64) 85 | if intParseError != nil { 86 | return intParseError 87 | } 88 | clusterConfig.NodeAddresses = append(clusterConfig.NodeAddresses, ClusterNode{Host: nodeHostPort[1], Port: nodePort}) 89 | } 90 | } 91 | 92 | } 93 | cb(clusterConfig) 94 | return nil 95 | } 96 | -------------------------------------------------------------------------------- /memcache/cluster_config_parser_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 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 memcache provides a client for the memcached cache server. 18 | package memcache 19 | 20 | import ( 21 | "bufio" 22 | "fmt" 23 | "reflect" 24 | "strconv" 25 | "strings" 26 | "testing" 27 | ) 28 | 29 | func prepareConfigResponse(discoveryID int, discoveryAddress [][]string) string { 30 | var temp strings.Builder 31 | temp.WriteString("") 32 | temp.WriteString("CONFIG cluster 0 80\r\n") 33 | temp.WriteString(fmt.Sprintf("%d", discoveryID)) 34 | temp.WriteString("\r\n") 35 | for i, address := range discoveryAddress { 36 | temp.WriteString(fmt.Sprintf("%s|%s|%s", address[0], address[0], address[1])) 37 | if i < len(discoveryAddress)-1 { 38 | temp.WriteString(" ") 39 | } 40 | } 41 | temp.WriteString("\n\r\n") 42 | return temp.String() 43 | } 44 | 45 | func buildClusterConfig(discoveryID int, discoveryAddress [][]string) *ClusterConfig { 46 | cc := &ClusterConfig{ConfigID: int64(discoveryID)} 47 | cc.NodeAddresses = make([]ClusterNode, len(discoveryAddress)) 48 | for i, address := range discoveryAddress { 49 | port, _ := strconv.ParseInt(address[1], 10, 64) 50 | cc.NodeAddresses[i] = ClusterNode{Host: address[0], Port: port} 51 | } 52 | return cc 53 | } 54 | 55 | func TestGoodClusterConfigs(t *testing.T) { 56 | configTests := []struct { 57 | discoveryID int 58 | discoveryAddresses [][]string 59 | }{ 60 | {2, [][]string{[]string{"localhost", "112233"}}}, 61 | {1000, [][]string{[]string{"localhost", "112233"}, []string{"127.0.0.4", "123435"}}}, 62 | {50, [][]string{[]string{"localhost", "112233"}, []string{"127.0.0.4", "123435"}, []string{"127.0", "123"}}}, 63 | } 64 | for _, tt := range configTests { 65 | config := prepareConfigResponse(tt.discoveryID, tt.discoveryAddresses) 66 | want := buildClusterConfig(tt.discoveryID, tt.discoveryAddresses) 67 | reader := bufio.NewReader(strings.NewReader(config)) 68 | got := &ClusterConfig{} 69 | f := func(cb *ClusterConfig) { 70 | got = cb 71 | } 72 | if err := parseConfigGetResponse(reader, f); err != nil { 73 | t.Errorf("parseConfigGetResponse(%q) had parse err:%v", config, err) 74 | } 75 | if !reflect.DeepEqual(*got, *want) { 76 | t.Errorf("configResponse(%q) = %v; want = %v", config, got, want) 77 | } 78 | } 79 | 80 | } 81 | 82 | func TestBrokenClusterConfigs(t *testing.T) { 83 | emptyConfig := ClusterConfig{} 84 | configTests := []struct { 85 | configResponse string 86 | wantErrorText string 87 | wantConfig ClusterConfig 88 | }{ 89 | {"", "", emptyConfig}, // empty config returns no error with empty cluster config 90 | {"END\r\n", "", emptyConfig}, // empty config returns no error with empty cluster config 91 | {"CONFIG cluster 0 80\r\nbadCfg\r\n123.76|123.76|5432\r\nEND\r\n", "strconv.ParseInt: parsing \"badCfg\"", emptyConfig}, // error parsing port 92 | {"CONFIG cluster 0 80\r\n100\r\n123.76|123.76|portBroken\r\nEND\r\n", "strconv.ParseInt: parsing \"portBroken\"", emptyConfig}, // error parsing port 93 | {"CONFIG cluster 0 80\r\n100\r\n123.76123.76portBroken\r\nEND\r\n", "host address ([123.76123.76portBroken]) not in expected format", emptyConfig}, // error tokenizing due to no pipes 94 | {"CONFIG cluster 0 80\r\n100\r\n123.76|123.76|123123.76|123.76|123\r\nEND\r\n", "invalid syntax", emptyConfig}, // error tokenizing due to no spaces 95 | 96 | } 97 | for _, tt := range configTests { 98 | reader := bufio.NewReader(strings.NewReader(tt.configResponse)) 99 | got := &ClusterConfig{} 100 | f := func(cb *ClusterConfig) { 101 | got = cb 102 | } 103 | if gotError := parseConfigGetResponse(reader, f); gotError != nil && !strings.Contains(gotError.Error(), tt.wantErrorText) { 104 | t.Errorf("parseConfigGetResponse(%q) parse error mismatch, got:%v, wantText:%v", tt.configResponse, gotError, tt.wantErrorText) 105 | } 106 | if !reflect.DeepEqual(*got, tt.wantConfig) { 107 | t.Errorf("parseConfigGetResponse(%q), gotConfig:%v was not equal to wantConfig: %v", tt.configResponse, got, tt.wantConfig) 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /memcache/config_poller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 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 memcache 18 | 19 | import ( 20 | "log" 21 | "net" 22 | "strconv" 23 | "sync" 24 | "time" 25 | ) 26 | 27 | const clusterConfigName = "cluster" 28 | 29 | // configPoller is config service poller. 30 | // It is not safe for use by multiple concurrent goroutines. 31 | type configPoller struct { 32 | // pollingFrequency specified how often poller polls. 33 | pollingFrequency time.Duration 34 | 35 | tick *time.Ticker 36 | done chan bool 37 | once sync.Once 38 | 39 | // reference to selector which will used to update the servers for the main client 40 | serverList *ServerList 41 | 42 | mc *Client 43 | 44 | clusterConfigMU sync.RWMutex 45 | prevClusterConfig *ClusterConfig 46 | } 47 | 48 | // creates a new cluster config poller 49 | func newConfigPoller(frequency time.Duration, servers *ServerList, mc *Client) *configPoller { 50 | 51 | poller := &configPoller{ 52 | pollingFrequency: frequency, 53 | serverList: servers, 54 | mc: mc, 55 | tick: time.NewTicker(frequency), 56 | done: make(chan bool), 57 | } 58 | // Hold the thread to initialize before returning. 59 | err := poller.readConfigAndUpdateServerList() 60 | if err != nil { 61 | // no action required unless stop is explicitly called 62 | log.Printf("Warning: First poll for discovery service failed due to %v", err) 63 | } 64 | go poller.readConfigPeriodically() 65 | return poller 66 | } 67 | 68 | func (c *configPoller) readConfigPeriodically() { 69 | for { 70 | select { 71 | case <-c.tick.C: 72 | err := c.readConfigAndUpdateServerList() 73 | if err != nil { 74 | // no action required unless stop is explicitly called 75 | log.Printf("Warning: Periodic poll for discovery service failed due to %v", err) 76 | } 77 | case <-c.done: 78 | return 79 | } 80 | } 81 | } 82 | 83 | // Stop the internal polling. 84 | func (c *configPoller) stopPolling() { 85 | c.once.Do(func() { 86 | close(c.done) 87 | }) 88 | } 89 | 90 | func (c *configPoller) readConfigAndUpdateServerList() error { 91 | clusterConfig, err := c.mc.GetConfig(clusterConfigName) 92 | if err != nil { 93 | // nothing to do in this round. 94 | return err 95 | } 96 | // compare existing config information with new config information 97 | updateClusterConf := false 98 | c.clusterConfigMU.RLock() 99 | if c.prevClusterConfig != nil { 100 | if clusterConfig.ConfigID > c.prevClusterConfig.ConfigID { 101 | updateClusterConf = true 102 | } 103 | } else { 104 | updateClusterConf = true 105 | } 106 | c.clusterConfigMU.RUnlock() 107 | 108 | if updateClusterConf { 109 | c.updateServerList(clusterConfig) 110 | } 111 | return nil 112 | } 113 | 114 | // updateServerList is not thread safe and should not be called without holding lock on clusterConfigMU 115 | func (c *configPoller) updateServerList(cc *ClusterConfig) error { 116 | s := getServerAddresses(cc) 117 | c.serverList.SetServers(s...) 118 | c.prevClusterConfig = cc 119 | return nil 120 | } 121 | 122 | func getServerAddresses(cc *ClusterConfig) []string { 123 | servers := make([]string, 0, len(cc.NodeAddresses)) 124 | for _, n := range cc.NodeAddresses { 125 | // Validation happens when main memcache client tries to connect to this address 126 | servers = append(servers, net.JoinHostPort(n.Host, strconv.FormatInt(n.Port, 10))) 127 | } 128 | return servers 129 | } 130 | -------------------------------------------------------------------------------- /memcache/fake_discovery_memcache_server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 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 memcache 18 | 19 | import ( 20 | "bufio" 21 | "context" 22 | "fmt" 23 | "net" 24 | "strings" 25 | "sync" 26 | ) 27 | 28 | const emptyResponse = "END" 29 | 30 | // Encapsulate a tcp server which can fake cluster config response also called discovery response. 31 | type fakeDiscoveryMemcacheServer struct { 32 | discoveryResponseMutex sync.RWMutex 33 | 34 | // Usage instructions: 35 | // Either use (discoveryConfigID and discoveryPorts) OR discoveryConfigResponse 36 | // Using one clears other. 37 | discoveryConfigID int 38 | discoveryPorts []int 39 | discoveryConfigResponse string 40 | 41 | // Output only 42 | currentAddress string 43 | 44 | // internal bookeeping 45 | listener net.Listener 46 | ctx context.Context 47 | } 48 | 49 | // starts a tcp server on any free port 50 | func (s *fakeDiscoveryMemcacheServer) start(ctx context.Context) error { 51 | l, err := net.Listen("tcp", "") 52 | if err != nil { 53 | return err 54 | } 55 | s.currentAddress = l.Addr().String() 56 | s.listener = l 57 | s.ctx = ctx 58 | 59 | go s.listenForClients() 60 | 61 | return nil 62 | } 63 | 64 | func (s *fakeDiscoveryMemcacheServer) stop() error { 65 | return s.listener.Close() 66 | } 67 | 68 | func (s *fakeDiscoveryMemcacheServer) listenForClients() { 69 | for { 70 | conn, err := s.listener.Accept() 71 | if err != nil { 72 | continue 73 | } 74 | select { 75 | case <-s.ctx.Done(): 76 | return 77 | default: 78 | go s.handleFakeDiscoveryMemcacheRequest(conn) 79 | } 80 | } 81 | } 82 | 83 | func (s *fakeDiscoveryMemcacheServer) getResponseToSend() string { 84 | s.discoveryResponseMutex.RLock() 85 | defer s.discoveryResponseMutex.RUnlock() 86 | 87 | if s.discoveryConfigResponse != "" { 88 | return s.discoveryConfigResponse 89 | } 90 | 91 | if s.discoveryConfigID == 0 { 92 | return emptyResponse 93 | } 94 | 95 | var result strings.Builder 96 | result.WriteString("CONFIG cluster 0 80\r\n") 97 | result.WriteString(fmt.Sprintf("%d", s.discoveryConfigID)) 98 | result.WriteString("\r\n") 99 | for i, port := range s.discoveryPorts { 100 | result.WriteString(fmt.Sprintf("localhost|localhost|%d", port)) 101 | if i < len(s.discoveryPorts)-1 { 102 | result.WriteString(" ") 103 | } 104 | } 105 | result.WriteString("\n\r\n") 106 | return result.String() 107 | } 108 | 109 | func (s *fakeDiscoveryMemcacheServer) handleFakeDiscoveryMemcacheRequest(c net.Conn) { 110 | _, err := bufio.NewReader(c).ReadString('\n') 111 | if err != nil { 112 | fmt.Println(err) 113 | return 114 | } 115 | 116 | c.Write([]byte(s.getResponseToSend())) 117 | c.Close() 118 | } 119 | 120 | func (s *fakeDiscoveryMemcacheServer) updateDiscoveryResponse(response string) error { 121 | s.discoveryResponseMutex.Lock() 122 | defer s.discoveryResponseMutex.Unlock() 123 | s.discoveryConfigResponse = response 124 | s.discoveryConfigID = 0 125 | s.discoveryPorts = nil 126 | return nil 127 | } 128 | 129 | func (s *fakeDiscoveryMemcacheServer) updateDiscoveryInformation(id int, ports []int) error { 130 | s.discoveryResponseMutex.Lock() 131 | defer s.discoveryResponseMutex.Unlock() 132 | s.discoveryConfigID = id 133 | s.discoveryPorts = ports 134 | s.discoveryConfigResponse = "" 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /memcache/memcache.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 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 memcache provides a client for the memcached cache server. 18 | package memcache 19 | 20 | import ( 21 | "bufio" 22 | "bytes" 23 | "errors" 24 | "fmt" 25 | "io" 26 | "math/rand" 27 | "net" 28 | 29 | "strconv" 30 | "strings" 31 | "sync" 32 | "time" 33 | ) 34 | 35 | // Similar to: 36 | // https://godoc.org/google.golang.org/appengine/memcache 37 | 38 | var ( 39 | // ErrCacheMiss means that a Get failed because the item wasn't present. 40 | ErrCacheMiss = errors.New("memcache: cache miss") 41 | 42 | // ErrCASConflict means that a CompareAndSwap call failed due to the 43 | // cached value being modified between the Get and the CompareAndSwap. 44 | // If the cached value was simply evicted rather than replaced, 45 | // ErrNotStored will be returned instead. 46 | ErrCASConflict = errors.New("memcache: compare-and-swap conflict") 47 | 48 | // ErrNotStored means that a conditional write operation (i.e. Add or 49 | // CompareAndSwap) failed because the condition was not satisfied. 50 | ErrNotStored = errors.New("memcache: item not stored") 51 | 52 | // ErrServer means that a server error occurred. 53 | ErrServerError = errors.New("memcache: server error") 54 | 55 | // ErrNoStats means that no statistics were available. 56 | ErrNoStats = errors.New("memcache: no statistics available") 57 | 58 | // ErrMalformedKey is returned when an invalid key is used. 59 | // Keys must be at maximum 250 bytes long and not 60 | // contain whitespace or control characters. 61 | ErrMalformedKey = errors.New("malformed: key is too long or contains invalid characters") 62 | 63 | // ErrNoServers is returned when no servers are configured or available. 64 | ErrNoServers = errors.New("memcache: no servers configured or available") 65 | 66 | // ErrInvalidPollingDuration is returned when discovery polling is invalid 67 | ErrInvalidPollingDuration = errors.New("Discovery polling duration is invalid") 68 | 69 | // ErrClusterConfigMiss means that GetConfig failed as cluster config was not present 70 | ErrClusterConfigMiss = errors.New("memcache: cluster config miss") 71 | ) 72 | 73 | const ( 74 | // DefaultTimeout is the default socket read/write timeout. 75 | DefaultTimeout = 100 * time.Millisecond 76 | 77 | // DefaultMaxIdleConns is the default maximum number of idle connections 78 | // kept for any single address. 79 | DefaultMaxIdleConns = 2 80 | ) 81 | 82 | const buffered = 8 // arbitrary buffered channel size, for readability 83 | 84 | // resumableError returns true if err is only a protocol-level cache error. 85 | // This is used to determine whether or not a server connection should 86 | // be re-used or not. If an error occurs, by default we don't reuse the 87 | // connection, unless it was just a cache error. 88 | func resumableError(err error) bool { 89 | switch err { 90 | case ErrCacheMiss, ErrCASConflict, ErrNotStored, ErrMalformedKey: 91 | return true 92 | } 93 | return false 94 | } 95 | 96 | func legalKey(key string) bool { 97 | if len(key) > 250 { 98 | return false 99 | } 100 | for i := 0; i < len(key); i++ { 101 | if key[i] <= ' ' || key[i] == 0x7f { 102 | return false 103 | } 104 | } 105 | return true 106 | } 107 | 108 | var ( 109 | crlf = []byte("\r\n") 110 | space = []byte(" ") 111 | resultOK = []byte("OK\r\n") 112 | resultStored = []byte("STORED\r\n") 113 | resultNotStored = []byte("NOT_STORED\r\n") 114 | resultExists = []byte("EXISTS\r\n") 115 | resultNotFound = []byte("NOT_FOUND\r\n") 116 | resultDeleted = []byte("DELETED\r\n") 117 | resultEnd = []byte("END\r\n") 118 | resultOk = []byte("OK\r\n") 119 | resultTouched = []byte("TOUCHED\r\n") 120 | 121 | resultClientErrorPrefix = []byte("CLIENT_ERROR ") 122 | versionPrefix = []byte("VERSION") 123 | ) 124 | 125 | // NewDiscoveryClient returns a discovery config enabled client which polls 126 | // periodically for new information and update server list if new information is found. 127 | // All the servers which are found are used with equal weight. 128 | // discoveryAddress should be in following form "ipv4-address:port" 129 | // Note: pollingDuration should be at least 1 second. 130 | func NewDiscoveryClient(discoveryAddress string, pollingDuration time.Duration) (*Client, error) { 131 | // Validate pollingDuration 132 | if pollingDuration.Seconds() < 1.0 { 133 | return nil, ErrInvalidPollingDuration 134 | } 135 | return newDiscoveryClient(discoveryAddress, pollingDuration) 136 | } 137 | 138 | // for the unit test 139 | func newDiscoveryClient(discoveryAddress string, pollingDuration time.Duration) (*Client, error) { 140 | // creates a new ServerList object which contains all the server eventually. 141 | rand.Seed(time.Now().UnixNano()) 142 | ss := new(ServerList) 143 | mcCfgPollerHelper := New(discoveryAddress) 144 | cfgPoller := newConfigPoller(pollingDuration, ss, mcCfgPollerHelper) 145 | // cfgPoller starts polling immediately. 146 | mcClient := NewFromSelector(ss) 147 | mcClient.StopPolling = cfgPoller.stopPolling 148 | return mcClient, nil 149 | } 150 | 151 | // New returns a memcache client using the provided server(s) 152 | // with equal weight. If a server is listed multiple times, 153 | // it gets a proportional amount of weight. 154 | func New(server ...string) *Client { 155 | ss := new(ServerList) 156 | ss.SetServers(server...) 157 | return NewFromSelector(ss) 158 | } 159 | 160 | // NewFromSelector returns a new Client using the provided ServerSelector. 161 | func NewFromSelector(ss ServerSelector) *Client { 162 | return &Client{selector: ss} 163 | } 164 | 165 | type stop func() 166 | 167 | // Client is a memcache client. 168 | // It is safe for unlocked use by multiple concurrent goroutines. 169 | type Client struct { 170 | // Timeout specifies the socket read/write timeout. 171 | // If zero, DefaultTimeout is used. 172 | Timeout time.Duration 173 | 174 | // MaxIdleConns specifies the maximum number of idle connections that will 175 | // be maintained per address. If less than one, DefaultMaxIdleConns will be 176 | // used. 177 | // 178 | // Consider your expected traffic rates and latency carefully. This should 179 | // be set to a number higher than your peak parallel requests. 180 | MaxIdleConns int 181 | 182 | selector ServerSelector 183 | StopPolling stop 184 | 185 | lk sync.Mutex 186 | freeconn map[string][]*conn 187 | } 188 | 189 | // Item is an item to be got or stored in a memcached server. 190 | type Item struct { 191 | // Key is the Item's key (250 bytes maximum). 192 | Key string 193 | 194 | // Value is the Item's value. 195 | Value []byte 196 | 197 | // Flags are server-opaque flags whose semantics are entirely 198 | // up to the app. 199 | Flags uint32 200 | 201 | // Expiration is the cache expiration time, in seconds: either a relative 202 | // time from now (up to 1 month), or an absolute Unix epoch time. 203 | // Zero means the Item has no expiration time. 204 | Expiration int32 205 | 206 | // Compare and swap ID. 207 | casid uint64 208 | } 209 | 210 | // conn is a connection to a server. 211 | type conn struct { 212 | nc net.Conn 213 | rw *bufio.ReadWriter 214 | addr net.Addr 215 | c *Client 216 | } 217 | 218 | // release returns this connection back to the client's free pool 219 | func (cn *conn) release() { 220 | cn.c.putFreeConn(cn.addr, cn) 221 | } 222 | 223 | func (cn *conn) extendDeadline() { 224 | cn.nc.SetDeadline(time.Now().Add(cn.c.netTimeout())) 225 | } 226 | 227 | // condRelease releases this connection if the error pointed to by err 228 | // is nil (not an error) or is only a protocol level error (e.g. a 229 | // cache miss). The purpose is to not recycle TCP connections that 230 | // are bad. 231 | func (cn *conn) condRelease(err *error) { 232 | if *err == nil || resumableError(*err) { 233 | cn.release() 234 | } else { 235 | cn.nc.Close() 236 | } 237 | } 238 | 239 | func (c *Client) putFreeConn(addr net.Addr, cn *conn) { 240 | c.lk.Lock() 241 | defer c.lk.Unlock() 242 | if c.freeconn == nil { 243 | c.freeconn = make(map[string][]*conn) 244 | } 245 | freelist := c.freeconn[addr.String()] 246 | if len(freelist) >= c.maxIdleConns() { 247 | cn.nc.Close() 248 | return 249 | } 250 | c.freeconn[addr.String()] = append(freelist, cn) 251 | } 252 | 253 | func (c *Client) getFreeConn(addr net.Addr) (cn *conn, ok bool) { 254 | c.lk.Lock() 255 | defer c.lk.Unlock() 256 | if c.freeconn == nil { 257 | return nil, false 258 | } 259 | freelist, ok := c.freeconn[addr.String()] 260 | if !ok || len(freelist) == 0 { 261 | return nil, false 262 | } 263 | cn = freelist[len(freelist)-1] 264 | c.freeconn[addr.String()] = freelist[:len(freelist)-1] 265 | return cn, true 266 | } 267 | 268 | func (c *Client) netTimeout() time.Duration { 269 | if c.Timeout != 0 { 270 | return c.Timeout 271 | } 272 | return DefaultTimeout 273 | } 274 | 275 | func (c *Client) maxIdleConns() int { 276 | if c.MaxIdleConns > 0 { 277 | return c.MaxIdleConns 278 | } 279 | return DefaultMaxIdleConns 280 | } 281 | 282 | // ConnectTimeoutError is the error type used when it takes 283 | // too long to connect to the desired host. This level of 284 | // detail can generally be ignored. 285 | type ConnectTimeoutError struct { 286 | Addr net.Addr 287 | } 288 | 289 | func (cte *ConnectTimeoutError) Error() string { 290 | return "memcache: connect timeout to " + cte.Addr.String() 291 | } 292 | 293 | func (c *Client) dial(addr net.Addr) (net.Conn, error) { 294 | type connError struct { 295 | cn net.Conn 296 | err error 297 | } 298 | 299 | nc, err := net.DialTimeout(addr.Network(), addr.String(), c.netTimeout()) 300 | if err == nil { 301 | return nc, nil 302 | } 303 | 304 | if ne, ok := err.(net.Error); ok && ne.Timeout() { 305 | return nil, &ConnectTimeoutError{addr} 306 | } 307 | 308 | return nil, err 309 | } 310 | 311 | func (c *Client) getConn(addr net.Addr) (*conn, error) { 312 | cn, ok := c.getFreeConn(addr) 313 | if ok { 314 | cn.extendDeadline() 315 | return cn, nil 316 | } 317 | nc, err := c.dial(addr) 318 | if err != nil { 319 | return nil, err 320 | } 321 | cn = &conn{ 322 | nc: nc, 323 | addr: addr, 324 | rw: bufio.NewReadWriter(bufio.NewReader(nc), bufio.NewWriter(nc)), 325 | c: c, 326 | } 327 | cn.extendDeadline() 328 | return cn, nil 329 | } 330 | 331 | func (c *Client) onItem(item *Item, fn func(*Client, *bufio.ReadWriter, *Item) error) error { 332 | addr, err := c.selector.PickServer(item.Key) 333 | if err != nil { 334 | return err 335 | } 336 | cn, err := c.getConn(addr) 337 | if err != nil { 338 | return err 339 | } 340 | defer cn.condRelease(&err) 341 | if err = fn(c, cn.rw, item); err != nil { 342 | return err 343 | } 344 | return nil 345 | } 346 | 347 | func (c *Client) FlushAll() error { 348 | return c.selector.Each(c.flushAllFromAddr) 349 | } 350 | 351 | // Get gets the item for the given key. ErrCacheMiss is returned for a 352 | // memcache cache miss. The key must be at most 250 bytes in length. 353 | func (c *Client) Get(key string) (item *Item, err error) { 354 | err = c.withKeyAddr(key, func(addr net.Addr) error { 355 | return c.getFromAddr(addr, []string{key}, func(it *Item) { item = it }) 356 | }) 357 | if err == nil && item == nil { 358 | err = ErrCacheMiss 359 | } 360 | return 361 | } 362 | 363 | // Touch updates the expiry for the given key. The seconds parameter is either 364 | // a Unix timestamp or, if seconds is less than 1 month, the number of seconds 365 | // into the future at which time the item will expire. Zero means the item has 366 | // no expiration time. ErrCacheMiss is returned if the key is not in the cache. 367 | // The key must be at most 250 bytes in length. 368 | func (c *Client) Touch(key string, seconds int32) (err error) { 369 | return c.withKeyAddr(key, func(addr net.Addr) error { 370 | return c.touchFromAddr(addr, []string{key}, seconds) 371 | }) 372 | } 373 | 374 | func (c *Client) withKeyAddr(key string, fn func(net.Addr) error) (err error) { 375 | if !legalKey(key) { 376 | return ErrMalformedKey 377 | } 378 | addr, err := c.selector.PickServer(key) 379 | if err != nil { 380 | return err 381 | } 382 | return fn(addr) 383 | } 384 | 385 | func (c *Client) withAddrRw(addr net.Addr, fn func(*bufio.ReadWriter) error) (err error) { 386 | cn, err := c.getConn(addr) 387 | if err != nil { 388 | return err 389 | } 390 | defer cn.condRelease(&err) 391 | return fn(cn.rw) 392 | } 393 | 394 | func (c *Client) withKeyRw(key string, fn func(*bufio.ReadWriter) error) error { 395 | return c.withKeyAddr(key, func(addr net.Addr) error { 396 | return c.withAddrRw(addr, fn) 397 | }) 398 | } 399 | 400 | func (c *Client) getFromAddr(addr net.Addr, keys []string, cb func(*Item)) error { 401 | return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { 402 | if _, err := fmt.Fprintf(rw, "gets %s\r\n", strings.Join(keys, " ")); err != nil { 403 | return err 404 | } 405 | if err := rw.Flush(); err != nil { 406 | return err 407 | } 408 | if err := parseGetResponse(rw.Reader, cb); err != nil { 409 | return err 410 | } 411 | return nil 412 | }) 413 | } 414 | 415 | // flushAllFromAddr send the flush_all command to the given addr 416 | func (c *Client) flushAllFromAddr(addr net.Addr) error { 417 | return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { 418 | if _, err := fmt.Fprintf(rw, "flush_all\r\n"); err != nil { 419 | return err 420 | } 421 | if err := rw.Flush(); err != nil { 422 | return err 423 | } 424 | line, err := rw.ReadSlice('\n') 425 | if err != nil { 426 | return err 427 | } 428 | switch { 429 | case bytes.Equal(line, resultOk): 430 | break 431 | default: 432 | return fmt.Errorf("memcache: unexpected response line from flush_all: %q", string(line)) 433 | } 434 | return nil 435 | }) 436 | } 437 | 438 | // ping sends the version command to the given addr 439 | func (c *Client) ping(addr net.Addr) error { 440 | return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { 441 | if _, err := fmt.Fprintf(rw, "version\r\n"); err != nil { 442 | return err 443 | } 444 | if err := rw.Flush(); err != nil { 445 | return err 446 | } 447 | line, err := rw.ReadSlice('\n') 448 | if err != nil { 449 | return err 450 | } 451 | 452 | switch { 453 | case bytes.HasPrefix(line, versionPrefix): 454 | break 455 | default: 456 | return fmt.Errorf("memcache: unexpected response line from ping: %q", string(line)) 457 | } 458 | return nil 459 | }) 460 | } 461 | 462 | func (c *Client) touchFromAddr(addr net.Addr, keys []string, expiration int32) error { 463 | return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { 464 | for _, key := range keys { 465 | if _, err := fmt.Fprintf(rw, "touch %s %d\r\n", key, expiration); err != nil { 466 | return err 467 | } 468 | if err := rw.Flush(); err != nil { 469 | return err 470 | } 471 | line, err := rw.ReadSlice('\n') 472 | if err != nil { 473 | return err 474 | } 475 | switch { 476 | case bytes.Equal(line, resultTouched): 477 | break 478 | case bytes.Equal(line, resultNotFound): 479 | return ErrCacheMiss 480 | default: 481 | return fmt.Errorf("memcache: unexpected response line from touch: %q", string(line)) 482 | } 483 | } 484 | return nil 485 | }) 486 | } 487 | 488 | // GetMulti is a batch version of Get. The returned map from keys to 489 | // items may have fewer elements than the input slice, due to memcache 490 | // cache misses. Each key must be at most 250 bytes in length. 491 | // If no error is returned, the returned map will also be non-nil. 492 | func (c *Client) GetMulti(keys []string) (map[string]*Item, error) { 493 | var lk sync.Mutex 494 | m := make(map[string]*Item) 495 | addItemToMap := func(it *Item) { 496 | lk.Lock() 497 | defer lk.Unlock() 498 | m[it.Key] = it 499 | } 500 | 501 | keyMap := make(map[net.Addr][]string) 502 | for _, key := range keys { 503 | if !legalKey(key) { 504 | return nil, ErrMalformedKey 505 | } 506 | addr, err := c.selector.PickServer(key) 507 | if err != nil { 508 | return nil, err 509 | } 510 | keyMap[addr] = append(keyMap[addr], key) 511 | } 512 | 513 | ch := make(chan error, buffered) 514 | for addr, keys := range keyMap { 515 | go func(addr net.Addr, keys []string) { 516 | ch <- c.getFromAddr(addr, keys, addItemToMap) 517 | }(addr, keys) 518 | } 519 | 520 | var err error 521 | for _ = range keyMap { 522 | if ge := <-ch; ge != nil { 523 | err = ge 524 | } 525 | } 526 | return m, err 527 | } 528 | 529 | // parseGetResponse reads a GET response from r and calls cb for each 530 | // read and allocated Item 531 | func parseGetResponse(r *bufio.Reader, cb func(*Item)) error { 532 | for { 533 | line, err := r.ReadSlice('\n') 534 | if err != nil { 535 | return err 536 | } 537 | if bytes.Equal(line, resultEnd) { 538 | return nil 539 | } 540 | it := new(Item) 541 | size, err := scanGetResponseLine(line, it) 542 | if err != nil { 543 | return err 544 | } 545 | it.Value = make([]byte, size+2) 546 | _, err = io.ReadFull(r, it.Value) 547 | if err != nil { 548 | it.Value = nil 549 | return err 550 | } 551 | if !bytes.HasSuffix(it.Value, crlf) { 552 | it.Value = nil 553 | return fmt.Errorf("memcache: corrupt get result read") 554 | } 555 | it.Value = it.Value[:size] 556 | cb(it) 557 | } 558 | } 559 | 560 | // scanGetResponseLine populates it and returns the declared size of the item. 561 | // It does not read the bytes of the item. 562 | func scanGetResponseLine(line []byte, it *Item) (size int, err error) { 563 | pattern := "VALUE %s %d %d %d\r\n" 564 | dest := []interface{}{&it.Key, &it.Flags, &size, &it.casid} 565 | if bytes.Count(line, space) == 3 { 566 | pattern = "VALUE %s %d %d\r\n" 567 | dest = dest[:3] 568 | } 569 | n, err := fmt.Sscanf(string(line), pattern, dest...) 570 | if err != nil || n != len(dest) { 571 | return -1, fmt.Errorf("memcache: unexpected line in get response: %q", line) 572 | } 573 | return size, nil 574 | } 575 | 576 | // Set writes the given item, unconditionally. 577 | func (c *Client) Set(item *Item) error { 578 | return c.onItem(item, (*Client).set) 579 | } 580 | 581 | func (c *Client) set(rw *bufio.ReadWriter, item *Item) error { 582 | return c.populateOne(rw, "set", item) 583 | } 584 | 585 | // Add writes the given item, if no value already exists for its 586 | // key. ErrNotStored is returned if that condition is not met. 587 | func (c *Client) Add(item *Item) error { 588 | return c.onItem(item, (*Client).add) 589 | } 590 | 591 | func (c *Client) add(rw *bufio.ReadWriter, item *Item) error { 592 | return c.populateOne(rw, "add", item) 593 | } 594 | 595 | // Replace writes the given item, but only if the server *does* 596 | // already hold data for this key 597 | func (c *Client) Replace(item *Item) error { 598 | return c.onItem(item, (*Client).replace) 599 | } 600 | 601 | func (c *Client) replace(rw *bufio.ReadWriter, item *Item) error { 602 | return c.populateOne(rw, "replace", item) 603 | } 604 | 605 | // CompareAndSwap writes the given item that was previously returned 606 | // by Get, if the value was neither modified or evicted between the 607 | // Get and the CompareAndSwap calls. The item's Key should not change 608 | // between calls but all other item fields may differ. ErrCASConflict 609 | // is returned if the value was modified in between the 610 | // calls. ErrNotStored is returned if the value was evicted in between 611 | // the calls. 612 | func (c *Client) CompareAndSwap(item *Item) error { 613 | return c.onItem(item, (*Client).cas) 614 | } 615 | 616 | func (c *Client) cas(rw *bufio.ReadWriter, item *Item) error { 617 | return c.populateOne(rw, "cas", item) 618 | } 619 | 620 | func (c *Client) populateOne(rw *bufio.ReadWriter, verb string, item *Item) error { 621 | if !legalKey(item.Key) { 622 | return ErrMalformedKey 623 | } 624 | var err error 625 | if verb == "cas" { 626 | _, err = fmt.Fprintf(rw, "%s %s %d %d %d %d\r\n", 627 | verb, item.Key, item.Flags, item.Expiration, len(item.Value), item.casid) 628 | } else { 629 | _, err = fmt.Fprintf(rw, "%s %s %d %d %d\r\n", 630 | verb, item.Key, item.Flags, item.Expiration, len(item.Value)) 631 | } 632 | if err != nil { 633 | return err 634 | } 635 | if _, err = rw.Write(item.Value); err != nil { 636 | return err 637 | } 638 | if _, err := rw.Write(crlf); err != nil { 639 | return err 640 | } 641 | if err := rw.Flush(); err != nil { 642 | return err 643 | } 644 | line, err := rw.ReadSlice('\n') 645 | if err != nil { 646 | return err 647 | } 648 | switch { 649 | case bytes.Equal(line, resultStored): 650 | return nil 651 | case bytes.Equal(line, resultNotStored): 652 | return ErrNotStored 653 | case bytes.Equal(line, resultExists): 654 | return ErrCASConflict 655 | case bytes.Equal(line, resultNotFound): 656 | return ErrCacheMiss 657 | } 658 | return fmt.Errorf("memcache: unexpected response line from %q: %q", verb, string(line)) 659 | } 660 | 661 | func writeReadLine(rw *bufio.ReadWriter, format string, args ...interface{}) ([]byte, error) { 662 | _, err := fmt.Fprintf(rw, format, args...) 663 | if err != nil { 664 | return nil, err 665 | } 666 | if err := rw.Flush(); err != nil { 667 | return nil, err 668 | } 669 | line, err := rw.ReadSlice('\n') 670 | return line, err 671 | } 672 | 673 | func writeExpectf(rw *bufio.ReadWriter, expect []byte, format string, args ...interface{}) error { 674 | line, err := writeReadLine(rw, format, args...) 675 | if err != nil { 676 | return err 677 | } 678 | switch { 679 | case bytes.Equal(line, resultOK): 680 | return nil 681 | case bytes.Equal(line, expect): 682 | return nil 683 | case bytes.Equal(line, resultNotStored): 684 | return ErrNotStored 685 | case bytes.Equal(line, resultExists): 686 | return ErrCASConflict 687 | case bytes.Equal(line, resultNotFound): 688 | return ErrCacheMiss 689 | } 690 | return fmt.Errorf("memcache: unexpected response line: %q", string(line)) 691 | } 692 | 693 | // Delete deletes the item with the provided key. The error ErrCacheMiss is 694 | // returned if the item didn't already exist in the cache. 695 | func (c *Client) Delete(key string) error { 696 | return c.withKeyRw(key, func(rw *bufio.ReadWriter) error { 697 | return writeExpectf(rw, resultDeleted, "delete %s\r\n", key) 698 | }) 699 | } 700 | 701 | // DeleteAll deletes all items in the cache. 702 | func (c *Client) DeleteAll() error { 703 | return c.withKeyRw("", func(rw *bufio.ReadWriter) error { 704 | return writeExpectf(rw, resultDeleted, "flush_all\r\n") 705 | }) 706 | } 707 | 708 | // Ping checks all instances if they are alive. Returns error if any 709 | // of them is down. 710 | func (c *Client) Ping() error { 711 | return c.selector.Each(c.ping) 712 | } 713 | 714 | // Increment atomically increments key by delta. The return value is 715 | // the new value after being incremented or an error. If the value 716 | // didn't exist in memcached the error is ErrCacheMiss. The value in 717 | // memcached must be an decimal number, or an error will be returned. 718 | // On 64-bit overflow, the new value wraps around. 719 | func (c *Client) Increment(key string, delta uint64) (newValue uint64, err error) { 720 | return c.incrDecr("incr", key, delta) 721 | } 722 | 723 | // Decrement atomically decrements key by delta. The return value is 724 | // the new value after being decremented or an error. If the value 725 | // didn't exist in memcached the error is ErrCacheMiss. The value in 726 | // memcached must be an decimal number, or an error will be returned. 727 | // On underflow, the new value is capped at zero and does not wrap 728 | // around. 729 | func (c *Client) Decrement(key string, delta uint64) (newValue uint64, err error) { 730 | return c.incrDecr("decr", key, delta) 731 | } 732 | 733 | func (c *Client) incrDecr(verb, key string, delta uint64) (uint64, error) { 734 | var val uint64 735 | err := c.withKeyRw(key, func(rw *bufio.ReadWriter) error { 736 | line, err := writeReadLine(rw, "%s %s %d\r\n", verb, key, delta) 737 | if err != nil { 738 | return err 739 | } 740 | switch { 741 | case bytes.Equal(line, resultNotFound): 742 | return ErrCacheMiss 743 | case bytes.HasPrefix(line, resultClientErrorPrefix): 744 | errMsg := line[len(resultClientErrorPrefix) : len(line)-2] 745 | return errors.New("memcache: client error: " + string(errMsg)) 746 | } 747 | val, err = strconv.ParseUint(string(line[:len(line)-2]), 10, 64) 748 | if err != nil { 749 | return err 750 | } 751 | return nil 752 | }) 753 | return val, err 754 | } 755 | 756 | // GetConfig gets the config type. ErrClusterConfigMiss is returned if config 757 | // for the type cluster is not found. The type must be at most 250 bytes in length. 758 | func (c *Client) GetConfig(configType string) (clusterConfig *ClusterConfig, err error) { 759 | clusterConfig, err = c.getConfig(configType) 760 | if err != nil { 761 | return nil, err 762 | } 763 | 764 | if clusterConfig == nil { 765 | return nil, ErrClusterConfigMiss 766 | } 767 | 768 | return clusterConfig, nil 769 | } 770 | 771 | // TODO-GO Implement setConfig as well. 772 | // getConfig gets the config type. ErrClusterConfigMiss is returned if config 773 | // for the type cluster is not found. The type must be at most 250 bytes in length. 774 | func (c *Client) getConfig(configType string) (clusterConfig *ClusterConfig, err error) { 775 | s1Adrr, err := c.selector.PickAnyServer() 776 | if err != nil { 777 | return nil, err 778 | } 779 | err = c.getConfigFromAddr(s1Adrr, configType, func(cc *ClusterConfig) { clusterConfig = cc }) 780 | if err != nil { 781 | return nil, err 782 | } 783 | if clusterConfig == nil { 784 | err = ErrClusterConfigMiss 785 | } 786 | return 787 | } 788 | 789 | func (c *Client) getConfigFromAddr(addr net.Addr, configType string, cb func(*ClusterConfig)) error { 790 | return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { 791 | if _, err := fmt.Fprintf(rw, "config get %s\r\n", configType); err != nil { 792 | return err 793 | } 794 | if err := rw.Flush(); err != nil { 795 | return err 796 | } 797 | if err := parseConfigGetResponse(rw.Reader, cb); err != nil { 798 | return err 799 | } 800 | return nil 801 | }) 802 | } 803 | -------------------------------------------------------------------------------- /memcache/memcache_discovery_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 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 memcache provides a client for the memcached cache server. 18 | package memcache 19 | 20 | import ( 21 | "context" 22 | "errors" 23 | "sort" 24 | "strconv" 25 | "strings" 26 | 27 | "net" 28 | "os/exec" 29 | 30 | "testing" 31 | "time" 32 | ) 33 | 34 | const ( 35 | testPollingTime = 2 * time.Millisecond 36 | portLow = 49152 37 | portHigh = 65535 38 | memcachedCreationTimeout = 5 * time.Second 39 | 40 | teardownTimeout = 100 * time.Millisecond 41 | discoveryStabilizationDuration = 10 * time.Millisecond 42 | memcachedStabilizationDuration = 20 * time.Millisecond 43 | ) 44 | 45 | var fakeDiscoveryServer = fakeDiscoveryMemcacheServer{} 46 | var ready = make(chan bool) 47 | var exit = make(chan bool) 48 | 49 | // Run the memcached binary as a child process and connect to its unix socket. 50 | func TestDiscoveryUnixSocket(t *testing.T) { 51 | ctx := context.Background() 52 | defer ctx.Done() 53 | fakeDiscoveryServer.start(ctx) 54 | t.Cleanup(teardown) 55 | openPorts := findOpenLocalHostPort(portLow, portHigh, 2) 56 | if openPorts == nil || len(openPorts) < 2 { 57 | t.Fatalf("could not find two open ports, openPorts:%v", openPorts) 58 | return 59 | } 60 | 61 | go startMemcachedServer(t, openPorts[0], ready, exit) 62 | go startMemcachedServer(t, openPorts[1], ready, exit) 63 | s1Ready, waitErr := waitOnChannelWithTimeout(ready, memcachedCreationTimeout) 64 | if waitErr != nil { 65 | t.Fatalf("memcache server could not be created due to %v", waitErr) 66 | } 67 | s2Ready, waitErr := waitOnChannelWithTimeout(ready, memcachedCreationTimeout) 68 | if waitErr != nil { 69 | t.Fatalf("memcache server could not be created due to %v", waitErr) 70 | } 71 | if !s1Ready || !s2Ready { 72 | t.Skipf("one of the memcached server was not ready.") 73 | } 74 | 75 | fakeDiscoveryServer.updateDiscoveryInformation(1, openPorts) 76 | discoveryClient, err := newDiscoveryClient(fakeDiscoveryServer.currentAddress, testPollingTime) 77 | if err != nil { 78 | t.Fatalf("could not create discovery client due to %v", err) 79 | } 80 | testWithDiscoveryClient(t, &fakeDiscoveryServer, discoveryClient) 81 | discoveryClient.StopPolling() 82 | } 83 | 84 | func waitOnChannelWithTimeout(c chan bool, timeout time.Duration) (bool, error) { 85 | select { 86 | case res := <-c: 87 | return res, nil 88 | case <-time.After(timeout): 89 | return false, errors.New("channel timed out") 90 | } 91 | } 92 | 93 | func teardown() { 94 | fakeDiscoveryServer.stop() 95 | close(exit) 96 | time.Sleep(teardownTimeout) 97 | } 98 | 99 | func getCurrentServerPorts(c *Client) []int { 100 | serverAddress := make([]int, 0, 3) 101 | recordAllServers := func(a net.Addr) error { 102 | _, portStr, err := net.SplitHostPort(a.String()) 103 | if err != nil { 104 | return err 105 | } 106 | port, err := strconv.ParseInt(portStr, 10, 0) 107 | if err != nil { 108 | return err 109 | } 110 | serverAddress = append(serverAddress, int(port)) 111 | return nil 112 | } 113 | c.selector.Each(recordAllServers) 114 | sort.Ints(serverAddress) 115 | return serverAddress 116 | } 117 | 118 | // Equal tells whether a and b contain the same elements. 119 | func equalIntArray(slice1, slice2 []int) bool { 120 | if len(slice1) != len(slice2) { 121 | return false 122 | } 123 | for i, value := range slice1 { 124 | if value != slice2[i] { 125 | return false 126 | } 127 | } 128 | return true 129 | } 130 | 131 | func testSingleConfigCase(t *testing.T, fakeMemcacheServer *fakeDiscoveryMemcacheServer, c *Client, discoveryID int, portsToSet, expectedPorts []int) { 132 | fakeMemcacheServer.updateDiscoveryInformation(discoveryID, portsToSet) 133 | time.Sleep(discoveryStabilizationDuration) 134 | discoveryPorts := getCurrentServerPorts(c) 135 | if !equalIntArray(expectedPorts, discoveryPorts) { 136 | t.Fatalf("configId:%v want: %v != got %v", discoveryID, expectedPorts, discoveryPorts) 137 | } 138 | } 139 | 140 | func testSingleInvalidConfigCase(t *testing.T, fakeMemcacheServer *fakeDiscoveryMemcacheServer, c *Client, discoveryResponse string, expectedPorts []int) { 141 | fakeMemcacheServer.updateDiscoveryResponse(discoveryResponse) 142 | time.Sleep(discoveryStabilizationDuration) 143 | discoveryPorts := getCurrentServerPorts(c) 144 | if !equalIntArray(expectedPorts, discoveryPorts) { 145 | t.Fatalf("discoveryResponse:%v want: %v != got %v", discoveryResponse, expectedPorts, discoveryPorts) 146 | } 147 | } 148 | 149 | func testValidConfigChange(t *testing.T, fakeMemcacheServer *fakeDiscoveryMemcacheServer, c *Client) { 150 | originalPortList := getCurrentServerPorts(c) 151 | 152 | // Greater config id should update discovery information 153 | newPorts := []int{1, 2, 3} 154 | testSingleConfigCase(t, fakeMemcacheServer, c, 3, newPorts, newPorts) 155 | 156 | // Update to original configuration 157 | testSingleConfigCase(t, fakeMemcacheServer, c, 20, originalPortList, originalPortList) 158 | 159 | // Same config id should not change the config 160 | testSingleConfigCase(t, fakeMemcacheServer, c, 20, newPorts, originalPortList) 161 | 162 | // Older config id should 163 | testSingleConfigCase(t, fakeMemcacheServer, c, 19, newPorts, originalPortList) 164 | 165 | // Not found case with config id 0 166 | testSingleConfigCase(t, fakeMemcacheServer, c, 0, newPorts, originalPortList) 167 | } 168 | 169 | func testInvalidConfigChange(t *testing.T, fakeMemcacheServer *fakeDiscoveryMemcacheServer, c *Client) { 170 | originalPortList := getCurrentServerPorts(c) 171 | 172 | // Completely broken response 173 | testSingleInvalidConfigCase(t, fakeMemcacheServer, c, "broken", originalPortList) 174 | 175 | // Partially broken response with intparse error 176 | var result strings.Builder 177 | result.WriteString("CONFIG cluster 0 80\r\n") 178 | result.WriteString("100\r\n") 179 | result.WriteString("localhost|localhost|brokenInt") 180 | result.WriteString("\n\r\n") 181 | testSingleInvalidConfigCase(t, fakeMemcacheServer, c, result.String(), originalPortList) 182 | } 183 | 184 | func testWithDiscoveryClient(t *testing.T, fakeMemcacheServer *fakeDiscoveryMemcacheServer, c *Client) { 185 | // Run discovery config tests 186 | if fakeMemcacheServer != nil { 187 | testValidConfigChange(t, fakeMemcacheServer, c) 188 | testInvalidConfigChange(t, fakeMemcacheServer, c) 189 | } 190 | 191 | // reuse the other test library 192 | testWithClient(t, c) 193 | } 194 | 195 | func startMemcachedServer(t *testing.T, port int, ready chan<- bool, exit <-chan bool) error { 196 | t.Logf("starting memcached server on port: %d", port) 197 | cmd := exec.Command("memcached", "-p", strconv.Itoa(port)) 198 | t.Logf("starting memcached server with command : %v", cmd) 199 | if err := cmd.Start(); err != nil { 200 | ready <- false 201 | return errors.New("could not find memcached server") 202 | } 203 | t.Logf("started memcached server on port:%d", port) 204 | // Allow the server to come up 205 | time.Sleep(memcachedStabilizationDuration) 206 | ready <- true 207 | 208 | <-exit 209 | cmd.Process.Kill() 210 | time.Sleep(memcachedStabilizationDuration) 211 | t.Logf("memcached server on port:%d exited", port) 212 | return nil 213 | } 214 | 215 | func findOpenLocalHostPort(portLow, portHigh, openPortsToFind int) []int { 216 | timeout := 10 * time.Millisecond 217 | openPorts := make([]int, 0, 2) 218 | for i := portLow; i < portHigh; i++ { 219 | addressToTry := net.JoinHostPort("localhost", strconv.Itoa(i)) 220 | conn, err := net.DialTimeout("tcp", addressToTry, timeout) 221 | // if connection is refused, it could be a free port 222 | if err != nil && strings.Contains(err.Error(), "connection refused") { 223 | // try opening a tcp connection and if it succeeds this is a good port 224 | l, err1 := net.Listen("tcp", addressToTry) 225 | if err1 == nil { 226 | openPorts = append(openPorts, i) 227 | l.Close() 228 | if len(openPorts) == 2 { 229 | break 230 | } 231 | } 232 | } else if conn != nil { 233 | conn.Close() 234 | } 235 | } 236 | return openPorts 237 | } 238 | -------------------------------------------------------------------------------- /memcache/memcache_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 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 memcache provides a client for the memcached cache server. 18 | package memcache 19 | 20 | import ( 21 | "bufio" 22 | "fmt" 23 | "io" 24 | "io/ioutil" 25 | "net" 26 | "os" 27 | "os/exec" 28 | "strings" 29 | "testing" 30 | "time" 31 | ) 32 | 33 | const testServer = "localhost:11211" 34 | 35 | func setup(t *testing.T) bool { 36 | c, err := net.Dial("tcp", testServer) 37 | if err != nil { 38 | t.Skipf("skipping test; no server running at %s", testServer) 39 | } 40 | c.Write([]byte("flush_all\r\n")) 41 | c.Close() 42 | return true 43 | } 44 | 45 | func TestLocalhost(t *testing.T) { 46 | if !setup(t) { 47 | return 48 | } 49 | testWithClient(t, New(testServer)) 50 | } 51 | 52 | // Run the memcached binary as a child process and connect to its unix socket. 53 | func TestUnixSocket(t *testing.T) { 54 | sock := fmt.Sprintf("/tmp/test-gomemcache-%d.sock", os.Getpid()) 55 | cmd := exec.Command("memcached", "-s", sock) 56 | if err := cmd.Start(); err != nil { 57 | t.Skipf("skipping test; couldn't find memcached") 58 | return 59 | } 60 | defer cmd.Wait() 61 | defer cmd.Process.Kill() 62 | 63 | // Wait a bit for the socket to appear. 64 | for i := 0; i < 10; i++ { 65 | if _, err := os.Stat(sock); err == nil { 66 | break 67 | } 68 | time.Sleep(time.Duration(25*i) * time.Millisecond) 69 | } 70 | 71 | testWithClient(t, New(sock)) 72 | } 73 | 74 | func mustSetF(t *testing.T, c *Client) func(*Item) { 75 | return func(it *Item) { 76 | if err := c.Set(it); err != nil { 77 | t.Fatalf("failed to Set %#v: %v", *it, err) 78 | } 79 | } 80 | } 81 | 82 | func testWithClient(t *testing.T, c *Client) { 83 | checkErr := func(err error, format string, args ...interface{}) { 84 | if err != nil { 85 | t.Fatalf(format, args...) 86 | } 87 | } 88 | mustSet := mustSetF(t, c) 89 | 90 | // Set 91 | foo := &Item{Key: "foo", Value: []byte("fooval"), Flags: 123} 92 | err := c.Set(foo) 93 | checkErr(err, "first set(foo): %v", err) 94 | err = c.Set(foo) 95 | checkErr(err, "second set(foo): %v", err) 96 | 97 | // Get 98 | it, err := c.Get("foo") 99 | checkErr(err, "get(foo): %v", err) 100 | if it.Key != "foo" { 101 | t.Errorf("get(foo) Key = %q, want foo", it.Key) 102 | } 103 | if string(it.Value) != "fooval" { 104 | t.Errorf("get(foo) Value = %q, want fooval", string(it.Value)) 105 | } 106 | if it.Flags != 123 { 107 | t.Errorf("get(foo) Flags = %v, want 123", it.Flags) 108 | } 109 | 110 | // Get and set a unicode key 111 | quxKey := "Hello_世界" 112 | qux := &Item{Key: quxKey, Value: []byte("hello world")} 113 | err = c.Set(qux) 114 | checkErr(err, "first set(Hello_世界): %v", err) 115 | it, err = c.Get(quxKey) 116 | checkErr(err, "get(Hello_世界): %v", err) 117 | if it.Key != quxKey { 118 | t.Errorf("get(Hello_世界) Key = %q, want Hello_世界", it.Key) 119 | } 120 | if string(it.Value) != "hello world" { 121 | t.Errorf("get(Hello_世界) Value = %q, want hello world", string(it.Value)) 122 | } 123 | 124 | // Set malformed keys 125 | malFormed := &Item{Key: "foo bar", Value: []byte("foobarval")} 126 | err = c.Set(malFormed) 127 | if err != ErrMalformedKey { 128 | t.Errorf("set(foo bar) should return ErrMalformedKey instead of %v", err) 129 | } 130 | malFormed = &Item{Key: "foo" + string(0x7f), Value: []byte("foobarval")} 131 | err = c.Set(malFormed) 132 | if err != ErrMalformedKey { 133 | t.Errorf("set(foo<0x7f>) should return ErrMalformedKey instead of %v", err) 134 | } 135 | 136 | // Add 137 | bar := &Item{Key: "bar", Value: []byte("barval")} 138 | err = c.Add(bar) 139 | checkErr(err, "first add(foo): %v", err) 140 | if err := c.Add(bar); err != ErrNotStored { 141 | t.Fatalf("second add(foo) want ErrNotStored, got %v", err) 142 | } 143 | 144 | // Replace 145 | baz := &Item{Key: "baz", Value: []byte("bazvalue")} 146 | if err := c.Replace(baz); err != ErrNotStored { 147 | t.Fatalf("expected replace(baz) to return ErrNotStored, got %v", err) 148 | } 149 | err = c.Replace(bar) 150 | checkErr(err, "replaced(foo): %v", err) 151 | 152 | // GetMulti 153 | m, err := c.GetMulti([]string{"foo", "bar"}) 154 | checkErr(err, "GetMulti: %v", err) 155 | if g, e := len(m), 2; g != e { 156 | t.Errorf("GetMulti: got len(map) = %d, want = %d", g, e) 157 | } 158 | if _, ok := m["foo"]; !ok { 159 | t.Fatalf("GetMulti: didn't get key 'foo'") 160 | } 161 | if _, ok := m["bar"]; !ok { 162 | t.Fatalf("GetMulti: didn't get key 'bar'") 163 | } 164 | if g, e := string(m["foo"].Value), "fooval"; g != e { 165 | t.Errorf("GetMulti: foo: got %q, want %q", g, e) 166 | } 167 | if g, e := string(m["bar"].Value), "barval"; g != e { 168 | t.Errorf("GetMulti: bar: got %q, want %q", g, e) 169 | } 170 | 171 | // Delete 172 | err = c.Delete("foo") 173 | checkErr(err, "Delete: %v", err) 174 | it, err = c.Get("foo") 175 | if err != ErrCacheMiss { 176 | t.Errorf("post-Delete want ErrCacheMiss, got %v", err) 177 | } 178 | 179 | // Incr/Decr 180 | mustSet(&Item{Key: "num", Value: []byte("42")}) 181 | n, err := c.Increment("num", 8) 182 | checkErr(err, "Increment num + 8: %v", err) 183 | if n != 50 { 184 | t.Fatalf("Increment num + 8: want=50, got=%d", n) 185 | } 186 | n, err = c.Decrement("num", 49) 187 | checkErr(err, "Decrement: %v", err) 188 | if n != 1 { 189 | t.Fatalf("Decrement 49: want=1, got=%d", n) 190 | } 191 | err = c.Delete("num") 192 | checkErr(err, "delete num: %v", err) 193 | n, err = c.Increment("num", 1) 194 | if err != ErrCacheMiss { 195 | t.Fatalf("increment post-delete: want ErrCacheMiss, got %v", err) 196 | } 197 | mustSet(&Item{Key: "num", Value: []byte("not-numeric")}) 198 | n, err = c.Increment("num", 1) 199 | if err == nil || !strings.Contains(err.Error(), "client error") { 200 | t.Fatalf("increment non-number: want client error, got %v", err) 201 | } 202 | testTouchWithClient(t, c) 203 | 204 | // Test Delete All 205 | err = c.DeleteAll() 206 | checkErr(err, "DeleteAll: %v", err) 207 | it, err = c.Get("bar") 208 | if err != ErrCacheMiss { 209 | t.Errorf("post-DeleteAll want ErrCacheMiss, got %v", err) 210 | } 211 | 212 | // Test Ping 213 | err = c.Ping() 214 | checkErr(err, "error ping: %s", err) 215 | } 216 | 217 | func testTouchWithClient(t *testing.T, c *Client) { 218 | if testing.Short() { 219 | t.Log("Skipping testing memcache Touch with testing in Short mode") 220 | return 221 | } 222 | 223 | mustSet := mustSetF(t, c) 224 | 225 | const secondsToExpiry = int32(2) 226 | 227 | // We will set foo and bar to expire in 2 seconds, then we'll keep touching 228 | // foo every second 229 | // After 3 seconds, we expect foo to be available, and bar to be expired 230 | foo := &Item{Key: "foo", Value: []byte("fooval"), Expiration: secondsToExpiry} 231 | bar := &Item{Key: "bar", Value: []byte("barval"), Expiration: secondsToExpiry} 232 | 233 | setTime := time.Now() 234 | mustSet(foo) 235 | mustSet(bar) 236 | 237 | for s := 0; s < 3; s++ { 238 | time.Sleep(time.Duration(1 * time.Second)) 239 | err := c.Touch(foo.Key, secondsToExpiry) 240 | if nil != err { 241 | t.Errorf("error touching foo: %v", err.Error()) 242 | } 243 | } 244 | 245 | _, err := c.Get("foo") 246 | if err != nil { 247 | if err == ErrCacheMiss { 248 | t.Fatalf("touching failed to keep item foo alive") 249 | } else { 250 | t.Fatalf("unexpected error retrieving foo after touching: %v", err.Error()) 251 | } 252 | } 253 | 254 | _, err = c.Get("bar") 255 | if nil == err { 256 | t.Fatalf("item bar did not expire within %v seconds", time.Now().Sub(setTime).Seconds()) 257 | } else { 258 | if err != ErrCacheMiss { 259 | t.Fatalf("unexpected error retrieving bar: %v", err.Error()) 260 | } 261 | } 262 | } 263 | 264 | func BenchmarkOnItem(b *testing.B) { 265 | fakeServer, err := net.Listen("tcp", "localhost:0") 266 | if err != nil { 267 | b.Fatal("Could not open fake server: ", err) 268 | } 269 | defer fakeServer.Close() 270 | go func() { 271 | for { 272 | if c, err := fakeServer.Accept(); err == nil { 273 | go func() { io.Copy(ioutil.Discard, c) }() 274 | } else { 275 | return 276 | } 277 | } 278 | }() 279 | 280 | addr := fakeServer.Addr() 281 | c := New(addr.String()) 282 | if _, err := c.getConn(addr); err != nil { 283 | b.Fatal("failed to initialize connection to fake server") 284 | } 285 | 286 | item := Item{Key: "foo"} 287 | dummyFn := func(_ *Client, _ *bufio.ReadWriter, _ *Item) error { return nil } 288 | b.ResetTimer() 289 | for i := 0; i < b.N; i++ { 290 | c.onItem(&item, dummyFn) 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /memcache/selector.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 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 memcache 18 | 19 | import ( 20 | "hash/crc32" 21 | "math/rand" 22 | "net" 23 | "strings" 24 | "sync" 25 | ) 26 | 27 | // Note: If you are implementing ServerSelector, you will have to implement the 28 | // new method PickAnyServer 29 | 30 | // ServerSelector is the interface that selects a memcache server 31 | // as a function of the item's key. 32 | // 33 | // All ServerSelector implementations must be safe for concurrent use 34 | // by multiple goroutines. 35 | type ServerSelector interface { 36 | // PickServer returns the server address that a given item 37 | // should be shared onto. 38 | PickServer(key string) (net.Addr, error) 39 | 40 | // PickAnyServer returns any active server, preferably not the 41 | // same one every time in order to distribute the load. 42 | // This can be used to get information which is server agnostic. 43 | PickAnyServer() (net.Addr, error) 44 | Each(func(net.Addr) error) error 45 | } 46 | 47 | // ServerList is a simple ServerSelector. Its zero value is usable. 48 | type ServerList struct { 49 | mu sync.RWMutex 50 | addrs []net.Addr 51 | } 52 | 53 | // staticAddr caches the Network() and String() values from any net.Addr. 54 | type staticAddr struct { 55 | ntw, str string 56 | } 57 | 58 | func newStaticAddr(a net.Addr) net.Addr { 59 | return &staticAddr{ 60 | ntw: a.Network(), 61 | str: a.String(), 62 | } 63 | } 64 | 65 | func (s *staticAddr) Network() string { return s.ntw } 66 | func (s *staticAddr) String() string { return s.str } 67 | 68 | // SetServers changes a ServerList's set of servers at runtime and is 69 | // safe for concurrent use by multiple goroutines. 70 | // 71 | // Each server is given equal weight. A server is given more weight 72 | // if it's listed multiple times. 73 | // 74 | // SetServers returns an error if any of the server names fail to 75 | // resolve. No attempt is made to connect to the server. If any error 76 | // is returned, no changes are made to the ServerList. 77 | func (ss *ServerList) SetServers(servers ...string) error { 78 | naddr := make([]net.Addr, len(servers)) 79 | for i, server := range servers { 80 | if strings.Contains(server, "/") { 81 | addr, err := net.ResolveUnixAddr("unix", server) 82 | if err != nil { 83 | return err 84 | } 85 | naddr[i] = newStaticAddr(addr) 86 | } else { 87 | tcpaddr, err := net.ResolveTCPAddr("tcp", server) 88 | if err != nil { 89 | return err 90 | } 91 | naddr[i] = newStaticAddr(tcpaddr) 92 | } 93 | } 94 | 95 | ss.mu.Lock() 96 | defer ss.mu.Unlock() 97 | ss.addrs = naddr 98 | return nil 99 | } 100 | 101 | // Each iterates over each server calling the given function 102 | func (ss *ServerList) Each(f func(net.Addr) error) error { 103 | ss.mu.RLock() 104 | defer ss.mu.RUnlock() 105 | for _, a := range ss.addrs { 106 | if err := f(a); nil != err { 107 | return err 108 | } 109 | } 110 | return nil 111 | } 112 | 113 | // keyBufPool returns []byte buffers for use by PickServer's call to 114 | // crc32.ChecksumIEEE to avoid allocations. (but doesn't avoid the 115 | // copies, which at least are bounded in size and small) 116 | var keyBufPool = sync.Pool{ 117 | New: func() interface{} { 118 | b := make([]byte, 256) 119 | return &b 120 | }, 121 | } 122 | 123 | // PickAnyServer picks any active server 124 | // This can be used to get information which is not linked to a key or which could be on any server. 125 | func (ss *ServerList) PickAnyServer() (net.Addr, error) { 126 | ss.mu.RLock() 127 | defer ss.mu.RUnlock() 128 | if len(ss.addrs) == 0 { 129 | return nil, ErrNoServers 130 | } 131 | 132 | if len(ss.addrs) == 1 { 133 | return ss.addrs[0], nil 134 | } 135 | return ss.addrs[rand.Intn(len(ss.addrs))], nil 136 | 137 | } 138 | 139 | func (ss *ServerList) PickServer(key string) (net.Addr, error) { 140 | ss.mu.RLock() 141 | defer ss.mu.RUnlock() 142 | if len(ss.addrs) == 0 { 143 | return nil, ErrNoServers 144 | } 145 | if len(ss.addrs) == 1 { 146 | return ss.addrs[0], nil 147 | } 148 | bufp := keyBufPool.Get().(*[]byte) 149 | n := copy(*bufp, key) 150 | cs := crc32.ChecksumIEEE((*bufp)[:n]) 151 | keyBufPool.Put(bufp) 152 | 153 | return ss.addrs[cs%uint32(len(ss.addrs))], nil 154 | } 155 | -------------------------------------------------------------------------------- /memcache/selector_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 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 memcache 18 | 19 | import ( 20 | "net" 21 | "testing" 22 | ) 23 | 24 | func BenchmarkPickServer(b *testing.B) { 25 | // at least two to avoid 0 and 1 special cases: 26 | benchPickServer(b, "127.0.0.1:1234", "127.0.0.1:1235") 27 | } 28 | 29 | func BenchmarkPickServer_Single(b *testing.B) { 30 | benchPickServer(b, "127.0.0.1:1234") 31 | } 32 | 33 | func benchPickServer(b *testing.B, servers ...string) { 34 | b.ReportAllocs() 35 | var ss ServerList 36 | ss.SetServers(servers...) 37 | for i := 0; i < b.N; i++ { 38 | if _, err := ss.PickServer("some key"); err != nil { 39 | b.Fatal(err) 40 | } 41 | } 42 | } 43 | 44 | func BenchmarkPickAnyServer(b *testing.B) { 45 | // at least two to avoid 0 and 1 special cases: 46 | benchPickAnyServer(b, "127.0.0.1:1234", "127.0.0.1:1235") 47 | } 48 | 49 | func TestPickAnyServer(t *testing.T) { 50 | pickServerTests := []struct { 51 | serverList []string 52 | expectedServersPicked int 53 | }{ 54 | {[]string{"127.0.0.1:1234"}, 1}, 55 | {[]string{"127.0.0.1:1234", "127.0.0.1:1235", "127.0.0.1:1236"}, 2}, 56 | } 57 | for _, tt := range pickServerTests { 58 | var ss ServerList 59 | ss.SetServers(tt.serverList...) 60 | serverCounter := make(map[string]int) 61 | for i := 0; i < 1000; i++ { 62 | var addr net.Addr 63 | var err error 64 | if addr, err = ss.PickAnyServer(); err != nil { 65 | t.Errorf("pickAnyServer(%v) failed due to %v", tt.serverList, err) 66 | } 67 | serverCounter[addr.String()]++ 68 | } 69 | // Verify that server counter contains at least 2 values. 70 | if len(serverCounter) < tt.expectedServersPicked { 71 | t.Errorf("failed to randomize server list (%v), serverCounter (%v). got:%v, want at least:%v", tt.serverList, serverCounter, len(serverCounter), tt.expectedServersPicked) 72 | } 73 | } 74 | } 75 | 76 | func TestPickAnyServerThrows(t *testing.T) { 77 | var ss ServerList 78 | if _, err := ss.PickAnyServer(); err != ErrNoServers { 79 | t.Errorf("expected error with no servers, got:%v", err) 80 | } 81 | } 82 | 83 | func BenchmarkPickAnyServer_Single(b *testing.B) { 84 | benchPickAnyServer(b, "127.0.0.1:1234") 85 | } 86 | 87 | func benchPickAnyServer(b *testing.B, servers ...string) { 88 | b.ReportAllocs() 89 | var ss ServerList 90 | ss.SetServers(servers...) 91 | for i := 0; i < b.N; i++ { 92 | if _, err := ss.PickAnyServer(); err != nil { 93 | b.Fatal(err) 94 | } 95 | } 96 | } 97 | --------------------------------------------------------------------------------