Skip to content

Commit

Permalink
Merge branch 'refactor-cache' of github.com:wxiaoguang/gitea into wxi…
Browse files Browse the repository at this point in the history
…aoguang-refactor-cache
  • Loading branch information
lunny committed Apr 12, 2024
2 parents 487b127 + 1062c03 commit 4e91b25
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 218 deletions.
2 changes: 2 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ linters-settings:
desc: do not use the internal package, use AddXxx function instead
- pkg: gopkg.in/ini.v1
desc: do not use the ini package, use gitea's config system instead
- pkg: gitea.com/go-chi/cache
desc: do not use the go-chi cache package, use gitea's cache system

issues:
max-issues-per-linter: 0
Expand Down
138 changes: 32 additions & 106 deletions modules/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,149 +4,75 @@
package cache

import (
"fmt"
"strconv"
"time"

"code.gitea.io/gitea/modules/setting"

mc "gitea.com/go-chi/cache"

_ "gitea.com/go-chi/cache/memcache" // memcache plugin for cache
)

var conn mc.Cache

func newCache(cacheConfig setting.Cache) (mc.Cache, error) {
return mc.NewCacher(mc.Options{
Adapter: cacheConfig.Adapter,
AdapterConfig: cacheConfig.Conn,
Interval: cacheConfig.Interval,
})
}
var defaultCache StringCache

// Init start cache service
func Init() error {
var err error

if conn == nil {
if conn, err = newCache(setting.CacheService.Cache); err != nil {
if defaultCache == nil {
c, err := NewStringCache(setting.CacheService.Cache)
if err != nil {
return err
}
if err = conn.Ping(); err != nil {
for i := 0; i < 10; i++ {
if err = c.Ping(); err == nil {
break
}
time.Sleep(time.Second)
}
if err != nil {
return err
}
defaultCache = c
}

return err
return nil
}

// GetCache returns the currently configured cache
func GetCache() mc.Cache {
return conn
func GetCache() StringCache {
return defaultCache
}

// GetString returns the key value from cache with callback when no key exists in cache
func GetString(key string, getFunc func() (string, error)) (string, error) {
if conn == nil || setting.CacheService.TTL == 0 {
if defaultCache == nil || setting.CacheService.TTL == 0 {
return getFunc()
}

cached := conn.Get(key)

if cached == nil {
cached, exist := defaultCache.Get(key)
if !exist {
value, err := getFunc()
if err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}

if value, ok := cached.(string); ok {
return value, nil
}

if stringer, ok := cached.(fmt.Stringer); ok {
return stringer.String(), nil
}

return fmt.Sprintf("%s", cached), nil
}

// GetInt returns key value from cache with callback when no key exists in cache
func GetInt(key string, getFunc func() (int, error)) (int, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
}

cached := conn.Get(key)

if cached == nil {
value, err := getFunc()
if err != nil {
return value, err
}

return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}

switch v := cached.(type) {
case int:
return v, nil
case string:
value, err := strconv.Atoi(v)
if err != nil {
return 0, err
}
return value, nil
default:
value, err := getFunc()
if err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
return value, defaultCache.Put(key, value, setting.CacheService.TTLSeconds())
}
return cached, nil
}

// GetInt64 returns key value from cache with callback when no key exists in cache
func GetInt64(key string, getFunc func() (int64, error)) (int64, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
}

cached := conn.Get(key)

if cached == nil {
value, err := getFunc()
if err != nil {
return value, err
}

return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
s, err := GetString(key, func() (string, error) {
v, err := getFunc()
return strconv.FormatInt(v, 10), err
})
if err != nil {
return 0, err
}

switch v := conn.Get(key).(type) {
case int64:
return v, nil
case string:
value, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return 0, err
}
return value, nil
default:
value, err := getFunc()
if err != nil {
return value, err
}

return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
if s == "" {
return 0, nil
}
return strconv.ParseInt(s, 10, 64)
}

// Remove key from cache
func Remove(key string) {
if conn == nil {
if defaultCache == nil {
return
}
_ = conn.Delete(key)
_ = defaultCache.Delete(key)
}
2 changes: 1 addition & 1 deletion modules/cache/cache_redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/nosql"

"gitea.com/go-chi/cache"
"gitea.com/go-chi/cache" //nolint:depguard
"github.com/redis/go-redis/v9"
)

Expand Down
40 changes: 2 additions & 38 deletions modules/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

func createTestCache() {
conn, _ = newCache(setting.Cache{
defaultCache, _ = NewStringCache(setting.Cache{
Adapter: "memory",
TTL: time.Minute,
})
Expand All @@ -25,7 +25,7 @@ func TestNewContext(t *testing.T) {
assert.NoError(t, Init())

setting.CacheService.Cache = setting.Cache{Adapter: "redis", Conn: "some random string"}
con, err := newCache(setting.Cache{
con, err := NewStringCache(setting.Cache{
Adapter: "rand",
Conn: "false conf",
Interval: 100,
Expand Down Expand Up @@ -76,42 +76,6 @@ func TestGetString(t *testing.T) {
Remove("key")
}

func TestGetInt(t *testing.T) {
createTestCache()

data, err := GetInt("key", func() (int, error) {
return 0, fmt.Errorf("some error")
})
assert.Error(t, err)
assert.Equal(t, 0, data)

data, err = GetInt("key", func() (int, error) {
return 0, nil
})
assert.NoError(t, err)
assert.Equal(t, 0, data)

data, err = GetInt("key", func() (int, error) {
return 100, nil
})
assert.NoError(t, err)
assert.Equal(t, 0, data)
Remove("key")

data, err = GetInt("key", func() (int, error) {
return 100, nil
})
assert.NoError(t, err)
assert.Equal(t, 100, data)

data, err = GetInt("key", func() (int, error) {
return 0, fmt.Errorf("some error")
})
assert.NoError(t, err)
assert.Equal(t, 100, data)
Remove("key")
}

func TestGetInt64(t *testing.T) {
createTestCache()

Expand Down
2 changes: 1 addition & 1 deletion modules/cache/cache_twoqueue.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

"code.gitea.io/gitea/modules/json"

mc "gitea.com/go-chi/cache"
mc "gitea.com/go-chi/cache" //nolint:depguard
lru "github.com/hashicorp/golang-lru/v2"
)

Expand Down
120 changes: 120 additions & 0 deletions modules/cache/string_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package cache

import (
"errors"
"strings"

"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"

chi_cache "gitea.com/go-chi/cache" //nolint:depguard
)

type GetJSONError struct {
err error
cachedError string // Golang error can't be stored in cache, only the string message could be stored
}

func (e *GetJSONError) ToError() error {
if e.err != nil {
return e.err
}
return errors.New("cached error: " + e.cachedError)
}

type StringCache interface {
Ping() error

Get(key string) (string, bool)
Put(key, value string, ttl int64) error
Delete(key string) error
IsExist(key string) bool

PutJSON(key string, v any, ttl int64) error
GetJSON(key string, ptr any) (exist bool, err *GetJSONError)

ChiCache() chi_cache.Cache
}

type stringCache struct {
chiCache chi_cache.Cache
}

func NewStringCache(cacheConfig setting.Cache) (StringCache, error) {
adapter := util.IfZero(cacheConfig.Adapter, "memory")
interval := util.IfZero(cacheConfig.Interval, 60)
cc, err := chi_cache.NewCacher(chi_cache.Options{
Adapter: adapter,
AdapterConfig: cacheConfig.Conn,
Interval: interval,
})
if err != nil {
return nil, err
}
return &stringCache{chiCache: cc}, nil
}

func (sc *stringCache) Ping() error {
return sc.chiCache.Ping()
}

func (sc *stringCache) Get(key string) (string, bool) {
v := sc.chiCache.Get(key)
if v == nil {
return "", false
}
s, ok := v.(string)
return s, ok
}

func (sc *stringCache) Put(key, value string, ttl int64) error {
return sc.chiCache.Put(key, value, ttl)
}

func (sc *stringCache) Delete(key string) error {
return sc.chiCache.Delete(key)
}

func (sc *stringCache) IsExist(key string) bool {
return sc.chiCache.IsExist(key)
}

const cachedErrorPrefix = "<CACHED-ERROR>:"

func (sc *stringCache) PutJSON(key string, v any, ttl int64) error {
var s string
switch v := v.(type) {
case error:
s = cachedErrorPrefix + v.Error()
default:
b, err := json.Marshal(v)
if err != nil {
return err
}
s = util.UnsafeBytesToString(b)
}
return sc.chiCache.Put(key, s, ttl)
}

func (sc *stringCache) GetJSON(key string, ptr any) (exist bool, getErr *GetJSONError) {
s, ok := sc.Get(key)
if !ok || s == "" {
return false, nil
}
s, isCachedError := strings.CutPrefix(s, cachedErrorPrefix)
if isCachedError {
return true, &GetJSONError{cachedError: s}
}
if err := json.Unmarshal(util.UnsafeStringToBytes(s), ptr); err != nil {
return false, &GetJSONError{err: err}
}
return true, nil
}

func (sc *stringCache) ChiCache() chi_cache.Cache {
return sc.chiCache
}
Loading

0 comments on commit 4e91b25

Please sign in to comment.