You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package cache
  4. import (
  5. "context"
  6. "sync"
  7. "time"
  8. "code.gitea.io/gitea/modules/log"
  9. )
  10. // cacheContext is a context that can be used to cache data in a request level context
  11. // This is useful for caching data that is expensive to calculate and is likely to be
  12. // used multiple times in a request.
  13. type cacheContext struct {
  14. data map[any]map[any]any
  15. lock sync.RWMutex
  16. created time.Time
  17. discard bool
  18. }
  19. func (cc *cacheContext) Get(tp, key any) any {
  20. cc.lock.RLock()
  21. defer cc.lock.RUnlock()
  22. return cc.data[tp][key]
  23. }
  24. func (cc *cacheContext) Put(tp, key, value any) {
  25. cc.lock.Lock()
  26. defer cc.lock.Unlock()
  27. if cc.discard {
  28. return
  29. }
  30. d := cc.data[tp]
  31. if d == nil {
  32. d = make(map[any]any)
  33. cc.data[tp] = d
  34. }
  35. d[key] = value
  36. }
  37. func (cc *cacheContext) Delete(tp, key any) {
  38. cc.lock.Lock()
  39. defer cc.lock.Unlock()
  40. delete(cc.data[tp], key)
  41. }
  42. func (cc *cacheContext) Discard() {
  43. cc.lock.Lock()
  44. defer cc.lock.Unlock()
  45. cc.data = nil
  46. cc.discard = true
  47. }
  48. func (cc *cacheContext) isDiscard() bool {
  49. cc.lock.RLock()
  50. defer cc.lock.RUnlock()
  51. return cc.discard
  52. }
  53. // cacheContextLifetime is the max lifetime of cacheContext.
  54. // Since cacheContext is used to cache data in a request level context, 10s is enough.
  55. // If a cacheContext is used more than 10s, it's probably misuse.
  56. const cacheContextLifetime = 10 * time.Second
  57. var timeNow = time.Now
  58. func (cc *cacheContext) Expired() bool {
  59. return timeNow().Sub(cc.created) > cacheContextLifetime
  60. }
  61. var cacheContextKey = struct{}{}
  62. /*
  63. Since there are both WithCacheContext and WithNoCacheContext,
  64. it may be confusing when there is nesting.
  65. Some cases to explain the design:
  66. When:
  67. - A, B or C means a cache context.
  68. - A', B' or C' means a discard cache context.
  69. - ctx means context.Backgrand().
  70. - A(ctx) means a cache context with ctx as the parent context.
  71. - B(A(ctx)) means a cache context with A(ctx) as the parent context.
  72. - With is alias of WithCacheContext.
  73. - WithNo is alias of WithNoCacheContext.
  74. So:
  75. - With(ctx) -> A(ctx)
  76. - With(With(ctx)) -> A(ctx), not B(A(ctx)), always reuse parent cache context if possible.
  77. - With(With(With(ctx))) -> A(ctx), not C(B(A(ctx))), ditto.
  78. - WithNo(ctx) -> ctx, not A'(ctx), don't create new cache context if we don't have to.
  79. - WithNo(With(ctx)) -> A'(ctx)
  80. - WithNo(WithNo(With(ctx))) -> A'(ctx), not B'(A'(ctx)), don't create new cache context if we don't have to.
  81. - With(WithNo(With(ctx))) -> B(A'(ctx)), not A(ctx), never reuse a discard cache context.
  82. - WithNo(With(WithNo(With(ctx)))) -> B'(A'(ctx))
  83. - With(WithNo(With(WithNo(With(ctx))))) -> C(B'(A'(ctx))), so there's always only one not-discard cache context.
  84. */
  85. func WithCacheContext(ctx context.Context) context.Context {
  86. if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok {
  87. if !c.isDiscard() {
  88. // reuse parent context
  89. return ctx
  90. }
  91. }
  92. return context.WithValue(ctx, cacheContextKey, &cacheContext{
  93. data: make(map[any]map[any]any),
  94. created: timeNow(),
  95. })
  96. }
  97. func WithNoCacheContext(ctx context.Context) context.Context {
  98. if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok {
  99. // The caller want to run long-life tasks, but the parent context is a cache context.
  100. // So we should disable and clean the cache data, or it will be kept in memory for a long time.
  101. c.Discard()
  102. return ctx
  103. }
  104. return ctx
  105. }
  106. func GetContextData(ctx context.Context, tp, key any) any {
  107. if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok {
  108. if c.Expired() {
  109. // The warning means that the cache context is misused for long-life task,
  110. // it can be resolved with WithNoCacheContext(ctx).
  111. log.Warn("cache context is expired, may be misused for long-life tasks: %v", c)
  112. return nil
  113. }
  114. return c.Get(tp, key)
  115. }
  116. return nil
  117. }
  118. func SetContextData(ctx context.Context, tp, key, value any) {
  119. if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok {
  120. if c.Expired() {
  121. // The warning means that the cache context is misused for long-life task,
  122. // it can be resolved with WithNoCacheContext(ctx).
  123. log.Warn("cache context is expired, may be misused for long-life tasks: %v", c)
  124. return
  125. }
  126. c.Put(tp, key, value)
  127. return
  128. }
  129. }
  130. func RemoveContextData(ctx context.Context, tp, key 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, may be misused for long-life tasks: %v", c)
  136. return
  137. }
  138. c.Delete(tp, key)
  139. }
  140. }
  141. // GetWithContextCache returns the cache value of the given key in the given context.
  142. func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) {
  143. v := GetContextData(ctx, cacheGroupKey, cacheTargetID)
  144. if vv, ok := v.(T); ok {
  145. return vv, nil
  146. }
  147. t, err := f()
  148. if err != nil {
  149. return t, err
  150. }
  151. SetContextData(ctx, cacheGroupKey, cacheTargetID, t)
  152. return t, nil
  153. }