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