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 }