// ConsensusSetSubscribe accepts a new subscriber who will receive a call to // ProcessConsensusChange every time there is a change in the consensus set. func (cs *ConsensusSet) ConsensusSetSubscribe(subscriber modules.ConsensusSetSubscriber) { id := cs.mu.Lock() cs.subscribers = append(cs.subscribers, subscriber) for i := range cs.changeLog { cc, err := cs.computeConsensusChange(i) if err != nil && build.DEBUG { panic(err) } subscriber.ProcessConsensusChange(cc) } cs.mu.Unlock(id) }
// initializeSubscribe will take a subscriber and feed them all of the // consensus changes that have occurred since the change provided. // // As a special case, using an empty id as the start will have all the changes // sent to the modules starting with the genesis block. func (cs *ConsensusSet) initializeSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID) error { return cs.db.View(func(tx *bolt.Tx) error { // 'exists' and 'entry' are going to be pointed to the first entry that // has not yet been seen by subscriber. var exists bool var entry changeEntry if start == modules.ConsensusChangeBeginning { // Special case: for modules.ConsensusChangeBeginning, create an // initial node pointing to the genesis block. The subscriber will // receive the diffs for all blocks in the consensus set, including // the genesis block. entry = cs.genesisEntry() exists = true } else if start == modules.ConsensusChangeRecent { // Special case: for modules.ConsensusChangeRecent, set up the // subscriber to start receiving only new blocks, but the // subscriber does not need to do any catch-up. For this // implementation, a no-op will have this effect. return nil } else { // The subscriber has provided an existing consensus change. // Because the subscriber already has this consensus change, // 'entry' and 'exists' need to be pointed at the next consensus // change. entry, exists = getEntry(tx, start) if !exists { // modules.ErrInvalidConsensusChangeID is a named error that // signals a break in synchronization between the consensus set // persistence and the subscriber persistence. Typically, // receiving this error means that the subscriber needs to // perform a rescan of the consensus set. return modules.ErrInvalidConsensusChangeID } entry, exists = entry.NextEntry(tx) } // Send all remaining consensus changes to the subscriber. for exists { cc, err := cs.computeConsensusChange(tx, entry) if err != nil { return err } subscriber.ProcessConsensusChange(cc) entry, exists = entry.NextEntry(tx) } return nil }) }
// initializePersistentSubscribe will take a subscriber and feed them all of the // consensus changes that have occurred since the change provided. // // As a special case, using an empty id as the start will have all the changes // sent to the modules starting with the genesis block. func (cs *ConsensusSet) initializePersistentSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID) error { return cs.db.View(func(tx *bolt.Tx) error { // 'exists' and 'entry' are going to be pointed to the first entry that // has not yet been seen by subscriber. var exists bool var entry changeEntry if start == (modules.ConsensusChangeID{}) { // Special case: if 'start' is blank, create an initial node // pointing to the genesis block. The subscriber will recieve the // diffs for all blocks in the consensus set, including the genesis // block. entry = cs.genesisEntry() exists = true } else { // The subscriber has provided an existing consensus change. // Because the subscriber already has this consensus change, // 'entry' and 'exists' need to be pointed at the next consensus // change. entry, exists = getEntry(tx, start) if !exists { // modules.ErrInvalidConsensusChangeID is a named error that // signals a break in synchronization between the consensus set // persistence and the subscriber persistence. Typically, // receiving this error means that the subscriber needs to // perform a rescan of the consensus set. return modules.ErrInvalidConsensusChangeID } entry, exists = entry.NextEntry(tx) } // Send all remaining consensus changes to the subscriber. for exists { cc, err := cs.computeConsensusChange(tx, entry) if err != nil { return err } subscriber.ProcessConsensusChange(cc) entry, exists = entry.NextEntry(tx) } return nil }) }
func (cs *rescanCS) ConsensusSetSubscribe(s modules.ConsensusSetSubscriber, lastChange modules.ConsensusChangeID) error { var start int if lastChange != (modules.ConsensusChangeID{}) { start = -1 for i, cc := range cs.changes { if cc.ID == lastChange { start = i break } } if start == -1 { return modules.ErrInvalidConsensusChangeID } } for _, cc := range cs.changes[start:] { s.ProcessConsensusChange(cc) } return nil }
// ConsensusSetSubscribe accepts a new subscriber who will receive a call to // ProcessConsensusChange every time there is a change in the consensus set. func (cs *ConsensusSet) ConsensusSetSubscribe(subscriber modules.ConsensusSetSubscriber) { cs.mu.Lock() cs.subscribers = append(cs.subscribers, subscriber) cs.mu.Demote() defer cs.mu.DemotedUnlock() err := cs.db.View(func(tx *bolt.Tx) error { for i := range cs.changeLog { cc, err := cs.computeConsensusChange(tx, cs.changeLog[i]) if err != nil && build.DEBUG { panic(err) } subscriber.ProcessConsensusChange(cc) } return nil }) if build.DEBUG && err != nil { panic(err) } }
// threadedSendUpdates sends updates to a specific subscriber as they become // available. One thread is needed per subscriber. A separate function was // needed due to race conditions; subscribers must receive updates in the // correct order. Furthermore, a deadlocked subscriber should not interfere // with consensus; updates cannot make blocking calls from any thread that is // holding a lock on consensus. The result is a construction where all updates // are added to a list of updates in the consensus set while the consensus set // is locked. Then, a separate thread for each subscriber will be notified (via // the update chan) that there are new updates. The thread will lock the // consensus set for long enough to get the updates, and then will unlock the // consensus set while it makes a blocking call to the subscriber. If the // subscriber deadlocks or has problems, the thread will stall indefinitely, // but the rest of consensus will not be disrupted. func (s *State) threadedSendUpdates(update chan struct{}, subscriber modules.ConsensusSetSubscriber) { i := 0 for { id := s.mu.RLock() updateCount := len(s.consensusChanges) s.mu.RUnlock(id) for i < updateCount { // Get the set of blocks that changed since the previous update. id := s.mu.RLock() cc := s.consensusChanges[i] s.mu.RUnlock(id) // Update the subscriber with the changes. subscriber.ReceiveConsensusSetUpdate(cc) i++ } // Wait until there has been another update. <-update } }
// ConsensusSetPersistentSubscribe adds a subscriber to the list of // subscribers, and gives them every consensus change that has occured since // the change with the provided id. // // As a special case, using an empty id as the start will have all the changes // sent to the modules starting with the genesis block. func (cs *ConsensusSet) ConsensusSetPersistentSubscribe(subscriber modules.ConsensusSetSubscriber, start modules.ConsensusChangeID) error { // Add the subscriber to the list of subscribers under lock, and then // demote while sending the subscriber all of the changes they've missed. cs.mu.Lock() cs.subscribers = append(cs.subscribers, subscriber) cs.mu.Demote() defer cs.mu.DemotedUnlock() err := cs.db.View(func(tx *bolt.Tx) error { var exists bool var entry changeEntry // Special case: if 'start' is blank, create an initial node pointing to // the genesis block. if start == (modules.ConsensusChangeID{}) { entry = cs.genesisEntry() exists = true } else { entry, exists = getEntry(tx, start) if !exists { return errChangeEntryNotFound } entry, exists = entry.NextEntry(tx) } for exists { cc, err := cs.computeConsensusChange(tx, entry) if err != nil { return err } subscriber.ProcessConsensusChange(cc) entry, exists = entry.NextEntry(tx) } return nil }) if err != nil { return err } return nil }