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  }