storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/bucket/bandwidth/throttle.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2020 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package bandwidth 18 19 import ( 20 "context" 21 "sync" 22 "sync/atomic" 23 "time" 24 ) 25 26 const ( 27 throttleInternal = 250 * time.Millisecond 28 ) 29 30 // throttle implements the throttling for bandwidth 31 type throttle struct { 32 generateTicker *time.Ticker // Ticker to generate available bandwidth 33 freeBytes int64 // unused bytes in the interval 34 bytesPerSecond int64 // max limit for bandwidth 35 bytesPerInterval int64 // bytes allocated for the interval 36 clusterBandwidth int64 // Cluster wide bandwidth needed for reporting 37 cond *sync.Cond // Used to notify waiting threads for bandwidth availability 38 goGenerate int64 // Flag to track if generate routine should be running. 0 == stopped 39 ctx context.Context // Context for generate 40 } 41 42 // newThrottle returns a new bandwidth throttle. Set bytesPerSecond to 0 for no limit 43 func newThrottle(ctx context.Context, bytesPerSecond int64, clusterBandwidth int64) *throttle { 44 if bytesPerSecond == 0 { 45 return &throttle{} 46 } 47 t := &throttle{ 48 bytesPerSecond: bytesPerSecond, 49 generateTicker: time.NewTicker(throttleInternal), 50 clusterBandwidth: clusterBandwidth, 51 ctx: ctx, 52 } 53 54 t.cond = sync.NewCond(&sync.Mutex{}) 55 t.SetBandwidth(bytesPerSecond, clusterBandwidth) 56 t.freeBytes = t.bytesPerInterval 57 return t 58 } 59 60 // GetLimitForBytes gets the bytes that are possible to send within the limit 61 // if want is <= 0 or no bandwidth limit set, returns want. 62 // Otherwise a value > 0 will always be returned. 63 func (t *throttle) GetLimitForBytes(want int64) int64 { 64 if want <= 0 || atomic.LoadInt64(&t.bytesPerInterval) == 0 { 65 return want 66 } 67 t.cond.L.Lock() 68 defer t.cond.L.Unlock() 69 for { 70 var send int64 71 freeBytes := atomic.LoadInt64(&t.freeBytes) 72 send = want 73 if freeBytes < want { 74 send = freeBytes 75 if send <= 0 { 76 t.cond.Wait() 77 continue 78 } 79 } 80 atomic.AddInt64(&t.freeBytes, -send) 81 82 // Bandwidth was consumed, start generate routine to allocate bandwidth 83 if atomic.CompareAndSwapInt64(&t.goGenerate, 0, 1) { 84 go t.generateBandwidth(t.ctx) 85 } 86 return send 87 } 88 } 89 90 // SetBandwidth sets a new bandwidth limit in bytes per second. 91 func (t *throttle) SetBandwidth(bandwidthBiPS int64, clusterBandwidth int64) { 92 bpi := int64(throttleInternal) * bandwidthBiPS / int64(time.Second) 93 atomic.StoreInt64(&t.bytesPerInterval, bpi) 94 } 95 96 // ReleaseUnusedBandwidth releases bandwidth that was allocated for a user 97 func (t *throttle) ReleaseUnusedBandwidth(bytes int64) { 98 atomic.AddInt64(&t.freeBytes, bytes) 99 } 100 101 // generateBandwidth periodically allocates new bandwidth to use 102 func (t *throttle) generateBandwidth(ctx context.Context) { 103 for { 104 select { 105 case <-t.generateTicker.C: 106 if atomic.LoadInt64(&t.freeBytes) == atomic.LoadInt64(&t.bytesPerInterval) { 107 // No bandwidth consumption stop the routine. 108 atomic.StoreInt64(&t.goGenerate, 0) 109 return 110 } 111 // A new window is available 112 t.cond.L.Lock() 113 atomic.StoreInt64(&t.freeBytes, atomic.LoadInt64(&t.bytesPerInterval)) 114 t.cond.Broadcast() 115 t.cond.L.Unlock() 116 case <-ctx.Done(): 117 return 118 } 119 } 120 }