Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor cache and disable go-chi cache #30417

Merged
merged 5 commits into from Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .golangci.yml
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
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)
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
}
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
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
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
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
@@ -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
}