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  }