github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/idalloc/id_alloc.go (about)

     1  // Copyright 2014 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package idalloc
    12  
    13  import (
    14  	"context"
    15  	"sync"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/base"
    18  	"github.com/cockroachdb/cockroach/pkg/kv"
    19  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    20  	"github.com/cockroachdb/cockroach/pkg/util/log"
    21  	"github.com/cockroachdb/cockroach/pkg/util/retry"
    22  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    23  	"github.com/cockroachdb/errors"
    24  )
    25  
    26  // Incrementer abstracts over the database which holds the key counter.
    27  type Incrementer func(_ context.Context, _ roachpb.Key, inc int64) (updated int64, _ error)
    28  
    29  // DBIncrementer wraps a suitable subset of *kv.DB for use with an allocator.
    30  func DBIncrementer(
    31  	db interface {
    32  		Inc(ctx context.Context, key interface{}, value int64) (kv.KeyValue, error)
    33  	},
    34  ) Incrementer {
    35  	return func(ctx context.Context, key roachpb.Key, inc int64) (int64, error) {
    36  		res, err := db.Inc(ctx, key, inc)
    37  		if err != nil {
    38  			return 0, err
    39  		}
    40  		return res.Value.GetInt()
    41  	}
    42  }
    43  
    44  // Options are the options passed to NewAllocator.
    45  type Options struct {
    46  	AmbientCtx  log.AmbientContext
    47  	Key         roachpb.Key
    48  	Incrementer Incrementer
    49  	BlockSize   int64
    50  	Stopper     *stop.Stopper
    51  	Fatalf      func(context.Context, string, ...interface{}) // defaults to log.Fatalf
    52  }
    53  
    54  // An Allocator is used to increment a key in allocation blocks of arbitrary
    55  // size.
    56  type Allocator struct {
    57  	log.AmbientContext
    58  	opts Options
    59  
    60  	ids  chan int64 // Channel of available IDs
    61  	once sync.Once
    62  }
    63  
    64  // NewAllocator creates a new ID allocator which increments the specified key in
    65  // allocation blocks of size blockSize. If the key exists, it's assumed to have
    66  // an int value (and it needs to be positive since id 0 is a sentinel used
    67  // internally by the allocator that can't be generated). The first value
    68  // returned is the existing value + 1, or 1 if the key did not previously exist.
    69  func NewAllocator(opts Options) (*Allocator, error) {
    70  	if opts.BlockSize == 0 {
    71  		return nil, errors.Errorf("blockSize must be a positive integer: %d", opts.BlockSize)
    72  	}
    73  	if opts.Fatalf == nil {
    74  		opts.Fatalf = log.Fatalf
    75  	}
    76  	opts.AmbientCtx.AddLogTag("idalloc", nil)
    77  	return &Allocator{
    78  		AmbientContext: opts.AmbientCtx,
    79  		opts:           opts,
    80  		ids:            make(chan int64, opts.BlockSize/2+1),
    81  	}, nil
    82  }
    83  
    84  // Allocate allocates a new ID from the global KV DB.
    85  func (ia *Allocator) Allocate(ctx context.Context) (int64, error) {
    86  	ia.once.Do(ia.start)
    87  
    88  	select {
    89  	case id := <-ia.ids:
    90  		// when the channel is closed, the zero value is returned.
    91  		if id == 0 {
    92  			return id, errors.Errorf("could not allocate ID; system is draining")
    93  		}
    94  		return id, nil
    95  	case <-ctx.Done():
    96  		return 0, ctx.Err()
    97  	}
    98  }
    99  
   100  func (ia *Allocator) start() {
   101  	ctx := ia.AnnotateCtx(context.Background())
   102  	ia.opts.Stopper.RunWorker(ctx, func(ctx context.Context) {
   103  		defer close(ia.ids)
   104  
   105  		var prevValue int64 // for assertions
   106  		for {
   107  			var newValue int64
   108  			var err error
   109  			for r := retry.Start(base.DefaultRetryOptions()); r.Next(); {
   110  				if stopperErr := ia.opts.Stopper.RunTask(ctx, "idalloc: allocating block",
   111  					func(ctx context.Context) {
   112  						newValue, err = ia.opts.Incrementer(ctx, ia.opts.Key, ia.opts.BlockSize)
   113  					}); stopperErr != nil {
   114  					return
   115  				}
   116  				if err == nil {
   117  					break
   118  				}
   119  
   120  				log.Warningf(
   121  					ctx,
   122  					"unable to allocate %d ids from %s: %+v",
   123  					ia.opts.BlockSize,
   124  					ia.opts.Key,
   125  					err,
   126  				)
   127  			}
   128  			if err != nil {
   129  				ia.opts.Fatalf(ctx, "unexpectedly exited id allocation retry loop: %s", err)
   130  				return
   131  			}
   132  			if prevValue != 0 && newValue < prevValue+ia.opts.BlockSize {
   133  				ia.opts.Fatalf(
   134  					ctx,
   135  					"counter corrupt: incremented to %d, expected at least %d + %d",
   136  					newValue, prevValue, ia.opts.BlockSize,
   137  				)
   138  				return
   139  			}
   140  
   141  			end := newValue + 1
   142  			start := end - ia.opts.BlockSize
   143  			if start <= 0 {
   144  				ia.opts.Fatalf(ctx, "allocator initialized with negative key")
   145  				return
   146  			}
   147  			prevValue = newValue
   148  
   149  			// Add all new ids to the channel for consumption.
   150  			for i := start; i < end; i++ {
   151  				select {
   152  				case ia.ids <- i:
   153  				case <-ia.opts.Stopper.ShouldStop():
   154  					return
   155  				}
   156  			}
   157  		}
   158  	})
   159  }