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.
 
 
 

217 line
5.9 KiB

  1. // Copyright 2015 Google Inc. All rights reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package trafficshape
  15. import (
  16. "errors"
  17. "sync"
  18. "sync/atomic"
  19. "time"
  20. "github.com/google/martian/log"
  21. )
  22. // Bucket is a generic leaky bucket that drains at a configurable interval and
  23. // fills at user defined rate. The bucket may be used concurrently.
  24. type Bucket struct {
  25. capacity int64 // atomic
  26. fill int64 // atomic
  27. mu sync.Mutex
  28. t *time.Ticker
  29. closec chan struct{}
  30. }
  31. var (
  32. // ErrBucketOverflow is an error that indicates the bucket has been overflown
  33. // by the user. This error is only returned iff fill > capacity.
  34. ErrBucketOverflow = errors.New("trafficshape: bucket overflow")
  35. errFillClosedBucket = errors.New("trafficshape: fill on closed bucket")
  36. )
  37. // NewBucket returns a new leaky bucket with capacity that is drained
  38. // at interval.
  39. func NewBucket(capacity int64, interval time.Duration) *Bucket {
  40. b := &Bucket{
  41. capacity: capacity,
  42. t: time.NewTicker(interval),
  43. closec: make(chan struct{}),
  44. }
  45. go b.loop()
  46. return b
  47. }
  48. // Capacity returns the capacity of the bucket.
  49. func (b *Bucket) Capacity() int64 {
  50. return atomic.LoadInt64(&b.capacity)
  51. }
  52. // SetCapacity sets the capacity for the bucket and resets the fill to zero.
  53. func (b *Bucket) SetCapacity(capacity int64) {
  54. log.Infof("trafficshape: set capacity: %d", capacity)
  55. atomic.StoreInt64(&b.capacity, capacity)
  56. atomic.StoreInt64(&b.fill, 0)
  57. }
  58. // Close stops the drain loop and marks the bucket as closed.
  59. func (b *Bucket) Close() error {
  60. log.Debugf("trafficshape: closing bucket")
  61. // Allow b to be closed multiple times without panicking.
  62. if b.closed() {
  63. return nil
  64. }
  65. b.t.Stop()
  66. close(b.closec)
  67. return nil
  68. }
  69. // FillThrottle calls fn with the available capacity remaining (capacity-fill)
  70. // and fills the bucket with the number of tokens returned by fn. If the
  71. // remaining capacity is <= 0, FillThrottle will wait for the next drain before
  72. // running fn.
  73. //
  74. // If fn returns an error, it will be returned by FillThrottle along with the
  75. // number of tokens processed by fn.
  76. //
  77. // fn is provided the remaining capacity as a soft maximum, fn is allowed to
  78. // use more than the remaining capacity without incurring spillage.
  79. //
  80. // If the bucket is closed when FillThrottle is called, or while waiting for
  81. // the next drain, fn will not be executed and FillThrottle will return with an
  82. // error.
  83. func (b *Bucket) FillThrottle(fn func(int64) (int64, error)) (int64, error) {
  84. for {
  85. if b.closed() {
  86. log.Errorf("trafficshape: fill on closed bucket")
  87. return 0, errFillClosedBucket
  88. }
  89. fill := atomic.LoadInt64(&b.fill)
  90. capacity := atomic.LoadInt64(&b.capacity)
  91. if fill < capacity {
  92. log.Debugf("trafficshape: under capacity (%d/%d)", fill, capacity)
  93. n, err := fn(capacity - fill)
  94. fill = atomic.AddInt64(&b.fill, n)
  95. return n, err
  96. }
  97. log.Debugf("trafficshape: bucket full (%d/%d)", fill, capacity)
  98. }
  99. }
  100. // FillThrottleLocked is like FillThrottle, except that it uses a lock to protect
  101. // the critical section between accessing the fill value and updating it.
  102. func (b *Bucket) FillThrottleLocked(fn func(int64) (int64, error)) (int64, error) {
  103. for {
  104. if b.closed() {
  105. log.Errorf("trafficshape: fill on closed bucket")
  106. return 0, errFillClosedBucket
  107. }
  108. b.mu.Lock()
  109. fill := atomic.LoadInt64(&b.fill)
  110. capacity := atomic.LoadInt64(&b.capacity)
  111. if fill < capacity {
  112. n, err := fn(capacity - fill)
  113. fill = atomic.AddInt64(&b.fill, n)
  114. b.mu.Unlock()
  115. return n, err
  116. }
  117. b.mu.Unlock()
  118. log.Debugf("trafficshape: bucket full (%d/%d)", fill, capacity)
  119. }
  120. }
  121. // Fill calls fn with the available capacity remaining (capacity-fill) and
  122. // fills the bucket with the number of tokens returned by fn. If the remaining
  123. // capacity is 0, Fill returns 0, nil. If the remaining capacity is < 0, Fill
  124. // returns 0, ErrBucketOverflow.
  125. //
  126. // If fn returns an error, it will be returned by Fill along with the remaining
  127. // capacity.
  128. //
  129. // fn is provided the remaining capacity as a soft maximum, fn is allowed to
  130. // use more than the remaining capacity without incurring spillage, though this
  131. // will cause subsequent calls to Fill to return ErrBucketOverflow until the
  132. // next drain.
  133. //
  134. // If the bucket is closed when Fill is called, fn will not be executed and
  135. // Fill will return with an error.
  136. func (b *Bucket) Fill(fn func(int64) (int64, error)) (int64, error) {
  137. if b.closed() {
  138. log.Errorf("trafficshape: fill on closed bucket")
  139. return 0, errFillClosedBucket
  140. }
  141. fill := atomic.LoadInt64(&b.fill)
  142. capacity := atomic.LoadInt64(&b.capacity)
  143. switch {
  144. case fill < capacity:
  145. log.Debugf("trafficshape: under capacity (%d/%d)", fill, capacity)
  146. n, err := fn(capacity - fill)
  147. fill = atomic.AddInt64(&b.fill, n)
  148. return n, err
  149. case fill > capacity:
  150. log.Debugf("trafficshape: bucket overflow (%d/%d)", fill, capacity)
  151. return 0, ErrBucketOverflow
  152. }
  153. log.Debugf("trafficshape: bucket full (%d/%d)", fill, capacity)
  154. return 0, nil
  155. }
  156. // loop drains the fill at interval and returns when the bucket is closed.
  157. func (b *Bucket) loop() {
  158. log.Debugf("trafficshape: started drain loop")
  159. defer log.Debugf("trafficshape: stopped drain loop")
  160. for {
  161. select {
  162. case t := <-b.t.C:
  163. atomic.StoreInt64(&b.fill, 0)
  164. log.Debugf("trafficshape: fill reset @ %s", t)
  165. case <-b.closec:
  166. log.Debugf("trafficshape: bucket closed")
  167. return
  168. }
  169. }
  170. }
  171. func (b *Bucket) closed() bool {
  172. select {
  173. case <-b.closec:
  174. return true
  175. default:
  176. return false
  177. }
  178. }