func TestRebuildRoleChannels(t *testing.T) { computer := mockComputer{channels: ch.AtSequence(ch.SetOf("derived1", "derived2"), 1)} auth := NewAuthenticator(gTestBucket, &computer) role, _ := auth.NewRole("testRole", ch.SetOf("explicit1")) err := auth.InvalidateChannels(role) assert.Equals(t, err, nil) role2, err := auth.GetRole("testRole") assert.Equals(t, err, nil) assert.DeepEquals(t, role2.Channels(), ch.AtSequence(ch.SetOf("explicit1", "derived1", "derived2", "!"), 1)) }
func TestRebuildUserChannels(t *testing.T) { computer := mockComputer{channels: ch.AtSequence(ch.SetOf("derived1", "derived2"), 1)} auth := NewAuthenticator(gTestBucket, &computer) user, _ := auth.NewUser("testUser", "password", ch.SetOf("explicit1")) user.setChannels(nil) err := auth.Save(user) assert.Equals(t, err, nil) user2, err := auth.GetUser("testUser") assert.Equals(t, err, nil) assert.DeepEquals(t, user2.Channels(), ch.AtSequence(ch.SetOf("explicit1", "derived1", "derived2", "!"), 1)) }
func TestRebuildUserRoles(t *testing.T) { computer := mockComputer{roles: ch.AtSequence(base.SetOf("role1", "role2"), 3)} auth := NewAuthenticator(gTestBucket, &computer) user, _ := auth.NewUser("testUser", "letmein", nil) user.SetExplicitRoles(ch.TimedSet{"role3": ch.NewVbSimpleSequence(1), "role1": ch.NewVbSimpleSequence(1)}) err := auth.InvalidateRoles(user) assert.Equals(t, err, nil) user2, err := auth.GetUser("testUser") assert.Equals(t, err, nil) expected := ch.AtSequence(base.SetOf("role1", "role3"), 1) expected.AddChannel("role2", 3) assert.DeepEquals(t, user2.RoleNames(), expected) }
// Updates a document's channel/role UserAccessMap with new access settings from an AccessMap. // Returns an array of the user/role names whose access has changed as a result. func (accessMap *UserAccessMap) updateAccess(doc *document, newAccess channels.AccessMap) (changedUsers []string) { // Update users already appearing in doc.Access: for name, access := range *accessMap { if access.UpdateAtSequence(newAccess[name], doc.Sequence) { if len(access) == 0 { delete(*accessMap, name) } changedUsers = append(changedUsers, name) } } // Add new users who are in newAccess but not accessMap: for name, access := range newAccess { if _, existed := (*accessMap)[name]; !existed { if *accessMap == nil { *accessMap = UserAccessMap{} } (*accessMap)[name] = channels.AtSequence(access, doc.Sequence) changedUsers = append(changedUsers, name) } } if changedUsers != nil { what := "channel" if accessMap == &doc.RoleAccess { what = "role" } base.LogTo("Access", "Doc %q grants %s access: %v", doc.ID, what, *accessMap) } return changedUsers }
func (auth *Authenticator) defaultGuestUser() User { user := &userImpl{ roleImpl: roleImpl{ ExplicitChannels_: ch.AtSequence(ch.SetOf(), 1), }, userImplBody: userImplBody{ Disabled_: true, }, auth: auth, } user.Channels_ = user.ExplicitChannels_.Copy() return user }
func (user *userImpl) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &user.userImplBody); err != nil { return err } else if err := json.Unmarshal(data, &user.roleImpl); err != nil { return err } // Migrate "admin_roles" field: if user.OldExplicitRoles_ != nil { user.ExplicitRoles_ = ch.AtSequence(base.SetFromArray(user.OldExplicitRoles_), 1) user.OldExplicitRoles_ = nil } return nil }
func TestAccessFunction(t *testing.T) { /* var logKeys = map[string]bool { "CRUD": true, "Access": true, } base.UpdateLogKeys(logKeys, true) */ db := setupTestDB(t) defer tearDownTestDB(t, db) authenticator := auth.NewAuthenticator(db.Bucket, db) var err error db.ChannelMapper = channels.NewChannelMapper(`function(doc){access(doc.users,doc.userChannels);}`) user, _ := authenticator.NewUser("naomi", "letmein", channels.SetOf("Netflix")) user.SetExplicitRoles(channels.TimedSet{"animefan": channels.NewVbSimpleSequence(1), "tumblr": channels.NewVbSimpleSequence(1)}) assertNoError(t, authenticator.Save(user), "Save") body := Body{"users": []string{"naomi"}, "userChannels": []string{"Hulu"}} _, err = db.Put("doc1", body) assertNoError(t, err, "") body = Body{"users": []string{"role:animefan"}, "userChannels": []string{"CrunchyRoll"}} _, err = db.Put("doc2", body) assertNoError(t, err, "") // Create the role _after_ creating the documents, to make sure the previously-indexed access // privileges are applied. role, _ := authenticator.NewRole("animefan", nil) authenticator.Save(role) user, err = authenticator.GetUser("naomi") assertNoError(t, err, "GetUser") expected := channels.AtSequence(channels.SetOf("Hulu", "Netflix", "!"), 1) assert.DeepEquals(t, user.Channels(), expected) expected.AddChannel("CrunchyRoll", 2) assert.DeepEquals(t, user.InheritedChannels(), expected) }
func WriteDirectWithChannelGrant(db *Database, channelArray []string, sequence uint64, username string, channelGrantArray []string) { docId := fmt.Sprintf("doc-%v", sequence) rev := "1-a" chanMap := make(map[string]*channels.ChannelRemoval, 10) for _, channel := range channelArray { chanMap[channel] = nil } accessMap := make(map[string]channels.TimedSet) channelTimedSet := channels.AtSequence(base.SetFromArray(channelGrantArray), sequence) accessMap[username] = channelTimedSet syncData := &syncData{ CurrentRev: rev, Sequence: sequence, Channels: chanMap, Access: accessMap, } db.Bucket.Add(docId, 0, Body{"_sync": syncData, "key": docId}) }
func TestRoleInheritance(t *testing.T) { // Create some roles: auth := NewAuthenticator(gTestBucket, nil) role, _ := auth.NewRole("square", ch.SetOf("dull", "duller", "dullest")) assert.Equals(t, auth.Save(role), nil) role, _ = auth.NewRole("frood", ch.SetOf("hoopy", "hoopier", "hoopiest")) assert.Equals(t, auth.Save(role), nil) user, _ := auth.NewUser("arthur", "password", ch.SetOf("britain")) user.(*userImpl).setRolesSince(ch.TimedSet{"square": ch.NewVbSimpleSequence(0x3), "nonexistent": ch.NewVbSimpleSequence(0x42), "frood": ch.NewVbSimpleSequence(0x4)}) assert.DeepEquals(t, user.RoleNames(), ch.TimedSet{"square": ch.NewVbSimpleSequence(0x3), "nonexistent": ch.NewVbSimpleSequence(0x42), "frood": ch.NewVbSimpleSequence(0x4)}) auth.Save(user) user2, err := auth.GetUser("arthur") assert.Equals(t, err, nil) log.Printf("Channels = %s", user2.Channels()) assert.DeepEquals(t, user2.Channels(), ch.AtSequence(ch.SetOf("!", "britain"), 1)) assert.DeepEquals(t, user2.InheritedChannels(), ch.TimedSet{"!": ch.NewVbSimpleSequence(0x1), "britain": ch.NewVbSimpleSequence(0x1), "dull": ch.NewVbSimpleSequence(0x3), "duller": ch.NewVbSimpleSequence(0x3), "dullest": ch.NewVbSimpleSequence(0x3), "hoopy": ch.NewVbSimpleSequence(0x4), "hoopier": ch.NewVbSimpleSequence(0x4), "hoopiest": ch.NewVbSimpleSequence(0x4)}) assert.True(t, user2.CanSeeChannel("britain")) assert.True(t, user2.CanSeeChannel("duller")) assert.True(t, user2.CanSeeChannel("hoopy")) assert.Equals(t, user2.AuthorizeAllChannels(ch.SetOf("britain", "dull", "hoopiest")), nil) }
// 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 userChangeCount uint64 var addedChannels base.Set // Tracks channels added to the user during changes processing. if options.Wait { // Note (Adam): I don't think there's a reason to set this to false here. We're outside the // main iteration loop (so the if check above should only happen once), and I don't believe // options.Wait is referenced elsewhere once MultiChangesFeed is called. Leaving it as-is // makes it possible for channels to identify whether a getChanges call has options.Wait set to true, // which is useful to identify active change listeners. However, it's possible there's a subtlety of // longpoll or continuous processing I'm missing here - leaving this note instead of just deleting for now. //options.Wait = false changeWaiter = db.startChangeWaiter(chans) userChangeCount = changeWaiter.CurrentUserCount() } 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 } // 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) } // 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) // 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) // 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: 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: userChangeCount, addedChannels, err = db.checkForUserUpdates(userChangeCount, 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 } } }() return output, nil }
func TestUserAccess(t *testing.T) { // User with no access: auth := NewAuthenticator(gTestBucket, nil) user, _ := auth.NewUser("foo", "password", nil) assert.DeepEquals(t, user.ExpandWildCardChannel(ch.SetOf("*")), ch.SetOf("!")) assert.False(t, user.CanSeeChannel("x")) assert.True(t, canSeeAllChannels(user, ch.SetOf())) assert.False(t, canSeeAllChannels(user, ch.SetOf("x"))) assert.False(t, canSeeAllChannels(user, ch.SetOf("x", "y"))) assert.False(t, canSeeAllChannels(user, ch.SetOf("*"))) assert.False(t, user.AuthorizeAllChannels(ch.SetOf("*")) == nil) assert.False(t, user.AuthorizeAnyChannel(ch.SetOf("x", "y")) == nil) assert.False(t, user.AuthorizeAnyChannel(ch.SetOf()) == nil) // User with access to one channel: user.setChannels(ch.AtSequence(ch.SetOf("x"), 1)) assert.DeepEquals(t, user.ExpandWildCardChannel(ch.SetOf("*")), ch.SetOf("x")) assert.True(t, canSeeAllChannels(user, ch.SetOf())) assert.True(t, canSeeAllChannels(user, ch.SetOf("x"))) assert.False(t, canSeeAllChannels(user, ch.SetOf("x", "y"))) assert.False(t, user.AuthorizeAllChannels(ch.SetOf("x", "y")) == nil) assert.False(t, user.AuthorizeAllChannels(ch.SetOf("*")) == nil) assert.True(t, user.AuthorizeAnyChannel(ch.SetOf("x", "y")) == nil) assert.False(t, user.AuthorizeAnyChannel(ch.SetOf("y")) == nil) assert.False(t, user.AuthorizeAnyChannel(ch.SetOf()) == nil) // User with access to one channel and one derived channel: user.setChannels(ch.AtSequence(ch.SetOf("x", "z"), 1)) assert.DeepEquals(t, user.ExpandWildCardChannel(ch.SetOf("*")), ch.SetOf("x", "z")) assert.DeepEquals(t, user.ExpandWildCardChannel(ch.SetOf("x")), ch.SetOf("x")) assert.True(t, canSeeAllChannels(user, ch.SetOf())) assert.True(t, canSeeAllChannels(user, ch.SetOf("x"))) assert.False(t, canSeeAllChannels(user, ch.SetOf("x", "y"))) assert.False(t, user.AuthorizeAllChannels(ch.SetOf("x", "y")) == nil) assert.False(t, user.AuthorizeAllChannels(ch.SetOf("*")) == nil) // User with access to two channels: user.setChannels(ch.AtSequence(ch.SetOf("x", "z"), 1)) assert.DeepEquals(t, user.ExpandWildCardChannel(ch.SetOf("*")), ch.SetOf("x", "z")) assert.DeepEquals(t, user.ExpandWildCardChannel(ch.SetOf("x")), ch.SetOf("x")) assert.True(t, canSeeAllChannels(user, ch.SetOf())) assert.True(t, canSeeAllChannels(user, ch.SetOf("x"))) assert.False(t, canSeeAllChannels(user, ch.SetOf("x", "y"))) assert.False(t, user.AuthorizeAllChannels(ch.SetOf("x", "y")) == nil) assert.False(t, user.AuthorizeAllChannels(ch.SetOf("*")) == nil) user.setChannels(ch.AtSequence(ch.SetOf("x", "y"), 1)) assert.DeepEquals(t, user.ExpandWildCardChannel(ch.SetOf("*")), ch.SetOf("x", "y")) assert.True(t, canSeeAllChannels(user, ch.SetOf())) assert.True(t, canSeeAllChannels(user, ch.SetOf("x"))) assert.True(t, canSeeAllChannels(user, ch.SetOf("x", "y"))) assert.False(t, canSeeAllChannels(user, ch.SetOf("x", "y", "z"))) assert.True(t, user.AuthorizeAllChannels(ch.SetOf("x", "y")) == nil) assert.False(t, user.AuthorizeAllChannels(ch.SetOf("*")) == nil) // User with wildcard access: user.setChannels(ch.AtSequence(ch.SetOf("*", "q"), 1)) assert.DeepEquals(t, user.ExpandWildCardChannel(ch.SetOf("*")), ch.SetOf("*", "q")) assert.True(t, user.CanSeeChannel("*")) assert.True(t, canSeeAllChannels(user, ch.SetOf())) assert.True(t, canSeeAllChannels(user, ch.SetOf("x"))) assert.True(t, canSeeAllChannels(user, ch.SetOf("x", "y"))) assert.True(t, user.AuthorizeAllChannels(ch.SetOf("x", "y")) == nil) assert.True(t, user.AuthorizeAllChannels(ch.SetOf("*")) == nil) assert.True(t, user.AuthorizeAnyChannel(ch.SetOf("x")) == nil) assert.True(t, user.AuthorizeAnyChannel(ch.SetOf("*")) == nil) assert.True(t, user.AuthorizeAnyChannel(ch.SetOf()) == nil) }
func (role *roleImpl) initRole(name string, channels base.Set) error { channels = ch.ExpandingStar(channels) role.Name_ = name role.ExplicitChannels_ = ch.AtSequence(channels, 1) return role.validate() }
// 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 userChangeCount uint64 var lowSequence uint64 var lateSequenceFeeds map[string]*lateSequenceFeed var addedChannels base.Set // Tracks channels added to the user during changes processing. // 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 } if options.Wait { options.Wait = false changeWaiter = db.startChangeWaiter(chans) userChangeCount = 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 { // 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) } // 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 userChangeCount, addedChannels, err = db.checkForUserUpdates(userChangeCount, 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 } // 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 }