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  }