@@ -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