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