@@ -5,176 +5,39 @@ package cache
5
5
6
6
import (
7
7
"context"
8
- "sync"
9
8
"time"
10
-
11
- "code.gitea.io/gitea/modules/log"
12
9
)
13
10
14
- // cacheContext is a context that can be used to cache data in a request level context
15
- // This is useful for caching data that is expensive to calculate and is likely to be
16
- // used multiple times in a request.
17
- type cacheContext struct {
18
- data map [any ]map [any ]any
19
- lock sync.RWMutex
20
- created time.Time
21
- discard bool
22
- }
23
-
24
- func (cc * cacheContext ) Get (tp , key any ) any {
25
- cc .lock .RLock ()
26
- defer cc .lock .RUnlock ()
27
- return cc.data [tp ][key ]
28
- }
29
-
30
- func (cc * cacheContext ) Put (tp , key , value any ) {
31
- cc .lock .Lock ()
32
- defer cc .lock .Unlock ()
33
-
34
- if cc .discard {
35
- return
36
- }
37
-
38
- d := cc .data [tp ]
39
- if d == nil {
40
- d = make (map [any ]any )
41
- cc .data [tp ] = d
42
- }
43
- d [key ] = value
44
- }
45
-
46
- func (cc * cacheContext ) Delete (tp , key any ) {
47
- cc .lock .Lock ()
48
- defer cc .lock .Unlock ()
49
- delete (cc .data [tp ], key )
50
- }
11
+ type cacheContextKeyType struct {}
51
12
52
- func (cc * cacheContext ) Discard () {
53
- cc .lock .Lock ()
54
- defer cc .lock .Unlock ()
55
- cc .data = nil
56
- cc .discard = true
57
- }
13
+ var cacheContextKey = cacheContextKeyType {}
58
14
59
- func (cc * cacheContext ) isDiscard () bool {
60
- cc .lock .RLock ()
61
- defer cc .lock .RUnlock ()
62
- return cc .discard
63
- }
64
-
65
- // cacheContextLifetime is the max lifetime of cacheContext.
66
- // Since cacheContext is used to cache data in a request level context, 5 minutes is enough.
67
- // If a cacheContext is used more than 5 minutes, it's probably misuse.
68
- const cacheContextLifetime = 5 * time .Minute
69
-
70
- var timeNow = time .Now
71
-
72
- func (cc * cacheContext ) Expired () bool {
73
- return timeNow ().Sub (cc .created ) > cacheContextLifetime
74
- }
75
-
76
- var cacheContextKey = struct {}{}
77
-
78
- /*
79
- Since there are both WithCacheContext and WithNoCacheContext,
80
- it may be confusing when there is nesting.
81
-
82
- Some cases to explain the design:
83
-
84
- When:
85
- - A, B or C means a cache context.
86
- - A', B' or C' means a discard cache context.
87
- - ctx means context.Backgrand().
88
- - A(ctx) means a cache context with ctx as the parent context.
89
- - B(A(ctx)) means a cache context with A(ctx) as the parent context.
90
- - With is alias of WithCacheContext.
91
- - WithNo is alias of WithNoCacheContext.
92
-
93
- So:
94
- - With(ctx) -> A(ctx)
95
- - With(With(ctx)) -> A(ctx), not B(A(ctx)), always reuse parent cache context if possible.
96
- - With(With(With(ctx))) -> A(ctx), not C(B(A(ctx))), ditto.
97
- - WithNo(ctx) -> ctx, not A'(ctx), don't create new cache context if we don't have to.
98
- - WithNo(With(ctx)) -> A'(ctx)
99
- - WithNo(WithNo(With(ctx))) -> A'(ctx), not B'(A'(ctx)), don't create new cache context if we don't have to.
100
- - With(WithNo(With(ctx))) -> B(A'(ctx)), not A(ctx), never reuse a discard cache context.
101
- - WithNo(With(WithNo(With(ctx)))) -> B'(A'(ctx))
102
- - With(WithNo(With(WithNo(With(ctx))))) -> C(B'(A'(ctx))), so there's always only one not-discard cache context.
103
- */
15
+ // contextCacheLifetime is the max lifetime of context cache.
16
+ // Since context cache is used to cache data in a request level context, 5 minutes is enough.
17
+ // If a context cache is used more than 5 minutes, it's probably abused.
18
+ const contextCacheLifetime = 5 * time .Minute
104
19
105
20
func WithCacheContext (ctx context.Context ) context.Context {
106
- if c , ok := ctx .Value (cacheContextKey ).(* cacheContext ); ok {
107
- if ! c .isDiscard () {
108
- // reuse parent context
109
- return ctx
110
- }
111
- }
112
- // FIXME: review the use of this nolint directive
113
- return context .WithValue (ctx , cacheContextKey , & cacheContext { //nolint:staticcheck
114
- data : make (map [any ]map [any ]any ),
115
- created : timeNow (),
116
- })
117
- }
118
-
119
- func WithNoCacheContext (ctx context.Context ) context.Context {
120
- if c , ok := ctx .Value (cacheContextKey ).(* cacheContext ); ok {
121
- // The caller want to run long-life tasks, but the parent context is a cache context.
122
- // So we should disable and clean the cache data, or it will be kept in memory for a long time.
123
- c .Discard ()
21
+ if c := GetContextCache (ctx ); c != nil {
124
22
return ctx
125
23
}
126
-
127
- return ctx
24
+ return context .WithValue (ctx , cacheContextKey , NewEphemeralCache (contextCacheLifetime ))
128
25
}
129
26
130
- func GetContextData (ctx context.Context , tp , key any ) any {
131
- if c , ok := ctx .Value (cacheContextKey ).(* cacheContext ); ok {
132
- if c .Expired () {
133
- // The warning means that the cache context is misused for long-life task,
134
- // it can be resolved with WithNoCacheContext(ctx).
135
- log .Warn ("cache context is expired, is highly likely to be misused for long-life tasks: %v" , c )
136
- return nil
137
- }
138
- return c .Get (tp , key )
139
- }
140
- return nil
141
- }
142
-
143
- func SetContextData (ctx context.Context , tp , key , value any ) {
144
- if c , ok := ctx .Value (cacheContextKey ).(* cacheContext ); ok {
145
- if c .Expired () {
146
- // The warning means that the cache context is misused for long-life task,
147
- // it can be resolved with WithNoCacheContext(ctx).
148
- log .Warn ("cache context is expired, is highly likely to be misused for long-life tasks: %v" , c )
149
- return
150
- }
151
- c .Put (tp , key , value )
152
- return
153
- }
154
- }
155
-
156
- func RemoveContextData (ctx context.Context , tp , key any ) {
157
- if c , ok := ctx .Value (cacheContextKey ).(* cacheContext ); ok {
158
- if c .Expired () {
159
- // The warning means that the cache context is misused for long-life task,
160
- // it can be resolved with WithNoCacheContext(ctx).
161
- log .Warn ("cache context is expired, is highly likely to be misused for long-life tasks: %v" , c )
162
- return
163
- }
164
- c .Delete (tp , key )
165
- }
27
+ func GetContextCache (ctx context.Context ) * EphemeralCache {
28
+ c , _ := ctx .Value (cacheContextKey ).(* EphemeralCache )
29
+ return c
166
30
}
167
31
168
32
// GetWithContextCache returns the cache value of the given key in the given context.
33
+ // FIXME: in some cases, the "context cache" should not be used, because it has uncontrollable behaviors
34
+ // For example, these calls:
35
+ // * GetWithContextCache(TargetID) -> OtherCodeCreateModel(TargetID) -> GetWithContextCache(TargetID)
36
+ // Will cause the second call is not able to get the correct created target.
37
+ // UNLESS it is certain that the target won't be changed during the request, DO NOT use it.
169
38
func GetWithContextCache [T , K any ](ctx context.Context , groupKey string , targetKey K , f func (context.Context , K ) (T , error )) (T , error ) {
170
- v := GetContextData (ctx , groupKey , targetKey )
171
- if vv , ok := v .(T ); ok {
172
- return vv , nil
173
- }
174
- t , err := f (ctx , targetKey )
175
- if err != nil {
176
- return t , err
39
+ if c := GetContextCache (ctx ); c != nil {
40
+ return GetWithEphemeralCache (ctx , c , groupKey , targetKey , f )
177
41
}
178
- SetContextData (ctx , groupKey , targetKey , t )
179
- return t , nil
42
+ return f (ctx , targetKey )
180
43
}
0 commit comments