go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/sync/dispatcher/buffer/options.go (about)

     1  // Copyright 2019 The LUCI Authors.
     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  
    15  package buffer
    16  
    17  import (
    18  	"time"
    19  
    20  	"go.chromium.org/luci/common/errors"
    21  	"go.chromium.org/luci/common/retry"
    22  )
    23  
    24  // Options configures policy for the Buffer.
    25  //
    26  // See Defaults for default values.
    27  type Options struct {
    28  	// [OPTIONAL] The maximum number of outstanding leases permitted.
    29  	//
    30  	// Attempting additional leases (with LeaseOne) while at the maximum will
    31  	// return nil.
    32  	//
    33  	// Requirement: Must be > 0
    34  	MaxLeases int
    35  
    36  	// [OPTIONAL] The maximum number of items to allow in a Batch before making it
    37  	// available to lease.
    38  	//
    39  	// Special value -1: unlimited
    40  	// Requirement: Must be == -1 (i.e. cut batches on BatchAgeMax/BatchSizeMax),
    41  	// or > 0
    42  	BatchItemsMax int
    43  
    44  	// [OPTIONAL] The maximum number of "size units" to allow in a Batch before
    45  	// making it available to lease.
    46  	//
    47  	// The units used here are arbitrary and are only checked vs the value
    48  	// provided to AddNoBlock.
    49  	//
    50  	// Size is explicitly provided to AddNoBlock by the caller.
    51  	//
    52  	// Inserting an item which exceeds BatchSizeMax will result in ErrItemTooLarge.
    53  	// It's up to the caller to ensure that this doesn't happen.
    54  	//
    55  	// Special value -1: unlimited
    56  	// Requirement: Must be == -1 (i.e. cut batches on BatchAgeMax/BatchItemsMax),
    57  	// or > 0
    58  	BatchSizeMax int
    59  
    60  	// [OPTIONAL] The maximum amount of time to wait before queuing a Batch for
    61  	// transmission. Note that batches are only cut by time when a worker is ready
    62  	// to process them (i.e. LeaseOne is invoked).
    63  	//
    64  	// Requirement: Must be > 0
    65  	BatchAgeMax time.Duration
    66  
    67  	// [OPTIONAL] Sets the policy for the Buffer around how many items the Buffer
    68  	// is allowed to hold, and what happens when that number is reached.
    69  	FullBehavior FullBehavior
    70  
    71  	// [OPTIONAL] If true, ensures that the next available batch is always the one
    72  	// with the oldest data.
    73  	//
    74  	// If this is false (the default), batches will be leased in the order that
    75  	// they're available to send; If a Batch has a retry with a high delay, it's
    76  	// possible that the next leased Batch actually contains newer data than
    77  	// a later batch.
    78  	//
    79  	// NOTE: if this is combined with high Retry values, it can lead to a
    80  	// head-of-line blocking situation.
    81  	//
    82  	// Requirement: May only be true if MaxLeases == 1
    83  	FIFO bool
    84  
    85  	// [OPTIONAL] Each batch will have a retry.Iterator assigned to it from this
    86  	// retry.Factory.
    87  	//
    88  	// When a Batch is NACK'd, it will be retried at "now" plus the Duration
    89  	// returned by the retry.Iterator.
    90  	//
    91  	// If the retry.Iterator returns retry.Stop, the Batch will be silently
    92  	// dropped.
    93  	Retry retry.Factory
    94  }
    95  
    96  // Defaults defines the defaults for Options when it contains 0-valued
    97  // fields.
    98  //
    99  // DO NOT ASSIGN/WRITE TO THIS STRUCT.
   100  var Defaults = Options{
   101  	MaxLeases:     4,
   102  	BatchItemsMax: 20,
   103  	BatchSizeMax:  -1,
   104  	BatchAgeMax:   10 * time.Second,
   105  	FullBehavior: &BlockNewItems{
   106  		MaxItems: 1000,
   107  	},
   108  	Retry: func() retry.Iterator {
   109  		return &retry.ExponentialBackoff{
   110  			Limited: retry.Limited{
   111  				Delay:   200 * time.Millisecond, // initial delay
   112  				Retries: -1,                     // no retry cap
   113  			},
   114  			Multiplier: 1.2,
   115  			MaxDelay:   60 * time.Second,
   116  		}
   117  	},
   118  }
   119  
   120  // normalize validates that Options is well formed and populates defaults
   121  // which are missing.
   122  func (o *Options) normalize() error {
   123  	switch {
   124  	case o.MaxLeases == 0:
   125  		o.MaxLeases = Defaults.MaxLeases
   126  	case o.MaxLeases > 0:
   127  	default:
   128  		return errors.Reason("MaxLeases must be > 0: got %d", o.MaxLeases).Err()
   129  	}
   130  
   131  	switch {
   132  	case o.BatchItemsMax == -1:
   133  	case o.BatchItemsMax == 0:
   134  		o.BatchItemsMax = Defaults.BatchItemsMax
   135  	case o.BatchItemsMax > 0:
   136  	default:
   137  		return errors.Reason("BatchItemsMax must be > 0 or == -1: got %d", o.BatchItemsMax).Err()
   138  	}
   139  
   140  	switch {
   141  	case o.BatchSizeMax == -1:
   142  	case o.BatchSizeMax == 0:
   143  		o.BatchSizeMax = Defaults.BatchSizeMax
   144  	case o.BatchSizeMax > 0:
   145  	default:
   146  		return errors.Reason("BatchSizeMax must be > 0 or == -1: got %d", o.BatchSizeMax).Err()
   147  	}
   148  
   149  	switch {
   150  	case o.BatchAgeMax == 0:
   151  		o.BatchAgeMax = Defaults.BatchAgeMax
   152  	case o.BatchAgeMax > 0:
   153  	default:
   154  		return errors.Reason("BatchAgeMax must be > 0: got %s", o.BatchAgeMax).Err()
   155  	}
   156  
   157  	if o.FIFO && o.MaxLeases != 1 {
   158  		return errors.Reason("FIFO is true, but MaxLeases != 1: got %d", o.MaxLeases).Err()
   159  	}
   160  
   161  	if o.FullBehavior == nil {
   162  		o.FullBehavior = Defaults.FullBehavior
   163  	}
   164  
   165  	if o.Retry == nil {
   166  		o.Retry = Defaults.Retry
   167  	}
   168  
   169  	return errors.Annotate(o.FullBehavior.Check(*o), "FullBehavior.Check").Err()
   170  }
   171  
   172  func (o *Options) batchItemsGuess() int {
   173  	if o.BatchItemsMax > 0 {
   174  		return o.BatchItemsMax
   175  	}
   176  	return 10
   177  }
   178  
   179  func (o *Options) checkItemSize(itemSize int) error {
   180  	if itemSize < 0 {
   181  		// We don't ever allow negative sizes.
   182  		return ErrItemTooSmall
   183  	}
   184  
   185  	switch {
   186  	case o.BatchSizeMax == -1:
   187  	case itemSize == 0:
   188  		return ErrItemTooSmall
   189  	case itemSize > o.BatchSizeMax:
   190  		return ErrItemTooLarge
   191  	}
   192  	return nil
   193  }