github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/refcounts_ns.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/mgo/v3" 9 "github.com/juju/mgo/v3/bson" 10 "github.com/juju/mgo/v3/txn" 11 12 "github.com/juju/juju/mongo" 13 ) 14 15 // refcountDoc holds a reference count. Refcounts are important to juju 16 // because mgo/txn offers no other mechanisms for safely coordinating 17 // deletion of unreferenced documents. 18 // 19 // TODO(fwereade) 2016-08-11 lp:1612163 20 // 21 // There are several places that use ad-hoc refcounts (application 22 // UnitCount and RelationCount; and model refs; and many many more) 23 // which should (1) be using separate refcount docs instead of dumping 24 // them in entity docs and (2) be using *this* refcount functionality 25 // rather than building their own ad-hoc variants. 26 type refcountDoc struct { 27 28 // The _id field should hold some globalKey to identify what's 29 // being referenced, but there's no reason to express it in this 30 // document directly. 31 32 // RefCount holds the reference count for whatever this doc is 33 // referencing. 34 RefCount int `bson:"refcount"` 35 } 36 37 var ( 38 errRefcountChanged = errors.New("refcount changed") 39 errRefcountAlreadyZero = errors.New("cannot decRef below 0") 40 ) 41 42 // nsRefcounts exposes methods for safely manipulating reference count 43 // documents. (You can also manipulate them unsafely via the Just* 44 // methods that don't keep track of DB state.) 45 var nsRefcounts = nsRefcounts_{} 46 47 // nsRefcounts_ backs nsRefcounts. 48 type nsRefcounts_ struct{} 49 50 // LazyCreateOp returns a txn.Op that creates a refcount document; or 51 // false if the document already exists. 52 func (ns nsRefcounts_) LazyCreateOp(coll mongo.Collection, key string) (txn.Op, bool, error) { 53 if exists, err := ns.exists(coll, key); err != nil { 54 return txn.Op{}, false, errors.Trace(err) 55 } else if exists { 56 return txn.Op{}, false, nil 57 } 58 return ns.JustCreateOp(coll.Name(), key, 0), true, nil 59 } 60 61 // StrictCreateOp returns a txn.Op that creates a refcount document as 62 // configured, or an error if the document already exists. 63 func (ns nsRefcounts_) StrictCreateOp(coll mongo.Collection, key string, value int) (txn.Op, error) { 64 if exists, err := ns.exists(coll, key); err != nil { 65 return txn.Op{}, errors.Trace(err) 66 } else if exists { 67 return txn.Op{}, errors.New("refcount already exists") 68 } 69 return ns.JustCreateOp(coll.Name(), key, value), nil 70 } 71 72 // CreateOrIncRefOp returns a txn.Op that creates a refcount document as 73 // configured with a specified value; or increments any such refcount doc 74 // that already exists. 75 func (ns nsRefcounts_) CreateOrIncRefOp(coll mongo.Collection, key string, n int) (txn.Op, error) { 76 if exists, err := ns.exists(coll, key); err != nil { 77 return txn.Op{}, errors.Trace(err) 78 } else if !exists { 79 return ns.JustCreateOp(coll.Name(), key, n), nil 80 } 81 return ns.JustIncRefOp(coll.Name(), key, n), nil 82 } 83 84 // StrictIncRefOp returns a txn.Op that increments the value of a 85 // refcount doc, or returns an error if it does not exist. 86 func (ns nsRefcounts_) StrictIncRefOp(coll mongo.Collection, key string, n int) (txn.Op, error) { 87 if exists, err := ns.exists(coll, key); err != nil { 88 return txn.Op{}, errors.Trace(err) 89 } else if !exists { 90 return txn.Op{}, errors.New("does not exist") 91 } 92 return ns.JustIncRefOp(coll.Name(), key, n), nil 93 } 94 95 // AliveDecRefOp returns a txn.Op that decrements the value of a 96 // refcount doc, or an error if the doc does not exist or the count 97 // would go below 0. 98 func (ns nsRefcounts_) AliveDecRefOp(coll mongo.Collection, key string) (txn.Op, error) { 99 if refcount, err := ns.read(coll, key); err != nil { 100 return txn.Op{}, errors.Trace(err) 101 } else if refcount < 1 { 102 return txn.Op{}, errors.Annotatef(errRefcountAlreadyZero, "%s(%s)", coll.Name(), key) 103 } 104 return ns.justDecRefOp(coll.Name(), key, 0), nil 105 } 106 107 // DyingDecRefOp returns a txn.Op that decrements the value of a 108 // refcount doc and deletes it if the count reaches 0; if the Op will 109 // cause a delete, the bool result will be true. It will return an error 110 // if the doc does not exist or the count would go below 0. 111 func (ns nsRefcounts_) DyingDecRefOp(coll mongo.Collection, key string) (txn.Op, bool, error) { 112 refcount, err := ns.read(coll, key) 113 if err != nil { 114 return txn.Op{}, false, errors.Trace(err) 115 } 116 if refcount < 1 { 117 return txn.Op{}, false, errors.Annotatef(errRefcountAlreadyZero, "%s(%s)", coll.Name(), key) 118 } else if refcount > 1 { 119 return ns.justDecRefOp(coll.Name(), key, 1), false, nil 120 } 121 return ns.JustRemoveOp(coll.Name(), key, 1), true, nil 122 } 123 124 // RemoveOp returns a txn.Op that removes a refcount doc so long as its 125 // refcount is the supplied value, or an error. 126 func (ns nsRefcounts_) RemoveOp(coll mongo.Collection, key string, value int) (txn.Op, error) { 127 refcount, err := ns.read(coll, key) 128 if err != nil { 129 return txn.Op{}, errors.Trace(err) 130 } 131 if refcount != value { 132 logger.Tracef("reference of %s(%q) had %d refs, expected %d", coll.Name(), key, refcount, value) 133 return txn.Op{}, errRefcountChanged 134 } 135 return ns.JustRemoveOp(coll.Name(), key, value), nil 136 } 137 138 // CurrentOp returns the current reference count value, and a txn.Op that 139 // asserts that the refcount has that value, or an error. If the refcount 140 // doc does not exist, then the op will assert that the document does not 141 // exist instead, and no error is returned. 142 func (ns nsRefcounts_) CurrentOp(coll mongo.Collection, key string) (txn.Op, int, error) { 143 refcount, err := ns.read(coll, key) 144 if errors.IsNotFound(err) { 145 return txn.Op{ 146 C: coll.Name(), 147 Id: key, 148 Assert: txn.DocMissing, 149 }, 0, nil 150 } 151 if err != nil { 152 return txn.Op{}, -1, errors.Trace(err) 153 } 154 return txn.Op{ 155 C: coll.Name(), 156 Id: key, 157 Assert: bson.D{{"refcount", refcount}}, 158 }, refcount, nil 159 } 160 161 // JustCreateOp returns a txn.Op that creates a refcount document as 162 // configured, *without* checking database state for sanity first. 163 // You should avoid using this method in most cases. 164 func (nsRefcounts_) JustCreateOp(collName, key string, value int) txn.Op { 165 return txn.Op{ 166 C: collName, 167 Id: key, 168 Assert: txn.DocMissing, 169 Insert: bson.D{{"refcount", value}}, 170 } 171 } 172 173 // JustIncRefOp returns a txn.Op that increments a refcount document by 174 // the specified amount, as configured, *without* checking database state 175 // for sanity first. You should avoid using this method in most cases. 176 func (nsRefcounts_) JustIncRefOp(collName, key string, n int) txn.Op { 177 return txn.Op{ 178 C: collName, 179 Id: key, 180 Assert: txn.DocExists, 181 Update: bson.D{{"$inc", bson.D{{"refcount", n}}}}, 182 } 183 } 184 185 // JustRemoveOp returns a txn.Op that deletes a refcount doc so long as 186 // the refcount matches count. You should avoid using this method in 187 // most cases. 188 func (ns nsRefcounts_) JustRemoveOp(collName, key string, count int) txn.Op { 189 op := txn.Op{ 190 C: collName, 191 Id: key, 192 Remove: true, 193 } 194 if count >= 0 { 195 op.Assert = bson.D{{"refcount", count}} 196 } 197 return op 198 } 199 200 // justDecRefOp returns a txn.Op that decrements a refcount document by 201 // 1, as configured, allowing it to drop no lower than limit; which must 202 // not be less than zero. It's unexported, meaningless though that may 203 // be, to encourage clients to *really* not use it: too many ways to 204 // mess it up if you're not precisely aware of the context. 205 func (nsRefcounts_) justDecRefOp(collName, key string, limit int) txn.Op { 206 return txn.Op{ 207 C: collName, 208 Id: key, 209 Assert: bson.D{{"refcount", bson.D{{"$gt", limit}}}}, 210 Update: bson.D{{"$inc", bson.D{{"refcount", -1}}}}, 211 } 212 } 213 214 // exists returns whether the identified refcount doc exists. 215 func (nsRefcounts_) exists(coll mongo.Collection, key string) (bool, error) { 216 count, err := coll.FindId(key).Count() 217 if err != nil { 218 return false, errors.Trace(err) 219 } 220 return count != 0, nil 221 } 222 223 // read returns the value stored in the identified refcount doc. 224 func (nsRefcounts_) read(coll mongo.Collection, key string) (int, error) { 225 var doc refcountDoc 226 if err := coll.FindId(key).One(&doc); err == mgo.ErrNotFound { 227 return 0, errors.NotFoundf("refcount %q", key) 228 } else if err != nil { 229 return 0, errors.Trace(err) 230 } 231 return doc.RefCount, nil 232 }