github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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/tomb" 16 17 "launchpad.net/juju-core/log" 18 ) 19 20 // Debug specifies whether the package will log debug 21 // messages. 22 // TODO(rog) allow debug level setting in the log package. 23 var Debug = false 24 25 // A Watcher can watch any number of collections and documents for changes. 26 type Watcher struct { 27 tomb tomb.Tomb 28 log *mgo.Collection 29 30 // watches holds the observers managed by Watch/Unwatch. 31 watches map[watchKey][]watchInfo 32 33 // current holds the current txn-revno values for all the observed 34 // documents known to exist. Documents not observed or deleted are 35 // omitted from this map and are considered to have revno -1. 36 current map[watchKey]int64 37 38 // needSync is set when a synchronization should take 39 // place. 40 needSync bool 41 42 // syncEvents and requestEvents contain the events to be 43 // dispatched to the watcher channels. They're queued during 44 // processing and flushed at the end to simplify the algorithm. 45 // The two queues are separated because events from sync are 46 // handled in reverse order due to the way the algorithm works. 47 syncEvents, requestEvents []event 48 49 // request is used to deliver requests from the public API into 50 // the the goroutine loop. 51 request chan interface{} 52 53 // lastId is the most recent transaction id observed by a sync. 54 lastId interface{} 55 } 56 57 // A Change holds information about a document change. 58 type Change struct { 59 // C and Id hold the collection name and document _id field value. 60 C string 61 Id interface{} 62 63 // Revno is the latest known value for the document's txn-revno 64 // field, or -1 if the document was deleted. 65 Revno int64 66 } 67 68 type watchKey struct { 69 c string 70 id interface{} // nil when watching collection 71 } 72 73 func (k watchKey) String() string { 74 coll := "collection " + k.c 75 if k.id == nil { 76 return coll 77 } 78 return fmt.Sprintf("document %v in %s", k.id, coll) 79 } 80 81 // match returns whether the receiving watch key, 82 // which may refer to a particular item or 83 // an entire collection, matches k1, which refers 84 // to a particular item. 85 func (k watchKey) match(k1 watchKey) bool { 86 if k.c != k1.c { 87 return false 88 } 89 if k.id == nil { 90 // k refers to entire collection 91 return true 92 } 93 return k.id == k1.id 94 } 95 96 type watchInfo struct { 97 ch chan<- Change 98 revno int64 99 filter func(interface{}) bool 100 } 101 102 type event struct { 103 ch chan<- Change 104 key watchKey 105 revno int64 106 } 107 108 // New returns a new Watcher observing the changelog collection, 109 // which must be a capped collection maintained by mgo/txn. 110 func New(changelog *mgo.Collection) *Watcher { 111 w := &Watcher{ 112 log: changelog, 113 watches: make(map[watchKey][]watchInfo), 114 current: make(map[watchKey]int64), 115 request: make(chan interface{}), 116 } 117 go func() { 118 w.tomb.Kill(w.loop()) 119 w.tomb.Done() 120 }() 121 return w 122 } 123 124 // Stop stops all the watcher activities. 125 func (w *Watcher) Stop() error { 126 w.tomb.Kill(nil) 127 return w.tomb.Wait() 128 } 129 130 // Dead returns a channel that is closed when the watcher has stopped. 131 func (w *Watcher) Dead() <-chan struct{} { 132 return w.tomb.Dead() 133 } 134 135 // Err returns the error with which the watcher stopped. 136 // It returns nil if the watcher stopped cleanly, tomb.ErrStillAlive 137 // if the watcher is still running properly, or the respective error 138 // if the watcher is terminating or has terminated with an error. 139 func (w *Watcher) Err() error { 140 return w.tomb.Err() 141 } 142 143 type reqWatch struct { 144 key watchKey 145 info watchInfo 146 } 147 148 type reqUnwatch struct { 149 key watchKey 150 ch chan<- Change 151 } 152 153 type reqSync struct{} 154 155 func (w *Watcher) sendReq(req interface{}) { 156 select { 157 case w.request <- req: 158 case <-w.tomb.Dying(): 159 } 160 } 161 162 // Watch starts watching the given collection and document id. 163 // An event will be sent onto ch whenever a matching document's txn-revno 164 // field is observed to change after a transaction is applied. The revno 165 // parameter holds the currently known revision number for the document. 166 // Non-existent documents are represented by a -1 revno. 167 func (w *Watcher) Watch(collection string, id interface{}, revno int64, ch chan<- Change) { 168 if id == nil { 169 panic("watcher: cannot watch a document with nil id") 170 } 171 w.sendReq(reqWatch{watchKey{collection, id}, watchInfo{ch, revno, nil}}) 172 } 173 174 // WatchCollection starts watching the given collection. 175 // An event will be sent onto ch whenever the txn-revno field is observed 176 // to change after a transaction is applied for any document in the collection. 177 func (w *Watcher) WatchCollection(collection string, ch chan<- Change) { 178 w.WatchCollectionWithFilter(collection, ch, nil) 179 } 180 181 // WatchCollectionWithFilter starts watching the given collection. 182 // An event will be sent onto ch whenever the txn-revno field is observed 183 // to change after a transaction is applied for any document in the collection, so long as the 184 // specified filter function returns true when called with the document id value. 185 func (w *Watcher) WatchCollectionWithFilter(collection string, ch chan<- Change, filter func(interface{}) bool) { 186 w.sendReq(reqWatch{watchKey{collection, nil}, watchInfo{ch, 0, filter}}) 187 } 188 189 // Unwatch stops watching the given collection and document id via ch. 190 func (w *Watcher) Unwatch(collection string, id interface{}, ch chan<- Change) { 191 if id == nil { 192 panic("watcher: cannot unwatch a document with nil id") 193 } 194 w.sendReq(reqUnwatch{watchKey{collection, id}, ch}) 195 } 196 197 // UnwatchCollection stops watching the given collection via ch. 198 func (w *Watcher) UnwatchCollection(collection string, ch chan<- Change) { 199 w.sendReq(reqUnwatch{watchKey{collection, nil}, ch}) 200 } 201 202 // StartSync forces the watcher to load new events from the database. 203 func (w *Watcher) StartSync() { 204 w.sendReq(reqSync{}) 205 } 206 207 // Period is the delay between each sync. 208 // It must not be changed when any watchers are active. 209 var Period time.Duration = 5 * time.Second 210 211 // loop implements the main watcher loop. 212 func (w *Watcher) loop() error { 213 next := time.After(Period) 214 w.needSync = true 215 if err := w.initLastId(); err != nil { 216 return err 217 } 218 for { 219 if w.needSync { 220 if err := w.sync(); err != nil { 221 return err 222 } 223 w.flush() 224 next = time.After(Period) 225 } 226 select { 227 case <-w.tomb.Dying(): 228 return tomb.ErrDying 229 case <-next: 230 next = time.After(Period) 231 w.needSync = true 232 case req := <-w.request: 233 w.handle(req) 234 w.flush() 235 } 236 } 237 } 238 239 // flush sends all pending events to their respective channels. 240 func (w *Watcher) flush() { 241 // refreshEvents are stored newest first. 242 for i := len(w.syncEvents) - 1; i >= 0; i-- { 243 e := &w.syncEvents[i] 244 for e.ch != nil { 245 select { 246 case <-w.tomb.Dying(): 247 return 248 case req := <-w.request: 249 w.handle(req) 250 continue 251 case e.ch <- Change{e.key.c, e.key.id, e.revno}: 252 } 253 break 254 } 255 } 256 // requestEvents are stored oldest first, and 257 // may grow during the loop. 258 for i := 0; i < len(w.requestEvents); i++ { 259 e := &w.requestEvents[i] 260 for e.ch != nil { 261 select { 262 case <-w.tomb.Dying(): 263 return 264 case req := <-w.request: 265 w.handle(req) 266 continue 267 case e.ch <- Change{e.key.c, e.key.id, e.revno}: 268 } 269 break 270 } 271 } 272 w.syncEvents = w.syncEvents[:0] 273 w.requestEvents = w.requestEvents[:0] 274 } 275 276 // handle deals with requests delivered by the public API 277 // onto the background watcher goroutine. 278 func (w *Watcher) handle(req interface{}) { 279 debugf("state/watcher: got request: %#v", req) 280 switch r := req.(type) { 281 case reqSync: 282 w.needSync = true 283 case reqWatch: 284 for _, info := range w.watches[r.key] { 285 if info.ch == r.info.ch { 286 panic(fmt.Errorf("tried to re-add channel %v for %s", info.ch, r.key)) 287 } 288 } 289 if revno, ok := w.current[r.key]; ok && (revno > r.info.revno || revno == -1 && r.info.revno >= 0) { 290 r.info.revno = revno 291 w.requestEvents = append(w.requestEvents, event{r.info.ch, r.key, revno}) 292 } 293 w.watches[r.key] = append(w.watches[r.key], r.info) 294 case reqUnwatch: 295 watches := w.watches[r.key] 296 removed := false 297 for i, info := range watches { 298 if info.ch == r.ch { 299 watches[i] = watches[len(watches)-1] 300 w.watches[r.key] = watches[:len(watches)-1] 301 removed = true 302 break 303 } 304 } 305 if !removed { 306 panic(fmt.Errorf("tried to remove missing channel %v for %s", r.ch, r.key)) 307 } 308 for i := range w.requestEvents { 309 e := &w.requestEvents[i] 310 if r.key.match(e.key) && e.ch == r.ch { 311 e.ch = nil 312 } 313 } 314 for i := range w.syncEvents { 315 e := &w.syncEvents[i] 316 if r.key.match(e.key) && e.ch == r.ch { 317 e.ch = nil 318 } 319 } 320 default: 321 panic(fmt.Errorf("unknown request: %T", req)) 322 } 323 } 324 325 type logInfo struct { 326 Docs []interface{} `bson:"d"` 327 Revnos []int64 `bson:"r"` 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{} "_id" 336 } 337 err := w.log.Find(nil).Sort("-$natural").One(&entry) 338 if err != nil && err != mgo.ErrNotFound { 339 return 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 debugf("state/watcher: 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 debugf("state/watcher: 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 log.Warningf("state/watcher: 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 log.Warningf("state/watcher: 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 iter.Err() != nil { 424 return fmt.Errorf("watcher iteration error: %v", iter.Err()) 425 } 426 return nil 427 } 428 429 func debugf(f string, a ...interface{}) { 430 if Debug { 431 log.Debugf(f, a...) 432 } 433 }