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:
parent
8fd8978b49
commit
c248f010ad
@ -86,6 +86,8 @@ linters-settings:
|
|||||||
desc: do not use the internal package, use AddXxx function instead
|
desc: do not use the internal package, use AddXxx function instead
|
||||||
- pkg: gopkg.in/ini.v1
|
- pkg: gopkg.in/ini.v1
|
||||||
desc: do not use the ini package, use gitea's config system instead
|
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:
|
issues:
|
||||||
max-issues-per-linter: 0
|
max-issues-per-linter: 0
|
||||||
|
138
modules/cache/cache.go
vendored
138
modules/cache/cache.go
vendored
@ -4,149 +4,75 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"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
|
var defaultCache StringCache
|
||||||
|
|
||||||
func newCache(cacheConfig setting.Cache) (mc.Cache, error) {
|
|
||||||
return mc.NewCacher(mc.Options{
|
|
||||||
Adapter: cacheConfig.Adapter,
|
|
||||||
AdapterConfig: cacheConfig.Conn,
|
|
||||||
Interval: cacheConfig.Interval,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init start cache service
|
// Init start cache service
|
||||||
func Init() error {
|
func Init() error {
|
||||||
var err error
|
if defaultCache == nil {
|
||||||
|
c, err := NewStringCache(setting.CacheService.Cache)
|
||||||
if conn == nil {
|
if err != nil {
|
||||||
if conn, err = newCache(setting.CacheService.Cache); err != nil {
|
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
defaultCache = c
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCache returns the currently configured cache
|
// GetCache returns the currently configured cache
|
||||||
func GetCache() mc.Cache {
|
func GetCache() StringCache {
|
||||||
return conn
|
return defaultCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetString returns the key value from cache with callback when no key exists in cache
|
// 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) {
|
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()
|
return getFunc()
|
||||||
}
|
}
|
||||||
|
cached, exist := defaultCache.Get(key)
|
||||||
cached := conn.Get(key)
|
if !exist {
|
||||||
|
|
||||||
if cached == nil {
|
|
||||||
value, err := getFunc()
|
value, err := getFunc()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
return value, defaultCache.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 cached, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt64 returns key value from cache with callback when no key exists in cache
|
// GetInt64 returns key value from cache with callback when no key exists in cache
|
||||||
func GetInt64(key string, getFunc func() (int64, error)) (int64, error) {
|
func GetInt64(key string, getFunc func() (int64, error)) (int64, error) {
|
||||||
if conn == nil || setting.CacheService.TTL == 0 {
|
s, err := GetString(key, func() (string, error) {
|
||||||
return getFunc()
|
v, err := getFunc()
|
||||||
|
return strconv.FormatInt(v, 10), err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
if s == "" {
|
||||||
cached := conn.Get(key)
|
return 0, nil
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
return strconv.ParseInt(s, 10, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove key from cache
|
// Remove key from cache
|
||||||
func Remove(key string) {
|
func Remove(key string) {
|
||||||
if conn == nil {
|
if defaultCache == nil {
|
||||||
return
|
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/graceful"
|
||||||
"code.gitea.io/gitea/modules/nosql"
|
"code.gitea.io/gitea/modules/nosql"
|
||||||
|
|
||||||
"gitea.com/go-chi/cache"
|
"gitea.com/go-chi/cache" //nolint:depguard
|
||||||
"github.com/redis/go-redis/v9"
|
"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() {
|
func createTestCache() {
|
||||||
conn, _ = newCache(setting.Cache{
|
defaultCache, _ = NewStringCache(setting.Cache{
|
||||||
Adapter: "memory",
|
Adapter: "memory",
|
||||||
TTL: time.Minute,
|
TTL: time.Minute,
|
||||||
})
|
})
|
||||||
@ -25,7 +25,7 @@ func TestNewContext(t *testing.T) {
|
|||||||
assert.NoError(t, Init())
|
assert.NoError(t, Init())
|
||||||
|
|
||||||
setting.CacheService.Cache = setting.Cache{Adapter: "redis", Conn: "some random string"}
|
setting.CacheService.Cache = setting.Cache{Adapter: "redis", Conn: "some random string"}
|
||||||
con, err := newCache(setting.Cache{
|
con, err := NewStringCache(setting.Cache{
|
||||||
Adapter: "rand",
|
Adapter: "rand",
|
||||||
Conn: "false conf",
|
Conn: "false conf",
|
||||||
Interval: 100,
|
Interval: 100,
|
||||||
@ -76,42 +76,6 @@ func TestGetString(t *testing.T) {
|
|||||||
Remove("key")
|
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) {
|
func TestGetInt64(t *testing.T) {
|
||||||
createTestCache()
|
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"
|
"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"
|
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"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"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 {
|
func getCacheKey(repoPath, commitID, entryPath string) string {
|
||||||
hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, commitID, entryPath)))
|
hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, commitID, entryPath)))
|
||||||
return fmt.Sprintf("last_commit:%x", hashBytes)
|
return fmt.Sprintf("last_commit:%x", hashBytes)
|
||||||
@ -30,11 +23,11 @@ type LastCommitCache struct {
|
|||||||
ttl func() int64
|
ttl func() int64
|
||||||
repo *Repository
|
repo *Repository
|
||||||
commitCache map[string]*Commit
|
commitCache map[string]*Commit
|
||||||
cache Cache
|
cache cache.StringCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLastCommitCache creates a new last commit cache for repo
|
// 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 {
|
if cache == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -65,7 +58,7 @@ func (c *LastCommitCache) Get(ref, entryPath string) (*Commit, error) {
|
|||||||
return nil, nil
|
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 == "" {
|
if !ok || commitID == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,7 @@ func NodeInfo(ctx *context.APIContext) {
|
|||||||
|
|
||||||
nodeInfoUsage := structs.NodeInfoUsage{}
|
nodeInfoUsage := structs.NodeInfoUsage{}
|
||||||
if setting.Federation.ShareUserStatistics {
|
if setting.Federation.ShareUserStatistics {
|
||||||
var cached bool
|
cached, _ := ctx.Cache.GetJSON(cacheKeyNodeInfoUsage, &nodeInfoUsage)
|
||||||
nodeInfoUsage, cached = ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage)
|
|
||||||
|
|
||||||
if !cached {
|
if !cached {
|
||||||
usersTotal := int(user_model.CountUsers(ctx, nil))
|
usersTotal := int(user_model.CountUsers(ctx, nil))
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@ -53,7 +51,7 @@ func NodeInfo(ctx *context.APIContext) {
|
|||||||
LocalComments: int(allComments),
|
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)
|
ctx.InternalServerError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/httpcache"
|
"code.gitea.io/gitea/modules/httpcache"
|
||||||
@ -21,15 +21,13 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
web_types "code.gitea.io/gitea/modules/web/types"
|
web_types "code.gitea.io/gitea/modules/web/types"
|
||||||
|
|
||||||
"gitea.com/go-chi/cache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIContext is a specific context for API service
|
// APIContext is a specific context for API service
|
||||||
type APIContext struct {
|
type APIContext struct {
|
||||||
*Base
|
*Base
|
||||||
|
|
||||||
Cache cache.Cache
|
Cache cache.StringCache
|
||||||
|
|
||||||
Doer *user_model.User // current signed-in user
|
Doer *user_model.User // current signed-in user
|
||||||
IsSigned bool
|
IsSigned bool
|
||||||
@ -217,7 +215,7 @@ func APIContexter() func(http.Handler) http.Handler {
|
|||||||
base, baseCleanUp := NewBaseContext(w, req)
|
base, baseCleanUp := NewBaseContext(w, req)
|
||||||
ctx := &APIContext{
|
ctx := &APIContext{
|
||||||
Base: base,
|
Base: base,
|
||||||
Cache: mc.GetCache(),
|
Cache: cache.GetCache(),
|
||||||
Repo: &Repository{PullRequest: &PullRequest{}},
|
Repo: &Repository{PullRequest: &PullRequest{}},
|
||||||
Org: &APIOrganization{},
|
Org: &APIOrganization{},
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ func GetImageCaptcha() *captcha.Captcha {
|
|||||||
cpt = captcha.NewCaptcha(captcha.Options{
|
cpt = captcha.NewCaptcha(captcha.Options{
|
||||||
SubURL: setting.AppSubURL,
|
SubURL: setting.AppSubURL,
|
||||||
})
|
})
|
||||||
cpt.Store = cache.GetCache()
|
cpt.Store = cache.GetCache().ChiCache()
|
||||||
})
|
})
|
||||||
return cpt
|
return cpt
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/httpcache"
|
"code.gitea.io/gitea/modules/httpcache"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -27,7 +27,6 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
web_types "code.gitea.io/gitea/modules/web/types"
|
web_types "code.gitea.io/gitea/modules/web/types"
|
||||||
|
|
||||||
"gitea.com/go-chi/cache"
|
|
||||||
"gitea.com/go-chi/session"
|
"gitea.com/go-chi/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ type Context struct {
|
|||||||
Render Render
|
Render Render
|
||||||
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
|
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
|
Csrf CSRFProtector
|
||||||
Flash *middleware.Flash
|
Flash *middleware.Flash
|
||||||
Session session.Store
|
Session session.Store
|
||||||
@ -111,7 +110,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
|
|||||||
Render: render,
|
Render: render,
|
||||||
Session: session,
|
Session: session,
|
||||||
|
|
||||||
Cache: mc.GetCache(),
|
Cache: cache.GetCache(),
|
||||||
Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"),
|
Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"),
|
||||||
Repo: &Repository{PullRequest: &PullRequest{}},
|
Repo: &Repository{PullRequest: &PullRequest{}},
|
||||||
Org: &Organization{},
|
Org: &Organization{},
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/queue"
|
"code.gitea.io/gitea/modules/queue"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
notify_service "code.gitea.io/gitea/services/notify"
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
files_service "code.gitea.io/gitea/services/repository/files"
|
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
|
// getDivergenceFromCache gets the divergence from cache
|
||||||
func getDivergenceFromCache(repoID int64, branchName string) (*git.DivergeObject, bool) {
|
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{
|
res := git.DivergeObject{
|
||||||
Ahead: -1,
|
Ahead: -1,
|
||||||
Behind: -1,
|
Behind: -1,
|
||||||
}
|
}
|
||||||
s, ok := data.([]byte)
|
if !ok || data == "" {
|
||||||
if !ok || len(s) == 0 {
|
|
||||||
return &res, false
|
return &res, false
|
||||||
}
|
}
|
||||||
|
if err := json.Unmarshal(util.UnsafeStringToBytes(data), &res); err != nil {
|
||||||
if err := json.Unmarshal(s, &res); err != nil {
|
|
||||||
log.Error("json.UnMarshal failed: %v", err)
|
log.Error("json.UnMarshal failed: %v", err)
|
||||||
return &res, false
|
return &res, false
|
||||||
}
|
}
|
||||||
@ -141,7 +140,7 @@ func putDivergenceFromCache(repoID int64, branchName string, divergence *git.Div
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
func DelDivergenceFromCache(repoID int64, branchName string) error {
|
||||||
|
@ -34,7 +34,7 @@ type commitStatusCacheValue struct {
|
|||||||
|
|
||||||
func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheValue {
|
func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheValue {
|
||||||
c := cache.GetCache()
|
c := cache.GetCache()
|
||||||
statusStr, ok := c.Get(getCacheKey(repoID, branchName)).(string)
|
statusStr, ok := c.Get(getCacheKey(repoID, branchName))
|
||||||
if ok && statusStr != "" {
|
if ok && statusStr != "" {
|
||||||
var cv commitStatusCacheValue
|
var cv commitStatusCacheValue
|
||||||
err := json.Unmarshal([]byte(statusStr), &cv)
|
err := json.Unmarshal([]byte(statusStr), &cv)
|
||||||
|
@ -17,13 +17,12 @@ import (
|
|||||||
"code.gitea.io/gitea/models/avatars"
|
"code.gitea.io/gitea/models/avatars"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
"gitea.com/go-chi/cache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -79,13 +78,13 @@ func findLastSundayBeforeDate(dateStr string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetContributorStats returns contributors stats for git commits for given revision or default branch
|
// 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
|
// as GetContributorStats is resource intensive we cache the result
|
||||||
cacheKey := fmt.Sprintf(contributorStatsCacheKey, repo.FullName(), revision)
|
cacheKey := fmt.Sprintf(contributorStatsCacheKey, repo.FullName(), revision)
|
||||||
if !cache.IsExist(cacheKey) {
|
if !cache.IsExist(cacheKey) {
|
||||||
genReady := make(chan struct{})
|
genReady := make(chan struct{})
|
||||||
|
|
||||||
// dont start multible async generations
|
// dont start multiple async generations
|
||||||
_, run := generateLock.Load(cacheKey)
|
_, run := generateLock.Load(cacheKey)
|
||||||
if run {
|
if run {
|
||||||
return nil, ErrAwaitGeneration
|
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)
|
// TODO: renew timeout of cache cache.UpdateTimeout(cacheKey, contributorStatsCacheTimeout)
|
||||||
|
var res map[string]*ContributorData
|
||||||
switch v := cache.Get(cacheKey).(type) {
|
if _, cacheErr := cache.GetJSON(cacheKey, &res); cacheErr != nil {
|
||||||
case error:
|
return nil, fmt.Errorf("cached error: %w", cacheErr.ToError())
|
||||||
return nil, v
|
|
||||||
case map[string]*ContributorData:
|
|
||||||
return v, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unexpected type in cache detected")
|
|
||||||
}
|
}
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getExtendedCommitStats return the list of *ExtendedCommitStats for the given revision
|
// 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
|
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()
|
ctx := graceful.GetManager().HammerContext()
|
||||||
|
|
||||||
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
|
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("OpenRepository: %w", err)
|
_ = cache.PutJSON(cacheKey, fmt.Errorf("OpenRepository: %w", err), contributorStatsCacheTimeout)
|
||||||
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer closer.Close()
|
defer closer.Close()
|
||||||
@ -221,13 +215,11 @@ func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey
|
|||||||
}
|
}
|
||||||
extendedCommitStats, err := getExtendedCommitStats(gitRepo, revision)
|
extendedCommitStats, err := getExtendedCommitStats(gitRepo, revision)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("ExtendedCommitStats: %w", err)
|
_ = cache.PutJSON(cacheKey, fmt.Errorf("ExtendedCommitStats: %w", err), contributorStatsCacheTimeout)
|
||||||
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(extendedCommitStats) == 0 {
|
if len(extendedCommitStats) == 0 {
|
||||||
err := fmt.Errorf("no commit stats returned for revision '%s'", revision)
|
_ = cache.PutJSON(cacheKey, fmt.Errorf("no commit stats returned for revision '%s'", revision), contributorStatsCacheTimeout)
|
||||||
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,7 +301,7 @@ func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey
|
|||||||
total.TotalCommits++
|
total.TotalCommits++
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = cache.Put(cacheKey, contributorsCommitStats, contributorStatsCacheTimeout)
|
_ = cache.PutJSON(cacheKey, contributorsCommitStats, contributorStatsCacheTimeout)
|
||||||
generateLock.Delete(cacheKey)
|
generateLock.Delete(cacheKey)
|
||||||
if genDone != nil {
|
if genDone != nil {
|
||||||
genDone <- struct{}{}
|
genDone <- struct{}{}
|
||||||
|
@ -10,9 +10,9 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"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"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,20 +20,18 @@ func TestRepository_ContributorsGraph(t *testing.T) {
|
|||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
assert.NoError(t, repo.LoadOwner(db.DefaultContext))
|
assert.NoError(t, repo.LoadOwner(db.DefaultContext))
|
||||||
mockCache, err := cache.NewCacher(cache.Options{
|
mockCache, err := cache.NewStringCache(setting.Cache{})
|
||||||
Adapter: "memory",
|
|
||||||
Interval: 24 * 60,
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
generateContributorStats(nil, mockCache, "key", repo, "404ref")
|
generateContributorStats(nil, mockCache, "key", repo, "404ref")
|
||||||
err, isErr := mockCache.Get("key").(error)
|
var data map[string]*ContributorData
|
||||||
assert.True(t, isErr)
|
_, getErr := mockCache.GetJSON("key", &data)
|
||||||
assert.ErrorAs(t, err, &git.ErrNotExist{})
|
assert.NotNil(t, getErr)
|
||||||
|
assert.ErrorContains(t, getErr.ToError(), "object does not exist")
|
||||||
|
|
||||||
generateContributorStats(nil, mockCache, "key2", repo, "master")
|
generateContributorStats(nil, mockCache, "key2", repo, "master")
|
||||||
data, isData := mockCache.Get("key2").(map[string]*ContributorData)
|
exist, _ := mockCache.GetJSON("key2", &data)
|
||||||
assert.True(t, isData)
|
assert.True(t, exist)
|
||||||
var keys []string
|
var keys []string
|
||||||
for k := range data {
|
for k := range data {
|
||||||
keys = append(keys, k)
|
keys = append(keys, k)
|
||||||
|
Loading…
Reference in New Issue
Block a user