@@ -5,176 +5,39 @@ package cache
55
66import (
77 "context"
8- "sync"
98 "time"
10-
11- "code.gitea.io/gitea/modules/log"
129)
1310
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 {}
5112
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 {}
5814
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
10419
10520func 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 {
12422 return ctx
12523 }
126-
127- return ctx
24+ return context .WithValue (ctx , cacheContextKey , NewEphemeralCache (contextCacheLifetime ))
12825}
12926
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
16630}
16731
16832// 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.
16938func 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 )
17741 }
178- SetContextData (ctx , groupKey , targetKey , t )
179- return t , nil
42+ return f (ctx , targetKey )
18043}
0 commit comments