loading up the forgejo repo on tangled to test page performance
at forgejo 4.3 kB view raw
1// Copyright 2021 The Gitea Authors. All rights reserved. 2// SPDX-License-Identifier: MIT 3 4package cache 5 6import ( 7 "strconv" 8 "sync" 9 "time" 10 11 "forgejo.org/modules/json" 12 13 mc "code.forgejo.org/go-chi/cache" 14 lru "github.com/hashicorp/golang-lru/v2" 15) 16 17// TwoQueueCache represents a LRU 2Q cache adapter implementation 18type TwoQueueCache struct { 19 lock sync.Mutex 20 cache *lru.TwoQueueCache[string, any] 21 interval int 22} 23 24// TwoQueueCacheConfig describes the configuration for TwoQueueCache 25type TwoQueueCacheConfig struct { 26 Size int `ini:"SIZE" json:"size"` 27 RecentRatio float64 `ini:"RECENT_RATIO" json:"recent_ratio"` 28 GhostRatio float64 `ini:"GHOST_RATIO" json:"ghost_ratio"` 29} 30 31// MemoryItem represents a memory cache item. 32type MemoryItem struct { 33 Val any 34 Created int64 35 Timeout int64 36} 37 38func (item *MemoryItem) hasExpired() bool { 39 return item.Timeout > 0 && 40 (time.Now().Unix()-item.Created) >= item.Timeout 41} 42 43var _ mc.Cache = &TwoQueueCache{} 44 45// Put puts value into cache with key and expire time. 46func (c *TwoQueueCache) Put(key string, val any, timeout int64) error { 47 item := &MemoryItem{ 48 Val: val, 49 Created: time.Now().Unix(), 50 Timeout: timeout, 51 } 52 c.lock.Lock() 53 defer c.lock.Unlock() 54 c.cache.Add(key, item) 55 return nil 56} 57 58// Get gets cached value by given key. 59func (c *TwoQueueCache) Get(key string) any { 60 c.lock.Lock() 61 defer c.lock.Unlock() 62 cached, ok := c.cache.Get(key) 63 if !ok { 64 return nil 65 } 66 item, ok := cached.(*MemoryItem) 67 68 if !ok || item.hasExpired() { 69 c.cache.Remove(key) 70 return nil 71 } 72 73 return item.Val 74} 75 76// Delete deletes cached value by given key. 77func (c *TwoQueueCache) Delete(key string) error { 78 c.lock.Lock() 79 defer c.lock.Unlock() 80 c.cache.Remove(key) 81 return nil 82} 83 84// Incr increases cached int-type value by given key as a counter. 85func (c *TwoQueueCache) Incr(key string) error { 86 c.lock.Lock() 87 defer c.lock.Unlock() 88 cached, ok := c.cache.Get(key) 89 if !ok { 90 return nil 91 } 92 item, ok := cached.(*MemoryItem) 93 94 if !ok || item.hasExpired() { 95 c.cache.Remove(key) 96 return nil 97 } 98 99 var err error 100 item.Val, err = mc.Incr(item.Val) 101 return err 102} 103 104// Decr decreases cached int-type value by given key as a counter. 105func (c *TwoQueueCache) Decr(key string) error { 106 c.lock.Lock() 107 defer c.lock.Unlock() 108 cached, ok := c.cache.Get(key) 109 if !ok { 110 return nil 111 } 112 item, ok := cached.(*MemoryItem) 113 114 if !ok || item.hasExpired() { 115 c.cache.Remove(key) 116 return nil 117 } 118 119 var err error 120 item.Val, err = mc.Decr(item.Val) 121 return err 122} 123 124// IsExist returns true if cached value exists. 125func (c *TwoQueueCache) IsExist(key string) bool { 126 c.lock.Lock() 127 defer c.lock.Unlock() 128 cached, ok := c.cache.Peek(key) 129 if !ok { 130 return false 131 } 132 item, ok := cached.(*MemoryItem) 133 if !ok || item.hasExpired() { 134 c.cache.Remove(key) 135 return false 136 } 137 138 return true 139} 140 141// Flush deletes all cached data. 142func (c *TwoQueueCache) Flush() error { 143 c.lock.Lock() 144 defer c.lock.Unlock() 145 c.cache.Purge() 146 return nil 147} 148 149func (c *TwoQueueCache) checkAndInvalidate(key string) { 150 c.lock.Lock() 151 defer c.lock.Unlock() 152 cached, ok := c.cache.Peek(key) 153 if !ok { 154 return 155 } 156 item, ok := cached.(*MemoryItem) 157 if !ok || item.hasExpired() { 158 c.cache.Remove(key) 159 } 160} 161 162func (c *TwoQueueCache) startGC() { 163 if c.interval < 0 { 164 return 165 } 166 for _, key := range c.cache.Keys() { 167 c.checkAndInvalidate(key) 168 } 169 time.AfterFunc(time.Duration(c.interval)*time.Second, c.startGC) 170} 171 172// StartAndGC starts GC routine based on config string settings. 173func (c *TwoQueueCache) StartAndGC(opts mc.Options) error { 174 var err error 175 size := 50000 176 if opts.AdapterConfig != "" { 177 size, err = strconv.Atoi(opts.AdapterConfig) 178 } 179 if err != nil { 180 if !json.Valid([]byte(opts.AdapterConfig)) { 181 return err 182 } 183 184 cfg := &TwoQueueCacheConfig{ 185 Size: 50000, 186 RecentRatio: lru.Default2QRecentRatio, 187 GhostRatio: lru.Default2QGhostEntries, 188 } 189 _ = json.Unmarshal([]byte(opts.AdapterConfig), cfg) 190 c.cache, err = lru.New2QParams[string, any](cfg.Size, cfg.RecentRatio, cfg.GhostRatio) 191 } else { 192 c.cache, err = lru.New2Q[string, any](size) 193 } 194 c.interval = opts.Interval 195 if c.interval > 0 { 196 go c.startGC() 197 } 198 return err 199} 200 201// Ping tests if the cache is alive. 202func (c *TwoQueueCache) Ping() error { 203 return mc.GenericPing(c) 204} 205 206func init() { 207 mc.Register("twoqueue", &TwoQueueCache{}) 208}