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  }