func RunNewPuller(schedule RunSchedule, c *api.SyncGatewayClient, channel, name, feedType string, wg *sync.WaitGroup) { defer wg.Done() glExpvars.Add("user_active", 1) var wakeupTime = time.Now() var lastSeq interface{} lastSeqFloat := c.GetLastSeq() if lastSeqFloat < 0 { Log("Puller, unable to establish last sequence number, exiting") return } lastSeq = lastSeqFloat if lastSeqFloat > MaxFirstFetch { //FIX: This generates a sequence ID using internal knowledge of the gateway's sequence format. lastSeq = lastSeqFloat - MaxFirstFetch // (for use with simple_sequences branch) } var changesFeed <-chan *api.Change var changesResponse *http.Response var cancelChangesFeed *bool var pendingChanges []*api.Change var fetchTimer <-chan time.Time var checkpointSeqId int64 = 0 var checkpointTimer <-chan time.Time online := false scheduleIndex := 0 start := time.Now() timer := time.NewTimer(schedule[scheduleIndex].start) Log("Puller %s first transition at %v", name, schedule[scheduleIndex].start) outer: for { select { case <-timer.C: // timer went off, transition modes timeOffset := time.Since(start) if online { glExpvars.Add("user_awake", -1) online = false scheduleIndex++ if scheduleIndex < len(schedule) { nextOnIn := schedule[scheduleIndex].start - timeOffset timer = time.NewTimer(nextOnIn) Log("Puller %s going offline, next on at %v", name, nextOnIn) if nextOnIn < 0 { log.Printf("WARNING: puller negative timer, exiting") return } } else { Log("Puller %s going offline, for good", name) } // transitioning off, cancel the changes feed, nil our changes feed channel *cancelChangesFeed = false if changesResponse != nil { changesResponse.Body.Close() } changesFeed = nil fetchTimer = nil checkpointTimer = nil pendingChanges = nil } else { glExpvars.Add("user_awake", 1) online = true if schedule[scheduleIndex].end != -1 { nextOffIn := schedule[scheduleIndex].end - timeOffset timer = time.NewTimer(nextOffIn) Log("Puller %s going online, next off at %v", name, nextOffIn) if nextOffIn < 0 { log.Printf("WARNING: puller negative timer, exiting") glExpvars.Add("user_awake", -1) return } } else { Log("Puller %s going online, for good", name) } // reset our wakeupTime to now wakeupTime = time.Now() Log("new wakeup time %v", wakeupTime) // transitioning on, start a changes feed changesFeed, cancelChangesFeed, changesResponse = c.GetChangesFeed(feedType, lastSeq) Log("** Puller %s watching changes using %s feed...", name, feedType) } case change, ok := <-changesFeed: // Received a change from the feed: if !ok { break outer } Log("Puller %s received %+v", name, *change) pendingChanges = append(pendingChanges, change) if fetchTimer == nil { fetchTimer = time.NewTimer(FetchDelay).C } case <-fetchTimer: // Time to get documents from the server: fetchTimer = nil var nDocs int nDocs, lastSeq = pullChanges(c, pendingChanges, wakeupTime) pendingChanges = nil Log("Puller %s done reading %d docs", name, nDocs) if nDocs > 0 && checkpointTimer == nil { checkpointTimer = time.NewTimer(CheckpointInterval).C } case <-checkpointTimer: // Time to save a checkpoint: checkpointTimer = nil checkpoint := api.Checkpoint{LastSequence: lastSeq} checkpointHash := fmt.Sprintf("%s-%s", name, Hash(strconv.FormatInt(checkpointSeqId, 10))) // save checkpoint asynchronously go c.SaveCheckpoint(checkpointHash, checkpoint) checkpointSeqId += 1 Log("Puller %s saved remote checkpoint", name) } } }