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  }