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