github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  type logInfo struct {
   331  	Docs   []interface{} `bson:"d"`
   332  	Revnos []int64       `bson:"r"`
   333  }
   334  
   335  // initLastId reads the most recent changelog document and initializes
   336  // lastId with it. This causes all history that precedes the creation
   337  // of the watcher to be ignored.
   338  func (w *Watcher) initLastId() error {
   339  	var entry struct {
   340  		Id interface{} `bson:"_id"`
   341  	}
   342  	err := w.log.Find(nil).Sort("-$natural").One(&entry)
   343  	if err != nil && err != mgo.ErrNotFound {
   344  		return errors.Trace(err)
   345  	}
   346  	w.lastId = entry.Id
   347  	return nil
   348  }
   349  
   350  // sync updates the watcher knowledge from the database, and
   351  // queues events to observing channels.
   352  func (w *Watcher) sync() error {
   353  	w.needSync = false
   354  	// Iterate through log events in reverse insertion order (newest first).
   355  	iter := w.log.Find(nil).Batch(10).Sort("-$natural").Iter()
   356  	seen := make(map[watchKey]bool)
   357  	first := true
   358  	lastId := w.lastId
   359  	var entry bson.D
   360  	for iter.Next(&entry) {
   361  		if len(entry) == 0 {
   362  			logger.Tracef("got empty changelog document")
   363  		}
   364  		id := entry[0]
   365  		if id.Name != "_id" {
   366  			panic("watcher: _id field isn't first entry")
   367  		}
   368  		if first {
   369  			w.lastId = id.Value
   370  			first = false
   371  		}
   372  		if id.Value == lastId {
   373  			break
   374  		}
   375  		logger.Tracef("got changelog document: %#v", entry)
   376  		for _, c := range entry[1:] {
   377  			// See txn's Runner.ChangeLog for the structure of log entries.
   378  			var d, r []interface{}
   379  			dr, _ := c.Value.(bson.D)
   380  			for _, item := range dr {
   381  				switch item.Name {
   382  				case "d":
   383  					d, _ = item.Value.([]interface{})
   384  				case "r":
   385  					r, _ = item.Value.([]interface{})
   386  				}
   387  			}
   388  			if len(d) == 0 || len(d) != len(r) {
   389  				logger.Warningf("changelog has invalid collection document: %#v", c)
   390  				continue
   391  			}
   392  			for i := len(d) - 1; i >= 0; i-- {
   393  				key := watchKey{c.Name, d[i]}
   394  				if seen[key] {
   395  					continue
   396  				}
   397  				seen[key] = true
   398  				revno, ok := r[i].(int64)
   399  				if !ok {
   400  					logger.Warningf("changelog has revno with type %T: %#v", r[i], r[i])
   401  					continue
   402  				}
   403  				if revno < 0 {
   404  					revno = -1
   405  				}
   406  				if w.current[key] == revno {
   407  					continue
   408  				}
   409  				w.current[key] = revno
   410  				// Queue notifications for per-collection watches.
   411  				for _, info := range w.watches[watchKey{c.Name, nil}] {
   412  					if info.filter != nil && !info.filter(d[i]) {
   413  						continue
   414  					}
   415  					w.syncEvents = append(w.syncEvents, event{info.ch, key, revno})
   416  				}
   417  				// Queue notifications for per-document watches.
   418  				infos := w.watches[key]
   419  				for i, info := range infos {
   420  					if revno > info.revno || revno < 0 && info.revno >= 0 {
   421  						infos[i].revno = revno
   422  						w.syncEvents = append(w.syncEvents, event{info.ch, key, revno})
   423  					}
   424  				}
   425  			}
   426  		}
   427  	}
   428  	if err := iter.Close(); err != nil {
   429  		return errors.Errorf("watcher iteration error: %v", err)
   430  	}
   431  	return nil
   432  }