// Returns the (ordered) union of all of the changes made to multiple channels. func (db *Database) VectorMultiChangesFeed(chans base.Set, options ChangesOptions) (<-chan *ChangeEntry, error) { to := "" var userVbNo uint16 if db.user != nil && db.user.Name() != "" { to = fmt.Sprintf(" (to %s)", db.user.Name()) userVbNo = uint16(db.Bucket.VBHash(db.user.DocID())) } base.LogTo("Changes+", "Vector MultiChangesFeed(%s, %+v) ... %s", chans, options, to) output := make(chan *ChangeEntry, 50) go func() { var cumulativeClock *base.SyncSequenceClock var lastHashedValue string hashedEntryCount := 0 defer func() { base.LogTo("Changes+", "MultiChangesFeed done %s", to) close(output) }() var changeWaiter *changeWaiter var userCounter uint64 // Wait counter used to identify changes to the user document var addedChannels base.Set // Tracks channels added to the user during changes processing. var userChanged bool // Whether the user document has changed // Restrict to available channels, expand wild-card, and find since when these channels // have been available to the user: var channelsSince channels.TimedSet if db.user != nil { channelsSince = db.user.FilterToAvailableChannels(chans) } else { channelsSince = channels.AtSequence(chans, 0) } if options.Wait { changeWaiter = db.startChangeWaiter(channelsSince.AsSet()) userCounter = changeWaiter.CurrentUserCount() db.initializePrincipalPolling(changeWaiter.GetUserKeys()) } cumulativeClock = base.NewSyncSequenceClock() cumulativeClock.SetTo(getChangesClock(options.Since)) // This loop is used to re-run the fetch after every database change, in Wait mode outer: for { // Get the last polled stable sequence. We don't return anything later than stable sequence in each iteration stableClock, err := db.changeCache.GetStableClock(true) if err != nil { base.Warn("MultiChangesFeed got error reading stable sequence: %v", err) return } // Updates the changeWaiter to the current set of available channels. if changeWaiter != nil { changeWaiter.UpdateChannels(channelsSince) } base.LogTo("Changes+", "MultiChangesFeed: channels expand to %#v ... %s", channelsSince.String(), to) // Build the channel feeds. feeds, err := db.initializeChannelFeeds(channelsSince, options, addedChannels, userVbNo) if err != nil { return } // This loop reads the available entries from all the feeds in parallel, merges them, // and writes them to the output channel: current := make([]*ChangeEntry, len(feeds)) var sentSomething bool nextEntry := getNextSequenceFromFeeds(current, feeds) for { minEntry := nextEntry if minEntry == nil { break // Exit the loop when there are no more entries } // Calculate next entry here, to help identify whether minEntry is the last entry we're sending, // to guarantee hashing nextEntry = getNextSequenceFromFeeds(current, feeds) if options.ActiveOnly { if minEntry.Deleted || minEntry.allRemoved { continue } } // Don't send any entries later than the stable sequence if stableClock.GetSequence(minEntry.Seq.vbNo) < minEntry.Seq.Seq { continue } // Add the doc body or the conflicting rev IDs, if those options are set: if options.IncludeDocs || options.Conflicts { db.addDocToChangeEntry(minEntry, options) } // Clock handling if minEntry.Seq.TriggeredBy == 0 { // Update the cumulative clock, and stick it on the entry. cumulativeClock.SetMaxSequence(minEntry.Seq.vbNo, minEntry.Seq.Seq) // Force new hash generation for non-continuous changes feeds if this is the last entry to be sent - either // because there are no more entries in the channel feeds, or we're going to hit the limit. forceHash := false if options.Continuous == false && (nextEntry == nil || options.Limit == 1) { forceHash = true } lastHashedValue = db.calculateHashWhenNeeded( options, minEntry, cumulativeClock, &hashedEntryCount, lastHashedValue, forceHash, ) } else { // For backfill (triggered by), we don't want to update the cumulative clock. All entries triggered by the // same sequence reference the same triggered by clock, so it should only need to get hashed once. // If this is the first entry for this triggered by, initialize the triggered by clock's // hash value. if minEntry.Seq.TriggeredByClock.GetHashedValue() == "" { cumulativeClock.SetMaxSequence(minEntry.Seq.TriggeredByVbNo, minEntry.Seq.TriggeredBy) clockHash, err := db.SequenceHasher.GetHash(cumulativeClock) if err != nil { base.Warn("Error calculating hash for triggered by clock:%v", base.PrintClock(cumulativeClock)) } else { minEntry.Seq.TriggeredByClock.SetHashedValue(clockHash) } } } // Send the entry, and repeat the loop: base.LogTo("Changes+", "MultiChangesFeed sending %+v %s", minEntry, to) select { case <-options.Terminator: return case output <- minEntry: } sentSomething = true // Stop when we hit the limit (if any): if options.Limit > 0 { options.Limit-- if options.Limit == 0 { break outer } } } if !options.Continuous && (sentSomething || changeWaiter == nil) { break } // Update options.Since for use in the next outer loop iteration. options.Since.Clock = cumulativeClock // If nothing found, and in wait mode: wait for the db to change, then run again. // First notify the reader that we're waiting by sending a nil. base.LogTo("Changes+", "MultiChangesFeed waiting... %s", to) output <- nil waitForChanges: for { waitResponse := changeWaiter.Wait() if waitResponse == WaiterClosed { break outer } else if waitResponse == WaiterHasChanges { select { case <-options.Terminator: return default: break waitForChanges } } else if waitResponse == WaiterCheckTerminated { // Check whether I was terminated while waiting for a change. If not, resume wait. select { case <-options.Terminator: return default: } } } // Before checking again, update the User object in case its channel access has // changed while waiting: userChanged, userCounter, addedChannels, err = db.checkForUserUpdates(userCounter, changeWaiter) if userChanged && db.user != nil { channelsSince = db.user.FilterToAvailableChannels(chans) } if err != nil { change := makeErrorEntry("User not found during reload - terminating changes feed") base.LogTo("Changes+", "User not found during reload - terminating changes feed with entry %+v", change) output <- &change return } } }() return output, nil }
// Returns the (ordered) union of all of the changes made to multiple channels. func (db *Database) SimpleMultiChangesFeed(chans base.Set, options ChangesOptions) (<-chan *ChangeEntry, error) { to := "" if db.user != nil && db.user.Name() != "" { to = fmt.Sprintf(" (to %s)", db.user.Name()) } base.LogTo("Changes", "MultiChangesFeed(%s, %+v) ... %s", chans, options, to) output := make(chan *ChangeEntry, 50) go func() { defer func() { base.LogTo("Changes", "MultiChangesFeed done %s", to) close(output) }() var changeWaiter *changeWaiter var lowSequence uint64 var lateSequenceFeeds map[string]*lateSequenceFeed var userCounter uint64 // Wait counter used to identify changes to the user document var addedChannels base.Set // Tracks channels added to the user during changes processing. var userChanged bool // Whether the user document has changed in a given iteration loop // lowSequence is used to send composite keys to clients, so that they can obtain any currently // skipped sequences in a future iteration or request. oldestSkipped := db.changeCache.getOldestSkippedSequence() if oldestSkipped > 0 { lowSequence = oldestSkipped - 1 } else { lowSequence = 0 } // Restrict to available channels, expand wild-card, and find since when these channels // have been available to the user: var channelsSince channels.TimedSet if db.user != nil { channelsSince = db.user.FilterToAvailableChannels(chans) } else { channelsSince = channels.AtSequence(chans, 0) } if options.Wait { options.Wait = false changeWaiter = db.startChangeWaiter(channelsSince.AsSet()) userCounter = changeWaiter.CurrentUserCount() } // If a request has a low sequence that matches the current lowSequence, // ignore the low sequence. This avoids infinite looping of the records between // low::high. It also means any additional skipped sequences between low::high won't // be sent until low arrives or is abandoned. if options.Since.LowSeq != 0 && options.Since.LowSeq == lowSequence { options.Since.LowSeq = 0 } // For a continuous feed, initialise the lateSequenceFeeds that track late-arriving sequences // to the channel caches. if options.Continuous { lateSequenceFeeds = make(map[string]*lateSequenceFeed) } // This loop is used to re-run the fetch after every database change, in Wait mode outer: for { // Updates the changeWaiter to the current set of available channels if changeWaiter != nil { changeWaiter.UpdateChannels(channelsSince) } base.LogTo("Changes+", "MultiChangesFeed: channels expand to %#v ... %s", channelsSince, to) // lowSequence is used to send composite keys to clients, so that they can obtain any currently // skipped sequences in a future iteration or request. oldestSkipped = db.changeCache.getOldestSkippedSequence() if oldestSkipped > 0 { lowSequence = oldestSkipped - 1 } else { lowSequence = 0 } // Populate the parallel arrays of channels and names: feeds := make([]<-chan *ChangeEntry, 0, len(channelsSince)) names := make([]string, 0, len(channelsSince)) // Get read lock for late-arriving sequences, to avoid sending the same late arrival in // two different changes iterations. e.g. without the RLock, a late-arriving sequence // could be written to channel X during one iteration, and channel Y during another. Users // with access to both channels would see two versions on the feed. for name, vbSeqAddedAt := range channelsSince { chanOpts := options seqAddedAt := vbSeqAddedAt.Sequence // Check whether requires backfill based on addedChannels in this _changes feed isNewChannel := false if addedChannels != nil { _, isNewChannel = addedChannels[name] } // Check whether requires backfill based on current sequence, seqAddedAt // Triggered by handling: // 1. options.Since.TriggeredBy == seqAddedAt : We're in the middle of backfill for this channel, based // on the access grant in sequence options.Since.TriggeredBy. Normally the entire backfill would be done in one // changes feed iteration, but this can be split over multiple iterations when 'limit' is used. // 2. options.Since.TriggeredBy == 0 : Not currently doing a backfill // 3. options.Since.TriggeredBy != 0 and <= seqAddedAt: We're in the middle of a backfill for another channel, but the backfill for // this channel is still pending. Initiate the backfill for this channel - will be ordered below in the usual way (iterating over all channels) // Backfill required when seqAddedAt is before current sequence backfillRequired := seqAddedAt > 1 && options.Since.Before(SequenceID{Seq: seqAddedAt}) // Ensure backfill isn't already in progress for this seqAddedAt backfillPending := options.Since.TriggeredBy == 0 || options.Since.TriggeredBy < seqAddedAt if isNewChannel || (backfillRequired && backfillPending) { // Newly added channel so initiate backfill: chanOpts.Since = SequenceID{Seq: 0, TriggeredBy: seqAddedAt} } feed, err := db.changesFeed(name, chanOpts) if err != nil { base.Warn("MultiChangesFeed got error reading changes feed %q: %v", name, err) return } feeds = append(feeds, feed) names = append(names, name) // Late sequence handling - for out-of-order sequences prior to options.Since that // have arrived in the channel cache since this changes request started. Only need for // continuous feeds - one-off changes requests only need the standard channel cache. if options.Continuous { lateSequenceFeedHandler := lateSequenceFeeds[name] if lateSequenceFeedHandler != nil { latefeed, err := db.getLateFeed(lateSequenceFeedHandler) if err != nil { base.Warn("MultiChangesFeed got error reading late sequence feed %q: %v", name, err) } else { // Mark feed as actively used in this iteration. Used to remove lateSequenceFeeds // when the user loses channel access lateSequenceFeedHandler.active = true feeds = append(feeds, latefeed) names = append(names, fmt.Sprintf("late_%s", name)) } } else { // Initialize lateSequenceFeeds[name] for next iteration lateSequenceFeeds[name] = db.newLateSequenceFeed(name) } } } // If the user object has changed, create a special pseudo-feed for it: if db.user != nil { feeds, names = db.appendUserFeed(feeds, names, options) } current := make([]*ChangeEntry, len(feeds)) // This loop reads the available entries from all the feeds in parallel, merges them, // and writes them to the output channel: var sentSomething bool for { // Read more entries to fill up the current[] array: for i, cur := range current { if cur == nil && feeds[i] != nil { var ok bool current[i], ok = <-feeds[i] if !ok { feeds[i] = nil } } } // Find the current entry with the minimum sequence: minSeq := MaxSequenceID var minEntry *ChangeEntry for _, cur := range current { if cur != nil && cur.Seq.Before(minSeq) { minSeq = cur.Seq minEntry = cur } } if minEntry == nil { break // Exit the loop when there are no more entries } // Clear the current entries for the sequence just sent: if minEntry.Removed != nil { minEntry.allRemoved = true } for i, cur := range current { if cur != nil && cur.Seq == minSeq { current[i] = nil // Track whether this is a removal from all user's channels if cur.Removed == nil && minEntry.allRemoved == true { minEntry.allRemoved = false } // Also concatenate the matching entries' Removed arrays: if cur != minEntry && cur.Removed != nil { if minEntry.Removed == nil { minEntry.Removed = cur.Removed } else { minEntry.Removed = minEntry.Removed.Union(cur.Removed) } } } } if options.ActiveOnly { if minEntry.Deleted || minEntry.allRemoved { continue } } // Update options.Since for use in the next outer loop iteration. Only update // when minSeq is greater than the previous options.Since value - we don't want to // roll back the Since value when we get an late sequence is processed. if options.Since.Before(minSeq) { options.Since = minSeq } // Add the doc body or the conflicting rev IDs, if those options are set: if options.IncludeDocs || options.Conflicts { db.addDocToChangeEntry(minEntry, options) } // Update the low sequence on the entry we're going to send minEntry.Seq.LowSeq = lowSequence // Send the entry, and repeat the loop: base.LogTo("Changes+", "MultiChangesFeed sending %+v %s", minEntry, to) select { case <-options.Terminator: return case output <- minEntry: } sentSomething = true // Stop when we hit the limit (if any): if options.Limit > 0 { options.Limit-- if options.Limit == 0 { break outer } } } if !options.Continuous && (sentSomething || changeWaiter == nil) { break } // If nothing found, and in wait mode: wait for the db to change, then run again. // First notify the reader that we're waiting by sending a nil. base.LogTo("Changes+", "MultiChangesFeed waiting... %s", to) output <- nil waitForChanges: for { waitResponse := changeWaiter.Wait() if waitResponse == WaiterClosed { break outer } else if waitResponse == WaiterHasChanges { select { case <-options.Terminator: return default: break waitForChanges } } else if waitResponse == WaiterCheckTerminated { // Check whether I was terminated while waiting for a change. If not, resume wait. select { case <-options.Terminator: return default: } } } // Check whether user channel access has changed while waiting: var err error userChanged, userCounter, addedChannels, err = db.checkForUserUpdates(userCounter, changeWaiter) if err != nil { change := makeErrorEntry("User not found during reload - terminating changes feed") base.LogTo("Changes+", "User not found during reload - terminating changes feed with entry %+v", change) output <- &change return } if userChanged && db.user != nil { channelsSince = db.user.FilterToAvailableChannels(chans) } // Clean up inactive lateSequenceFeeds (because user has lost access to the channel) for channel, lateFeed := range lateSequenceFeeds { if !lateFeed.active { db.closeLateFeed(lateFeed) delete(lateSequenceFeeds, channel) } else { lateFeed.active = false } } } }() return output, nil }