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