github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/sequence.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/mgo/v3"
    11  	"github.com/juju/mgo/v3/bson"
    12  )
    13  
    14  type sequenceDoc struct {
    15  	DocID     string `bson:"_id"`
    16  	Name      string `bson:"name"`
    17  	ModelUUID string `bson:"model-uuid"`
    18  	Counter   int    // This stores the *next* value to return.
    19  }
    20  
    21  // sequence safely increments a database backed sequence, returning
    22  // the next value.
    23  func sequence(mb modelBackend, name string) (int, error) {
    24  	sequences, closer := mb.db().GetCollection(sequenceC)
    25  	defer closer()
    26  	query := sequences.FindId(name)
    27  	inc := mgo.Change{
    28  		Update: bson.M{
    29  			"$set": bson.M{
    30  				"name":       name,
    31  				"model-uuid": mb.ModelUUID(),
    32  			},
    33  			"$inc": bson.M{"counter": 1},
    34  		},
    35  		Upsert: true,
    36  	}
    37  	result := &sequenceDoc{}
    38  	_, err := query.Apply(inc, result)
    39  	if err != nil {
    40  		return -1, fmt.Errorf("cannot increment %q sequence number: %v", name, err)
    41  	}
    42  	return result.Counter, nil
    43  }
    44  
    45  func resetSequence(mb modelBackend, name string) error {
    46  	sequences, closer := mb.db().GetCollection(sequenceC)
    47  	defer closer()
    48  	err := sequences.Writeable().RemoveId(name)
    49  	if err != nil && errors.Cause(err) != mgo.ErrNotFound {
    50  		return errors.Annotatef(err, "can not reset sequence for %q", name)
    51  	}
    52  	return nil
    53  }
    54  
    55  // sequenceWithMin safely increments a database backed sequence,
    56  // allowing for a minimum value for the sequence to be specified. The
    57  // minimum value is used as an initial value for the first use of a
    58  // particular sequence. The minimum value will also cause a sequence
    59  // value to jump ahead if the minimum is provided that is higher than
    60  // the current sequence value.
    61  //
    62  // The data manipulated by `sequence` and `sequenceWithMin` is the
    63  // same. It is safe to mix the 2 methods for the same sequence.
    64  //
    65  // `sequence` is more efficient than `sequenceWithMin` and should be
    66  // preferred if there is no minimum value requirement.
    67  func sequenceWithMin(mb modelBackend, name string, minVal int) (int, error) {
    68  	sequences, closer := mb.db().GetRawCollection(sequenceC)
    69  	defer closer()
    70  	updater := newDbSeqUpdater(sequences, mb.ModelUUID(), name)
    71  	return updateSeqWithMin(updater, minVal)
    72  }
    73  
    74  // seqUpdater abstracts away the database operations required for
    75  // updating a sequence.
    76  type seqUpdater interface {
    77  	// read returns the current value of the sequence. If the sequence
    78  	// doesn't exist yet it returns 0.
    79  	read() (int, error)
    80  
    81  	// create attempts to create a new sequence with the initial value
    82  	// provided. It returns (true, nil) on success, (false, nil) if
    83  	// the sequence already existed and (false, <some error>) if any
    84  	// other error occurred.
    85  	create(value int) (bool, error)
    86  
    87  	// set attempts to update the sequence value to a new value. It
    88  	// takes the expected current value of the sequence as well as the
    89  	// new value to set it to. (true, nil) is returned if the value
    90  	// was updated successfully. (false, nil) is returned if the
    91  	// sequence was not at the expected value (indicating a concurrent
    92  	// update). (false, <some error>) is returned for any other
    93  	// problem.
    94  	set(expected, next int) (bool, error)
    95  }
    96  
    97  // Sequences returns the model's sequence names and their next values.
    98  func (st *State) Sequences() (map[string]int, error) {
    99  	sequences, closer := st.db().GetCollection(sequenceC)
   100  	defer closer()
   101  
   102  	var docs []sequenceDoc
   103  	if err := sequences.Find(nil).All(&docs); err != nil {
   104  		return nil, errors.Trace(err)
   105  	}
   106  
   107  	result := make(map[string]int)
   108  	for _, doc := range docs {
   109  		result[doc.Name] = doc.Counter
   110  	}
   111  	return result, nil
   112  }
   113  
   114  const maxSeqRetries = 20
   115  
   116  // updateSeqWithMin implements the abstract logic for incrementing a
   117  // database backed sequence in a concurrency aware way.
   118  //
   119  // It is complicated because MongoDB's atomic update primitives don't
   120  // provide a way to upsert a counter while also providing an initial
   121  // value.  Instead, a number of database operations are used for each
   122  // sequence update, relying on the atomicity guarantees that MongoDB
   123  // offers. Optimistic database updates are attempted with retries when
   124  // contention is observed.
   125  func updateSeqWithMin(sequence seqUpdater, minVal int) (int, error) {
   126  	for try := 0; try < maxSeqRetries; try++ {
   127  		curVal, err := sequence.read()
   128  		if err != nil {
   129  			return -1, errors.Annotate(err, "could not read sequence")
   130  		}
   131  		if curVal == 0 {
   132  			// No sequence document exists, create one.
   133  			ok, err := sequence.create(minVal + 1)
   134  			if err != nil {
   135  				return -1, errors.Annotate(err, "could not create sequence")
   136  			}
   137  			if ok {
   138  				return minVal, nil
   139  			}
   140  			// Someone else created the sequence document at the same
   141  			// time, try again.
   142  		} else {
   143  			// Increment an existing sequence document, respecting the
   144  			// minimum value provided.
   145  			nextVal := curVal + 1
   146  			if nextVal <= minVal {
   147  				nextVal = minVal + 1
   148  			}
   149  			ok, err := sequence.set(curVal, nextVal)
   150  			if err != nil {
   151  				return -1, errors.Annotate(err, "could not set sequence")
   152  			}
   153  			if ok {
   154  				return nextVal - 1, nil
   155  			}
   156  			// Someone else incremented the sequence at the same time,
   157  			// try again.
   158  		}
   159  	}
   160  	return -1, errors.New("too much contention while updating sequence")
   161  }
   162  
   163  // dbSeqUpdater implements seqUpdater.
   164  type dbSeqUpdater struct {
   165  	coll      *mgo.Collection
   166  	modelUUID string
   167  	name      string
   168  	id        string
   169  }
   170  
   171  func newDbSeqUpdater(coll *mgo.Collection, modelUUID, name string) *dbSeqUpdater {
   172  	return &dbSeqUpdater{
   173  		coll:      coll,
   174  		modelUUID: modelUUID,
   175  		name:      name,
   176  		id:        modelUUID + ":" + name,
   177  	}
   178  }
   179  
   180  func (su *dbSeqUpdater) read() (int, error) {
   181  	var doc bson.M
   182  	err := su.coll.FindId(su.id).One(&doc)
   183  	if errors.Cause(err) == mgo.ErrNotFound {
   184  		return 0, nil
   185  	} else if err != nil {
   186  		return -1, errors.Trace(err)
   187  	}
   188  	return doc["counter"].(int), nil
   189  }
   190  
   191  func (su *dbSeqUpdater) create(value int) (bool, error) {
   192  	err := su.coll.Insert(bson.M{
   193  		"_id":        su.id,
   194  		"name":       su.name,
   195  		"model-uuid": su.modelUUID,
   196  		"counter":    value,
   197  	})
   198  	if mgo.IsDup(errors.Cause(err)) {
   199  		return false, nil
   200  	} else if err != nil {
   201  		return false, errors.Trace(err)
   202  	}
   203  	return true, nil
   204  }
   205  
   206  func (su *dbSeqUpdater) set(expected, next int) (bool, error) {
   207  	err := su.coll.Update(
   208  		bson.M{"_id": su.id, "counter": expected},
   209  		bson.M{"$set": bson.M{"counter": next}},
   210  	)
   211  	if errors.Cause(err) == mgo.ErrNotFound {
   212  		return false, nil
   213  	} else if err != nil {
   214  		return false, errors.Trace(err)
   215  	}
   216  	return true, nil
   217  }
   218  
   219  func (su *dbSeqUpdater) ensure(next int) error {
   220  	curVal, err := su.read()
   221  	if err != nil {
   222  		return errors.Trace(err)
   223  	}
   224  
   225  	var ok bool
   226  	if curVal == 0 {
   227  		ok, err = su.create(next)
   228  	} else {
   229  		// Sequences should never go backwards.
   230  		if next <= curVal {
   231  			return nil
   232  		}
   233  		ok, err = su.set(curVal, next)
   234  	}
   235  	if !ok {
   236  		return errors.New("unexpected contention while updating sequence")
   237  	}
   238  	return errors.Trace(err)
   239  }