├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── TODO.md ├── bench_test.go ├── client.go ├── client_test.go ├── config.go ├── failure_test.go ├── go.mod ├── hasher.go ├── mock_conn.go ├── multi_node_test.go ├── protocol.go ├── server.go ├── server_conn.go └── utils_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.6 2 | *.a 3 | *.out 4 | *.prof 5 | .DS_Store 6 | _go_.6 7 | _gotest_.6 8 | _testmain.go 9 | mc.test 10 | src/github.com 11 | bin 12 | pkg 13 | test.pids 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/github.com/bmizerany/assert"] 2 | path = vendor/github.com/bmizerany/assert 3 | url = https://github.com/bmizerany/assert.git 4 | [submodule "vendor/github.com/kr/pretty"] 5 | path = vendor/github.com/kr/pretty 6 | url = https://github.com/kr/pretty.git 7 | [submodule "vendor/github.com/kr/text"] 8 | path = vendor/github.com/kr/text 9 | url = https://github.com/kr/text.git 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.8.x 5 | - 1.9.x 6 | - 1.10.x 7 | - 1.11.x 8 | - 1.12.x 9 | - 1.13.x 10 | - 1.14.x 11 | - tip 12 | 13 | env: 14 | - GO111MODULE=on 15 | 16 | matrix: 17 | allow_failures: 18 | - go: tip 19 | 20 | before_install: 21 | - travis_retry sudo apt-get update 22 | 23 | install: 24 | - travis_retry sudo apt-get install memcached 25 | - memcached -h | head -1 26 | 27 | script: 28 | - make 29 | - make test-full 30 | - make test-multinode 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 by David Terei 2 | Copyright (C) 2011 by Blake Mizerany 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL:=/bin/bash 2 | ifndef GO 3 | GO:=go 4 | endif 5 | 6 | .PHONY: all clean fmt vet lint help 7 | 8 | all: build 9 | 10 | build: *.go 11 | $(GO) build 12 | 13 | test: build 14 | @memcached -p 11289 & echo $$! > test.pids 15 | @GOPATH=$(CURDIR) $(GO) test -test.short -v; ST=$?; \ 16 | cd $(CURDIR); cat test.pids | xargs kill; rm test.pids 17 | @exit ${ST} 18 | 19 | test-full: build 20 | @memcached -p 11289 & echo $$! > test.pids 21 | @GOPATH=$(CURDIR) GO15VENDOREXPERIMENT=1 $(GO) test -v; ST=$?; \ 22 | cd $(CURDIR); cat test.pids | xargs kill; rm test.pids 23 | @exit ${ST} 24 | 25 | test-multinode: build 26 | @memcached -p 11289 & echo $$! > test.pids 27 | @memcached -p 11290 & echo $$! >> test.pids 28 | @memcached -p 11291 & echo $$! >> test.pids 29 | @GOPATH=$(CURDIR) GO15VENDOREXPERIMENT=1 $(GO) test -run '^TestMultiNode' -v -tags=multinode; ST=$?; \ 30 | cat test.pids | xargs kill; rm test.pids 31 | @exit ${ST} 32 | 33 | bench: build 34 | @memcached -p 11289 & echo $$! > test.pids 35 | @GOPATH=$(CURDIR) $(GO) test -run "notests" -bench ".*"; ST=$?; \ 36 | cd $(CURDIR); cat test.pids | xargs kill; rm test.pids 37 | @exit ${ST} 38 | 39 | clean: 40 | @$(GO) clean 41 | 42 | fmt: 43 | @$(GO) fmt 44 | 45 | vet: 46 | @$(GO) vet 47 | 48 | lint: 49 | @command -v golint >/dev/null 2>&1 \ 50 | || { echo >&2 "The 'golint' tool is required, please install"; exit 1; } 51 | @golint 52 | 53 | help: 54 | @echo "Build Targets" 55 | @echo " build - Build mc" 56 | @echo " test - Quick test of mc (against memcached on port 11289)" 57 | @echo " test-full - Longer test of mc (against memcached on port 11289)" 58 | @echo " clean - Remove built sources" 59 | @echo " fmt - Format the source code using 'go fmt'" 60 | @echo " vet - Analyze the source code for potential errors" 61 | @echo " lint - Analyze the source code for style mistakes" 62 | @echo "" 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mc.go: A Go client for Memcached 2 | 3 | [![godoc](https://godoc.org/github.com/memcachier/mc?status.svg)](http://godoc.org/github.com/memcachier/mc) 4 | [![Build Status](https://img.shields.io/travis/memcachier/mc.svg?style=flat)](https://travis-ci.org/memcachier/mc) 5 | 6 | This is a (pure) Go client for [Memcached](http://memcached.org). It supports 7 | the binary Memcached protocol, SASL authentication and Compression. It's thread-safe. 8 | It allows connections to entire Memcached clusters and supports connection 9 | pools, timeouts, and failover. 10 | 11 | ## Install 12 | 13 | Module-aware mode: 14 | 15 | ``` 16 | $ go get github.com/memcachier/mc/v3 17 | ``` 18 | 19 | Legacy GOPATH mode: 20 | 21 | ``` 22 | $ go get github.com/memcachier/mc 23 | ``` 24 | 25 | ## Use 26 | 27 | ```go 28 | import "github.com/memcachier/mc/v3" 29 | // Legacy GOPATH mode: 30 | // import "github.com/memcachier/mc" 31 | 32 | func main() { 33 | // Error handling omitted for demo 34 | 35 | // Only PLAIN SASL auth supported right now 36 | c := mc.NewMC("localhost:11211", "username", "password") 37 | defer c.Quit() 38 | 39 | exp := 3600 // 2 hours 40 | cas, err = c.Set("foo", "bar", flags, exp, cas) 41 | if err != nil { 42 | ... 43 | } 44 | 45 | val, flags, cas, err = c.Get("foo") 46 | if err != nil { 47 | ... 48 | } 49 | 50 | err = c.Del("foo") 51 | if err != nil { 52 | ... 53 | } 54 | } 55 | ``` 56 | 57 | ## Using zlib Compression 58 | 59 | ```go 60 | import ( 61 | "github.com/memcachier/mc/v3" 62 | "compress/zlib" 63 | ) 64 | // Legacy GOPATH mode: 65 | // import "github.com/memcachier/mc" 66 | 67 | func main() { 68 | // Error handling omitted for demo 69 | 70 | // Only PLAIN SASL auth supported right now 71 | config := mc.DefaultConfig() 72 | 73 | // You have to set the functions to compress and descompress 74 | // At this example we are using zlib. 75 | 76 | config.Compression.Decompress = func(value string) (string, error) { 77 | var compressedValue bytes.Buffer 78 | zw, err := zlib.NewWriterLevel(&compressedValue, -1) 79 | if err != nil { 80 | return value, err 81 | } 82 | if _, err = zw.Write([]byte(value)); err != nil { 83 | return value, err 84 | } 85 | zw.Close() 86 | return compressedValue.String(), nil 87 | } 88 | 89 | config.Compression.Compress = func(value string) (string, error) { 90 | if value == "" { 91 | return value, nil 92 | } 93 | zr, err := zlib.NewReader(strings.NewReader(value)) 94 | if err != nil { 95 | return value, nil // Does not return error, the value could be not compressed 96 | } 97 | defer zr.Close() 98 | var unCompressedValue bytes.Buffer 99 | _, err = io.Copy(&unCompressedValue, zr) 100 | if err != nil { 101 | return value, nil 102 | } 103 | return unCompressedValue.String(), nil 104 | } 105 | 106 | c := mc.NewMCwithConfig("localhost:11211", "username", "password", config) 107 | defer c.Quit() 108 | 109 | exp := 3600 // 2 hours 110 | cas, err = c.Set("foo", "bar", flags, exp, cas) 111 | if err != nil { 112 | ... 113 | } 114 | 115 | val, flags, cas, err = c.Get("foo") 116 | if err != nil { 117 | ... 118 | } 119 | 120 | err = c.Del("foo") 121 | if err != nil { 122 | ... 123 | } 124 | } 125 | ``` 126 | 127 | ## Using gzip Compression 128 | 129 | ```go 130 | import ( 131 | "github.com/memcachier/mc/v3" 132 | "compress/gzip" 133 | ) 134 | // Legacy GOPATH mode: 135 | // import "github.com/memcachier/mc" 136 | 137 | func main() { 138 | // Error handling omitted for demo 139 | 140 | // Only PLAIN SASL auth supported right now 141 | config := mc.DefaultConfig() 142 | 143 | // You have to set the functions to compress and descompress 144 | // At this example we are using gzip. 145 | 146 | config.Compression.Decompress = func(value string) (string, error) { 147 | var compressedValue bytes.Buffer 148 | zw, err := gzip.NewWriterLevel(&compressedValue, -1) 149 | if err != nil { 150 | return value, err 151 | } 152 | if _, err = zw.Write([]byte(value)); err != nil { 153 | return value, err 154 | } 155 | zw.Close() 156 | return compressedValue.String(), nil 157 | } 158 | 159 | config.Compression.Compress = func(value string) (string, error) { 160 | if value == "" { 161 | return value, nil 162 | } 163 | zr, err := gzip.NewReader(strings.NewReader(value)) 164 | if err != nil { 165 | return value, nil // Does not return error, the value could be not compressed 166 | } 167 | defer zr.Close() 168 | var unCompressedValue bytes.Buffer 169 | _, err = io.Copy(&unCompressedValue, zr) 170 | if err != nil { 171 | return value, nil 172 | } 173 | return unCompressedValue.String(), nil 174 | } 175 | 176 | c := mc.NewMCwithConfig("localhost:11211", "username", "password", config) 177 | defer c.Quit() 178 | 179 | exp := 3600 // 2 hours 180 | cas, err = c.Set("foo", "bar", flags, exp, cas) 181 | if err != nil { 182 | ... 183 | } 184 | 185 | val, flags, cas, err = c.Get("foo") 186 | if err != nil { 187 | ... 188 | } 189 | 190 | err = c.Del("foo") 191 | if err != nil { 192 | ... 193 | } 194 | } 195 | ``` 196 | 197 | ## Missing Feature 198 | 199 | There is nearly coverage of the Memcached protocol. 200 | The biggest missing protocol feature is support for `multi_get` and other 201 | batched operations. 202 | 203 | There is also no support for asynchronous IO. 204 | 205 | ## Performance 206 | 207 | Right now we use a single per-connection mutex and don't support pipe-lining any 208 | operations. There is however support for connection pools which should make up 209 | for it. 210 | 211 | ## Get involved! 212 | 213 | We are happy to receive bug reports, fixes, documentation enhancements, 214 | and other improvements. 215 | 216 | Please report bugs via the 217 | [github issue tracker](http://github.com/memcachier/mc/issues). 218 | 219 | Master [git repository](http://github.com/memcachier/mc): 220 | 221 | - `git clone git://github.com/memcachier/mc.git` 222 | 223 | ## Licensing 224 | 225 | This library is MIT-licensed. 226 | 227 | ## Authors 228 | 229 | This library is written and maintained by MemCachier. 230 | It was originally written by [Blake Mizerany](https://github.com/bmizerany/mc). 231 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Client 2 | 3 | Features: 4 | 5 | - Multi (batch) support 6 | - Asynchronous IO 7 | 8 | Nice-to-have: 9 | 10 | - Simple namespacing 11 | - Split large keys 12 | 13 | Performance: 14 | 15 | - Pipelining 16 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package mc 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkSet(b *testing.B) { 8 | b.StopTimer() 9 | c := NewMC(mcAddr, user, pass) 10 | // Lazy connection. Make sure it connects before starting benchmark. 11 | _, err := c.Set("foo", "bar", 0, 0, 0) 12 | if err != nil { 13 | panic(err) 14 | } 15 | 16 | b.StartTimer() 17 | defer b.StopTimer() 18 | 19 | for i := 0; i < b.N; i++ { 20 | _, err := c.Set("foo", "bar", 0, 0, 0) 21 | if err != nil { 22 | panic(err) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // Package mc is a memcache client for Go supporting binary protocol and SASL 2 | // authentication. 3 | package mc 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // Protocol: 12 | // Contains the actual memcache commands a user cares about. 13 | // We document the protocol a little with each command, you can find the 14 | // official documentation at: 15 | // * https://github.com/memcached/memcached/blob/master/doc/protocol-binary.xml 16 | // * https://github.com/memcached/memcached/blob/master/doc/protocol.txt 17 | // * http://code.google.com/p/memcached/wiki/SASLAuthProtocol 18 | // * http://tools.ietf.org/html/rfc4422 (SASL) 19 | // However, sadly none of these are 100% accurate and you have to look at the 20 | // memcached source code to find any missing cases or mismatches. 21 | // 22 | // * Protocol uses standard network order for bytes (big-endian) 23 | 24 | // Command Variants: 25 | // One quick note on memcache commands, many of them support the following 26 | // variants [R, Q, K, KQ], e.g., GET can be GETK or GETQ... 27 | // * K : Include the KEY in the response. 28 | // * Q : Quiet version of a command. This means if the key doesn't exist, 29 | // no response is sent. 30 | // * R : Ranged version of the command. Not actually implemented by 31 | // memcached, just there for future extension if needed. So we 32 | // ignore in this client. 33 | 34 | // Multi-Get: 35 | // Simply implemented using GETQ. It's used for 'pipelining' requests, where the 36 | // client sends many GETQ's without checking the response until the very end 37 | // (batching). The memcached server doesn't do anything special, it sends 38 | // response straight away, so it relies on the clients socket queuing up the 39 | // responses on its buffer. 40 | 41 | // Response: 42 | // In addition to the key, value & extras we always get back in a response the 43 | // status, CAS and opaque (although a user of a memcache client probably never 44 | // cares about opaque, only we, the implementer of a memcache client, may care 45 | // as it can be used for matching request with responses...) 46 | 47 | // Expiration. 48 | // * In seconds - when value of int is less than or equal 49 | // to: 60 * 60 * 24 * 30 (e.g., seconds in a month). 50 | // * As a UNIX timestamp - otherwise. 51 | // 52 | // * Memcached accounts for time passing with a single global counter updated 53 | // once a second, so it therefore has an error margin of 1 second (as you 54 | // may do a set with expiration 0.5 seconds before it does a global time 55 | // update, and that 0.5 seconds will expire your key by 1 whole second). 56 | // * Error margin is always under time, not over. E.g., a expiration of 4 57 | // seconds will actually expire somewhere in the range of (3,4) seconds. 58 | 59 | // Client represents a memcached client that is connected to a list of servers 60 | type Client struct { 61 | servers []*server 62 | config *Config 63 | } 64 | 65 | // NewMC creates a new client with the default configuration. For the default 66 | // configuration see DefaultConfig. 67 | func NewMC(servers, username, password string) *Client { 68 | return NewMCwithConfig(servers, username, password, DefaultConfig()) 69 | } 70 | 71 | // NewMCwithConfig creates a new client for a given configuration 72 | func NewMCwithConfig(servers, username, password string, config *Config) *Client { 73 | return newMockableMC(servers, username, password, config, newServerConn) 74 | } 75 | 76 | // newMockableMC creates a new client for testing that allows to mock the server 77 | // connection 78 | func newMockableMC(servers, username, password string, config *Config, newMcConn connGen) *Client { 79 | client := &Client{config: config} 80 | 81 | s := func(r rune) bool { 82 | return r == ',' || r == ';' || r == ' ' 83 | } 84 | serverList := strings.FieldsFunc(servers, s) 85 | for _, addr := range serverList { 86 | client.servers = append(client.servers, 87 | newServer(addr, username, password, config, newMcConn)) 88 | } 89 | 90 | client.config.Hasher.update(client.servers) 91 | 92 | return client 93 | } 94 | 95 | func (c *Client) perform(m *msg) error { 96 | // failover on error 97 | for { 98 | s, err := c.getServer(m.key) 99 | if err != nil { 100 | return err 101 | } 102 | err = s.perform(m) 103 | if err != nil && err.(*Error).Status == StatusNetworkError && c.config.Failover { 104 | // Failover on network errors 105 | if s.changeAlive(false) { 106 | go c.wakeUp(s) 107 | } 108 | continue 109 | } 110 | return err 111 | } 112 | return nil 113 | } 114 | 115 | func (c *Client) wakeUp(s *server) { 116 | time.Sleep(c.config.DownRetryDelay) 117 | s.changeAlive(true) 118 | } 119 | 120 | func (c *Client) getServer(key string) (*server, error) { 121 | idx, err := c.config.Hasher.getServerIndex(key) 122 | if err != nil { 123 | return nil, err 124 | } 125 | nServers := uint(len(c.servers)) 126 | for i := uint(0); i < nServers; i++ { 127 | s := c.servers[(idx+i)%nServers] 128 | if s.isAlive { 129 | return s, nil 130 | } 131 | } 132 | return nil, &Error{StatusNetworkError, "All server currently dead", nil} 133 | } 134 | 135 | // Get retrieves a value from the cache. 136 | func (c *Client) Get(key string) (val string, flags uint32, cas uint64, err error) { 137 | // Variants: [R] Get [Q, K, KQ] 138 | // Request : MUST key; MUST NOT value, extras 139 | // Response: MAY key, value, extras ([0..3] flags) 140 | return c.getCAS(key, 0) 141 | } 142 | 143 | // getCAS retrieves a value in the cache but only if the CAS specified matches 144 | // the CAS argument. 145 | // 146 | // NOTE: GET doesn't actually care about CAS, but we want this internally for 147 | // testing purposes, to be able to test that a memcache server obeys the proper 148 | // semantics of ignoring CAS with GETs. 149 | func (c *Client) getCAS(key string, ocas uint64) (val string, flags uint32, cas uint64, err error) { 150 | m := &msg{ 151 | header: header{ 152 | Op: opGet, 153 | CAS: uint64(ocas), 154 | }, 155 | oextras: []interface{}{&flags}, 156 | key: key, 157 | } 158 | 159 | err = c.perform(m) 160 | if c.config.Compression.Decompress != nil && err == nil { 161 | m.val, err = c.config.Compression.Decompress(m.val) 162 | } 163 | return m.val, flags, m.CAS, err 164 | } 165 | 166 | // GAT (get and touch) retrieves the value associated with the key and updates 167 | // its expiration time. 168 | func (c *Client) GAT(key string, exp uint32) (val string, flags uint32, cas uint64, err error) { 169 | // Variants: GAT [Q, K, KQ] 170 | // Request : MUST key, extras; MUST NOT value 171 | // Response: MAY key, value, extras ([0..3] flags) 172 | m := &msg{ 173 | header: header{ 174 | Op: opGAT, 175 | }, 176 | iextras: []interface{}{exp}, 177 | oextras: []interface{}{&flags}, 178 | key: key, 179 | } 180 | 181 | err = c.perform(m) 182 | return m.val, flags, m.CAS, err 183 | } 184 | 185 | // Touch updates the expiration time on a key/value pair in the cache. 186 | func (c *Client) Touch(key string, exp uint32) (cas uint64, err error) { 187 | // Variants: Touch 188 | // Request : MUST key, extras; MUST NOT value 189 | // Response: MUST NOT key, value, extras 190 | m := &msg{ 191 | header: header{ 192 | Op: opTouch, 193 | }, 194 | iextras: []interface{}{exp}, 195 | key: key, 196 | } 197 | 198 | err = c.perform(m) 199 | return m.CAS, err 200 | } 201 | 202 | // Set sets a key/value pair in the cache. 203 | func (c *Client) Set(key, val string, flags, exp uint32, ocas uint64) (cas uint64, err error) { 204 | // Variants: [R] Set [Q] 205 | return c.setGeneric(opSet, key, val, ocas, flags, exp) 206 | } 207 | 208 | // Replace replaces an existing key/value in the cache. Fails if key doesn't 209 | // already exist in cache. 210 | func (c *Client) Replace(key, val string, flags, exp uint32, ocas uint64) (cas uint64, err error) { 211 | // Variants: Replace [Q] 212 | return c.setGeneric(opReplace, key, val, ocas, flags, exp) 213 | } 214 | 215 | // Add adds a new key/value to the cache. Fails if the key already exists in the 216 | // cache. 217 | func (c *Client) Add(key, val string, flags, exp uint32) (cas uint64, err error) { 218 | // Variants: Add [Q] 219 | return c.setGeneric(opAdd, key, val, 0, flags, exp) 220 | } 221 | 222 | // Set/Add/Replace a key/value pair in the cache. 223 | func (c *Client) setGeneric(op opCode, key, val string, ocas uint64, flags, exp uint32) (cas uint64, err error) { 224 | // Request : MUST key, value, extras ([0..3] flags, [4..7] expiration) 225 | // Response: MUST NOT key, value, extras 226 | // CAS: If a CAS is specified (non-zero), all sets only succeed if the key 227 | // exists and has the CAS specified. Otherwise, an error is returned. 228 | m := &msg{ 229 | header: header{ 230 | Op: op, 231 | CAS: ocas, 232 | }, 233 | iextras: []interface{}{flags, exp}, 234 | key: key, 235 | val: val, 236 | } 237 | if c.config.Compression.Compress != nil { 238 | m.val, err = c.config.Compression.Compress(m.val) 239 | if err != nil { 240 | return m.CAS, err 241 | } 242 | } 243 | err = c.perform(m) 244 | return m.CAS, err 245 | } 246 | 247 | // Incr increments a value in the cache. The value must be an unsigned 64bit 248 | // integer stored as an ASCII string. It will wrap when incremented outside the 249 | // range. 250 | func (c *Client) Incr(key string, delta, init uint64, exp uint32, ocas uint64) (n, cas uint64, err error) { 251 | return c.incrdecr(opIncrement, key, delta, init, exp, ocas) 252 | } 253 | 254 | // Decr decrements a value in the cache. The value must be an unsigned 64bit 255 | // integer stored as an ASCII string. It can't be decremented below 0. 256 | func (c *Client) Decr(key string, delta, init uint64, exp uint32, ocas uint64) (n, cas uint64, err error) { 257 | return c.incrdecr(opDecrement, key, delta, init, exp, ocas) 258 | } 259 | 260 | // Incr/Decr a key/value pair in the cache. 261 | func (c *Client) incrdecr(op opCode, key string, delta, init uint64, exp uint32, ocas uint64) (n, cas uint64, err error) { 262 | // Variants: [R] Incr [Q], [R] Decr [Q] 263 | // Request : MUST key, extras; MUST NOT value 264 | // Extras: [ 0.. 7] Amount to add/sub 265 | // [ 8..15] Initial value for counter (if key doesn't exist) 266 | // [16..20] Expiration 267 | // Response: MUST value; MUST NOT key, extras 268 | 269 | // * response value is 64 bit unsigned binary number. 270 | // * if the key doesn't exist and the expiration is all 1's (0xffffffff) then 271 | // the operation will fail with NOT_FOUND. 272 | m := &msg{ 273 | header: header{ 274 | Op: op, 275 | CAS: ocas, 276 | }, 277 | iextras: []interface{}{delta, init, exp}, 278 | key: key, 279 | } 280 | 281 | err = c.perform(m) 282 | if err != nil { 283 | return 284 | } 285 | // value is returned as an unsigned 64bit integer (i.e., not as a string) 286 | return readInt(m.val), m.CAS, nil 287 | } 288 | 289 | // Convert string stored to an uint64 (where no actual byte changes are needed). 290 | func readInt(b string) uint64 { 291 | switch len(b) { 292 | case 8: // 64 bit 293 | return uint64(uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | 294 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56) 295 | } 296 | 297 | panic(fmt.Sprintf("mc: don't know how to parse string with %d bytes", len(b))) 298 | } 299 | 300 | // Append appends the value to the existing value for the key specified. An 301 | // error is thrown if the key doesn't exist. 302 | func (c *Client) Append(key, val string, ocas uint64) (cas uint64, err error) { 303 | // Variants: [R] Append [Q] 304 | // Request : MUST key, value; MUST NOT extras 305 | // Response: MUST NOT key, value, extras 306 | m := &msg{ 307 | header: header{ 308 | Op: opAppend, 309 | CAS: ocas, 310 | }, 311 | key: key, 312 | val: val, 313 | } 314 | 315 | err = c.perform(m) 316 | return m.CAS, err 317 | } 318 | 319 | // Prepend prepends the value to the existing value for the key specified. An 320 | // error is thrown if the key doesn't exist. 321 | func (c *Client) Prepend(key, val string, ocas uint64) (cas uint64, err error) { 322 | // Variants: [R] Append [Q] 323 | // Request : MUST key, value; MUST NOT extras 324 | // Response: MUST NOT key, value, extras 325 | m := &msg{ 326 | header: header{ 327 | Op: opPrepend, 328 | CAS: ocas, 329 | }, 330 | key: key, 331 | val: val, 332 | } 333 | 334 | err = c.perform(m) 335 | return m.CAS, err 336 | } 337 | 338 | // Del deletes a key/value from the cache. 339 | func (c *Client) Del(key string) (err error) { 340 | return c.DelCAS(key, 0) 341 | } 342 | 343 | // DelCAS deletes a key/value from the cache but only if the CAS specified 344 | // matches the CAS in the cache. 345 | func (c *Client) DelCAS(key string, cas uint64) (err error) { 346 | // Variants: [R] Del [Q] 347 | // Request : MUST key; MUST NOT value, extras 348 | // Response: MUST NOT key, value, extras 349 | m := &msg{ 350 | header: header{ 351 | Op: opDelete, 352 | CAS: cas, 353 | }, 354 | key: key, 355 | } 356 | 357 | return c.perform(m) 358 | } 359 | 360 | // Flush flushes the cache, that is, invalidate all keys. Note, this doesn't 361 | // typically free memory on a memcache server (doing so compromises the O(1) 362 | // nature of memcache). Instead nearly all servers do lazy expiration, where 363 | // they don't free memory but won't return any keys to you that have expired. 364 | func (c *Client) Flush(when uint32) (err error) { 365 | // Variants: Flush [Q] 366 | // Request : MUST NOT key, value; MAY extras ([0..3] expiration) 367 | // Response: MUST NOT key, value, extras 368 | 369 | // optional expiration means that the flush won't become active until that 370 | // point in time, hence why the argument is called 'when' as that is more 371 | // descriptive of its function. 372 | m := &msg{ 373 | header: header{ 374 | Op: opFlush, 375 | }, 376 | iextras: []interface{}{when}, 377 | } 378 | 379 | for _, s := range c.servers { 380 | if s.isAlive { 381 | var ms msg = *m 382 | err = s.perform(&ms) 383 | } 384 | } 385 | return err // retrns err from last perform but maybe should handle differently 386 | } 387 | 388 | // NoOp sends a No-Op message to the memcache server. This can be used as a 389 | // heartbeat for the server to check it's functioning fine still. 390 | func (c *Client) NoOp() (err error) { 391 | // Variants: NoOp 392 | // Request : MUST NOT key, value, extras 393 | // Response: MUST NOT key, value, extras 394 | m := &msg{ 395 | header: header{ 396 | Op: opNoop, 397 | }, 398 | } 399 | 400 | for _, s := range c.servers { 401 | if s.isAlive { 402 | var ms msg = *m 403 | err = s.perform(&ms) 404 | } 405 | } 406 | return err // retrns err from last perform but maybe should handle differently 407 | } 408 | 409 | // Version gets the version of the memcached server connected to. 410 | func (c *Client) Version() (vers map[string]string, err error) { 411 | // Variants: Version 412 | // Request : MUST NOT key, value, extras 413 | // Response: MUST NOT key, extras; MUST value 414 | 415 | // value is the version as a string in form "X.Y.Z" 416 | m := &msg{ 417 | header: header{ 418 | Op: opVersion, 419 | }, 420 | } 421 | 422 | vers = make(map[string]string) 423 | for _, s := range c.servers { 424 | if s.isAlive { 425 | var ms msg = *m 426 | err = s.perform(&ms) 427 | if err == nil { 428 | vers[s.address] = ms.val 429 | } 430 | } 431 | } 432 | 433 | return 434 | } 435 | 436 | // Quit closes the connection with memcached server (nicely). 437 | func (c *Client) Quit() { 438 | // Variants: Quit [Q] 439 | // Request : MUST NOT key, value, extras 440 | // Response: MUST NOT key, value, extras 441 | m := &msg{ 442 | header: header{ 443 | Op: opQuit, 444 | }, 445 | } 446 | 447 | for _, s := range c.servers { 448 | var ms msg = *m 449 | s.quit(&ms) 450 | } 451 | } 452 | 453 | // StatsWithKey returns some statistics about the memcached server. It supports 454 | // sending across a key to the server to select which statistics should be 455 | // returned. 456 | func (c *Client) StatsWithKey(key string) (map[string]McStats, error) { 457 | // Variants: Stats 458 | // Request : MAY HAVE key, MUST NOT value, extra 459 | // Response: Serries of responses that MUST HAVE key, value; followed by one 460 | // response that MUST NOT have key, value. ALL MUST NOT extras. 461 | m := &msg{ 462 | header: header{ 463 | Op: opStat, 464 | }, 465 | key: key, 466 | } 467 | 468 | allStats := make(map[string]McStats) 469 | for _, s := range c.servers { 470 | if s.isAlive { 471 | stats, err := s.performStats(m) 472 | if err != nil { 473 | return nil, err 474 | } 475 | allStats[s.address] = stats 476 | } 477 | } 478 | 479 | return allStats, nil 480 | } 481 | 482 | // Stats returns some statistics about the memcached server. 483 | func (c *Client) Stats() (stats map[string]McStats, err error) { 484 | return c.StatsWithKey("") 485 | } 486 | 487 | // StatsReset resets the statistics stored at the memcached server. 488 | func (c *Client) StatsReset() (err error) { 489 | _, err = c.StatsWithKey("reset") 490 | return err 491 | } 492 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package mc 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "regexp" 7 | "strconv" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | const ( 13 | mcAddr = "localhost:11289" 14 | badAddr = "127.0.0.2:23111" 15 | user = "user-1" 16 | pass = "pass" 17 | ) 18 | 19 | var mcNil error 20 | 21 | // Some basic tests that functions work 22 | func TestMCSimple(t *testing.T) { 23 | c := testInit(t) 24 | 25 | const ( 26 | Key1 = "foo" 27 | Val1 = "bar" 28 | Val2 = "bar-bad" 29 | Val3 = "bar-good" 30 | ) 31 | 32 | // fmt.Printf("test init: %v", c) 33 | val, flags, cs, err := c.Get(Key1) 34 | if err != ErrNotFound { 35 | t.Errorf("val: %v, flags: %v, cas: %v", val, flags, cs) 36 | t.Fatalf("expected missing key: %v", err) 37 | } 38 | 39 | // unconditional SET 40 | _, err = c.Set(Key1, Val1, 0, 0, 0) 41 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 42 | cas, err := c.Set(Key1, Val1, 0, 0, 0) 43 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 44 | 45 | // make sure CAS works 46 | _, err = c.Set(Key1, Val2, 0, 0, cas+1) 47 | assertEqualf(t, ErrKeyExists, err, "expected CAS mismatch: %v", err) 48 | 49 | // check SET actually set the correct value... 50 | v, _, cas2, err := c.Get(Key1) 51 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 52 | assertEqualf(t, Val1, v, "wrong value: %s", v) 53 | assertEqualf(t, cas, cas2, "CAS shouldn't have changed: %d, %d", cas, cas2) 54 | 55 | // use correct CAS... 56 | cas2, err = c.Set(Key1, Val3, 0, 0, cas) 57 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 58 | assertNotEqualf(t, cas, cas2, "CAS should not be the same") 59 | } 60 | 61 | // Test GET, does it care about CAS? 62 | // NOTE: No it shouldn't, memcached mainline doesn't... 63 | func TestGet(t *testing.T) { 64 | c := testInit(t) 65 | 66 | const ( 67 | Key1 = "fab" 68 | Val1 = "faz" 69 | ) 70 | 71 | _, err := c.Set(Key1, Val1, 0, 0, 0) 72 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 73 | 74 | // retrieve value with 0 CAS... 75 | v1, _, cas1, err := c.getCAS(Key1, 0) 76 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 77 | assertEqualf(t, Val1, v1, "wrong value: %s", v1) 78 | 79 | // retrieve value with good CAS... 80 | v2, _, cas2, err := c.getCAS(Key1, cas1) 81 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 82 | assertEqualf(t, v1, v2, "value changed when it shouldn't: %s, %s", v1, v2) 83 | assertEqualf(t, cas1, cas2, "CAS changed when it shouldn't: %d, %d", cas1, cas2) 84 | 85 | // retrieve value with bad CAS... 86 | v3, _, cas1, err := c.getCAS(Key1, cas1+1) 87 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 88 | assertEqualf(t, v3, v2, "value changed when it shouldn't: %s, %s", v3, v2) 89 | assertEqualf(t, cas1, cas2, "CAS changed when it shouldn't: %d, %d", cas1, cas2) 90 | 91 | // really make sure CAS is bad (above could be an off by one bug...) 92 | v4, _, cas1, err := c.getCAS(Key1, cas1+992313128) 93 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 94 | assertEqualf(t, v4, v2, "value changed when it shouldn't: %s, %s", v4, v2) 95 | assertEqualf(t, cas1, cas2, "CAS changed when it shouldn't: %d, %d", cas1, cas2) 96 | } 97 | 98 | // Test some edge cases of memcached. This was originally done to better 99 | // understand the protocol but servers as a good test for the client and 100 | // server... 101 | 102 | // Test SET behaviour with CAS... 103 | func TestSet(t *testing.T) { 104 | c := testInit(t) 105 | 106 | const ( 107 | Key1 = "foo" 108 | Key2 = "goo" 109 | Val1 = "bar" 110 | Val2 = "zar" 111 | ) 112 | 113 | cas1, err := c.Set(Key1, Val1, 0, 0, 0) 114 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 115 | v, _, cas2, err := c.Get(Key1) 116 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 117 | assertEqualf(t, Val1, v, "wrong value: %v", v) 118 | assertEqualf(t, cas1, cas2, "CAS don't match: %d != %d", cas1, cas2) 119 | 120 | // do two sets of same key, make sure CAS changes... 121 | cas1, err = c.Set(Key2, Val1, 0, 0, 0) 122 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 123 | cas2, err = c.Set(Key2, Val1, 0, 0, 0) 124 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 125 | assertNotEqualf(t, cas1, cas2, "CAS don't match: %d == %d", cas1, cas2) 126 | 127 | // get back the val from Key2... 128 | v, _, cas2, err = c.Get(Key2) 129 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 130 | assertEqualf(t, Val1, v, "wrong value: %v", v) 131 | 132 | // make sure changing value works... 133 | _, err = c.Set(Key1, Val2, 0, 0, 0) 134 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 135 | v, _, cas1, err = c.Get(Key1) 136 | assertEqualf(t, Val2, v, "wrong value: %s", v) 137 | 138 | // Delete Key1 and check it worked, needed for next test... 139 | err = c.Del(Key1) 140 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 141 | _, _, _, err = c.Get(Key1) 142 | assertEqualf(t, ErrNotFound, err, "wrong error: %v", err) 143 | 144 | // What happens when I set a new key and specify a CAS? 145 | // (should fail, bad CAS, can't specify a CAS for a non-existent key, it fails, 146 | // doesn't just ignore the CAS...) 147 | cas, err := c.Set(Key1, Val1, 0, 0, 1) 148 | assertEqualf(t, ErrNotFound, err, "wrong error: %v", err) 149 | assertEqualf(t, uint64(0), cas, "CAS should be nil: %d", cas) 150 | 151 | // make sure it really didn't set it... 152 | v, _, _, err = c.Get(Key1) 153 | assertEqualf(t, ErrNotFound, err, "wrong error: %v", err) 154 | // TODO: On errors a human readable error description should be returned. So 155 | // could test that. 156 | 157 | // Setting an existing value with bad CAS... should fail 158 | _, err = c.Set(Key2, Val2, 0, 0, cas2+1) 159 | assertEqualf(t, ErrKeyExists, err, "wrong error: %v", err) 160 | v, _, cas1, err = c.Get(Key2) 161 | assertEqualf(t, Val1, v, "value shouldn't have changed: %s", v) 162 | assertEqualf(t, cas1, cas2, "CAS shouldn't have changed: %d, %d", cas1, cas2) 163 | } 164 | 165 | // Testing Max SIZE of values... 166 | // Testing if when you set a key/value with a bad value (e.g > 1MB) does that 167 | // remove the existing key/value still or leave it intact? 168 | func TestSetBadRemovePrevious(t *testing.T) { 169 | c := testInit(t) 170 | 171 | const ( 172 | // Larger than this memcached doesn't like for key 'foo' (with defaults) 173 | MaxValSize = 1024*1024 - 80 174 | Key = "foo" 175 | Val = "bar" 176 | ) 177 | 178 | // check basic get/set works first 179 | _, err := c.Set(Key, Val, 0, 0, 0) 180 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 181 | v, _, _, err := c.Get(Key) 182 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 183 | assertEqualf(t, Val, v, "wrong value: %s", v) 184 | 185 | // Max GOOD ValUE 186 | 187 | // generate random bytes 188 | data := make([]byte, MaxValSize) 189 | for i := 0; i < MaxValSize; i++ { 190 | data[i] = byte(rand.Int()) 191 | } 192 | 193 | val := string(data) 194 | _, err = c.Set(Key, val, 0, 0, 0) 195 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 196 | v, _, _, err = c.Get(Key) 197 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 198 | assertEqualf(t, val, v, "wrong value: (too big to print)") 199 | 200 | // Max GOOD ValUE * 2 201 | 202 | // generate random bytes 203 | data = make([]byte, 2*MaxValSize) 204 | for i := 0; i < 2*MaxValSize; i++ { 205 | data[i] = byte(rand.Int()) 206 | } 207 | 208 | val2 := string(data) 209 | _, err = c.Set(Key, val2, 0, 0, 0) 210 | assertEqualf(t, ErrValueTooLarge, err, "expected too large error: %v", err) 211 | v, _, _, err = c.Get(Key) 212 | if err == mcNil { 213 | fmt.Println("\tmemcached removes the old value... so expecting no key") 214 | fmt.Println("\tnot an error but just a different semantics than memcached") 215 | // well it should at least be the old value stil.. 216 | assertEqualf(t, val, v, "wrong value: (too big to print)") 217 | } else { 218 | assertEqualf(t, ErrNotFound, err, "expected no key: %v", err) 219 | } 220 | } 221 | 222 | // Test ADD. 223 | func TestAdd(t *testing.T) { 224 | c := testInit(t) 225 | 226 | const ( 227 | Key1 = "foo" 228 | Val1 = "bar" 229 | ) 230 | 231 | c.Del(Key1) 232 | 233 | // check add works... (key not already present) 234 | _, err := c.Add(Key1, Val1, 0, 0) 235 | assertEqualf(t, mcNil, err, "unexpected error adding key: %v", err) 236 | 237 | v, _, _, err := c.Get(Key1) 238 | assertEqualf(t, mcNil, err, "unexpected error getting key: %v", err) 239 | assertEqualf(t, v, Val1, "unexpected value for key: %v", v) 240 | 241 | // check add works... (key already present) 242 | _, err = c.Add(Key1, Val1, 0, 0) 243 | assertEqualf(t, ErrKeyExists, err, 244 | "expected an error adding existing key: %v", err) 245 | 246 | v, _, _, err = c.Get(Key1) 247 | assertEqualf(t, mcNil, err, "unexpected error getting key: %v", err) 248 | assertEqualf(t, v, Val1, "unexpected value for key: %v", v) 249 | } 250 | 251 | // Test Replace. 252 | func TestReplace(t *testing.T) { 253 | c := testInit(t) 254 | 255 | const ( 256 | Key1 = "foo" 257 | Val1 = "bar" 258 | Val2 = "car" 259 | ) 260 | 261 | c.Del(Key1) 262 | 263 | // check replace works... (key not already present) 264 | _, err := c.Replace(Key1, Val1, 0, 0, 0) 265 | assertEqualf(t, ErrNotFound, err, 266 | "expected an error replacing non-existent key: %v", err) 267 | _, _, _, err = c.Get(Key1) 268 | assertEqualf(t, ErrNotFound, err, "expected error getting key: %v", err) 269 | 270 | // check replace works...(key already present) 271 | _, err = c.Set(Key1, Val1, 0, 0, 0) 272 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 273 | v, _, _, err := c.Get(Key1) 274 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 275 | assertEqualf(t, Val1, v, "wrong value: %v", v) 276 | _, err = c.Replace(Key1, Val2, 0, 0, 0) 277 | v, _, _, err = c.Get(Key1) 278 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 279 | assertEqualf(t, Val2, v, "wrong value: %v", v) 280 | 281 | // check replace works [2nd take]... (key not already present) 282 | c.Del(Key1) 283 | _, err = c.Replace(Key1, Val1, 0, 0, 0) 284 | assertEqualf(t, ErrNotFound, err, 285 | "expected an error replacing non-existent key: %v", err) 286 | _, _, _, err = c.Get(Key1) 287 | assertEqualf(t, ErrNotFound, err, "expected error getting key: %v", err) 288 | 289 | // What happens when I replace a value and give a good CAS?... 290 | cas, err := c.Set(Key1, Val1, 0, 0, 0) 291 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 292 | cas, err = c.Replace(Key1, Val1, 0, 0, cas) 293 | assertEqualf(t, mcNil, err, "replace with good CAS failed: %v", err) 294 | 295 | // bad CAS 296 | _, err = c.Replace(Key1, Val2, 0, 0, cas+1) 297 | assertEqualf(t, ErrKeyExists, err, "replace with bad CAS failed: %v", err) 298 | } 299 | 300 | // Test Delete. 301 | func TestDelete(t *testing.T) { 302 | c := testInit(t) 303 | 304 | const ( 305 | Key1 = "foo" 306 | Val1 = "bar" 307 | ) 308 | 309 | // delete existing key... 310 | _, err := c.Set(Key1, Val1, 0, 0, 0) 311 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 312 | err = c.Del(Key1) 313 | assertEqualf(t, mcNil, err, "error deleting key: %v", err) 314 | 315 | // delete non-existent key... 316 | err = c.Del(Key1) 317 | assertEqualf(t, ErrNotFound, err, 318 | "no error deleting non-existent key: %v", err) 319 | 320 | // delete existing key with 0 CAS... 321 | cas1, err := c.Set(Key1, Val1, 0, 0, 0) 322 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 323 | err = c.DelCAS(Key1, cas1+1) 324 | assertEqualf(t, ErrKeyExists, err, 325 | "expected an error for deleting key with wrong CAS: %v", err) 326 | 327 | // confirm it isn't gone... 328 | v, _, cas1, err := c.Get(Key1) 329 | assertEqualf(t, mcNil, err, 330 | "delete with wrong CAS seems to have succeeded: %v", err) 331 | assertEqualf(t, v, Val1, "corrupted value in cache: %v", v) 332 | 333 | // now delete with good CAS... 334 | err = c.DelCAS(Key1, cas1) 335 | assertEqualf(t, mcNil, err, 336 | "unexpected error for deleting key with correct CAS: %v", err) 337 | 338 | // delete existing key with good CAS... 339 | cas1, err = c.Set(Key1, Val1, 0, 0, 0) 340 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 341 | err = c.DelCAS(Key1, cas1) 342 | assertEqualf(t, mcNil, err, 343 | "unexpected error for deleting key with correct CAS: %v", err) 344 | v, _, cas1, err = c.Get(Key1) 345 | assertEqualf(t, ErrNotFound, err, 346 | "delete with wrong CAS seems to have succeeded: %v", err) 347 | 348 | // delete existing key with 0 CAS... 349 | cas1, err = c.Set(Key1, Val1, 0, 0, 0) 350 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 351 | err = c.DelCAS(Key1, 0) 352 | assertEqualf(t, mcNil, err, 353 | "unexpected error for deleting key with 0 CAS: %v", err) 354 | v, _, cas1, err = c.Get(Key1) 355 | assertEqualf(t, ErrNotFound, err, 356 | "delete with wrong CAS seems to have succeeded: %v", err) 357 | } 358 | 359 | // Test behaviour of errors and cache removal. 360 | // NOTE: calling incr/decr on a non-numeric returns an error BUT also seems to 361 | // 362 | // remove it from the cache... 363 | // 364 | // NOTE: I think above may have been a bug present in memcache 1.4.12 but is 365 | // 366 | // fixed in 1.4.13... 367 | func TestIncrDecrNonNumeric(t *testing.T) { 368 | c := testInit(t) 369 | 370 | const ( 371 | Key1 = "n" 372 | NStart uint64 = 10 373 | NVal = defaultPort 374 | Val = "nup" 375 | ) 376 | 377 | _, err := c.Set(Key1, Val, 0, 0, 0) 378 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 379 | v, _, _, err := c.Get(Key1) 380 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 381 | assertEqualf(t, v, Val, "wrong value: %v", v) 382 | 383 | _, _, err = c.Incr(Key1, 1, NStart, 0, 0) 384 | assertEqualf(t, ErrNonNumeric, err, "unexpected error: %v", err) 385 | 386 | _, _, err = c.Decr(Key1, 1, NStart, 0, 0) 387 | assertEqualf(t, ErrNonNumeric, err, "unexpected error: %v", err) 388 | 389 | v, _, _, err = c.Get(Key1) 390 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 391 | assertEqualf(t, v, Val, "wrong value: %v", v) 392 | } 393 | 394 | // Test Incr/Decr works... 395 | func TestIncrDecr(t *testing.T) { 396 | c := testInit(t) 397 | 398 | const ( 399 | Key1 = "n" 400 | NStart uint64 = 10 401 | NVal = defaultPort 402 | ) 403 | 404 | // check DEL of non-existing key fails... 405 | err := c.Del(Key1) 406 | if err != ErrNotFound { 407 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 408 | } 409 | err = c.Del(Key1) 410 | assertEqualf(t, ErrNotFound, err, "expected missing key: %v", err) 411 | 412 | // test INCR/DECR... 413 | 414 | exp := NStart // track what we expect 415 | n, cas, err := c.Incr(Key1, 1, NStart, 0, 0) 416 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 417 | assertNotEqualf(t, 0, cas, "CAS should not be 0") 418 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 419 | 420 | exp = exp + 1 421 | n, cas, err = c.Incr(Key1, 1, 99, 0, 0) 422 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 423 | assertNotEqualf(t, 0, cas, "CAS should not be 0") 424 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 425 | 426 | exp = exp - 1 427 | n, cas, err = c.Decr(Key1, 1, 97, 0, 0) 428 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 429 | assertNotEqualf(t, 0, cas, "CAS should not be 0") 430 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 431 | 432 | // test big addition 433 | exp = exp + 1123139 434 | n, cas, err = c.Incr(Key1, 1123139, 97, 0, 0) 435 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 436 | assertNotEqualf(t, 0, cas, "CAS should not be 0") 437 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 438 | 439 | // test zero addition 440 | exp = exp + 0 441 | n, cas, err = c.Incr(Key1, 0, 97, 0, 0) 442 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 443 | assertNotEqualf(t, 0, cas, "CAS should not be 0") 444 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 445 | 446 | // test CAS works... (should match) 447 | exp = exp - 1 448 | n, cas, err = c.Decr(Key1, 1, 93, 0, cas) 449 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 450 | assertNotEqualf(t, 0, cas, "CAS should not be 0") 451 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 452 | 453 | // test CAS works... (should fail, doesn't match) 454 | exp = exp + 0 455 | n, cas, err = c.Decr(Key1, 1, 87, 0, cas+97) 456 | assertEqualf(t, ErrKeyExists, err, "expected CAS mismatch: %v", err) 457 | assertEqualf(t, uint64(0), n, "expected 0 due to CAS mismatch: %d", n) 458 | assertEqualf(t, uint64(0), cas, "expected 0 due to CAS mismatch: %d", cas) 459 | 460 | // test that get on a counter works... 461 | v, _, _, err := c.Get(Key1) 462 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 463 | vn := strconv.FormatUint(exp, 10) 464 | assertEqualf(t, vn, v, "wrong value: %d (expected %s)", n, vn) 465 | 466 | // test that set on a counter works... 467 | _, err = c.Set(Key1, NVal, 0, 0, 0) 468 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 469 | v, _, _, err = c.Get(Key1) 470 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 471 | assertEqualf(t, NVal, v, "wrong value: %s (expected %s)", v, NVal) 472 | exp, errNum := strconv.ParseUint(NVal, 10, 64) 473 | assertEqualf(t, nil, errNum, "unexpected error: %v", errNum) 474 | exp = exp + 1123139 475 | n, cas, err = c.Incr(Key1, 1123139, 97, 0, 0) 476 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 477 | assertNotEqualf(t, 0, cas, "CAS should not be 0") 478 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 479 | } 480 | 481 | // Test expiration works... 482 | func TestIncrTimeouts(t *testing.T) { 483 | c := testInit(t) 484 | 485 | const ( 486 | Key2 = "n" 487 | NStart uint64 = 10 488 | ) 489 | 490 | c.Del(Key2) 491 | 492 | // Incr (key, delta, initial, ttl, cas) 493 | exp := NStart 494 | n, _, err := c.Incr(Key2, 1, NStart, 0, 0) 495 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 496 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 497 | 498 | time.Sleep(1200 * time.Millisecond) 499 | 500 | // no delta_only set before, so should incr 501 | exp = exp + 39 502 | n, _, err = c.Incr(Key2, 39, NStart, 1, 0) 503 | assertEqualf(t, mcNil, err, "%v", err) 504 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 505 | } 506 | 507 | // Test Incr/Decr expiration field. 508 | // This is a stupid name for the field as it has nothing to do with expiration / 509 | // ttl. Instead its used to indicate that the incr/decr should fail if the key 510 | // doesn't already exist in the cache. (i.e., that is since the incr/decr 511 | // command takes both an initial value and a delta, the expiration field allows 512 | // us to say that only the delta should be applied and rather than use the 513 | // initial value when the key doesn't exist, throw an error). 514 | // 515 | // Only the value 0xffffffff is used to indicate that only the delta should be 516 | // applied, all other values for expiration allow either the initial or delta to 517 | // be used. 518 | func TestIncrExpiration(t *testing.T) { 519 | c := testInit(t) 520 | 521 | const ( 522 | Key1 = "n" 523 | NStart uint64 = 10 524 | OnlyDelta uint32 = 0xffffffff 525 | ) 526 | 527 | // fail as we only allow applying the delta with that expiration value. 528 | c.Del(Key1) 529 | _, _, err := c.Incr(Key1, 10, NStart, OnlyDelta, 0) 530 | assertEqualf(t, ErrNotFound, err, "unexpected error: %v", err) 531 | _, _, _, err = c.Get(Key1) 532 | assertEqualf(t, ErrNotFound, err, "key shouldn't exist in cache: %v", err) 533 | 534 | // succeed this time. Any value but OnlyDelta should succeed. 535 | exp := NStart 536 | n, _, err := c.Incr(Key1, 10, NStart, 0, 0) 537 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 538 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 539 | c.Del(Key1) 540 | 541 | // succeed this time. Any value but OnlyDelta should succeed. 542 | exp = NStart 543 | n, _, err = c.Incr(Key1, 10, NStart, 1, 0) 544 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 545 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 546 | c.Del(Key1) 547 | 548 | // succeed this time. Any value but OnlyDelta should succeed. 549 | exp = NStart 550 | n, _, err = c.Incr(Key1, 10, NStart, OnlyDelta-1, 0) 551 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 552 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 553 | c.Del(Key1) 554 | } 555 | 556 | // Test Incr/Decr overflow... 557 | func TestIncrDecrWrap(t *testing.T) { 558 | c := testInit(t) 559 | 560 | const ( 561 | Key1 = "n" 562 | NStart uint64 = 10 563 | Max1 uint64 = 0xfffffffffffffffe 564 | Max uint64 = 0xffffffffffffffff 565 | ) 566 | 567 | // setup... 568 | exp := NStart 569 | n, _, err := c.Decr(Key1, NStart+1, NStart, 0, 0) 570 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 571 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 572 | 573 | // can't decr past 0... 574 | exp = 0 575 | n, _, err = c.Decr(Key1, NStart+1, NStart, 0, 0) 576 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 577 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 578 | 579 | // test limit of incr... 580 | exp = Max1 581 | n, _, err = c.Incr(Key1, Max1, 0, 0, 0) 582 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 583 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 584 | 585 | exp = Max 586 | n, _, err = c.Incr(Key1, 1, 0, 0, 0) 587 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 588 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 589 | 590 | // overflow... wrap around 591 | exp = 0 592 | n, _, err = c.Incr(Key1, 1, 0, 0, 0) 593 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 594 | assertEqualf(t, exp, n, "wrong value: %d (expected %d)", n, exp) 595 | } 596 | 597 | // Test Append works... 598 | func TestAppend(t *testing.T) { 599 | c := testInit(t) 600 | 601 | const ( 602 | Key1 = "foo" 603 | Key2 = "goo" 604 | Val1 = "moo" 605 | Val2 = "bar" 606 | ) 607 | 608 | c.Del(Key1) 609 | c.Del(Key2) 610 | 611 | // normal append 612 | exp := Val1 613 | _, err := c.Set(Key1, Val1, 0, 0, 0) 614 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 615 | exp = exp + Val2 616 | _, err = c.Append(Key1, Val2, 0) 617 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 618 | v, _, _, err := c.Get(Key1) 619 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 620 | assertEqualf(t, exp, v, "wrong value: %s", v) 621 | 622 | // append to non-existent value 623 | exp = Val1 624 | _, err = c.Append(Key2, Val1, 0) 625 | if err != ErrValueNotStored { 626 | t.Errorf("expected 'value not stored error', got: %v", err) 627 | } 628 | v, _, _, err = c.Get(Key2) 629 | assertEqualf(t, ErrNotFound, err, "expected not found error: %v", err) 630 | 631 | // check CAS works... 632 | v, _, cas, err := c.Get(Key1) 633 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 634 | exp = v 635 | _, err = c.Append(Key1, Val2, cas+1) 636 | assertEqualf(t, ErrKeyExists, err, "expected key exists error: %v", err) 637 | v, _, cas2, err := c.Get(Key1) 638 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 639 | assertEqualf(t, exp, v, "wrong value: %s", v) 640 | assertEqualf(t, cas, cas2, "CAS shouldn't have changed: %d != %d", cas, cas2) 641 | exp = exp + Val2 642 | _, err = c.Append(Key1, Val2, cas) 643 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 644 | exp = exp + Val1 645 | 646 | // check 0 CAS... 647 | _, err = c.Append(Key1, Val1, 0) 648 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 649 | v, _, _, err = c.Get(Key1) 650 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 651 | assertEqualf(t, exp, v, "wrong value: %s", v) 652 | } 653 | 654 | // Test Prepend works... 655 | func TestPrepend(t *testing.T) { 656 | c := testInit(t) 657 | 658 | const ( 659 | Key1 = "foo" 660 | Key2 = "goo" 661 | Val1 = "moo" 662 | Val2 = "bar" 663 | ) 664 | 665 | c.Del(Key1) 666 | c.Del(Key2) 667 | 668 | // normal append 669 | exp := Val1 670 | _, err := c.Set(Key1, Val1, 0, 0, 0) 671 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 672 | exp = Val2 + exp 673 | _, err = c.Prepend(Key1, Val2, 0) 674 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 675 | v, _, _, err := c.Get(Key1) 676 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 677 | assertEqualf(t, exp, v, "wrong value: %s", v) 678 | 679 | // append to non-existent value 680 | exp = Val1 681 | _, err = c.Prepend(Key2, Val1, 0) 682 | if err != ErrValueNotStored { 683 | t.Errorf("expected 'value not stored error', got: %v", err) 684 | } 685 | v, _, _, err = c.Get(Key2) 686 | assertEqualf(t, ErrNotFound, err, "expected not found error: %v", err) 687 | 688 | // check CAS works... 689 | v, _, cas, err := c.Get(Key1) 690 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 691 | exp = v 692 | _, err = c.Prepend(Key1, Val2, cas+1) 693 | assertEqualf(t, ErrKeyExists, err, "expected key exists error: %v", err) 694 | v, _, cas2, err := c.Get(Key1) 695 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 696 | assertEqualf(t, exp, v, "wrong value: %s", v) 697 | assertEqualf(t, cas, cas2, "CAS shouldn't have changed: %d != %d", cas, cas2) 698 | exp = Val2 + exp 699 | _, err = c.Prepend(Key1, Val2, cas) 700 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 701 | exp = Val1 + exp 702 | 703 | // check 0 CAS... 704 | _, err = c.Prepend(Key1, Val1, 0) 705 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 706 | v, _, _, err = c.Get(Key1) 707 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 708 | assertEqualf(t, exp, v, "wrong value: %s", v) 709 | } 710 | 711 | // Test NoOp works... (by putting NoOps all between the prepend tests) 712 | func TestNoOp(t *testing.T) { 713 | c := testInit(t) 714 | 715 | const ( 716 | Key1 = "foo" 717 | Key2 = "goo" 718 | Val1 = "moo" 719 | Val2 = "bar" 720 | ) 721 | 722 | err := c.NoOp() 723 | assertEqualf(t, mcNil, err, "noop unexpected error: %v", err) 724 | err = c.NoOp() 725 | assertEqualf(t, mcNil, err, "noop unexpected error: %v", err) 726 | err = c.NoOp() 727 | err = c.NoOp() 728 | err = c.NoOp() 729 | err = c.NoOp() 730 | err = c.NoOp() 731 | err = c.NoOp() 732 | assertEqualf(t, mcNil, err, "noop unexpected error: %v", err) 733 | 734 | c.Del(Key1) 735 | err = c.NoOp() 736 | assertEqualf(t, mcNil, err, "noop unexpected error: %v", err) 737 | c.Del(Key2) 738 | 739 | // normal append 740 | exp := Val1 741 | _, err = c.Set(Key1, Val1, 0, 0, 0) 742 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 743 | err = c.NoOp() 744 | assertEqualf(t, mcNil, err, "noop unexpected error: %v", err) 745 | exp = Val2 + exp 746 | _, err = c.Prepend(Key1, Val2, 0) 747 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 748 | v, _, _, err := c.Get(Key1) 749 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 750 | assertEqualf(t, exp, v, "wrong value: %s", v) 751 | err = c.NoOp() 752 | assertEqualf(t, mcNil, err, "noop unexpected error: %v", err) 753 | 754 | // append to non-existent value 755 | exp = Val1 756 | _, err = c.Prepend(Key2, Val1, 0) 757 | if err != ErrValueNotStored { 758 | t.Errorf("expected 'value not stored error', got: %v", err) 759 | } 760 | v, _, _, err = c.Get(Key2) 761 | assertEqualf(t, ErrNotFound, err, "expected not found error: %v", err) 762 | 763 | // check CAS works... 764 | err = c.NoOp() 765 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 766 | v, _, cas, err := c.Get(Key1) 767 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 768 | exp = v 769 | _, err = c.Prepend(Key1, Val2, cas+1) 770 | assertEqualf(t, ErrKeyExists, err, "expected key exists error: %v", err) 771 | err = c.NoOp() 772 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 773 | v, _, cas2, err := c.Get(Key1) 774 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 775 | assertEqualf(t, exp, v, "wrong value: %s", v) 776 | assertEqualf(t, cas, cas2, "CAS shouldn't have changed: %d != %d", cas, cas2) 777 | err = c.NoOp() 778 | assertEqualf(t, mcNil, err, "noop unexpected error: %v", err) 779 | exp = Val2 + exp 780 | _, err = c.Prepend(Key1, Val2, cas) 781 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 782 | exp = Val1 + exp 783 | err = c.NoOp() 784 | assertEqualf(t, mcNil, err, "noop unexpected error: %v", err) 785 | 786 | // check 0 CAS... 787 | _, err = c.Prepend(Key1, Val1, 0) 788 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 789 | v, _, _, err = c.Get(Key1) 790 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 791 | assertEqualf(t, exp, v, "wrong value: %s", v) 792 | err = c.NoOp() 793 | assertEqualf(t, mcNil, err, "noop unexpected error: %v", err) 794 | } 795 | 796 | // Test Flush behaviour 797 | func TestFlush(t *testing.T) { 798 | c := testInit(t) 799 | 800 | const ( 801 | Key1 = "foo" 802 | Key2 = "goo" 803 | Key3 = "hoo" 804 | Val1 = "bar" 805 | Val2 = "zar" 806 | Val3 = "gar" 807 | ) 808 | 809 | err := c.Flush(0) 810 | assertEqualf(t, mcNil, err, "flush produced error: %v", err) 811 | 812 | _, err = c.Set(Key1, Val1, 0, 0, 0) 813 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 814 | v, _, _, err := c.Get(Key1) 815 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 816 | assertEqualf(t, Val1, v, "wrong value: %v", v) 817 | 818 | err = c.Flush(0) 819 | assertEqualf(t, mcNil, err, "flush produced error: %v", err) 820 | v, _, _, err = c.Get(Key1) 821 | assertEqualf(t, ErrNotFound, err, "shouldn't have found key as flushed: %v", err) 822 | 823 | // do two sets of same key, make sure CAS changes... 824 | cas1, err := c.Set(Key2, Val1, 0, 0, 0) 825 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 826 | cas2, err := c.Set(Key2, Val1, 0, 0, 0) 827 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 828 | assertNotEqualf(t, cas1, cas2, "CAS don't match: %d == %d", cas1, cas2) 829 | 830 | // try to get back the vals... 831 | err = c.Flush(0) 832 | assertEqualf(t, mcNil, err, "flush produced error: %v", err) 833 | v, _, _, err = c.Get(Key1) 834 | assertEqualf(t, ErrNotFound, err, "shouldn't have found key as flushed: %v", err) 835 | v, _, _, err = c.Get(Key2) 836 | assertEqualf(t, ErrNotFound, err, "shouldn't have found key as flushed: %v", err) 837 | 838 | err = c.Del(Key1) 839 | assertEqualf(t, ErrNotFound, err, "shouldn't have found key as flushed: %v", err) 840 | err = c.Del(Key2) 841 | assertEqualf(t, ErrNotFound, err, "shouldn't have found key as flushed: %v", err) 842 | 843 | // do two sets 844 | _, err = c.Set(Key1, Val1, 0, 0, 0) 845 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 846 | _, err = c.Set(Key2, Val2, 0, 0, 0) 847 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 848 | 849 | // flush in future! 850 | err = c.Flush(3) 851 | 852 | // set a key now, after sending flush in future command. Should this key be 853 | // included in flush when it applies? 854 | _, err = c.Set(Key3, Val3, 0, 0, 0) 855 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 856 | 857 | // keys should still survive as the flush hasn't applied yet. 858 | time.Sleep(900 * time.Millisecond) 859 | _, _, _, err = c.Get(Key1) 860 | assertEqualf(t, mcNil, err, "should have found key as flushed in future!: %v", err) 861 | time.Sleep(100 * time.Millisecond) 862 | _, _, _, err = c.Get(Key2) 863 | assertEqualf(t, mcNil, err, "should have found key as flushed in future!: %v", err) 864 | 865 | // now keys should all be flushed 866 | time.Sleep(2200 * time.Millisecond) 867 | _, _, _, err = c.Get(Key1) 868 | assertEqualf(t, ErrNotFound, err, "shouldn't have found key as flushed: %v", err) 869 | _, _, _, err = c.Get(Key2) 870 | assertEqualf(t, ErrNotFound, err, "shouldn't have found key as flushed: %v", err) 871 | _, _, _, err = c.Get(Key3) 872 | assertEqualf(t, ErrNotFound, err, "shouldn't have found key as flushed: %v", err) 873 | 874 | // do two sets 875 | _, err = c.Set(Key1, Val1, 0, 0, 0) 876 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 877 | _, err = c.Set(Key2, Val2, 0, 0, 0) 878 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 879 | 880 | // flush in future! (should overwrite old flush in futures...) 881 | err = c.Flush(3) 882 | time.Sleep(900 * time.Millisecond) 883 | _, _, _, err = c.Get(Key1) 884 | assertEqualf(t, mcNil, err, "should have found key as flushed in future!: %v", err) 885 | time.Sleep(100 * time.Millisecond) 886 | _, _, _, err = c.Get(Key2) 887 | assertEqualf(t, mcNil, err, "should have found key as flushed in future!: %v", err) 888 | err = c.Flush(4) 889 | time.Sleep(2000 * time.Millisecond) 890 | _, _, _, err = c.Get(Key1) 891 | assertEqualf(t, mcNil, err, "should have found key as flushed in future!: %v", err) 892 | _, _, _, err = c.Get(Key2) 893 | assertEqualf(t, mcNil, err, "should have found key as flushed in future!: %v", err) 894 | time.Sleep(2000 * time.Millisecond) 895 | v, _, _, err = c.Get(Key1) 896 | assertEqualf(t, ErrNotFound, err, "shouldn't have found key as flushed: %v", err) 897 | v, _, _, err = c.Get(Key2) 898 | assertEqualf(t, ErrNotFound, err, "shouldn't have found key as flushed: %v", err) 899 | } 900 | 901 | // Test flush, flush future. 902 | func TestFlushFuture(t *testing.T) { 903 | c := testInit(t) 904 | 905 | const ( 906 | Key1 = "foo" 907 | Key2 = "goo" 908 | Val1 = "bar" 909 | Val2 = "zar" 910 | ) 911 | 912 | // clear cache 913 | err := c.Flush(0) 914 | assertEqualf(t, mcNil, err, "flush produced error: %v", err) 915 | 916 | // set Key1, Key2 917 | _, err = c.Set(Key1, Val1, 0, 0, 0) 918 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 919 | _, err = c.Set(Key2, Val2, 0, 0, 0) 920 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 921 | 922 | // wait two seconds 923 | time.Sleep(2000 * time.Millisecond) 924 | 925 | // flush cache (Key1, Key2) 926 | err = c.Flush(0) 927 | assertEqualf(t, mcNil, err, "flush produced error: %v", err) 928 | 929 | // get Key1 -> null 930 | _, _, _, err = c.Get(Key1) 931 | assertEqualf(t, ErrNotFound, err, "shouldn't have found key! err: %v", err) 932 | 933 | // re-set Key1 934 | _, err = c.Set(Key1, Val1, 0, 0, 0) 935 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 936 | 937 | // flush again, but in future 938 | err = c.Flush(2) 939 | 940 | // XXX: Memcached is broken for this. 941 | // get Key2 -- memcached bug where flush in future can resurrect items 942 | // _, _, _, err = c.Get(Key2) 943 | // assertEqualf(t, ErrNotFound, err, "shouldn't have found key! err: %v", err) 944 | 945 | // get Key1 946 | _, _, _, err = c.Get(Key1) 947 | assertEqualf(t, mcNil, err, "should have found Key1! err: %v", err) 948 | 949 | // wait for flush to expire 950 | time.Sleep(2500 * time.Millisecond) 951 | 952 | _, _, _, err = c.Get(Key1) 953 | assertEqualf(t, ErrNotFound, err, "shouldn't have found key! err: %v", err) 954 | } 955 | 956 | // Test the version command works... 957 | func TestVersion(t *testing.T) { 958 | c := testInit(t) 959 | 960 | vers, err := c.Version() 961 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 962 | good, errRegex := regexp.MatchString("[0-9]+\\.[0-9]+\\.[0-9]+", vers[mcAddr]) 963 | assertEqualf(t, nil, errRegex, "unexpected error: %v", errRegex) 964 | assertEqualf(t, good, true, "version of unexcpected form: %s", vers[mcAddr]) 965 | } 966 | 967 | // Test the quit command works... 968 | func TestQuit(t *testing.T) { 969 | c := testInit(t) 970 | 971 | const ( 972 | Key1 = "fooz" 973 | Val1 = "barz" 974 | ) 975 | 976 | _, err := c.Set(Key1, Val1, 0, 0, 0) 977 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 978 | 979 | v, _, _, err := c.Get(Key1) 980 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 981 | assertEqualf(t, Val1, v, "wrong value: %s", v) 982 | 983 | c.Quit() 984 | 985 | _, _, _, err = c.Get(Key1) 986 | assertNotEqualf(t, mcNil, err, "expected an error (closed connection)") 987 | 988 | c.Quit() // should not panic 989 | } 990 | 991 | // Test expiration works... 992 | // See Note [Expiration] in mc.go for details of how expiration works. 993 | // NOTE: Can't really test long expirations properly... 994 | func TestExpiration(t *testing.T) { 995 | c := testInit(t) 996 | 997 | const ( 998 | Key0 = "zoo" 999 | Key1 = "foo" 1000 | Key2 = "goo" 1001 | Val1 = "moo" 1002 | Val2 = "bar" 1003 | ) 1004 | 1005 | // no expiration, should last forever... 1006 | _, err := c.Set(Key0, Val1, 0, 0, 0) 1007 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1008 | 1009 | v, _, _, err := c.Get(Key0) 1010 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1011 | assertEqualf(t, Val1, v, "wrong value: %v", v) 1012 | 1013 | // 1 second expiration... 1014 | _, err = c.Set(Key1, Val1, 0, 1, 0) 1015 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1016 | time.Sleep(1100 * time.Millisecond) 1017 | _, _, _, err = c.Get(Key1) 1018 | assertEqualf(t, ErrNotFound, err, "shouldn't be in cache anymore: %v", err) 1019 | 1020 | v, _, _, err = c.Get(Key0) 1021 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1022 | assertEqualf(t, Val1, v, "wrong value: %v", v) 1023 | } 1024 | 1025 | // Test expiration works... 1026 | // See Note [Expiration] in mc.go for details of how expiration works. 1027 | // NOTE: Can't really test long expirations properly... 1028 | func TestExpirationTouch(t *testing.T) { 1029 | if testing.Short() { 1030 | t.Skip("skipping test in short mode.") 1031 | } 1032 | 1033 | c := testInit(t) 1034 | 1035 | const ( 1036 | Key0 = "zoo" 1037 | Key1 = "foo" 1038 | Key2 = "goo" 1039 | Val1 = "moo" 1040 | Val2 = "bar" 1041 | ) 1042 | 1043 | // no expiration, should last forever... 1044 | _, err := c.Set(Key0, Val1, 0, 0, 0) 1045 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1046 | 1047 | // 2 second expiration... 1048 | _, err = c.Set(Key1, Val2, 0, 2, 0) 1049 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1050 | time.Sleep(100 * time.Millisecond) 1051 | v, _, _, err := c.Get(Key1) 1052 | assertEqualf(t, mcNil, err, "should be in cache still: %v", err) 1053 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1054 | // 800 total... 1055 | time.Sleep(700 * time.Millisecond) 1056 | v, _, _, err = c.Get(Key1) 1057 | assertEqualf(t, mcNil, err, "should be in cache still: %v", err) 1058 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1059 | // 900 total... 1060 | time.Sleep(200 * time.Millisecond) 1061 | v, _, _, err = c.Get(Key1) 1062 | assertEqualf(t, mcNil, err, "should be in cache still: %v", err) 1063 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1064 | // 2000 total... 1065 | time.Sleep(1100 * time.Millisecond) 1066 | _, _, _, err = c.Get(Key1) 1067 | assertEqualf(t, ErrNotFound, err, "shouldn't be in cache anymore: %v", err) 1068 | 1069 | // Test Touch... 1070 | // NOTE: This works for me with a memcached built from source but not with the 1071 | // one installed via homebrew... 1072 | // 2 second expiration... 1073 | _, err = c.Set(Key1, Val2, 0, 2, 0) 1074 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1075 | time.Sleep(100 * time.Millisecond) 1076 | v, _, _, err = c.Get(Key1) 1077 | assertEqualf(t, mcNil, err, "should be in cache still: %v", err) 1078 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1079 | // 800 total... 1080 | time.Sleep(700 * time.Millisecond) 1081 | v, _, _, err = c.Get(Key1) 1082 | assertEqualf(t, mcNil, err, "should be in cache still: %v", err) 1083 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1084 | 1085 | // make expiration 3 seconds from now (previously would expire 1 second from 1086 | // now, so a 4 second expiration in total...) 1087 | _, err = c.Touch(Key1, 3) 1088 | assertEqualf(t, mcNil, err, "touch failed: %v", err) 1089 | // 1200 (2000 total)... 1090 | time.Sleep(1200 * time.Millisecond) 1091 | v, _, _, err = c.Get(Key1) 1092 | assertEqualf(t, mcNil, err, "should be in cache still: %v", err) 1093 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1094 | // 1700 (2500 total)... 1095 | time.Sleep(500 * time.Millisecond) 1096 | v, _, _, err = c.Get(Key1) 1097 | assertEqualf(t, mcNil, err, "should be in cache still: %v", err) 1098 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1099 | // 1900 (2700 total)... 1100 | time.Sleep(200 * time.Millisecond) 1101 | v, _, _, err = c.Get(Key1) 1102 | assertEqualf(t, mcNil, err, "should be in cache still: %v", err) 1103 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1104 | // 3500 (4300) total... 1105 | time.Sleep(1600 * time.Millisecond) 1106 | _, _, _, err = c.Get(Key1) 1107 | assertEqualf(t, ErrNotFound, err, "shouldn't be in cache anymore: %v", err) 1108 | 1109 | // key0 still should be alive (no timeout) 1110 | v, _, _, err = c.Get(Key0) 1111 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1112 | assertEqualf(t, Val1, v, "wrong value: %v", v) 1113 | } 1114 | 1115 | // Test Touch command works... 1116 | func TestTouch(t *testing.T) { 1117 | if testing.Short() { 1118 | t.Skip("skipping test in short mode.") 1119 | } 1120 | 1121 | c := testInit(t) 1122 | 1123 | const ( 1124 | Key1 = "foo" 1125 | Val1 = "bar" 1126 | ) 1127 | 1128 | // no expiration, lets see if touch can set an expiration, not just extend... 1129 | _, err := c.Set(Key1, Val1, 0, 0, 0) 1130 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1131 | 1132 | c.Touch(Key1, 2) 1133 | 1134 | _, _, _, err = c.Get(Key1) 1135 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1136 | 1137 | time.Sleep(1000 * time.Millisecond) 1138 | 1139 | _, _, _, err = c.Get(Key1) 1140 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1141 | 1142 | time.Sleep(1500 * time.Millisecond) 1143 | 1144 | _, _, _, err = c.Get(Key1) 1145 | assertEqualf(t, ErrNotFound, err, "shouldn't be in cache: %v", err) 1146 | 1147 | // no expiration, let see if we can expire immediately with Touch... 1148 | // NO, 0 = ignore, so the Touch is a noop really... 1149 | _, err = c.Set(Key1, Val1, 0, 0, 0) 1150 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1151 | 1152 | c.Touch(Key1, 0) 1153 | 1154 | _, _, _, err = c.Get(Key1) 1155 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1156 | 1157 | time.Sleep(1000 * time.Millisecond) 1158 | 1159 | _, _, _, err = c.Get(Key1) 1160 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1161 | } 1162 | 1163 | // Test GAT (get-and-touch) works... 1164 | // See Note [Expiration] in mc.go for details of how expiration works. 1165 | func TestGAT(t *testing.T) { 1166 | if testing.Short() { 1167 | t.Skip("skipping test in short mode.") 1168 | } 1169 | 1170 | c := testInit(t) 1171 | 1172 | const ( 1173 | Key1 = "foo" 1174 | Key2 = "goo" 1175 | Val1 = "moo" 1176 | Val2 = "bar" 1177 | FLAGS uint32 = 921321 1178 | ) 1179 | 1180 | // no expiration, should last forever... 1181 | _, err := c.Set(Key1, Val1, FLAGS, 0, 0) 1182 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1183 | 1184 | v, f, _, err := c.Get(Key1) 1185 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1186 | assertEqualf(t, Val1, v, "wrong value: %v", v) 1187 | assertEqualf(t, FLAGS, f, "wrong flags: %v", f) 1188 | 1189 | // no expiration... 1190 | _, err = c.Set(Key2, Val2, FLAGS, 0, 0) 1191 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1192 | 1193 | // get + set 1 second expiration... 1194 | v, f, _, err = c.GAT(Key2, 1) 1195 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1196 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1197 | assertEqualf(t, FLAGS, f, "wrong flags: %v", f) 1198 | 1199 | v, f, _, err = c.Get(Key2) 1200 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1201 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1202 | assertEqualf(t, FLAGS, f, "wrong flags: %v", f) 1203 | 1204 | time.Sleep(1500 * time.Millisecond) 1205 | 1206 | _, _, _, err = c.Get(Key2) 1207 | assertEqualf(t, ErrNotFound, err, "shouldn't be in cache anymore: %v", err) 1208 | _, _, _, err = c.GAT(Key2, 1) 1209 | assertEqualf(t, ErrNotFound, err, "shouldn't be in cache anymore: %v", err) 1210 | 1211 | // Test GAT... 1212 | // 2 second expiration... 1213 | _, err = c.Set(Key2, Val2, FLAGS, 2, 0) 1214 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1215 | time.Sleep(100 * time.Millisecond) 1216 | v, _, _, err = c.Get(Key2) 1217 | assertEqualf(t, mcNil, err, "should be in cache still: %v", err) 1218 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1219 | // 800 total... 1220 | time.Sleep(700 * time.Millisecond) 1221 | v, _, _, err = c.Get(Key2) 1222 | assertEqualf(t, mcNil, err, "should be in cache still: %v", err) 1223 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1224 | 1225 | // make expiration 2 seconds from now (previously would expire 1 second from 1226 | // now, so a 3 second expiration in total...) 1227 | v, f, _, err = c.GAT(Key2, 2) 1228 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1229 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1230 | assertEqualf(t, FLAGS, f, "wrong flags: %v", f) 1231 | 1232 | // 900... 1233 | time.Sleep(900 * time.Millisecond) 1234 | 1235 | // reset ttl... 1236 | v, f, _, err = c.GAT(Key2, 2) 1237 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1238 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1239 | assertEqualf(t, FLAGS, f, "wrong flags: %v", f) 1240 | 1241 | // 900... 1242 | time.Sleep(900 * time.Millisecond) 1243 | 1244 | // reset ttl... 1245 | v, f, _, err = c.GAT(Key2, 2) 1246 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1247 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1248 | assertEqualf(t, FLAGS, f, "wrong flags: %v", f) 1249 | 1250 | // 900... 1251 | time.Sleep(800 * time.Millisecond) 1252 | 1253 | // reset ttl... 1254 | v, f, _, err = c.GAT(Key2, 2) 1255 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1256 | assertEqualf(t, Val2, v, "wrong value: %v", v) 1257 | assertEqualf(t, FLAGS, f, "wrong flags: %v", f) 1258 | 1259 | // 2000... 1260 | time.Sleep(2000 * time.Millisecond) 1261 | 1262 | _, _, _, err = c.Get(Key2) 1263 | assertEqualf(t, ErrNotFound, err, "shouldn't be in cache anymore: %v", err) 1264 | 1265 | // should be alive still (no expiration on this key) 1266 | v, _, _, err = c.Get(Key1) 1267 | assertEqualf(t, mcNil, err, "shouldn't be an error: %v", err) 1268 | assertEqualf(t, Val1, v, "wrong value: %v", v) 1269 | } 1270 | 1271 | // Some basic tests that functions work 1272 | func testThread(t *testing.T, c *Client, id int, ch chan bool) { 1273 | const ( 1274 | Key1 = "foo" 1275 | Val1 = "boo" 1276 | Key3 = "bar" 1277 | ) 1278 | 1279 | idx := strconv.Itoa(id) 1280 | key2 := Key1 + idx 1281 | 1282 | // lots of sets of this but should all be setting it to boo... 1283 | _, err := c.Set(Key1, Val1, 0, 0, 0) 1284 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1285 | 1286 | // should be unique to a thread... 1287 | cas2, err := c.Set(key2, idx, 0, 0, 0) 1288 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1289 | 1290 | // contention but all setting same value... 1291 | v, _, _, err := c.Get(Key1) 1292 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1293 | assertEqualf(t, Val1, v, "wrong value: %s", v) 1294 | 1295 | // key is unique to thread, so even CAS shouldn't change... 1296 | v, _, cas2x, err := c.Get(key2) 1297 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1298 | assertEqualf(t, idx, v, "wrong value: %s", v) 1299 | assertEqualf(t, cas2, cas2x, "CAS shouldn't have changed: %d, %d", cas2, cas2x) 1300 | 1301 | // lots of sets of this and with diff values... 1302 | cas1, err := c.Set(Key3, idx, 0, 0, 0) 1303 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1304 | 1305 | // try getting straight away... 1306 | v, _, cas1x, err := c.Get(Key3) 1307 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1308 | // if cas didn't change our value should have been returned... 1309 | if cas1 == cas1x { 1310 | assertEqualf(t, idx, v, "wrong value (cas didn't change): %s", v) 1311 | } 1312 | 1313 | ch <- true 1314 | } 1315 | 1316 | // Test threaded interaction... 1317 | func TestThreaded(t *testing.T) { 1318 | c := testInit(t) 1319 | 1320 | ch := make(chan bool) 1321 | 1322 | for i := 0; i < 30; i++ { 1323 | go testThread(t, c, i, ch) 1324 | } 1325 | 1326 | for i := 0; i < 30; i++ { 1327 | _ = <-ch 1328 | } 1329 | } 1330 | 1331 | func testAdvGet(t *testing.T, c *Client, op opCode, key string, expKey string, opq uint32) *msg { 1332 | var flags uint32 1333 | 1334 | m := &msg{ 1335 | header: header{ 1336 | Op: op, 1337 | CAS: uint64(0), 1338 | Opaque: uint32(opq), 1339 | }, 1340 | oextras: []interface{}{&flags}, 1341 | key: key, 1342 | } 1343 | 1344 | err := c.perform(m) 1345 | 1346 | assertEqualf(t, mcNil, err, "Unexpected error! %s", err) 1347 | // XXX: Issues here with new server send/recv split! Seems a golang bug to do 1348 | // with lifting variables to heap perhaps and sharing? 1349 | // assertEqualf(t, op, m.header.op, "Response has wrong op code! %d != %d", op, m.header.op) 1350 | // assertEqualf(t, opq, m.header.opaque, "Response has wrong opaque! %d != %d", opq, m.header.opaque) 1351 | // assertEqualf(t, expKey, m.key, "Get returned key! %s", m.key) 1352 | 1353 | return m 1354 | } 1355 | 1356 | // Test that the various get types work and that opaque works... e.g., all the 1357 | // components needed for multi_get. 1358 | func TestGetExotic(t *testing.T) { 1359 | const ( 1360 | Key = "key" 1361 | Val = "bar" 1362 | ) 1363 | 1364 | c := testInit(t) 1365 | 1366 | _, err := c.Set(Key, Val, 0, 0, 0) 1367 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1368 | 1369 | // TODO: Testing only when a key exists, need to also test functionality that 1370 | // on key miss, getq doesn't return a response. 1371 | 1372 | // get 1373 | testAdvGet(t, c, opGet, Key, "", 123) 1374 | testAdvGet(t, c, opGet, Key, "", 0) 1375 | testAdvGet(t, c, opGet, Key, "", 0xffffffff) 1376 | testAdvGet(t, c, opGet, Key, "", 0xfffffff0) 1377 | testAdvGet(t, c, opGet, Key, "", 0xf0f0f0f0) 1378 | 1379 | // getq 1380 | testAdvGet(t, c, opGetQ, Key, "", 123) 1381 | testAdvGet(t, c, opGetQ, Key, "", 0) 1382 | testAdvGet(t, c, opGetQ, Key, "", 0xffffffff) 1383 | testAdvGet(t, c, opGetQ, Key, "", 0xfffffff0) 1384 | testAdvGet(t, c, opGetQ, Key, "", 0xf0f0f0f0) 1385 | 1386 | // getk 1387 | testAdvGet(t, c, opGetK, Key, Key, 123) 1388 | testAdvGet(t, c, opGetK, Key, Key, 0) 1389 | testAdvGet(t, c, opGetK, Key, Key, 0xffffffff) 1390 | testAdvGet(t, c, opGetK, Key, Key, 0xfffffff0) 1391 | testAdvGet(t, c, opGetK, Key, Key, 0xf0f0f0f0) 1392 | 1393 | // getkq 1394 | testAdvGet(t, c, opGetKQ, Key, Key, 123) 1395 | testAdvGet(t, c, opGetKQ, Key, Key, 0) 1396 | testAdvGet(t, c, opGetKQ, Key, Key, 0xffffffff) 1397 | testAdvGet(t, c, opGetKQ, Key, Key, 0xfffffff0) 1398 | testAdvGet(t, c, opGetKQ, Key, Key, 0xf0f0f0f0) 1399 | } 1400 | 1401 | func testAdvGat(t *testing.T, c *Client, op opCode, key string, expKey string, opq uint32) *msg { 1402 | var exp uint32 1403 | var flags uint32 1404 | 1405 | m := &msg{ 1406 | header: header{ 1407 | Op: op, 1408 | CAS: uint64(0), 1409 | Opaque: uint32(opq), 1410 | }, 1411 | iextras: []interface{}{exp}, 1412 | oextras: []interface{}{&flags}, 1413 | key: key, 1414 | } 1415 | 1416 | err := c.perform(m) 1417 | 1418 | assertEqualf(t, mcNil, err, "Unexpected error! %s", err) 1419 | // XXX: Issues here with new server send/recv split! Seems a golang bug to do 1420 | // with lifting variables to heap perhaps and sharing? 1421 | // assertEqualf(t, op, m.header.op, "Response has wrong op code! %d != %d", op, m.header.op) 1422 | // assertEqualf(t, opq, m.header.opaque, "Response has wrong opaque! %d != %d", opq, m.header.opaque) 1423 | // assertEqualf(t, expKey, m.key, "Get returned key! %s", m.key) 1424 | 1425 | return m 1426 | } 1427 | 1428 | // Test that the various gat types work 1429 | func TestGatExotic(t *testing.T) { 1430 | const ( 1431 | Key = "key" 1432 | Val = "bar" 1433 | ) 1434 | 1435 | c := testInit(t) 1436 | 1437 | _, err := c.Set(Key, Val, 0, 0, 0) 1438 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1439 | 1440 | // TODO: Testing only when a key exists, need to also test functionality that 1441 | // on key miss, getq doesn't return a response. And test that the 'touch' 1442 | // aspect is functioning. 1443 | 1444 | // get 1445 | testAdvGat(t, c, opGAT, Key, "", 123) 1446 | testAdvGat(t, c, opGAT, Key, "", 0) 1447 | testAdvGat(t, c, opGAT, Key, "", 0xffffffff) 1448 | testAdvGat(t, c, opGAT, Key, "", 0xfffffff0) 1449 | testAdvGat(t, c, opGAT, Key, "", 0xf0f0f0f0) 1450 | 1451 | // getq 1452 | testAdvGat(t, c, opGATQ, Key, "", 123) 1453 | testAdvGat(t, c, opGATQ, Key, "", 0) 1454 | testAdvGat(t, c, opGATQ, Key, "", 0xffffffff) 1455 | testAdvGat(t, c, opGATQ, Key, "", 0xfffffff0) 1456 | testAdvGat(t, c, opGATQ, Key, "", 0xf0f0f0f0) 1457 | 1458 | // getk 1459 | testAdvGat(t, c, opGATK, Key, Key, 123) 1460 | testAdvGat(t, c, opGATK, Key, Key, 0) 1461 | testAdvGat(t, c, opGATK, Key, Key, 0xffffffff) 1462 | testAdvGat(t, c, opGATK, Key, Key, 0xfffffff0) 1463 | testAdvGat(t, c, opGATK, Key, Key, 0xf0f0f0f0) 1464 | 1465 | // getkq 1466 | testAdvGat(t, c, opGATKQ, Key, Key, 123) 1467 | testAdvGat(t, c, opGATKQ, Key, Key, 0) 1468 | testAdvGat(t, c, opGATKQ, Key, Key, 0xffffffff) 1469 | testAdvGat(t, c, opGATKQ, Key, Key, 0xfffffff0) 1470 | testAdvGat(t, c, opGATKQ, Key, Key, 0xf0f0f0f0) 1471 | } 1472 | 1473 | func TestGetStats(t *testing.T) { 1474 | c := testInit(t) 1475 | 1476 | const ( 1477 | Key1 = "exists" 1478 | Val1 = "bar" 1479 | Key2 = "noexists" 1480 | 1481 | GetHits = 12348 1482 | GetMisses = 1993 1483 | ) 1484 | 1485 | // wait for other tests to finish... 1486 | time.Sleep(2000 * time.Millisecond) 1487 | 1488 | // clear cache and get starting point. 1489 | c.Flush(0) 1490 | stats, err := c.Stats() 1491 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1492 | 1493 | assertTruef(t, len(stats[mcAddr]) > 0, "stats is empty! %v", stats[mcAddr]) 1494 | startMisses, errNum := strconv.ParseUint(stats[mcAddr]["get_misses"], 10, 64) 1495 | assertEqualf(t, nil, errNum, "unexpected error: %v, stats struct: %v", 1496 | errNum, stats[mcAddr]) 1497 | startHits, errNum := strconv.ParseUint(stats[mcAddr]["get_hits"], 10, 64) 1498 | assertEqualf(t, nil, errNum, "unexpected error: %v, stats struct: %v", 1499 | errNum, stats[mcAddr]) 1500 | 1501 | // setup key 1502 | _, err = c.Set(Key1, Val1, 0, 0, 0) 1503 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1504 | 1505 | ch := make(chan bool) 1506 | 1507 | // run get hit thread 1508 | go func() { 1509 | for i := 0; i < GetHits; i++ { 1510 | _, _, _, err := c.Get(Key1) 1511 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1512 | } 1513 | ch <- true 1514 | }() 1515 | 1516 | // run get miss thread 1517 | go func() { 1518 | for i := 0; i < GetMisses; i++ { 1519 | _, _, _, err := c.Get(Key2) 1520 | assertEqualf(t, ErrNotFound, err, "expected 'not found' error: %v", err) 1521 | } 1522 | ch <- true 1523 | }() 1524 | 1525 | // wait on both threads 1526 | _ = <-ch 1527 | _ = <-ch 1528 | stats, err = c.Stats() 1529 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1530 | 1531 | getMisses := strconv.FormatUint(GetMisses+startMisses, 10) 1532 | 1533 | if stats[mcAddr]["get_misses"] != getMisses { 1534 | t.Errorf("get_misses (%s) != expected (%s)\n", stats[mcAddr]["get_misses"], getMisses) 1535 | } 1536 | 1537 | getHits := strconv.FormatUint(GetHits+startHits, 10) 1538 | if stats[mcAddr]["get_hits"] != getHits { 1539 | t.Errorf("get_hits (%s) != expected (%s)\n", stats[mcAddr]["get_hits"], getHits) 1540 | } 1541 | } 1542 | 1543 | func TestErrorValue(t *testing.T) { 1544 | c := NewMC(badAddr, user, pass) 1545 | err := c.Flush(0) 1546 | if err == nil { 1547 | t.Errorf("expected network error!") 1548 | } 1549 | 1550 | mErr, ok := err.(*Error) 1551 | if !ok { 1552 | t.Errorf("type-cast of error to *Error failed!") 1553 | } 1554 | 1555 | assertEqualf(t, StatusNetworkError, mErr.Status, "expected 'StatusNetworkError' error: %v", mErr) 1556 | } 1557 | 1558 | // Test Stats reset. 1559 | func TestStatsReset(t *testing.T) { 1560 | c := testInit(t) 1561 | 1562 | const ( 1563 | Key1 = "fab" 1564 | ) 1565 | 1566 | // clear cache and get starting point. 1567 | c.Flush(0) 1568 | 1569 | // get current miss stats. 1570 | stats, err := c.Stats() 1571 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1572 | 1573 | assertTruef(t, len(stats[mcAddr]) > 0, "stats is empty! %v", stats[mcAddr]) 1574 | misses1, errNum := strconv.ParseUint(stats[mcAddr]["get_misses"], 10, 64) 1575 | assertEqualf(t, nil, errNum, "unexpected error: %v, stats struct: %v", 1576 | errNum, stats[mcAddr]) 1577 | 1578 | _, _, _, err = c.Get(Key1) 1579 | assertEqualf(t, ErrNotFound, err, "shouldn't be an error: %v", err) 1580 | 1581 | // get current miss stats. 1582 | stats, err = c.Stats() 1583 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1584 | 1585 | assertTruef(t, len(stats[mcAddr]) > 0, "stats is empty! %v", stats[mcAddr]) 1586 | misses2, errNum := strconv.ParseUint(stats[mcAddr]["get_misses"], 10, 64) 1587 | assertEqualf(t, nil, errNum, "unexpected error: %v, stats struct: %v", 1588 | errNum, stats[mcAddr]) 1589 | 1590 | // make sure they incremented by one 1591 | assertEqualf(t, misses1+1, misses2, 1592 | "miss stats didn't change as expected! (%d vs %d)", misses1, misses2) 1593 | 1594 | // reset stats 1595 | c.StatsReset() 1596 | 1597 | // get current miss stats. 1598 | stats, err = c.Stats() 1599 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1600 | for _, v := range stats { 1601 | assertTruef(t, len(v) > 0, "stats is empty! %v", v) 1602 | misses3, errNum := strconv.ParseUint(v["get_misses"], 10, 64) 1603 | assertEqualf(t, nil, errNum, "unexpected error: %v, stats struct: %v", 1604 | errNum, v) 1605 | 1606 | // make sure stats back to zero 1607 | assertEqualf(t, misses3, uint64(0), 1608 | "miss stats didn't change as expected! (%d vs %d)", misses3, uint64(0)) 1609 | break // in case there are multiple servers 1610 | } 1611 | } 1612 | 1613 | func TestGetServer(t *testing.T) { 1614 | c := NewMC(mcAddr+","+badAddr, user, pass) 1615 | c.servers[1].isAlive = false 1616 | 1617 | s, e := c.getServer("ok") 1618 | assertEqualf(t, nil, e, "err was not nil") 1619 | assertEqualf(t, mcAddr, s.address, "address of incorrect server returned") 1620 | } 1621 | 1622 | func TestMCZlibCompression(t *testing.T) { 1623 | c := testZlibCompress(t) 1624 | 1625 | const ( 1626 | Key1 = "foo" 1627 | Val1 = "bar" 1628 | Val2 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc fermentum arcu id libero maximus mollis. Nulla id lorem efficitur, maximus risus vitae, iaculis libero. Mauris non vehicula tortor. Etiam fringilla dictum elit. Donec dui justo, semper et nisl vitae, vestibulum tempus enim. Aenean id lacinia diam. Integer viverra viverra augue, vitae feugiat mauris posuere at. Praesent luctus, urna eu sollicitudin ultrices, enim tortor sagittis nunc, eget ultricies nisi nibh in neque. Integer eget commodo ipsum, non congue purus. Nullam erat felis, dictum vel ligula ut, maximus aliquet justo. Nunc volutpat magna vitae arcu consectetur, vitae molestie odio efficitur." 1629 | Val3 = "Praesent vel pretium elit. Donec volutpat placerat dolor eu tempus. Vivamus suscipit maximus tortor quis interdum. Cras venenatis consectetur pellentesque. Pellentesque gravida ut mi sit amet bibendum. Phasellus bibendum ex sit amet dolor condimentum mattis. Cras nunc diam, ornare quis velit sed, ullamcorper viverra erat. Nunc placerat tempus porttitor. Suspendisse vestibulum nisl a mauris mollis rhoncus. Morbi consequat felis sit amet magna iaculis scelerisque." 1630 | ) 1631 | 1632 | // fmt.Printf("test init: %v", c) 1633 | val, flags, cs, err := c.Get(Key1) 1634 | if err != ErrNotFound { 1635 | t.Errorf("val: %v, flags: %v, cas: %v", val, flags, cs) 1636 | t.Fatalf("expected missing key: %v", err) 1637 | } 1638 | 1639 | // unconditional SET 1640 | _, err = c.Set(Key1, Val1, 0, 0, 0) 1641 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1642 | cas, err := c.Set(Key1, Val1, 0, 0, 0) 1643 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1644 | 1645 | // make sure CAS works 1646 | _, err = c.Set(Key1, Val2, 0, 0, cas+1) 1647 | assertEqualf(t, ErrKeyExists, err, "expected CAS mismatch: %v", err) 1648 | 1649 | // check SET actually set the correct value... 1650 | v, _, cas2, err := c.Get(Key1) 1651 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1652 | assertEqualf(t, Val1, v, "wrong value: %s", v) 1653 | assertEqualf(t, cas, cas2, "CAS shouldn't have changed: %d, %d", cas, cas2) 1654 | 1655 | // use correct CAS... 1656 | cas2, err = c.Set(Key1, Val3, 0, 0, cas) 1657 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1658 | assertNotEqualf(t, cas, cas2, "CAS should not be the same") 1659 | } 1660 | 1661 | func TestMCGzipCompression(t *testing.T) { 1662 | c := testGzipCompress(t) 1663 | 1664 | const ( 1665 | Key1 = "foo" 1666 | Val1 = "bar" 1667 | Val2 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc fermentum arcu id libero maximus mollis. Nulla id lorem efficitur, maximus risus vitae, iaculis libero. Mauris non vehicula tortor. Etiam fringilla dictum elit. Donec dui justo, semper et nisl vitae, vestibulum tempus enim. Aenean id lacinia diam. Integer viverra viverra augue, vitae feugiat mauris posuere at. Praesent luctus, urna eu sollicitudin ultrices, enim tortor sagittis nunc, eget ultricies nisi nibh in neque. Integer eget commodo ipsum, non congue purus. Nullam erat felis, dictum vel ligula ut, maximus aliquet justo. Nunc volutpat magna vitae arcu consectetur, vitae molestie odio efficitur." 1668 | Val3 = "Praesent vel pretium elit. Donec volutpat placerat dolor eu tempus. Vivamus suscipit maximus tortor quis interdum. Cras venenatis consectetur pellentesque. Pellentesque gravida ut mi sit amet bibendum. Phasellus bibendum ex sit amet dolor condimentum mattis. Cras nunc diam, ornare quis velit sed, ullamcorper viverra erat. Nunc placerat tempus porttitor. Suspendisse vestibulum nisl a mauris mollis rhoncus. Morbi consequat felis sit amet magna iaculis scelerisque." 1669 | ) 1670 | 1671 | // fmt.Printf("test init: %v", c) 1672 | val, flags, cs, err := c.Get(Key1) 1673 | if err != ErrNotFound { 1674 | t.Errorf("val: %v, flags: %v, cas: %v", val, flags, cs) 1675 | t.Fatalf("expected missing key: %v", err) 1676 | } 1677 | 1678 | // unconditional SET 1679 | _, err = c.Set(Key1, Val1, 0, 0, 0) 1680 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1681 | cas, err := c.Set(Key1, Val1, 0, 0, 0) 1682 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1683 | 1684 | // make sure CAS works 1685 | _, err = c.Set(Key1, Val2, 0, 0, cas+1) 1686 | assertEqualf(t, ErrKeyExists, err, "expected CAS mismatch: %v", err) 1687 | 1688 | // check SET actually set the correct value... 1689 | v, _, cas2, err := c.Get(Key1) 1690 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1691 | assertEqualf(t, Val1, v, "wrong value: %s", v) 1692 | assertEqualf(t, cas, cas2, "CAS shouldn't have changed: %d, %d", cas, cas2) 1693 | 1694 | // use correct CAS... 1695 | cas2, err = c.Set(Key1, Val3, 0, 0, cas) 1696 | assertEqualf(t, mcNil, err, "unexpected error: %v", err) 1697 | assertNotEqualf(t, cas, cas2, "CAS should not be the same") 1698 | } 1699 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package mc 2 | 3 | // 4 | 5 | import ( 6 | "time" 7 | ) 8 | 9 | // Config holds the Memcache client configuration. Use DefaultConfig to get 10 | // an initialized version. 11 | type Config struct { 12 | Hasher hasher 13 | Retries int 14 | RetryDelay time.Duration 15 | Failover bool 16 | // ConnectionTimeout is currently used to timeout getting connections from 17 | // pool, as a sending deadline and as a reading deadline. Worst case this 18 | // means a request can take 3 times the ConnectionTimeout. 19 | ConnectionTimeout time.Duration 20 | DownRetryDelay time.Duration 21 | PoolSize int 22 | TcpKeepAlive bool 23 | TcpKeepAlivePeriod time.Duration 24 | TcpNoDelay bool 25 | Compression struct { 26 | Decompress func(value string) (string, error) 27 | Compress func(value string) (string, error) 28 | } 29 | } 30 | 31 | /* 32 | DefaultConfig returns a config object populated with the default values. 33 | The default values currently are: 34 | 35 | config{ 36 | Hasher: NewModuloHasher(), 37 | Retries: 2, 38 | RetryDelay: 200 * time.Millisecond, 39 | Failover: true, 40 | ConnectionTimeout: 2 * time.Second, 41 | DownRetryDelay: 60 * time.Second, 42 | PoolSize: 1, 43 | TcpKeepAlive: true, 44 | TcpKeepAlivePeriod: 60 * time.Second, 45 | TcpNoDelay: true, 46 | Compression struct { 47 | Decompress nil 48 | Compress nil 49 | } 50 | } 51 | */ 52 | func DefaultConfig() *Config { 53 | return &Config{ 54 | Hasher: NewModuloHasher(), 55 | Retries: 2, 56 | RetryDelay: 200 * time.Millisecond, 57 | Failover: true, 58 | ConnectionTimeout: 2 * time.Second, 59 | DownRetryDelay: 60 * time.Second, 60 | PoolSize: 1, 61 | TcpKeepAlive: true, 62 | TcpKeepAlivePeriod: 60 * time.Second, 63 | TcpNoDelay: true, 64 | Compression: struct { 65 | Decompress func(value string) (string, error) 66 | Compress func(value string) (string, error) 67 | }{Decompress: nil, Compress: nil}, 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /failure_test.go: -------------------------------------------------------------------------------- 1 | package mc 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | // Test successful retries 10 | func TestRetrySuccess(t *testing.T) { 11 | for i := 1; i < 4; i++ { 12 | config := DefaultConfig() 13 | config.Retries = i 14 | i_str := strconv.Itoa(i) 15 | c := newMockableMC("s1-"+i_str, "", "", config, newMockConn) 16 | 17 | val, flags, cs, err := c.Get("k1") 18 | if err != nil { 19 | t.Errorf("val: %v, flags: %v, cas: %v", val, flags, cs) 20 | t.Fatalf("expected no error: %v", err) 21 | } 22 | expectedVal := "k1,s1," + i_str 23 | if val != expectedVal { 24 | t.Fatalf("got wrong value: %v, expected: %v", val, expectedVal) 25 | } 26 | } 27 | } 28 | 29 | // Test failed retries 30 | func TestRetryFailure(t *testing.T) { 31 | for i := 2; i < 4; i++ { 32 | config := DefaultConfig() 33 | config.Retries = i - 1 34 | i_str := strconv.Itoa(i) 35 | c := newMockableMC("s1-"+i_str, "", "", config, newMockConn) 36 | 37 | val, flags, cs, err := c.Get("k1") 38 | if err == nil { 39 | t.Errorf("val: %v, flags: %v, cas: %v", val, flags, cs) 40 | t.Fatal("expected error but got none") 41 | } 42 | } 43 | } 44 | 45 | // Test successful failover 46 | func TestFailoverSuccess(t *testing.T) { 47 | config := DefaultConfig() 48 | config.DownRetryDelay = 2 * time.Second 49 | c := newMockableMC("s1-3,s2-1", "", "", config, newMockConn) 50 | 51 | key := "k2" // this key hashes to s1 52 | res := [3]string{key + ",s2,1", key + ",s2,2", key + ",s1,3"} 53 | // Expected behavior 54 | // 1st loop: try to get twice from s1, fails, marks s1 down, tries s2, succeeds 55 | // 2nd loop: get from s2 since s1 is still down, succeeds 56 | // 3rd loop: get from s1 since it is up again, succeeds 57 | for i := 0; i < len(res); i++ { 58 | val, flags, cs, err := c.Get(key) 59 | if err != nil { 60 | t.Errorf("val: %v, flags: %v, cas: %v", val, flags, cs) 61 | t.Fatalf("expected no error: %v", err) 62 | } 63 | expectedVal := res[i] 64 | if val != expectedVal { 65 | t.Fatalf("got wrong value: %v, expected: %v", val, expectedVal) 66 | } 67 | time.Sleep(1 * time.Second) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/memcachier/mc/v3 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /hasher.go: -------------------------------------------------------------------------------- 1 | package mc 2 | 3 | // 4 | 5 | import ( 6 | "hash" 7 | "hash/fnv" 8 | ) 9 | 10 | type hasher interface { 11 | update(servers []*server) 12 | getServerIndex(key string) (uint, error) 13 | } 14 | 15 | type moduloHasher struct { 16 | nServers uint 17 | h32 hash.Hash32 18 | } 19 | 20 | func NewModuloHasher() hasher { 21 | var h hasher = &moduloHasher{h32: fnv.New32a()} 22 | return h 23 | } 24 | 25 | func (h *moduloHasher) update(servers []*server) { 26 | h.nServers = uint(len(servers)) 27 | } 28 | 29 | func (h *moduloHasher) getServerIndex(key string) (uint, error) { 30 | if h.nServers < 1 { 31 | return 0, &Error{StatusNetworkError, "No server available", nil} 32 | } 33 | 34 | h.h32.Write([]byte(key)) 35 | defer h.h32.Reset() 36 | 37 | return uint(h.h32.Sum32()) % h.nServers, nil 38 | } 39 | -------------------------------------------------------------------------------- /mock_conn.go: -------------------------------------------------------------------------------- 1 | package mc 2 | 3 | // Mocks the connection between the client and memcached servers. 4 | 5 | import ( 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // mockConn is a mock connection. 11 | type mockConn struct { 12 | serverId string 13 | successMod int 14 | counter int 15 | backupMsg msg 16 | } 17 | 18 | // newMockConn creates a new mockConn which allows for a certain failure pattern 19 | // which is configured via the address parameter. The failure pattern consists 20 | // of an integer that defines which requests will succeed, e.g. 2 means every 21 | // second request succeeds. The address has the following structure: 22 | // -: (port gets automatically inserted). 23 | func newMockConn(address, scheme, username, password string, config *Config) mcConn { 24 | id_mod := strings.Split(strings.Split(address, ":")[0], "-") 25 | id := id_mod[0] 26 | mod := 1 27 | if len(id_mod) > 1 { 28 | var err error 29 | mod, err = strconv.Atoi(id_mod[1]) 30 | if err != nil { 31 | mod = 1 32 | } 33 | } 34 | 35 | mockConn := &mockConn{ 36 | serverId: id, 37 | successMod: mod, 38 | } 39 | return mockConn 40 | } 41 | 42 | func (mc *mockConn) perform(m *msg) error { 43 | mc.counter++ 44 | if mc.counter%mc.successMod == 0 { 45 | m.val = m.val + m.key + "," + mc.serverId + "," + strconv.Itoa(mc.counter) 46 | return nil 47 | } 48 | return &Error{StatusNetworkError, "Mock network error", nil} 49 | } 50 | 51 | func (mc *mockConn) performStats(m *msg) (McStats, error) { 52 | return nil, nil 53 | } 54 | 55 | func (mc *mockConn) quit(m *msg) { 56 | } 57 | 58 | func (mc *mockConn) backup(m *msg) { 59 | backupMsg(m, &mc.backupMsg) 60 | } 61 | 62 | func (mc *mockConn) restore(m *msg) { 63 | restoreMsg(m, &mc.backupMsg) 64 | } 65 | -------------------------------------------------------------------------------- /multi_node_test.go: -------------------------------------------------------------------------------- 1 | // +build multinode 2 | 3 | package mc 4 | 5 | import ( 6 | "fmt" 7 | "regexp" 8 | "testing" 9 | ) 10 | 11 | func TestMultiNodeVersion(t *testing.T) { 12 | c := testInitMultiNode(t, "localhost:11289 localhost:11290 localhost:11291", "", "") 13 | vers, err := c.Version() 14 | assertEqualf(t, nil, err, "unexpected error: %v", err) 15 | for k, v := range vers { 16 | fmt.Printf(" host: %s version: %s\n", k, v) 17 | good, errRegex := regexp.MatchString("[0-9]+\\.[0-9]+\\.[0-9]+", v) 18 | assertEqualf(t, nil, errRegex, "unexpected error: %v", errRegex) 19 | assertEqualf(t, good, true, "version of unexcpected form: %s", v) 20 | } 21 | c.Quit() 22 | } 23 | 24 | func TestMultiNodeNoOp(t *testing.T) { 25 | c := testInitMultiNode(t, "localhost:11289 localhost:11290 localhost:11291", "", "") 26 | err := c.NoOp() 27 | assertEqualf(t, nil, err, "unexpected error: %v", err) 28 | c.Quit() 29 | } 30 | 31 | func TestMultiNodeFlush(t *testing.T) { 32 | c := testInitMultiNode(t, "localhost:11289 localhost:11290 localhost:11291", "", "") 33 | err := c.Flush(0) 34 | assertEqualf(t, nil, err, "unexpected error: %v", err) 35 | c.Quit() 36 | } 37 | -------------------------------------------------------------------------------- /protocol.go: -------------------------------------------------------------------------------- 1 | package mc 2 | 3 | // Deal with the protocol specification of Memcached. 4 | 5 | // Error represents a MemCache error (including the status code). All function 6 | // in mc return error values of this type, despite the functions using the plain 7 | // error type. You can safely cast all error types returned by mc to *Error. If 8 | // needed, we take an underlying error value (such as a network error) and wrap 9 | // it in Error, storing the underlying value in WrappedError. 10 | // 11 | // error is used as the return typed instead of Error directly due to the 12 | // limitation in Go where error(nil) != *Error(nil). 13 | type Error struct { 14 | Status uint16 15 | Message string 16 | WrappedError error 17 | } 18 | 19 | // Error returns a human readable reason for the error (part of the error 20 | // interface). 21 | func (err Error) Error() string { 22 | return err.Message 23 | } 24 | 25 | // Errors mc may return. Some errors aren't represented here as the message is 26 | // dynamically generated. Status Code however captures all possible values for 27 | // Error.Status. 28 | var ( 29 | ErrNotFound = &Error{StatusNotFound, "mc: not found", nil} 30 | ErrKeyExists = &Error{StatusKeyExists, "mc: key exists", nil} 31 | ErrValueTooLarge = &Error{StatusValueNotStored, "mc: value to large", nil} 32 | ErrInvalidArgs = &Error{StatusInvalidArgs, "mc: invalid arguments", nil} 33 | ErrValueNotStored = &Error{StatusValueNotStored, "mc: value not stored", nil} 34 | ErrNonNumeric = &Error{StatusNonNumeric, "mc: incr/decr called on non-numeric value", nil} 35 | ErrAuthRequired = &Error{StatusAuthRequired, "mc: authentication required", nil} 36 | ErrAuthContinue = &Error{StatusAuthContinue, "mc: authentication continue (unsupported)", nil} 37 | ErrUnknownCommand = &Error{StatusUnknownCommand, "mc: unknown command", nil} 38 | ErrOutOfMemory = &Error{StatusOutOfMemory, "mc: out of memory", nil} 39 | ErrUnknownError = &Error{StatusUnknownError, "mc: unknown error from server", nil} 40 | ) 41 | 42 | // Status Codes that may be returned (usually as part of an Error). 43 | const ( 44 | StatusOK = uint16(0) 45 | StatusNotFound = uint16(1) 46 | StatusKeyExists = uint16(2) 47 | StatusValueTooLarge = uint16(3) 48 | StatusInvalidArgs = uint16(4) 49 | StatusValueNotStored = uint16(5) 50 | StatusNonNumeric = uint16(6) 51 | StatusAuthRequired = uint16(0x20) 52 | StatusAuthContinue = uint16(0x21) 53 | StatusUnknownCommand = uint16(0x81) 54 | StatusOutOfMemory = uint16(0x82) 55 | StatusAuthUnknown = uint16(0xffff) 56 | StatusNetworkError = uint16(0xfff1) 57 | StatusUnknownError = uint16(0xffff) 58 | ) 59 | 60 | // newError takes a status from the server and creates a matching Error. 61 | func newError(status uint16) error { 62 | switch status { 63 | case StatusOK: 64 | return nil 65 | case StatusNotFound: 66 | return ErrNotFound 67 | case StatusKeyExists: 68 | return ErrKeyExists 69 | case StatusValueTooLarge: 70 | return ErrValueTooLarge 71 | case StatusInvalidArgs: 72 | return ErrInvalidArgs 73 | case StatusValueNotStored: 74 | return ErrValueNotStored 75 | case StatusNonNumeric: 76 | return ErrNonNumeric 77 | case StatusAuthRequired: 78 | return ErrAuthRequired 79 | 80 | // we only support PLAIN auth, no mechanism that would make use of auth 81 | // continue, so make it an error for now for completeness. 82 | case StatusAuthContinue: 83 | return ErrAuthContinue 84 | case StatusUnknownCommand: 85 | return ErrUnknownCommand 86 | case StatusOutOfMemory: 87 | return ErrOutOfMemory 88 | } 89 | return ErrUnknownError 90 | } 91 | 92 | // wrapError wraps an existing error in an Error value. 93 | func wrapError(status uint16, err error) error { 94 | return &Error{status, err.Error(), err} 95 | } 96 | 97 | type opCode uint8 98 | 99 | // ops 100 | const ( 101 | opGet opCode = opCode(iota) 102 | opSet 103 | opAdd 104 | opReplace 105 | opDelete 106 | opIncrement 107 | opDecrement 108 | opQuit 109 | opFlush 110 | opGetQ 111 | opNoop 112 | opVersion 113 | opGetK 114 | opGetKQ 115 | opAppend 116 | opPrepend 117 | opStat 118 | opSetQ 119 | opAddQ 120 | opReplaceQ 121 | opDeleteQ 122 | opIncrementQ 123 | opDecrementQ 124 | opQuitQ 125 | opFlushQ 126 | opAppendQ 127 | opPrependQ 128 | opVerbosity // verbosity - not implemented in memcached (but other servers) 129 | opTouch 130 | opGAT 131 | opGATQ 132 | opGATK = opCode(0x23) 133 | opGATKQ = opCode(0x24) 134 | ) 135 | 136 | // Auth Ops 137 | const ( 138 | opAuthList opCode = opCode(iota + 0x20) 139 | opAuthStart 140 | opAuthStep 141 | ) 142 | 143 | // Magic Codes 144 | type magicCode uint8 145 | 146 | const ( 147 | magicSend magicCode = 0x80 148 | magicRecv magicCode = 0x81 149 | ) 150 | 151 | // Memcache header 152 | type header struct { 153 | Magic magicCode 154 | Op opCode 155 | KeyLen uint16 156 | ExtraLen uint8 157 | DataType uint8 // not used, memcached expects it to be 0x00. 158 | ResvOrStatus uint16 // for request this field is reserved / unused, for 159 | // response it indicates the status 160 | BodyLen uint32 161 | Opaque uint32 // copied back to you in response message (message id) 162 | CAS uint64 // version really 163 | } 164 | 165 | // Main Memcache message structure 166 | type msg struct { 167 | header // [0..23] 168 | iextras []interface{} // [24..(m-1)] Command specific extras (In) 169 | 170 | // Idea of this is we can pass in pointers to values that should appear in the 171 | // response extras in this field and the generic send/receive code can handle. 172 | oextras []interface{} // [24..(m-1)] Command specifc extras (Out) 173 | 174 | key string // [m..(n-1)] Key (as needed, length in header) 175 | val string // [n..x] Value (as needed, length in header) 176 | } 177 | 178 | // Memcache stats 179 | type McStats map[string]string 180 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package mc 2 | 3 | // Handles all server connections to a particular memcached servers. 4 | 5 | import ( 6 | "net" 7 | "net/url" 8 | "strings" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | // server represents a server and contains all connections to that server 14 | type server struct { 15 | address string 16 | scheme string 17 | config *Config 18 | // NOTE: organizing the pool as a chan makes the usage of the containing 19 | // connections treadsafe 20 | pool chan mcConn 21 | isAlive bool 22 | lock sync.Mutex 23 | } 24 | 25 | const defaultPort = "11211" 26 | 27 | func newServer(address, username, password string, config *Config, newMcConn connGen) *server { 28 | addr := address 29 | scheme := "tcp" 30 | 31 | if u, err := url.Parse(address); err == nil { 32 | switch strings.ToLower(u.Scheme) { 33 | case "tcp": 34 | if len(u.Port()) == 0 { 35 | addr = net.JoinHostPort(u.Host, defaultPort) 36 | } else { 37 | addr = u.Host 38 | } 39 | 40 | case "unix": 41 | addr = u.Path 42 | scheme = "unix" 43 | 44 | case "": 45 | if host, port, err := net.SplitHostPort(address); err == nil { 46 | addr = net.JoinHostPort(host, port) 47 | } else { 48 | addr = net.JoinHostPort(address, defaultPort) 49 | } 50 | } 51 | } 52 | 53 | server := &server{ 54 | address: addr, 55 | scheme: scheme, 56 | config: config, 57 | pool: make(chan mcConn, config.PoolSize), 58 | isAlive: true, 59 | } 60 | 61 | for i := 0; i < config.PoolSize; i++ { 62 | server.pool <- newMcConn(addr, scheme, username, password, config) 63 | } 64 | 65 | return server 66 | } 67 | 68 | func (s *server) perform(m *msg) error { 69 | var err error 70 | for i := 0; ; { 71 | timeout := time.After(s.config.ConnectionTimeout) 72 | select { 73 | case c := <-s.pool: 74 | // NOTE: this serverConn is no longer available in the pool (equivalent to locking) 75 | if c == nil { 76 | return &Error{StatusUnknownError, "Client is closed (did you call Quit?)", nil} 77 | } 78 | 79 | // backup request if a retry might be possible 80 | if i+1 < s.config.Retries { 81 | c.backup(m) 82 | } 83 | 84 | err = c.perform(m) 85 | s.pool <- c 86 | if err == nil { 87 | return nil 88 | } 89 | // Return Memcached errors except network errors. 90 | mErr := err.(*Error) 91 | if mErr.Status != StatusNetworkError { 92 | return err 93 | } 94 | 95 | // check if retry needed 96 | i++ 97 | if i < s.config.Retries { 98 | // restore request since m now contains the failed response 99 | c.restore(m) 100 | time.Sleep(s.config.RetryDelay) 101 | } else { 102 | return err 103 | } 104 | case <-timeout: 105 | // do not retry 106 | return &Error{StatusUnknownError, 107 | "Timed out while waiting for connection from pool. " + 108 | "Maybe increase your pool size?", 109 | nil} 110 | } 111 | } 112 | // return err 113 | } 114 | 115 | func (s *server) performStats(m *msg) (McStats, error) { 116 | timeout := time.After(s.config.ConnectionTimeout) 117 | select { 118 | case c := <-s.pool: 119 | // NOTE: this serverConn is no longer available in the pool (equivalent to locking) 120 | if c == nil { 121 | return nil, &Error{StatusUnknownError, "Client is closed (did you call Quit?)", nil} 122 | } 123 | 124 | stats, err := c.performStats(m) 125 | s.pool <- c 126 | return stats, err 127 | 128 | case <-timeout: 129 | // do not retry 130 | return nil, &Error{StatusUnknownError, 131 | "Timed out while waiting for connection from pool. " + 132 | "Maybe increase your pool size?", 133 | nil} 134 | } 135 | } 136 | 137 | func (s *server) quit(m *msg) { 138 | for i := 0; i < s.config.PoolSize; i++ { 139 | c := <-s.pool 140 | if c == nil { 141 | // Do not double quit 142 | return 143 | } 144 | c.quit(m) 145 | } 146 | close(s.pool) 147 | } 148 | 149 | func (s *server) changeAlive(alive bool) bool { 150 | s.lock.Lock() 151 | defer s.lock.Unlock() 152 | if s.isAlive != alive { 153 | s.isAlive = alive 154 | return true 155 | } 156 | return false 157 | } 158 | -------------------------------------------------------------------------------- /server_conn.go: -------------------------------------------------------------------------------- 1 | package mc 2 | 3 | // Handles the connection with the memcached servers. 4 | 5 | import ( 6 | "bytes" 7 | "encoding/binary" 8 | "fmt" 9 | "io" 10 | "net" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | type mcConn interface { 16 | perform(m *msg) error 17 | performStats(m *msg) (McStats, error) 18 | quit(m *msg) 19 | backup(m *msg) 20 | restore(m *msg) 21 | } 22 | 23 | type connGen func(address, scheme, username, password string, config *Config) mcConn 24 | 25 | // serverConn is a connection to a memcache server. 26 | type serverConn struct { 27 | address string 28 | scheme string 29 | username string 30 | password string 31 | config *Config 32 | conn net.Conn 33 | buf *bytes.Buffer 34 | opq uint32 35 | backupMsg msg 36 | } 37 | 38 | func newServerConn(address, scheme, username, password string, config *Config) mcConn { 39 | serverConn := &serverConn{ 40 | address: address, 41 | scheme: scheme, 42 | username: username, 43 | password: password, 44 | config: config, 45 | buf: new(bytes.Buffer), 46 | } 47 | return serverConn 48 | } 49 | 50 | func (sc *serverConn) perform(m *msg) error { 51 | // lazy connection 52 | if sc.conn == nil { 53 | err := sc.connect() 54 | if err != nil { 55 | return err 56 | } 57 | } 58 | return sc.sendRecv(m) 59 | } 60 | 61 | func (sc *serverConn) performStats(m *msg) (McStats, error) { 62 | // lazy connection 63 | if sc.conn == nil { 64 | err := sc.connect() 65 | if err != nil { 66 | return nil, err 67 | } 68 | } 69 | return sc.sendRecvStats(m) 70 | } 71 | 72 | func (sc *serverConn) quit(m *msg) { 73 | if sc.conn != nil { 74 | sc.sendRecv(m) 75 | 76 | if sc.conn != nil { 77 | sc.conn.Close() 78 | sc.conn = nil 79 | } 80 | } 81 | } 82 | 83 | func (sc *serverConn) connect() error { 84 | c, err := net.DialTimeout(sc.scheme, sc.address, sc.config.ConnectionTimeout) 85 | if err != nil { 86 | return wrapError(StatusNetworkError, err) 87 | } 88 | sc.conn = c 89 | if sc.scheme == "tcp" { 90 | tcpConn, ok := c.(*net.TCPConn) 91 | if !ok { 92 | return &Error{StatusNetworkError, "Cannot convert into TCP connection", nil} 93 | } 94 | 95 | tcpConn.SetKeepAlive(sc.config.TcpKeepAlive) 96 | tcpConn.SetKeepAlivePeriod(sc.config.TcpKeepAlivePeriod) 97 | tcpConn.SetNoDelay(sc.config.TcpNoDelay) 98 | } 99 | // authenticate 100 | err = sc.auth() 101 | if err != nil { 102 | // Error, except if the server doesn't support authentication 103 | mErr := err.(*Error) 104 | if mErr.Status != StatusUnknownCommand { 105 | if sc.conn != nil { 106 | sc.conn.Close() 107 | sc.conn = nil 108 | } 109 | return err 110 | } 111 | } 112 | return nil 113 | } 114 | 115 | // Auth performs SASL authentication (using the PLAIN method) with the server. 116 | func (sc *serverConn) auth() error { 117 | if len(sc.username) == 0 && len(sc.password) == 0 { 118 | return nil 119 | } 120 | s, err := sc.authList() 121 | if err != nil { 122 | return err 123 | } 124 | 125 | switch { 126 | case strings.Index(s, "PLAIN") != -1: 127 | return sc.authPlain() 128 | } 129 | 130 | return &Error{StatusAuthUnknown, fmt.Sprintf("mc: unknown auth types %q", s), nil} 131 | } 132 | 133 | // authList runs the SASL authentication list command with the server to 134 | // retrieve the list of support authentication mechanisms. 135 | func (sc *serverConn) authList() (string, error) { 136 | m := &msg{ 137 | header: header{ 138 | Op: opAuthList, 139 | }, 140 | } 141 | 142 | err := sc.sendRecv(m) 143 | return m.val, err 144 | } 145 | 146 | // authPlain performs SASL authentication using the PLAIN method. 147 | func (sc *serverConn) authPlain() error { 148 | m := &msg{ 149 | header: header{ 150 | Op: opAuthStart, 151 | }, 152 | 153 | key: "PLAIN", 154 | val: fmt.Sprintf("\x00%s\x00%s", sc.username, sc.password), 155 | } 156 | 157 | return sc.sendRecv(m) 158 | } 159 | 160 | // sendRecv sends and receives a complete memcache request/response exchange. 161 | func (sc *serverConn) sendRecv(m *msg) error { 162 | err := sc.send(m) 163 | if err != nil { 164 | sc.resetConn(err) 165 | return err 166 | } 167 | err = sc.recv(m) 168 | if err != nil { 169 | sc.resetConn(err) 170 | return err 171 | } 172 | return nil 173 | } 174 | 175 | // sendRecvStats 176 | func (sc *serverConn) sendRecvStats(m *msg) (stats McStats, err error) { 177 | err = sc.send(m) 178 | if err != nil { 179 | sc.resetConn(err) 180 | return 181 | } 182 | 183 | // collect all statistics 184 | stats = make(map[string]string) 185 | for { 186 | err = sc.recv(m) 187 | // error or termination message 188 | if err != nil || m.KeyLen == 0 { 189 | if err != nil { 190 | sc.resetConn(err) 191 | } 192 | return 193 | } 194 | stats[m.key] = m.val 195 | } 196 | return 197 | } 198 | 199 | // send sends a request to the memcache server. 200 | func (sc *serverConn) send(m *msg) error { 201 | m.Magic = magicSend 202 | m.ExtraLen = sizeOfExtras(m.iextras) 203 | m.KeyLen = uint16(len(m.key)) 204 | m.BodyLen = uint32(m.ExtraLen) + uint32(m.KeyLen) + uint32(len(m.val)) 205 | m.Opaque = sc.opq 206 | sc.opq++ 207 | 208 | // Request 209 | err := binary.Write(sc.buf, binary.BigEndian, m.header) 210 | if err != nil { 211 | return wrapError(StatusNetworkError, err) 212 | } 213 | 214 | for _, e := range m.iextras { 215 | err = binary.Write(sc.buf, binary.BigEndian, e) 216 | if err != nil { 217 | return wrapError(StatusNetworkError, err) 218 | } 219 | } 220 | 221 | _, err = io.WriteString(sc.buf, m.key) 222 | if err != nil { 223 | return wrapError(StatusNetworkError, err) 224 | } 225 | 226 | _, err = io.WriteString(sc.buf, m.val) 227 | if err != nil { 228 | return wrapError(StatusNetworkError, err) 229 | } 230 | 231 | // Make sure write does not block forever 232 | sc.conn.SetWriteDeadline(time.Now().Add(sc.config.ConnectionTimeout)) 233 | _, err = sc.buf.WriteTo(sc.conn) 234 | if err != nil { 235 | return wrapError(StatusNetworkError, err) 236 | } 237 | 238 | return nil 239 | } 240 | 241 | // recv receives a memcached response. It takes a msg into which to store the 242 | // response. 243 | func (sc *serverConn) recv(m *msg) error { 244 | // Make sure read does not block forever 245 | sc.conn.SetReadDeadline(time.Now().Add(sc.config.ConnectionTimeout)) 246 | 247 | err := binary.Read(sc.conn, binary.BigEndian, &m.header) 248 | if err != nil { 249 | return wrapError(StatusNetworkError, err) 250 | } 251 | 252 | bd := make([]byte, m.BodyLen) 253 | _, err = io.ReadFull(sc.conn, bd) 254 | if err != nil { 255 | return wrapError(StatusNetworkError, err) 256 | } 257 | 258 | buf := bytes.NewBuffer(bd) 259 | 260 | if m.ResvOrStatus == 0 && m.ExtraLen > 0 { 261 | for _, e := range m.oextras { 262 | err := binary.Read(buf, binary.BigEndian, e) 263 | if err != nil { 264 | return wrapError(StatusNetworkError, err) 265 | } 266 | } 267 | } 268 | 269 | m.key = string(buf.Next(int(m.KeyLen))) 270 | vlen := int(m.BodyLen) - int(m.ExtraLen) - int(m.KeyLen) 271 | m.val = string(buf.Next(int(vlen))) 272 | return newError(m.ResvOrStatus) 273 | } 274 | 275 | // sizeOfExtras returns the size of the extras field for the memcache request. 276 | func sizeOfExtras(extras []interface{}) (l uint8) { 277 | for _, e := range extras { 278 | switch e.(type) { 279 | default: 280 | panic(fmt.Sprintf("mc: unknown extra type (%T)", e)) 281 | case uint8: 282 | l += 8 / 8 283 | case uint16: 284 | l += 16 / 8 285 | case uint32: 286 | l += 32 / 8 287 | case uint64: 288 | l += 64 / 8 289 | } 290 | } 291 | return 292 | } 293 | 294 | // resetConn destroy connection if a network error occurred. serverConn will 295 | // reconnect on next usage. 296 | func (sc *serverConn) resetConn(err error) { 297 | if err.(*Error).Status == StatusNetworkError { 298 | sc.conn.Close() 299 | sc.conn = nil 300 | } 301 | } 302 | 303 | func (sc *serverConn) backup(m *msg) { 304 | backupMsg(m, &sc.backupMsg) 305 | } 306 | 307 | func backupMsg(m *msg, backupMsg *msg) { 308 | backupMsg.key = m.key 309 | backupMsg.val = m.val 310 | backupMsg.header.Magic = m.header.Magic 311 | backupMsg.header.Op = m.header.Op 312 | backupMsg.header.KeyLen = m.header.KeyLen 313 | backupMsg.header.ExtraLen = m.header.ExtraLen 314 | backupMsg.header.DataType = m.header.DataType 315 | backupMsg.header.ResvOrStatus = m.header.ResvOrStatus 316 | backupMsg.header.BodyLen = m.header.BodyLen 317 | backupMsg.header.Opaque = m.header.Opaque 318 | backupMsg.header.CAS = m.header.CAS 319 | backupMsg.iextras = nil // go way of clearing a slice, this is just fucked up 320 | for _, v := range m.iextras { 321 | backupMsg.iextras = append(backupMsg.iextras, v) 322 | } 323 | backupMsg.oextras = nil 324 | for _, v := range m.oextras { 325 | backupMsg.oextras = append(backupMsg.oextras, v) 326 | } 327 | } 328 | 329 | func (sc *serverConn) restore(m *msg) { 330 | restoreMsg(m, &sc.backupMsg) 331 | } 332 | 333 | func restoreMsg(m *msg, backupMsg *msg) { 334 | m.key = backupMsg.key 335 | m.val = backupMsg.val 336 | m.header.Magic = backupMsg.header.Magic 337 | m.header.Op = backupMsg.header.Op 338 | m.header.KeyLen = backupMsg.header.KeyLen 339 | m.header.ExtraLen = backupMsg.header.ExtraLen 340 | m.header.DataType = backupMsg.header.DataType 341 | m.header.ResvOrStatus = backupMsg.header.ResvOrStatus 342 | m.header.BodyLen = backupMsg.header.BodyLen 343 | m.header.Opaque = backupMsg.header.Opaque 344 | m.header.CAS = backupMsg.header.CAS 345 | m.iextras = nil // go way of clearing a slice, this is just fucked up 346 | for _, v := range backupMsg.iextras { 347 | m.iextras = append(m.iextras, v) 348 | } 349 | m.oextras = nil 350 | for _, v := range backupMsg.oextras { 351 | m.oextras = append(m.oextras, v) 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package mc 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "compress/zlib" 7 | "io" 8 | "reflect" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | // start connection 14 | func testZlibCompress(t *testing.T) *Client { 15 | config := DefaultConfig() 16 | config.Compression.Compress = func(value string) (string, error) { 17 | var compressedValue bytes.Buffer 18 | zw, err := zlib.NewWriterLevel(&compressedValue, -1) 19 | if err != nil { 20 | return value, err 21 | } 22 | if _, err = zw.Write([]byte(value)); err != nil { 23 | return value, err 24 | } 25 | zw.Close() 26 | return compressedValue.String(), nil 27 | } 28 | 29 | config.Compression.Decompress = func(value string) (string, error) { 30 | if value == "" { 31 | return value, nil 32 | } 33 | zr, err := zlib.NewReader(strings.NewReader(value)) 34 | if err != nil { 35 | return value, err 36 | } 37 | defer zr.Close() 38 | var unCompressedValue bytes.Buffer 39 | _, err = io.Copy(&unCompressedValue, zr) 40 | if err != nil { 41 | return value, err 42 | } 43 | return unCompressedValue.String(), nil 44 | } 45 | 46 | c := NewMCwithConfig(mcAddr, user, pass, config) 47 | err := c.Flush(0) 48 | assertEqualf(t, nil, err, "unexpected error during initial flush: %v", err) 49 | return c 50 | } 51 | 52 | func testGzipCompress(t *testing.T) *Client { 53 | config := DefaultConfig() 54 | config.Compression.Compress = func(value string) (string, error) { 55 | var compressedValue bytes.Buffer 56 | zw, err := gzip.NewWriterLevel(&compressedValue, -1) 57 | if err != nil { 58 | return value, err 59 | } 60 | if _, err = zw.Write([]byte(value)); err != nil { 61 | return value, err 62 | } 63 | zw.Close() 64 | return compressedValue.String(), nil 65 | } 66 | 67 | config.Compression.Decompress = func(value string) (string, error) { 68 | if value == "" { 69 | return value, nil 70 | } 71 | zr, err := gzip.NewReader(strings.NewReader(value)) 72 | if err != nil { 73 | return value, err 74 | } 75 | defer zr.Close() 76 | var unCompressedValue bytes.Buffer 77 | _, err = io.Copy(&unCompressedValue, zr) 78 | if err != nil { 79 | return value, err 80 | } 81 | return unCompressedValue.String(), nil 82 | } 83 | 84 | c := NewMCwithConfig(mcAddr, user, pass, config) 85 | err := c.Flush(0) 86 | assertEqualf(t, nil, err, "unexpected error during initial flush: %v", err) 87 | return c 88 | } 89 | 90 | // start connection 91 | func testInit(t *testing.T) *Client { 92 | c := NewMC(mcAddr, user, pass) 93 | err := c.Flush(0) 94 | assertEqualf(t, nil, err, "unexpected error during initial flush: %v", err) 95 | return c 96 | } 97 | 98 | // start connection 99 | func testInitMultiNode(t *testing.T, servers, username, password string) *Client { 100 | c := NewMC(servers, user, pass) 101 | err := c.Flush(0) 102 | assertEqualf(t, nil, err, "unexpected error during initial flush: %v", err) 103 | return c 104 | } 105 | 106 | // TODO: asserts gives just the location of FailNow, more of a stack trace would be nice 107 | 108 | func assertEqualf(t *testing.T, exp, got interface{}, format string, args ...interface{}) { 109 | if !reflect.DeepEqual(exp, got) { 110 | t.Errorf(format, args...) 111 | t.Errorf("expected: %v", exp) 112 | t.Errorf("got: %v", got) 113 | t.FailNow() 114 | } 115 | } 116 | 117 | func assertNotEqualf(t *testing.T, exp, got interface{}, format string, args ...interface{}) { 118 | if reflect.DeepEqual(exp, got) { 119 | t.Errorf(format, args...) 120 | t.Errorf("did not expect: %v", exp) 121 | t.FailNow() 122 | } 123 | } 124 | 125 | func assertTruef(t *testing.T, res bool, format string, args ...interface{}) { 126 | if !res { 127 | t.Fatalf(format, args...) 128 | } 129 | } 130 | --------------------------------------------------------------------------------