launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/state/watcher/watcher.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // The watcher package provides an interface for observing changes 5 // to arbitrary MongoDB documents that are maintained via the 6 // mgo/txn transaction package. 7 package watcher 8 9 import ( 10 "fmt" 11 "time" 12 13 "labix.org/v2/mgo" 14 "labix.org/v2/mgo/bson" 15 "launchpad.net/errgo/errors" 16 "launchpad.net/tomb" 17 18 "launchpad.net/juju-core/log" 19 ) 20 21 var mask = errors.Mask 22 23 // Debug specifies whether the package will log debug 24 // messages. 25 // TODO(rog) allow debug level setting in the log package. 26 var Debug = false 27 28 // A Watcher can watch any number of collections and documents for changes. 29 type Watcher struct { 30 tomb tomb.Tomb 31 log *mgo.Collection 32 33 // watches holds the observers managed by Watch/Unwatch. 34 watches map[watchKey][]watchInfo 35 36 // current holds the current txn-revno values for all the observed 37 // documents known to exist. Documents not observed or deleted are 38 // omitted from this map and are considered to have revno -1. 39 current map[watchKey]int64 40 41 // needSync is set when a synchronization should take 42 // place. 43 needSync bool 44 45 // syncEvents and requestEvents contain the events to be 46 // dispatched to the watcher channels. They're queued during 47 // processing and flushed at the end to simplify the algorithm. 48 // The two queues are separated because events from sync are 49 // handled in reverse order due to the way the algorithm works. 50 syncEvents, requestEvents []event 51 52 // request is used to deliver requests from the public API into 53 // the the goroutine loop. 54 request chan interface{} 55 56 // lastId is the most recent transaction id observed by a sync. 57 lastId interface{} 58 } 59 60 // A Change holds information about a document change. 61 type Change struct { 62 // C and Id hold the collection name and document _id field value. 63 C string 64 Id interface{} 65 66 // Revno is the latest known value for the document's txn-revno 67 // field, or -1 if the document was deleted. 68 Revno int64 69 } 70 71 type watchKey struct { 72 c string 73 id interface{} // nil when watching collection 74 } 75 76 func (k watchKey) String() string { 77 coll := "collection " + k.c 78 if k.id == nil { 79 return coll 80 } 81 return fmt.Sprintf("document %v in %s", k.id, coll) 82 } 83 84 // match returns whether the receiving watch key, 85 // which may refer to a particular item or 86 // an entire collection, matches k1, which refers 87 // to a particular item. 88 func (k watchKey) match(k1 watchKey) bool { 89 if k.c != k1.c { 90 return false 91 } 92 if k.id == nil { 93 // k refers to entire collection 94 return true 95 } 96 return k.id == k1.id 97 } 98 99 type watchInfo struct { 100 ch chan<- Change 101 revno int64 102 filter func(interface{}) bool 103 } 104 105 type event struct { 106 ch chan<- Change 107 key watchKey 108 revno int64 109 } 110 111 // New returns a new Watcher observing the changelog collection, 112 // which must be a capped collection maintained by mgo/txn. 113 func New(changelog *mgo.Collection) *Watcher { 114 w := &Watcher{ 115 log: changelog, 116 watches: make(map[watchKey][]watchInfo), 117 current: make(map[watchKey]int64), 118 request: make(chan interface{}), 119 } 120 go func() { 121 w.tomb.Kill(w.loop()) 122 w.tomb.Done() 123 }() 124 return w 125 } 126 127 // Stop stops all the watcher activities. 128 func (w *Watcher) Stop() error { 129 w.tomb.Kill(nil) 130 return w.tomb.Wait() 131 } 132 133 // Dead returns a channel that is closed when the watcher has stopped. 134 func (w *Watcher) Dead() <-chan struct{} { 135 return w.tomb.Dead() 136 } 137 138 // Err returns the error with which the watcher stopped. 139 // It returns nil if the watcher stopped cleanly, tomb.ErrStillAlive 140 // if the watcher is still running properly, or the respective error 141 // if the watcher is terminating or has terminated with an error. 142 func (w *Watcher) Err() error { 143 return w.tomb.Err() 144 } 145 146 type reqWatch struct { 147 key watchKey 148 info watchInfo 149 } 150 151 type reqUnwatch struct { 152 key watchKey 153 ch chan<- Change 154 } 155 156 type reqSync struct{} 157 158 func (w *Watcher) sendReq(req interface{}) { 159 select { 160 case w.request <- req: 161 case <-w.tomb.Dying(): 162 } 163 } 164 165 // Watch starts watching the given collection and document id. 166 // An event will be sent onto ch whenever a matching document's txn-revno 167 // field is observed to change after a transaction is applied. The revno 168 // parameter holds the currently known revision number for the document. 169 // Non-existent documents are represented by a -1 revno. 170 func (w *Watcher) Watch(collection string, id interface{}, revno int64, ch chan<- Change) { 171 if id == nil { 172 panic("watcher: cannot watch a document with nil id") 173 } 174 w.sendReq(reqWatch{watchKey{collection, id}, watchInfo{ch, revno, nil}}) 175 } 176 177 // WatchCollection starts watching the given collection. 178 // An event will be sent onto ch whenever the txn-revno field is observed 179 // to change after a transaction is applied for any document in the collection. 180 func (w *Watcher) WatchCollection(collection string, ch chan<- Change) { 181 w.WatchCollectionWithFilter(collection, ch, nil) 182 } 183 184 // WatchCollectionWithFilter starts watching the given collection. 185 // An event will be sent onto ch whenever the txn-revno field is observed 186 // to change after a transaction is applied for any document in the collection, so long as the 187 // specified filter function returns true when called with the document id value. 188 func (w *Watcher) WatchCollectionWithFilter(collection string, ch chan<- Change, filter func(interface{}) bool) { 189 w.sendReq(reqWatch{watchKey{collection, nil}, watchInfo{ch, 0, filter}}) 190 } 191 192 // Unwatch stops watching the given collection and document id via ch. 193 func (w *Watcher) Unwatch(collection string, id interface{}, ch chan<- Change) { 194 if id == nil { 195 panic("watcher: cannot unwatch a document with nil id") 196 } 197 w.sendReq(reqUnwatch{watchKey{collection, id}, ch}) 198 } 199 200 // UnwatchCollection stops watching the given collection via ch. 201 func (w *Watcher) UnwatchCollection(collection string, ch chan<- Change) { 202 w.sendReq(reqUnwatch{watchKey{collection, nil}, ch}) 203 } 204 205 // StartSync forces the watcher to load new events from the database. 206 func (w *Watcher) StartSync() { 207 w.sendReq(reqSync{}) 208 } 209 210 // Period is the delay between each sync. 211 // It must not be changed when any watchers are active. 212 var Period time.Duration = 5 * time.Second 213 214 // loop implements the main watcher loop. 215 func (w *Watcher) loop() error { 216 next := time.After(Period) 217 w.needSync = true 218 if err := w.initLastId(); err != nil { 219 return mask(err) 220 } 221 for { 222 if w.needSync { 223 if err := w.sync(); err != nil { 224 return mask(err) 225 } 226 w.flush() 227 next = time.After(Period) 228 } 229 select { 230 case <-w.tomb.Dying(): 231 return tomb.ErrDying 232 case <-next: 233 next = time.After(Period) 234 w.needSync = true 235 case req := <-w.request: 236 w.handle(req) 237 w.flush() 238 } 239 } 240 } 241 242 // flush sends all pending events to their respective channels. 243 func (w *Watcher) flush() { 244 // refreshEvents are stored newest first. 245 for i := len(w.syncEvents) - 1; i >= 0; i-- { 246 e := &w.syncEvents[i] 247 for e.ch != nil { 248 select { 249 case <-w.tomb.Dying(): 250 return 251 case req := <-w.request: 252 w.handle(req) 253 continue 254 case e.ch <- Change{e.key.c, e.key.id, e.revno}: 255 } 256 break 257 } 258 } 259 // requestEvents are stored oldest first, and 260 // may grow during the loop. 261 for i := 0; i < len(w.requestEvents); i++ { 262 e := &w.requestEvents[i] 263 for e.ch != nil { 264 select { 265 case <-w.tomb.Dying(): 266 return 267 case req := <-w.request: 268 w.handle(req) 269 continue 270 case e.ch <- Change{e.key.c, e.key.id, e.revno}: 271 } 272 break 273 } 274 } 275 w.syncEvents = w.syncEvents[:0] 276 w.requestEvents = w.requestEvents[:0] 277 } 278 279 // handle deals with requests delivered by the public API 280 // onto the background watcher goroutine. 281 func (w *Watcher) handle(req interface{}) { 282 debugf("state/watcher: got request: %#v", req) 283 switch r := req.(type) { 284 case reqSync: 285 w.needSync = true 286 case reqWatch: 287 for _, info := range w.watches[r.key] { 288 if info.ch == r.info.ch { 289 panic(errors.Newf("tried to re-add channel %v for %s", info.ch, r.key)) 290 } 291 } 292 if revno, ok := w.current[r.key]; ok && (revno > r.info.revno || revno == -1 && r.info.revno >= 0) { 293 r.info.revno = revno 294 w.requestEvents = append(w.requestEvents, event{r.info.ch, r.key, revno}) 295 } 296 w.watches[r.key] = append(w.watches[r.key], r.info) 297 case reqUnwatch: 298 watches := w.watches[r.key] 299 removed := false 300 for i, info := range watches { 301 if info.ch == r.ch { 302 watches[i] = watches[len(watches)-1] 303 w.watches[r.key] = watches[:len(watches)-1] 304 removed = true 305 break 306 } 307 } 308 if !removed { 309 panic(errors.Newf("tried to remove missing channel %v for %s", r.ch, r.key)) 310 } 311 for i := range w.requestEvents { 312 e := &w.requestEvents[i] 313 if r.key.match(e.key) && e.ch == r.ch { 314 e.ch = nil 315 } 316 } 317 for i := range w.syncEvents { 318 e := &w.syncEvents[i] 319 if r.key.match(e.key) && e.ch == r.ch { 320 e.ch = nil 321 } 322 } 323 default: 324 panic(errors.Newf("unknown request: %T", req)) 325 } 326 } 327 328 type logInfo struct { 329 Docs []interface{} `bson:"d"` 330 Revnos []int64 `bson:"r"` 331 } 332 333 // initLastId reads the most recent changelog document and initializes 334 // lastId with it. This causes all history that precedes the creation 335 // of the watcher to be ignored. 336 func (w *Watcher) initLastId() error { 337 var entry struct { 338 Id interface{} "_id" 339 } 340 err := w.log.Find(nil).Sort("-$natural").One(&entry) 341 if err != nil && err != mgo.ErrNotFound { 342 return err 343 } 344 w.lastId = entry.Id 345 return nil 346 } 347 348 // sync updates the watcher knowledge from the database, and 349 // queues events to observing channels. 350 func (w *Watcher) sync() error { 351 w.needSync = false 352 // Iterate through log events in reverse insertion order (newest first). 353 iter := w.log.Find(nil).Batch(10).Sort("-$natural").Iter() 354 seen := make(map[watchKey]bool) 355 first := true 356 lastId := w.lastId 357 var entry bson.D 358 for iter.Next(&entry) { 359 if len(entry) == 0 { 360 debugf("state/watcher: got empty changelog document") 361 } 362 id := entry[0] 363 if id.Name != "_id" { 364 panic("watcher: _id field isn't first entry") 365 } 366 if first { 367 w.lastId = id.Value 368 first = false 369 } 370 if id.Value == lastId { 371 break 372 } 373 debugf("state/watcher: got changelog document: %#v", entry) 374 for _, c := range entry[1:] { 375 // See txn's Runner.ChangeLog for the structure of log entries. 376 var d, r []interface{} 377 dr, _ := c.Value.(bson.D) 378 for _, item := range dr { 379 switch item.Name { 380 case "d": 381 d, _ = item.Value.([]interface{}) 382 case "r": 383 r, _ = item.Value.([]interface{}) 384 } 385 } 386 if len(d) == 0 || len(d) != len(r) { 387 log.Warningf("state/watcher: changelog has invalid collection document: %#v", c) 388 continue 389 } 390 for i := len(d) - 1; i >= 0; i-- { 391 key := watchKey{c.Name, d[i]} 392 if seen[key] { 393 continue 394 } 395 seen[key] = true 396 revno, ok := r[i].(int64) 397 if !ok { 398 log.Warningf("state/watcher: changelog has revno with type %T: %#v", r[i], r[i]) 399 continue 400 } 401 if revno < 0 { 402 revno = -1 403 } 404 if w.current[key] == revno { 405 continue 406 } 407 w.current[key] = revno 408 // Queue notifications for per-collection watches. 409 for _, info := range w.watches[watchKey{c.Name, nil}] { 410 if info.filter != nil && !info.filter(d[i]) { 411 continue 412 } 413 w.syncEvents = append(w.syncEvents, event{info.ch, key, revno}) 414 } 415 // Queue notifications for per-document watches. 416 infos := w.watches[key] 417 for i, info := range infos { 418 if revno > info.revno || revno < 0 && info.revno >= 0 { 419 infos[i].revno = revno 420 w.syncEvents = append(w.syncEvents, event{info.ch, key, revno}) 421 } 422 } 423 } 424 } 425 } 426 if iter.Err() != nil { 427 return errors.Newf("watcher iteration error: %v", iter.Err()) 428 } 429 return nil 430 } 431 432 func debugf(f string, a ...interface{}) { 433 if Debug { 434 log.Debugf(f, a...) 435 } 436 }