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