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  }