mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Refactor cache and disable go-chi cache (#30417)
use built-in cache package to wrap external go-chi cache package
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
							
								
								
									
										138
									
								
								modules/cache/cache.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										138
									
								
								modules/cache/cache.go
									
									
									
									
										vendored
									
									
								
							| @@ -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() | ||||
| 	s, err := GetString(key, func() (string, error) { | ||||
| 		v, err := getFunc() | ||||
| 		return strconv.FormatInt(v, 10), err | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
|  | ||||
| 	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 := 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
									
								
								modules/cache/cache_redis.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								modules/cache/cache_redis.go
									
									
									
									
										vendored
									
									
								
							| @@ -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" | ||||
| ) | ||||
|  | ||||
|   | ||||
							
								
								
									
										40
									
								
								modules/cache/cache_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								modules/cache/cache_test.go
									
									
									
									
										vendored
									
									
								
							| @@ -14,7 +14,7 @@ import ( | ||||
| ) | ||||
|  | ||||
| func createTestCache() { | ||||
| 	conn, _ = newCache(setting.Cache{ | ||||
| 	defaultCache, _ = NewStringCache(setting.Cache{ | ||||
| 		Adapter: "memory", | ||||
| 		TTL:     time.Minute, | ||||
| 	}) | ||||
| @@ -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, | ||||
| @@ -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() | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								modules/cache/cache_twoqueue.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								modules/cache/cache_twoqueue.go
									
									
									
									
										vendored
									
									
								
							| @@ -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" | ||||
| ) | ||||
|  | ||||
|   | ||||
							
								
								
									
										120
									
								
								modules/cache/string_cache.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								modules/cache/string_cache.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
| @@ -7,18 +7,11 @@ import ( | ||||
| 	"crypto/sha256" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/cache" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| ) | ||||
|  | ||||
| // Cache represents a caching interface | ||||
| type Cache interface { | ||||
| 	// Put puts value into cache with key and expire time. | ||||
| 	Put(key string, val any, timeout int64) error | ||||
| 	// Get gets cached value by given key. | ||||
| 	Get(key string) any | ||||
| } | ||||
|  | ||||
| func getCacheKey(repoPath, commitID, entryPath string) string { | ||||
| 	hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, commitID, entryPath))) | ||||
| 	return fmt.Sprintf("last_commit:%x", hashBytes) | ||||
| @@ -30,11 +23,11 @@ type LastCommitCache struct { | ||||
| 	ttl         func() int64 | ||||
| 	repo        *Repository | ||||
| 	commitCache map[string]*Commit | ||||
| 	cache       Cache | ||||
| 	cache       cache.StringCache | ||||
| } | ||||
|  | ||||
| // NewLastCommitCache creates a new last commit cache for repo | ||||
| func NewLastCommitCache(count int64, repoPath string, gitRepo *Repository, cache Cache) *LastCommitCache { | ||||
| func NewLastCommitCache(count int64, repoPath string, gitRepo *Repository, cache cache.StringCache) *LastCommitCache { | ||||
| 	if cache == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| @@ -65,7 +58,7 @@ func (c *LastCommitCache) Get(ref, entryPath string) (*Commit, error) { | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	commitID, ok := c.cache.Get(getCacheKey(c.repoPath, ref, entryPath)).(string) | ||||
| 	commitID, ok := c.cache.Get(getCacheKey(c.repoPath, ref, entryPath)) | ||||
| 	if !ok || commitID == "" { | ||||
| 		return nil, nil | ||||
| 	} | ||||
|   | ||||
| @@ -29,9 +29,7 @@ func NodeInfo(ctx *context.APIContext) { | ||||
|  | ||||
| 	nodeInfoUsage := structs.NodeInfoUsage{} | ||||
| 	if setting.Federation.ShareUserStatistics { | ||||
| 		var cached bool | ||||
| 		nodeInfoUsage, cached = ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage) | ||||
|  | ||||
| 		cached, _ := ctx.Cache.GetJSON(cacheKeyNodeInfoUsage, &nodeInfoUsage) | ||||
| 		if !cached { | ||||
| 			usersTotal := int(user_model.CountUsers(ctx, nil)) | ||||
| 			now := time.Now() | ||||
| @@ -53,7 +51,7 @@ func NodeInfo(ctx *context.APIContext) { | ||||
| 				LocalComments: int(allComments), | ||||
| 			} | ||||
|  | ||||
| 			if err := ctx.Cache.Put(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil { | ||||
| 			if err := ctx.Cache.PutJSON(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil { | ||||
| 				ctx.InternalServerError(err) | ||||
| 				return | ||||
| 			} | ||||
|   | ||||
| @@ -13,7 +13,7 @@ import ( | ||||
|  | ||||
| 	"code.gitea.io/gitea/models/unit" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	mc "code.gitea.io/gitea/modules/cache" | ||||
| 	"code.gitea.io/gitea/modules/cache" | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	"code.gitea.io/gitea/modules/gitrepo" | ||||
| 	"code.gitea.io/gitea/modules/httpcache" | ||||
| @@ -21,15 +21,13 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/web" | ||||
| 	web_types "code.gitea.io/gitea/modules/web/types" | ||||
|  | ||||
| 	"gitea.com/go-chi/cache" | ||||
| ) | ||||
|  | ||||
| // APIContext is a specific context for API service | ||||
| type APIContext struct { | ||||
| 	*Base | ||||
|  | ||||
| 	Cache cache.Cache | ||||
| 	Cache cache.StringCache | ||||
|  | ||||
| 	Doer        *user_model.User // current signed-in user | ||||
| 	IsSigned    bool | ||||
| @@ -217,7 +215,7 @@ func APIContexter() func(http.Handler) http.Handler { | ||||
| 			base, baseCleanUp := NewBaseContext(w, req) | ||||
| 			ctx := &APIContext{ | ||||
| 				Base:  base, | ||||
| 				Cache: mc.GetCache(), | ||||
| 				Cache: cache.GetCache(), | ||||
| 				Repo:  &Repository{PullRequest: &PullRequest{}}, | ||||
| 				Org:   &APIOrganization{}, | ||||
| 			} | ||||
|   | ||||
| @@ -30,7 +30,7 @@ func GetImageCaptcha() *captcha.Captcha { | ||||
| 		cpt = captcha.NewCaptcha(captcha.Options{ | ||||
| 			SubURL: setting.AppSubURL, | ||||
| 		}) | ||||
| 		cpt.Store = cache.GetCache() | ||||
| 		cpt.Store = cache.GetCache().ChiCache() | ||||
| 	}) | ||||
| 	return cpt | ||||
| } | ||||
|   | ||||
| @@ -17,7 +17,7 @@ import ( | ||||
|  | ||||
| 	"code.gitea.io/gitea/models/unit" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	mc "code.gitea.io/gitea/modules/cache" | ||||
| 	"code.gitea.io/gitea/modules/cache" | ||||
| 	"code.gitea.io/gitea/modules/gitrepo" | ||||
| 	"code.gitea.io/gitea/modules/httpcache" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| @@ -27,7 +27,6 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/web/middleware" | ||||
| 	web_types "code.gitea.io/gitea/modules/web/types" | ||||
|  | ||||
| 	"gitea.com/go-chi/cache" | ||||
| 	"gitea.com/go-chi/session" | ||||
| ) | ||||
|  | ||||
| @@ -46,7 +45,7 @@ type Context struct { | ||||
| 	Render   Render | ||||
| 	PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData` | ||||
|  | ||||
| 	Cache   cache.Cache | ||||
| 	Cache   cache.StringCache | ||||
| 	Csrf    CSRFProtector | ||||
| 	Flash   *middleware.Flash | ||||
| 	Session session.Store | ||||
| @@ -111,7 +110,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context { | ||||
| 		Render:  render, | ||||
| 		Session: session, | ||||
|  | ||||
| 		Cache: mc.GetCache(), | ||||
| 		Cache: cache.GetCache(), | ||||
| 		Link:  setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"), | ||||
| 		Repo:  &Repository{PullRequest: &PullRequest{}}, | ||||
| 		Org:   &Organization{}, | ||||
|   | ||||
| @@ -26,6 +26,7 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/queue" | ||||
| 	repo_module "code.gitea.io/gitea/modules/repository" | ||||
| 	"code.gitea.io/gitea/modules/timeutil" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| 	webhook_module "code.gitea.io/gitea/modules/webhook" | ||||
| 	notify_service "code.gitea.io/gitea/services/notify" | ||||
| 	files_service "code.gitea.io/gitea/services/repository/files" | ||||
| @@ -119,17 +120,15 @@ func getDivergenceCacheKey(repoID int64, branchName string) string { | ||||
|  | ||||
| // getDivergenceFromCache gets the divergence from cache | ||||
| func getDivergenceFromCache(repoID int64, branchName string) (*git.DivergeObject, bool) { | ||||
| 	data := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName)) | ||||
| 	data, ok := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName)) | ||||
| 	res := git.DivergeObject{ | ||||
| 		Ahead:  -1, | ||||
| 		Behind: -1, | ||||
| 	} | ||||
| 	s, ok := data.([]byte) | ||||
| 	if !ok || len(s) == 0 { | ||||
| 	if !ok || data == "" { | ||||
| 		return &res, false | ||||
| 	} | ||||
|  | ||||
| 	if err := json.Unmarshal(s, &res); err != nil { | ||||
| 	if err := json.Unmarshal(util.UnsafeStringToBytes(data), &res); err != nil { | ||||
| 		log.Error("json.UnMarshal failed: %v", err) | ||||
| 		return &res, false | ||||
| 	} | ||||
| @@ -141,7 +140,7 @@ func putDivergenceFromCache(repoID int64, branchName string, divergence *git.Div | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return cache.GetCache().Put(getDivergenceCacheKey(repoID, branchName), bs, 30*24*60*60) | ||||
| 	return cache.GetCache().Put(getDivergenceCacheKey(repoID, branchName), util.UnsafeBytesToString(bs), 30*24*60*60) | ||||
| } | ||||
|  | ||||
| func DelDivergenceFromCache(repoID int64, branchName string) error { | ||||
|   | ||||
| @@ -34,7 +34,7 @@ type commitStatusCacheValue struct { | ||||
|  | ||||
| func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheValue { | ||||
| 	c := cache.GetCache() | ||||
| 	statusStr, ok := c.Get(getCacheKey(repoID, branchName)).(string) | ||||
| 	statusStr, ok := c.Get(getCacheKey(repoID, branchName)) | ||||
| 	if ok && statusStr != "" { | ||||
| 		var cv commitStatusCacheValue | ||||
| 		err := json.Unmarshal([]byte(statusStr), &cv) | ||||
|   | ||||
| @@ -17,13 +17,12 @@ import ( | ||||
| 	"code.gitea.io/gitea/models/avatars" | ||||
| 	repo_model "code.gitea.io/gitea/models/repo" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"code.gitea.io/gitea/modules/cache" | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	"code.gitea.io/gitea/modules/gitrepo" | ||||
| 	"code.gitea.io/gitea/modules/graceful" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	api "code.gitea.io/gitea/modules/structs" | ||||
|  | ||||
| 	"gitea.com/go-chi/cache" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -79,13 +78,13 @@ func findLastSundayBeforeDate(dateStr string) (string, error) { | ||||
| } | ||||
|  | ||||
| // GetContributorStats returns contributors stats for git commits for given revision or default branch | ||||
| func GetContributorStats(ctx context.Context, cache cache.Cache, repo *repo_model.Repository, revision string) (map[string]*ContributorData, error) { | ||||
| func GetContributorStats(ctx context.Context, cache cache.StringCache, repo *repo_model.Repository, revision string) (map[string]*ContributorData, error) { | ||||
| 	// as GetContributorStats is resource intensive we cache the result | ||||
| 	cacheKey := fmt.Sprintf(contributorStatsCacheKey, repo.FullName(), revision) | ||||
| 	if !cache.IsExist(cacheKey) { | ||||
| 		genReady := make(chan struct{}) | ||||
|  | ||||
| 		// dont start multible async generations | ||||
| 		// dont start multiple async generations | ||||
| 		_, run := generateLock.Load(cacheKey) | ||||
| 		if run { | ||||
| 			return nil, ErrAwaitGeneration | ||||
| @@ -104,15 +103,11 @@ func GetContributorStats(ctx context.Context, cache cache.Cache, repo *repo_mode | ||||
| 		} | ||||
| 	} | ||||
| 	// TODO: renew timeout of cache cache.UpdateTimeout(cacheKey, contributorStatsCacheTimeout) | ||||
|  | ||||
| 	switch v := cache.Get(cacheKey).(type) { | ||||
| 	case error: | ||||
| 		return nil, v | ||||
| 	case map[string]*ContributorData: | ||||
| 		return v, nil | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("unexpected type in cache detected") | ||||
| 	var res map[string]*ContributorData | ||||
| 	if _, cacheErr := cache.GetJSON(cacheKey, &res); cacheErr != nil { | ||||
| 		return nil, fmt.Errorf("cached error: %w", cacheErr.ToError()) | ||||
| 	} | ||||
| 	return res, nil | ||||
| } | ||||
|  | ||||
| // getExtendedCommitStats return the list of *ExtendedCommitStats for the given revision | ||||
| @@ -205,13 +200,12 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int | ||||
| 	return extendedCommitStats, nil | ||||
| } | ||||
|  | ||||
| func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey string, repo *repo_model.Repository, revision string) { | ||||
| func generateContributorStats(genDone chan struct{}, cache cache.StringCache, cacheKey string, repo *repo_model.Repository, revision string) { | ||||
| 	ctx := graceful.GetManager().HammerContext() | ||||
|  | ||||
| 	gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo) | ||||
| 	if err != nil { | ||||
| 		err := fmt.Errorf("OpenRepository: %w", err) | ||||
| 		_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout) | ||||
| 		_ = cache.PutJSON(cacheKey, fmt.Errorf("OpenRepository: %w", err), contributorStatsCacheTimeout) | ||||
| 		return | ||||
| 	} | ||||
| 	defer closer.Close() | ||||
| @@ -221,13 +215,11 @@ func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey | ||||
| 	} | ||||
| 	extendedCommitStats, err := getExtendedCommitStats(gitRepo, revision) | ||||
| 	if err != nil { | ||||
| 		err := fmt.Errorf("ExtendedCommitStats: %w", err) | ||||
| 		_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout) | ||||
| 		_ = cache.PutJSON(cacheKey, fmt.Errorf("ExtendedCommitStats: %w", err), contributorStatsCacheTimeout) | ||||
| 		return | ||||
| 	} | ||||
| 	if len(extendedCommitStats) == 0 { | ||||
| 		err := fmt.Errorf("no commit stats returned for revision '%s'", revision) | ||||
| 		_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout) | ||||
| 		_ = cache.PutJSON(cacheKey, fmt.Errorf("no commit stats returned for revision '%s'", revision), contributorStatsCacheTimeout) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| @@ -309,7 +301,7 @@ func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey | ||||
| 		total.TotalCommits++ | ||||
| 	} | ||||
|  | ||||
| 	_ = cache.Put(cacheKey, contributorsCommitStats, contributorStatsCacheTimeout) | ||||
| 	_ = cache.PutJSON(cacheKey, contributorsCommitStats, contributorStatsCacheTimeout) | ||||
| 	generateLock.Delete(cacheKey) | ||||
| 	if genDone != nil { | ||||
| 		genDone <- struct{}{} | ||||
|   | ||||
| @@ -10,9 +10,9 @@ import ( | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	repo_model "code.gitea.io/gitea/models/repo" | ||||
| 	"code.gitea.io/gitea/models/unittest" | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	"code.gitea.io/gitea/modules/cache" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
|  | ||||
| 	"gitea.com/go-chi/cache" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| @@ -20,20 +20,18 @@ func TestRepository_ContributorsGraph(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
| 	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) | ||||
| 	assert.NoError(t, repo.LoadOwner(db.DefaultContext)) | ||||
| 	mockCache, err := cache.NewCacher(cache.Options{ | ||||
| 		Adapter:  "memory", | ||||
| 		Interval: 24 * 60, | ||||
| 	}) | ||||
| 	mockCache, err := cache.NewStringCache(setting.Cache{}) | ||||
| 	assert.NoError(t, err) | ||||
|  | ||||
| 	generateContributorStats(nil, mockCache, "key", repo, "404ref") | ||||
| 	err, isErr := mockCache.Get("key").(error) | ||||
| 	assert.True(t, isErr) | ||||
| 	assert.ErrorAs(t, err, &git.ErrNotExist{}) | ||||
| 	var data map[string]*ContributorData | ||||
| 	_, getErr := mockCache.GetJSON("key", &data) | ||||
| 	assert.NotNil(t, getErr) | ||||
| 	assert.ErrorContains(t, getErr.ToError(), "object does not exist") | ||||
|  | ||||
| 	generateContributorStats(nil, mockCache, "key2", repo, "master") | ||||
| 	data, isData := mockCache.Get("key2").(map[string]*ContributorData) | ||||
| 	assert.True(t, isData) | ||||
| 	exist, _ := mockCache.GetJSON("key2", &data) | ||||
| 	assert.True(t, exist) | ||||
| 	var keys []string | ||||
| 	for k := range data { | ||||
| 		keys = append(keys, k) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user