github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/lru/lru.go (about)

     1  package lru
     2  
     3  import (
     4  	json "encoding/json"
     5  	"reflect"
     6  	"sync"
     7  
     8  	lru "github.com/hashicorp/golang-lru"
     9  	libkb "github.com/keybase/client/go/libkb"
    10  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    11  	jsonw "github.com/keybase/go-jsonw"
    12  	context "golang.org/x/net/context"
    13  )
    14  
    15  type Cache struct {
    16  	sync.Mutex
    17  	mem     *lru.Cache
    18  	version int
    19  	typ     reflect.Type
    20  	stats   stats
    21  }
    22  
    23  type stats struct {
    24  	memHit    int
    25  	diskHit   int
    26  	diskStale int
    27  	miss      int
    28  }
    29  
    30  func NewLRU(ctx libkb.LRUContext, sz int, version int, exampleObj interface{}) *Cache {
    31  	cache, err := lru.New(sz)
    32  	if err != nil {
    33  		ctx.GetLog().Fatalf("Bad LRU constructor: %s", err.Error())
    34  	}
    35  	return &Cache{
    36  		mem:     cache,
    37  		version: version,
    38  		typ:     reflect.TypeOf(exampleObj),
    39  	}
    40  }
    41  
    42  type diskWrapper struct {
    43  	Version  int           `codec:"v"`
    44  	Data     string        `codex:"d"`
    45  	CachedAt keybase1.Time `codec:"t"`
    46  }
    47  
    48  func (c *Cache) ClearMemory() {
    49  	c.mem.Purge()
    50  }
    51  
    52  func (c *Cache) Get(ctx context.Context, lctx libkb.LRUContext, k libkb.LRUKeyer) (interface{}, error) {
    53  	c.Lock()
    54  	defer c.Unlock()
    55  	val, ok := c.mem.Get(k.MemKey())
    56  	if ok {
    57  		c.stats.memHit++
    58  		lctx.GetVDebugLog().CLogf(ctx, libkb.VLog3, "lru(%v): mem hit", k.MemKey())
    59  		return val, nil
    60  	}
    61  	var w diskWrapper
    62  	ok, err := lctx.GetKVStore().GetInto(&w, k.DbKey())
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	if !ok {
    67  		c.stats.miss++
    68  		lctx.GetVDebugLog().CLogf(ctx, libkb.VLog3, "lru(%v): miss", k.DbKey())
    69  		return nil, nil
    70  	}
    71  	if w.Version != c.version {
    72  		c.stats.diskStale++
    73  		lctx.GetVDebugLog().CLogf(ctx, libkb.VLog0, "lru(%v), old version: %d < %d", k.DbKey(), w.Version, c.version)
    74  		return nil, nil
    75  	}
    76  	var ret interface{}
    77  	if len(w.Data) > 0 {
    78  		tmp := reflect.New(c.typ)
    79  		ret = tmp.Interface()
    80  
    81  		if err = jsonw.EnsureMaxDepthBytesDefault([]byte(w.Data)); err != nil {
    82  			return nil, err
    83  		}
    84  
    85  		if err = json.Unmarshal([]byte(w.Data), ret); err != nil {
    86  			return nil, err
    87  		}
    88  	}
    89  	c.stats.diskHit++
    90  	lctx.GetVDebugLog().CLogf(ctx, libkb.VLog3, "lru(%v): disk hit", k.DbKey())
    91  	c.mem.Add(k.MemKey(), ret)
    92  	return ret, nil
    93  }
    94  
    95  func (c *Cache) Put(ctx context.Context, lctx libkb.LRUContext, k libkb.LRUKeyer, v interface{}) error {
    96  	c.Lock()
    97  	defer c.Unlock()
    98  	var data string
    99  	if v != nil {
   100  		b, err := json.Marshal(v)
   101  		if err != nil {
   102  			return err
   103  		}
   104  		data = string(b)
   105  	}
   106  	w := diskWrapper{
   107  		Version:  c.version,
   108  		Data:     data,
   109  		CachedAt: keybase1.ToTime(lctx.GetClock().Now()),
   110  	}
   111  	err := lctx.GetKVStore().PutObj(k.DbKey(), nil, w)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	c.mem.Add(k.MemKey(), v)
   116  	return nil
   117  }
   118  
   119  func (c *Cache) OnLogout(mctx libkb.MetaContext) error {
   120  	c.ClearMemory()
   121  	return nil
   122  }
   123  
   124  func (c *Cache) OnDbNuke(mctx libkb.MetaContext) error {
   125  	c.ClearMemory()
   126  	return nil
   127  }
   128  
   129  var _ libkb.LRUer = (*Cache)(nil)