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

limiter.go 7.0 kB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. package limiter
  2. import (
  3. "code.gitea.io/gitea/models"
  4. "code.gitea.io/gitea/modules/log"
  5. "code.gitea.io/gitea/modules/redis/redis_client"
  6. "code.gitea.io/gitea/modules/redis/redis_key"
  7. "code.gitea.io/gitea/services/task/period"
  8. "encoding/json"
  9. "errors"
  10. "fmt"
  11. "time"
  12. )
  13. type limiterRunner struct {
  14. limiters []models.LimitConfig
  15. index int
  16. userId int64
  17. amount int64
  18. limitCode string
  19. limitType models.LimitType
  20. rejectPolicy models.LimiterRejectPolicy
  21. resultMap map[int]limitResult
  22. minRealAmount int64
  23. }
  24. type limitResult struct {
  25. isLoss bool
  26. planAmount int64
  27. realAmount int64
  28. }
  29. func newLimitResult(isLoss bool, planAmount int64, realAmount int64) limitResult {
  30. return limitResult{
  31. isLoss: isLoss,
  32. planAmount: planAmount,
  33. realAmount: realAmount,
  34. }
  35. }
  36. func newLimiterRunner(limitCode string, limitType models.LimitType, userId, amount int64, policy models.LimiterRejectPolicy) *limiterRunner {
  37. return &limiterRunner{
  38. userId: userId,
  39. amount: amount,
  40. limitCode: limitCode,
  41. limitType: limitType,
  42. index: 0,
  43. rejectPolicy: policy,
  44. resultMap: make(map[int]limitResult, 0),
  45. }
  46. }
  47. //Run run all limiters
  48. //return real used amount(when choose the FillUp reject policy, amount may only be partially used)
  49. func (l *limiterRunner) Run() error {
  50. if err := l.LoadLimiters(); err != nil {
  51. return err
  52. }
  53. l.minRealAmount = l.amount
  54. for l.index < len(l.limiters) {
  55. err := l.limit(l.limiters[l.index])
  56. if err != nil {
  57. log.Info("limiter check failed,%v", err)
  58. l.Rollback()
  59. return err
  60. }
  61. result := l.resultMap[l.index]
  62. if result.isLoss {
  63. //find the minimum real amount
  64. if l.minRealAmount > result.realAmount {
  65. l.minRealAmount = result.realAmount
  66. }
  67. }
  68. l.index += 1
  69. }
  70. //post process
  71. l.PostProcess()
  72. return nil
  73. }
  74. //Rollback rollback the usedNum from limiters[0] to limiters[index]
  75. func (l *limiterRunner) Rollback() error {
  76. for i := l.index - 1; i >= 0; i-- {
  77. l.rollback(l.limiters[i], l.resultMap[i])
  78. }
  79. return nil
  80. }
  81. func (l *limiterRunner) rollback(r models.LimitConfig, result limitResult) error {
  82. p, err := period.GetPeriod(r.RefreshRate)
  83. if err != nil {
  84. return err
  85. }
  86. redisKey := redis_key.LimitCount(l.userId, r.LimitCode, r.LimitType, r.Scope, p)
  87. redis_client.IncrBy(redisKey, -1*result.realAmount)
  88. return nil
  89. }
  90. //PostProcess process loss,if realAmount < planAmount
  91. func (l *limiterRunner) PostProcess() error {
  92. for i := l.index - 1; i >= 0; i-- {
  93. l.postProcess(l.limiters[i], l.resultMap[i])
  94. }
  95. return nil
  96. }
  97. func (l *limiterRunner) postProcess(r models.LimitConfig, result limitResult) error {
  98. if result.realAmount == l.minRealAmount {
  99. return nil
  100. }
  101. p, err := period.GetPeriod(r.RefreshRate)
  102. if err != nil {
  103. return err
  104. }
  105. diff := result.realAmount - l.minRealAmount
  106. redisKey := redis_key.LimitCount(l.userId, r.LimitCode, r.LimitType, r.Scope, p)
  107. redis_client.IncrBy(redisKey, -1*diff)
  108. return nil
  109. }
  110. func (l *limiterRunner) limit(r models.LimitConfig) error {
  111. p, err := period.GetPeriod(r.RefreshRate)
  112. if err != nil {
  113. return err
  114. }
  115. redisKey := redis_key.LimitCount(l.userId, r.LimitCode, r.LimitType, r.Scope, p)
  116. usedNum, err := redis_client.IncrBy(redisKey, l.amount)
  117. if err != nil {
  118. return err
  119. }
  120. //if usedNum equals amount,it is the first operation in period or redis cache deleted
  121. //count in database to distinguish the two cases
  122. if usedNum == l.amount {
  123. n, err := l.countInPeriod(r, p)
  124. if err != nil {
  125. return err
  126. }
  127. if n > 0 {
  128. //means redis cache deleted,incr the cache with real value
  129. usedNum, err = redis_client.IncrBy(redisKey, n)
  130. }
  131. if p != nil {
  132. redis_client.Expire(redisKey, p.LeftTime)
  133. } else {
  134. //add default expire time if no period set
  135. redis_client.Expire(redisKey, 24*time.Hour)
  136. }
  137. }
  138. if usedNum > r.LimitNum {
  139. if usedNum-r.LimitNum >= l.amount {
  140. redis_client.IncrBy(redisKey, -1*l.amount)
  141. return errors.New(fmt.Sprintf("over limit,congfigId=%d", r.ID))
  142. }
  143. switch l.rejectPolicy {
  144. case models.FillUp:
  145. exceed := usedNum - r.LimitNum
  146. realAmount := l.amount - exceed
  147. redis_client.IncrBy(redisKey, -1*exceed)
  148. l.resultMap[l.index] = newLimitResult(true, l.amount, realAmount)
  149. return nil
  150. case models.JustReject:
  151. redis_client.IncrBy(redisKey, -1*l.amount)
  152. return errors.New(fmt.Sprintf("over limit,congfigId=%d", r.ID))
  153. case models.PermittedOnce:
  154. l.resultMap[l.index] = newLimitResult(false, l.amount, l.amount)
  155. return nil
  156. }
  157. }
  158. l.resultMap[l.index] = newLimitResult(false, l.amount, l.amount)
  159. return nil
  160. }
  161. func (l *limiterRunner) LoadLimiters() error {
  162. limiters, err := GetLimiters(l.limitCode, l.limitType)
  163. if err != nil {
  164. return err
  165. }
  166. if limiters != nil {
  167. l.limiters = limiters
  168. }
  169. return nil
  170. }
  171. func (l *limiterRunner) countInPeriod(r models.LimitConfig, p *models.PeriodResult) (int64, error) {
  172. switch r.LimitType {
  173. case models.LimitTypeTask.Name():
  174. return models.CountTaskAccomplishLogInTaskPeriod(r.LimitCode, l.userId, p)
  175. case models.LimitTypeRewardPoint.Name():
  176. return models.SumRewardAmountInTaskPeriod(models.RewardTypePoint.Name(), r.LimitCode, l.userId, p)
  177. default:
  178. return 0, nil
  179. }
  180. }
  181. func CheckLimit(limitCode string, limitType models.LimitType, userId, amount int64, rejectPolicy models.LimiterRejectPolicy) (int64, error) {
  182. if rejectPolicy == "" {
  183. rejectPolicy = models.JustReject
  184. }
  185. r := newLimiterRunner(limitCode, limitType, userId, amount, rejectPolicy)
  186. err := r.Run()
  187. if err != nil {
  188. return 0, err
  189. }
  190. return r.minRealAmount, nil
  191. }
  192. func GetLimiters(limitCode string, limitType models.LimitType) ([]models.LimitConfig, error) {
  193. limiters, err := GetLimitersByLimitType(limitType)
  194. if err != nil {
  195. return nil, err
  196. }
  197. result := make([]models.LimitConfig, 0)
  198. for i, v := range limiters {
  199. if v.LimitCode == "" || v.LimitCode == limitCode {
  200. result = append(result, limiters[i])
  201. }
  202. }
  203. return result, nil
  204. }
  205. func GetLimitersByLimitType(limitType models.LimitType) ([]models.LimitConfig, error) {
  206. redisKey := redis_key.LimitConfig(limitType.Name())
  207. val, _ := redis_client.Get(redisKey)
  208. if val != "" {
  209. if val == redis_key.EMPTY_REDIS_VAL {
  210. return nil, nil
  211. }
  212. limiters := make([]models.LimitConfig, 0)
  213. json.Unmarshal([]byte(val), &limiters)
  214. return limiters, nil
  215. }
  216. limiters, err := models.GetLimitConfigByLimitType(limitType)
  217. if err != nil {
  218. if models.IsErrRecordNotExist(err) {
  219. redis_client.Setex(redisKey, redis_key.EMPTY_REDIS_VAL, 5*time.Second)
  220. return nil, nil
  221. }
  222. return nil, err
  223. }
  224. jsonStr, _ := json.Marshal(limiters)
  225. redis_client.Setex(redisKey, string(jsonStr), 30*24*time.Hour)
  226. return limiters, nil
  227. }
  228. func GetLimitersByRelatedIdWithDeleted(limitType models.LimitType) ([]models.LimitConfig, error) {
  229. limiters, err := models.GetLimitersByRelatedIdWithDeleted(limitType)
  230. if err != nil {
  231. if models.IsErrRecordNotExist(err) {
  232. return nil, nil
  233. }
  234. return nil, err
  235. }
  236. return limiters, nil
  237. }