github.com/aarzilli/tools@v0.0.0-20151123112009-0d27094f75e0/dsu/put_get_memcache.go (about) 1 package dsu 2 3 import ( 4 "fmt" 5 "reflect" 6 "time" 7 8 "github.com/pbberlin/tools/util" 9 "golang.org/x/net/context" 10 "google.golang.org/appengine/memcache" 11 12 aelog "google.golang.org/appengine/log" 13 ) 14 15 // McacheSet is a generic memcache saving function. 16 // It takes scalars as well as structs. 17 // 18 // Integers and strings are put into the memcache Value []byte 19 // structs are put into the memcache *Object* - using memcache.JSON 20 // Todo: types WrapString and WrapInt should be handled like string/int 21 // 22 // Scalars are tentatively saved using the CAS (compare and save) methods 23 func McacheSet(c context.Context, skey string, str_int_struct interface{}) { 24 25 var err error 26 var val string 27 28 tMold := reflect.TypeOf(str_int_struct) 29 stMold := tMold.Name() // strangely this is empty 30 stMold = fmt.Sprintf("%T", str_int_struct) // unlike this 31 32 if stMold != "int" && 33 stMold != "string" && 34 stMold != "dsu.WrapInt" && 35 stMold != "dsu.WrapString" && 36 stMold != "*filesys.fs" && 37 stMold != "*filesys.fso" { 38 // struct - save it with JSON encoder 39 if ll > 2 { 40 aelog.Infof(c, "%v", stMold) 41 } 42 n := tMold.NumField() 43 _ = n 44 miPut := &memcache.Item{ 45 Key: skey, 46 Value: []byte(tMold.Name()), // sadly - value is ignored 47 Object: &str_int_struct, 48 Expiration: 3600 * time.Second, 49 } 50 memcache.JSON.Set(c, miPut) 51 if ll > 2 { 52 aelog.Infof(c, "mcache set obj key %v[%s] - err %v", skey, stMold, err) 53 } 54 55 } else { 56 // scalar value - save it 57 switch chamaeleon := str_int_struct.(type) { 58 default: 59 panic(fmt.Sprintf("only string or int - instead: -%T", str_int_struct)) 60 case nil: 61 val = "" 62 case WrapString: 63 val = chamaeleon.S 64 case string: 65 val = chamaeleon 66 case int: 67 val = util.Itos(chamaeleon) 68 case WrapInt: 69 val = util.Itos(chamaeleon.I) 70 } 71 72 /* 73 This is a Compare and Set (CAS) implementation of "set". 74 It implements optimistic locking. 75 76 We fetch the item first, then modify it, then put it back. 77 We rely on the hidden "casID" of the memcache item, 78 to detect intermittent changes by competitors. 79 80 Biggest downside is the additional roundtrip for the fetch. 81 Second downside: We should implement a retry after failure. 82 Instead I resorted to a simple "SET" 83 84 Upside: Prevention of race conditions. 85 But race conditions only matter if newval = f(oldval) 86 Otherwise last one wins should apply anyway. 87 88 */ 89 90 maxTries := 3 91 92 miCas, eget := memcache.Get(c, skey) // compare and swap 93 94 for i := 0; i <= maxTries; i++ { 95 96 if i == maxTries { 97 panic(fmt.Sprintf("memcache set CAS failed after %v attempts", maxTries)) 98 } 99 100 var eput error 101 var putMode = "" 102 if eget != memcache.ErrCacheMiss { 103 putMode = "CAS" 104 miCas.Value = []byte(val) 105 eput = memcache.CompareAndSwap(c, miCas) 106 } else { 107 putMode = "ADD" 108 miCas := &memcache.Item{ 109 Key: skey, 110 Value: []byte(val), 111 } 112 eput = memcache.Add(c, miCas) 113 } 114 115 if eput == memcache.ErrCASConflict { 116 aelog.Errorf(c, "\t memcache CAS FAILED - concurrent update?") 117 // we brutally fallback to set(): 118 miCas := &memcache.Item{ 119 Key: skey, 120 Value: []byte(val), 121 } 122 eset := memcache.Set(c, miCas) 123 if ll > 2 { 124 aelog.Infof(c, "%v", eset) 125 } 126 time.Sleep(10 * time.Millisecond) 127 continue 128 } 129 if eput == memcache.ErrNotStored { 130 aelog.Errorf(c, "\t memcache save FAILED - no idea why it would") 131 time.Sleep(10 * time.Millisecond) 132 continue 133 } 134 if ll > 2 { 135 aelog.Infof(c, "mcache set scalar %v[%T]=%v - mode %v - eget/eput: %v/%v", 136 skey, str_int_struct, val, putMode, eget, eput) 137 } 138 break 139 } 140 141 } 142 143 } 144 145 // McacheGet is our universal memcache retriever. 146 // Both scalars and structs are returned. 147 // 148 // Sadly, structs can only be casted into an *existing* object of the desired type. 149 // There is no way to create an object of desired type dynamically and return it. 150 // Therefore we need a pre-created object as argument for returning. 151 // 152 // Even for scalar values, the argument moldForReturn is required 153 // to indicate the scalar or struct type 154 // 155 // In addition, the returned value of type interface{} must be cumbersomely 156 // casted by the callee - thus the return value solution is always 157 // worse than simply passing a pre-created argument. 158 // 159 // For scalar values, the package has the types WrapString, WrapInt 160 // 161 // Todo: WrapString, WrapInt could be saved without JSON 162 func McacheGet(c context.Context, skey string, moldForReturn interface{}) bool { 163 164 tMold := reflect.TypeOf(moldForReturn) 165 stMold := tMold.Name() // strangely this is empty 166 stMold = fmt.Sprintf("%T", moldForReturn) // unlike this 167 msg1 := fmt.Sprintf("mcache requ type %s - key %v", stMold, skey) 168 if stMold == "string" || 169 stMold == "int" || 170 stMold == "*dsu.WrapInt" || 171 stMold == "*dsu.WrapString" { 172 if ll > 2 { 173 aelog.Infof(c, "%s %s", "scalar", msg1) 174 } 175 miGet, err := memcache.Get(c, skey) 176 if err != nil && err != memcache.ErrCacheMiss { 177 panic(err) 178 } 179 if err == memcache.ErrCacheMiss { 180 if stMold == "int" { 181 return false //xx 182 } else { 183 return false //xx 184 } 185 } 186 187 //var rval interface{} 188 if stMold == "int" { 189 panic("use wrappers") 190 //rval = util.Stoi(string(miGet.Value)) 191 } 192 if stMold == "string" { 193 //rval = string(miGet.Value) 194 panic("use wrappers") 195 } 196 if stMold == "*dsu.WrapInt" { 197 tmp := moldForReturn.(*WrapInt) 198 tmp.I = util.Stoi(string(miGet.Value)) 199 } 200 if stMold == "*dsu.WrapString" { 201 tmp := moldForReturn.(*WrapString) 202 tmp.S = string(miGet.Value) 203 } 204 if ll > 2 { 205 aelog.Infof(c, " mcache got scalar - key %v %v", skey, moldForReturn) 206 } 207 208 return true //xx 209 210 } else { 211 if ll > 2 { 212 aelog.Infof(c, "%s %s", "objct", msg1) 213 } 214 215 unparsedjson, err := memcache.JSON.Get(c, skey, &moldForReturn) 216 _ = unparsedjson 217 if err != nil && err != memcache.ErrCacheMiss { 218 panic(err) 219 } 220 if err == memcache.ErrCacheMiss { 221 return false //xx 222 } 223 if ll > 2 { 224 aelog.Infof(c, " mcache got obj - key %v", skey) 225 } 226 return true //xx 227 228 } 229 230 }