go.temporal.io/server@v1.23.0/common/tasks/rate_limited_scheduler.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package tasks 26 27 import ( 28 "time" 29 30 "go.temporal.io/server/common/clock" 31 "go.temporal.io/server/common/log" 32 "go.temporal.io/server/common/log/tag" 33 "go.temporal.io/server/common/metrics" 34 "go.temporal.io/server/common/quotas" 35 ) 36 37 type ( 38 QuotaRequestFn[T Task] func(T) quotas.Request 39 MetricTagsFn[T Task] func(T) []metrics.Tag 40 41 RateLimitedSchedulerOptions struct { 42 EnableShadowMode bool 43 } 44 45 RateLimitedScheduler[T Task] struct { 46 scheduler Scheduler[T] 47 48 rateLimiter quotas.RequestRateLimiter 49 timeSource clock.TimeSource 50 quotaRequestFn QuotaRequestFn[T] 51 metricTagsFn MetricTagsFn[T] 52 options RateLimitedSchedulerOptions 53 54 logger log.Logger 55 metricsHandler metrics.Handler 56 } 57 ) 58 59 func NewRateLimitedScheduler[T Task]( 60 scheduler Scheduler[T], 61 rateLimiter quotas.RequestRateLimiter, 62 timeSource clock.TimeSource, 63 quotaRequestFn QuotaRequestFn[T], 64 metricTagsFn MetricTagsFn[T], 65 options RateLimitedSchedulerOptions, 66 logger log.Logger, 67 metricsHandler metrics.Handler, 68 ) *RateLimitedScheduler[T] { 69 return &RateLimitedScheduler[T]{ 70 scheduler: scheduler, 71 rateLimiter: rateLimiter, 72 timeSource: timeSource, 73 quotaRequestFn: quotaRequestFn, 74 metricTagsFn: metricTagsFn, 75 options: options, 76 logger: logger, 77 metricsHandler: metricsHandler, 78 } 79 } 80 81 func (s *RateLimitedScheduler[T]) Submit(task T) { 82 s.wait(task) 83 s.scheduler.Submit(task) 84 } 85 86 func (s *RateLimitedScheduler[T]) TrySubmit(task T) bool { 87 if !s.allow(task) { 88 return false 89 } 90 91 // NOTE: if submission to the underlying scheduler fails, we are not cancelling the request token here. 92 // Because when that happens, the underlying scheduler is already busy and overloaded. 93 // There's no point in cancelling the token, which allows more tasks to be submitted the underlying scheduler. 94 return s.scheduler.TrySubmit(task) 95 } 96 97 func (s *RateLimitedScheduler[T]) Start() { 98 s.scheduler.Start() 99 } 100 101 func (s *RateLimitedScheduler[T]) Stop() { 102 s.scheduler.Stop() 103 } 104 105 func (s *RateLimitedScheduler[T]) wait(task T) { 106 now := s.timeSource.Now() 107 reservation := s.rateLimiter.Reserve( 108 s.timeSource.Now(), 109 s.quotaRequestFn(task), 110 ) 111 if !reservation.OK() { 112 s.logger.Error("unable to make reservation in rateLimitedScheduler, skip rate limiting", 113 tag.Key("quota-request"), 114 tag.Value(s.quotaRequestFn(task)), 115 ) 116 return 117 } 118 119 delay := reservation.DelayFrom(now) 120 if delay <= 0 { 121 return 122 } 123 124 s.metricsHandler.Counter(metrics.TaskSchedulerThrottled.Name()).Record(1, s.metricTagsFn(task)...) 125 if s.options.EnableShadowMode { 126 // in shadow mode, only emit metrics, but don't actually throttle 127 return 128 } 129 130 <-time.NewTimer(delay).C 131 } 132 133 func (s *RateLimitedScheduler[T]) allow(task T) bool { 134 if allow := s.rateLimiter.Allow( 135 s.timeSource.Now(), 136 s.quotaRequestFn(task), 137 ); allow { 138 return true 139 } 140 141 s.metricsHandler.Counter(metrics.TaskSchedulerThrottled.Name()).Record(1, s.metricTagsFn(task)...) 142 143 // in shadow mode, only emit metrics, but don't actually throttle 144 return s.options.EnableShadowMode 145 }