github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/hostdb/hostdb.go (about) 1 // Package hostdb provides a HostDB object that implements the renter.hostDB 2 // interface. The blockchain is scanned for host announcements and hosts that 3 // are found get added to the host database. The database continually scans the 4 // set of hosts it has found and updates who is online. 5 package hostdb 6 7 // TODO: There should be some mechanism that detects if the number of active 8 // hosts is low. Then either the user can be informed, or the hostdb can start 9 // scanning hosts that have been offline for a while and are no longer 10 // prioritized by the scan loop. 11 12 // TODO: There should be some mechanism for detecting if the hostdb cannot 13 // connect to the internet. If it cannot, hosts should not be penalized for 14 // appearing to be offline, because they may not actually be offline and it'll 15 // unfairly over-penalize the hosts with the highest uptime. 16 17 import ( 18 "errors" 19 "os" 20 "path/filepath" 21 "sync" 22 23 "github.com/NebulousLabs/Sia/modules" 24 "github.com/NebulousLabs/Sia/persist" 25 "github.com/NebulousLabs/Sia/types" 26 ) 27 28 const ( 29 // scanPoolSize sets the buffer size of the channel that holds hosts which 30 // need to be scanned. A thread pool pulls from the scan pool to query 31 // hosts that are due for an update. 32 scanPoolSize = 1000 33 ) 34 35 var ( 36 errNilCS = errors.New("cannot create hostdb with nil consensus set") 37 ) 38 39 // The HostDB is a database of potential hosts. It assigns a weight to each 40 // host based on their hosting parameters, and then can select hosts at random 41 // for uploading files. 42 type HostDB struct { 43 // dependencies 44 dialer dialer 45 log *persist.Logger 46 persist persister 47 sleeper sleeper 48 49 // The hostTree is the root node of the tree that organizes hosts by 50 // weight. The tree is necessary for selecting weighted hosts at 51 // random. 'activeHosts' provides a lookup from hostname to the the 52 // corresponding node, as the hostTree is unsorted. A host is active if 53 // it is currently responding to queries about price and other 54 // settings. 55 hostTree *hostNode 56 activeHosts map[modules.NetAddress]*hostNode 57 58 // allHosts is a simple list of all known hosts by their network address, 59 // including hosts that are currently offline. 60 allHosts map[modules.NetAddress]*hostEntry 61 62 // the scanPool is a set of hosts that need to be scanned. There are a 63 // handful of goroutines constantly waiting on the channel for hosts to 64 // scan. 65 scanPool chan *hostEntry 66 67 // closeChan is used to shutdown the scanning threads. 68 closeChan chan struct{} 69 70 // threadGroup is used to wait for scanning threads to shutdown. 71 threadGroup sync.WaitGroup 72 73 blockHeight types.BlockHeight 74 lastChange modules.ConsensusChangeID 75 76 mu sync.RWMutex 77 } 78 79 // New returns a new HostDB. 80 func New(cs consensusSet, persistDir string) (*HostDB, error) { 81 // Check for nil inputs. 82 if cs == nil { 83 return nil, errNilCS 84 } 85 86 // Create the persist directory if it does not yet exist. 87 err := os.MkdirAll(persistDir, 0700) 88 if err != nil { 89 return nil, err 90 } 91 // Create the logger. 92 logger, err := persist.NewFileLogger(filepath.Join(persistDir, "hostdb.log")) 93 if err != nil { 94 return nil, err 95 } 96 97 // Create HostDB using production dependencies. 98 return newHostDB(cs, stdDialer{}, stdSleeper{}, newPersist(persistDir), logger) 99 } 100 101 // newHostDB creates a HostDB using the provided dependencies. It loads the old 102 // persistence data, spawns the HostDB's scanning threads, and subscribes it to 103 // the consensusSet. 104 func newHostDB(cs consensusSet, d dialer, s sleeper, p persister, l *persist.Logger) (*HostDB, error) { 105 // Create the HostDB object. 106 hdb := &HostDB{ 107 dialer: d, 108 sleeper: s, 109 persist: p, 110 log: l, 111 112 // TODO: should index by pubkey, not ip 113 activeHosts: make(map[modules.NetAddress]*hostNode), 114 allHosts: make(map[modules.NetAddress]*hostEntry), 115 scanPool: make(chan *hostEntry, scanPoolSize), 116 117 closeChan: make(chan struct{}), 118 } 119 120 // Load the prior persistence structures. 121 err := hdb.load() 122 if err != nil && !os.IsNotExist(err) { 123 return nil, err 124 } 125 126 err = cs.ConsensusSetSubscribe(hdb, hdb.lastChange) 127 if err == modules.ErrInvalidConsensusChangeID { 128 hdb.lastChange = modules.ConsensusChangeBeginning 129 // clear the host sets 130 hdb.activeHosts = make(map[modules.NetAddress]*hostNode) 131 hdb.allHosts = make(map[modules.NetAddress]*hostEntry) 132 // subscribe again using the new ID 133 err = cs.ConsensusSetSubscribe(hdb, hdb.lastChange) 134 } 135 if err != nil { 136 return nil, errors.New("hostdb subscription failed: " + err.Error()) 137 } 138 139 // Begin listening to consensus and looking for hosts. 140 hdb.threadGroup.Add(scanningThreads) 141 for i := 0; i < scanningThreads; i++ { 142 go hdb.threadedProbeHosts() 143 } 144 hdb.threadGroup.Add(1) 145 go hdb.threadedScan() 146 return hdb, nil 147 } 148 149 // Close closes the hostdb, terminating its scanning threads 150 func (hdb *HostDB) Close() error { 151 close(hdb.scanPool) 152 close(hdb.closeChan) 153 // wait for threads to exit 154 hdb.threadGroup.Wait() 155 return nil 156 }