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