github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/hostdb/weightedlist.go (about) 1 package hostdb 2 3 // weightedlist.go manages a weighted list of nodes that can be queried 4 // randomly. The functions for inserting, removing, and fetching nodes from the 5 // list are housed in this file. 6 7 import ( 8 "crypto/rand" 9 "errors" 10 11 "github.com/NebulousLabs/Sia/build" 12 "github.com/NebulousLabs/Sia/modules" 13 "github.com/NebulousLabs/Sia/types" 14 ) 15 16 var ( 17 errOverweight = errors.New("requested a too-heavy weight") 18 ) 19 20 // hostNode is the node of an unsorted, balanced, weighted binary tree. When 21 // inserting elements, elements are inserted on the side of the tree with the 22 // fewest elements. When removing, the node is just made empty but the tree is 23 // not reorganized. The size of the tree will never decrease, but it will also 24 // not increase unless it has more entries than it has ever had before. 25 type hostNode struct { 26 parent *hostNode 27 count int // Cumulative count of this node and all children. 28 29 // Currently the only weight supported is priceWeight. Eventually, support 30 // will be added for multiple tunable types of weight. The different 31 // weights all represent the cumulative weight of this node and all 32 // children. 33 weight types.Currency 34 35 left *hostNode 36 right *hostNode 37 38 taken bool // Indicates whether there is an active host at this node or not. 39 hostEntry *hostEntry 40 } 41 42 // createNode makes a new node the fill a host entry. 43 func createNode(parent *hostNode, entry *hostEntry) *hostNode { 44 return &hostNode{ 45 parent: parent, 46 weight: entry.Weight, 47 count: 1, 48 49 taken: true, 50 hostEntry: entry, 51 } 52 } 53 54 // nodeAtWeight grabs an element in the tree that appears at the given weight. 55 // Though the tree has an arbitrary sorting, a sufficiently random weight will 56 // pull a random element. The tree is searched through in a post-ordered way. 57 func (hn *hostNode) nodeAtWeight(weight types.Currency) (*hostNode, error) { 58 // Sanity check - weight must be less than the total weight of the tree. 59 if weight.Cmp(hn.weight) > 0 { 60 return nil, errOverweight 61 } 62 63 // Check if the left or right child should be returned. 64 if hn.left != nil { 65 if weight.Cmp(hn.left.weight) < 0 { 66 return hn.left.nodeAtWeight(weight) 67 } 68 weight = weight.Sub(hn.left.weight) // Search from 0th index of right side. 69 } 70 if hn.right != nil && weight.Cmp(hn.right.weight) < 0 { 71 return hn.right.nodeAtWeight(weight) 72 } 73 74 // Sanity check 75 if build.DEBUG && !hn.taken { 76 build.Critical("nodeAtWeight should not be returning a nil entry") 77 } 78 79 // Return the root entry. 80 return hn, nil 81 } 82 83 // recursiveInsert is a recursive function for adding a hostNode to an existing tree 84 // of hostNodes. The first call should always be on hostdb.hostTree. Running 85 // time of recursiveInsert is log(n) in the maximum number of elements that have 86 // ever been in the tree. 87 func (hn *hostNode) recursiveInsert(entry *hostEntry) (nodesAdded int, newNode *hostNode) { 88 hn.weight = hn.weight.Add(entry.Weight) 89 90 // If the current node is empty, add the entry but don't increase the 91 // count. 92 if !hn.taken { 93 hn.taken = true 94 hn.hostEntry = entry 95 newNode = hn 96 return 97 } 98 99 // Insert the element into the lest populated side. 100 if hn.left == nil { 101 hn.left = createNode(hn, entry) 102 nodesAdded = 1 103 newNode = hn.left 104 } else if hn.right == nil { 105 hn.right = createNode(hn, entry) 106 nodesAdded = 1 107 newNode = hn.right 108 } else if hn.left.count < hn.right.count { 109 nodesAdded, newNode = hn.left.recursiveInsert(entry) 110 } else { 111 nodesAdded, newNode = hn.right.recursiveInsert(entry) 112 } 113 114 hn.count += nodesAdded 115 return 116 } 117 118 // insertNode inserts a host entry into the host tree, removing 119 // any conflicts. The host settings are assumed to be correct. Though hosts 120 // with 0 weight will never be selected, they are accepted into the tree. 121 func (hdb *HostDB) insertNode(entry *hostEntry) { 122 // If there's already a host of the same id, remove that host. 123 priorEntry, exists := hdb.activeHosts[entry.NetAddress] 124 if exists { 125 priorEntry.removeNode() 126 } 127 128 // Insert the updated entry into the host tree. 129 if hdb.hostTree == nil { 130 hdb.hostTree = createNode(nil, entry) 131 hdb.activeHosts[entry.NetAddress] = hdb.hostTree 132 } else { 133 _, hostNode := hdb.hostTree.recursiveInsert(entry) 134 hdb.activeHosts[entry.NetAddress] = hostNode 135 } 136 } 137 138 // remove takes a node and removes it from the tree by climbing through the 139 // list of parents. remove does not delete nodes. 140 func (hn *hostNode) removeNode() { 141 hn.weight = hn.weight.Sub(hn.hostEntry.Weight) 142 hn.taken = false 143 current := hn.parent 144 for current != nil { 145 current.weight = current.weight.Sub(hn.hostEntry.Weight) 146 current = current.parent 147 } 148 } 149 150 // isEmpty returns whether the hostTree contains no entries. 151 func (hdb *HostDB) isEmpty() bool { 152 return hdb.hostTree == nil || hdb.hostTree.weight.IsZero() 153 } 154 155 // RandomHosts will pull up to 'n' random hosts from the hostdb. There will be 156 // no repeats, but the length of the slice returned may be less than 'n', and 157 // may even be 0. The hosts that get returned first have the higher priority. 158 // Hosts specified in 'ignore' will not be considered; pass 'nil' if no 159 // blacklist is desired. 160 func (hdb *HostDB) RandomHosts(n int, ignore []modules.NetAddress) (hosts []modules.HostDBEntry) { 161 hdb.mu.Lock() 162 defer hdb.mu.Unlock() 163 if hdb.isEmpty() { 164 return 165 } 166 167 // These will be restored after selection is finished. 168 var removedEntries []*hostEntry 169 170 // Remove hosts that we want to ignore. 171 for _, addr := range ignore { 172 node, exists := hdb.activeHosts[addr] 173 if !exists { 174 continue 175 } 176 node.removeNode() 177 delete(hdb.activeHosts, addr) 178 removedEntries = append(removedEntries, node.hostEntry) 179 } 180 181 // Pick a host, remove it from the tree, and repeat until we have n hosts 182 // or the tree is empty. 183 for len(hosts) < n && !hdb.isEmpty() { 184 randWeight, err := rand.Int(rand.Reader, hdb.hostTree.weight.Big()) 185 if err != nil { 186 break 187 } 188 node, err := hdb.hostTree.nodeAtWeight(types.NewCurrency(randWeight)) 189 if err != nil { 190 break 191 } 192 hosts = append(hosts, node.hostEntry.HostDBEntry) 193 194 node.removeNode() 195 delete(hdb.activeHosts, node.hostEntry.NetAddress) 196 removedEntries = append(removedEntries, node.hostEntry) 197 } 198 199 // Add back all of the entries that got removed. 200 for i := range removedEntries { 201 hdb.insertNode(removedEntries[i]) 202 } 203 return hosts 204 }