github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/routing/dht/query.go (about)

     1  package dht
     2  
     3  import (
     4  	"sync"
     5  
     6  	key "github.com/ipfs/go-ipfs/blocks/key"
     7  	notif "github.com/ipfs/go-ipfs/notifications"
     8  	peer "github.com/ipfs/go-ipfs/p2p/peer"
     9  	queue "github.com/ipfs/go-ipfs/p2p/peer/queue"
    10  	"github.com/ipfs/go-ipfs/routing"
    11  	eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog"
    12  	u "github.com/ipfs/go-ipfs/util"
    13  	pset "github.com/ipfs/go-ipfs/util/peerset"
    14  	todoctr "github.com/ipfs/go-ipfs/util/todocounter"
    15  
    16  	process "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
    17  	ctxproc "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/context"
    18  	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
    19  )
    20  
    21  var maxQueryConcurrency = AlphaValue
    22  
    23  type dhtQuery struct {
    24  	dht         *IpfsDHT
    25  	key         key.Key   // the key we're querying for
    26  	qfunc       queryFunc // the function to execute per peer
    27  	concurrency int       // the concurrency parameter
    28  }
    29  
    30  type dhtQueryResult struct {
    31  	value         []byte          // GetValue
    32  	peer          peer.PeerInfo   // FindPeer
    33  	providerPeers []peer.PeerInfo // GetProviders
    34  	closerPeers   []peer.PeerInfo // *
    35  	success       bool
    36  }
    37  
    38  // constructs query
    39  func (dht *IpfsDHT) newQuery(k key.Key, f queryFunc) *dhtQuery {
    40  	return &dhtQuery{
    41  		key:         k,
    42  		dht:         dht,
    43  		qfunc:       f,
    44  		concurrency: maxQueryConcurrency,
    45  	}
    46  }
    47  
    48  // QueryFunc is a function that runs a particular query with a given peer.
    49  // It returns either:
    50  // - the value
    51  // - a list of peers potentially better able to serve the query
    52  // - an error
    53  type queryFunc func(context.Context, peer.ID) (*dhtQueryResult, error)
    54  
    55  // Run runs the query at hand. pass in a list of peers to use first.
    56  func (q *dhtQuery) Run(ctx context.Context, peers []peer.ID) (*dhtQueryResult, error) {
    57  	select {
    58  	case <-ctx.Done():
    59  		return nil, ctx.Err()
    60  	default:
    61  	}
    62  
    63  	ctx, cancel := context.WithCancel(ctx)
    64  	defer cancel()
    65  
    66  	runner := newQueryRunner(q)
    67  	return runner.Run(ctx, peers)
    68  }
    69  
    70  type dhtQueryRunner struct {
    71  	query          *dhtQuery        // query to run
    72  	peersSeen      *pset.PeerSet    // all peers queried. prevent querying same peer 2x
    73  	peersToQuery   *queue.ChanQueue // peers remaining to be queried
    74  	peersRemaining todoctr.Counter  // peersToQuery + currently processing
    75  
    76  	result *dhtQueryResult // query result
    77  	errs   u.MultiErr      // result errors. maybe should be a map[peer.ID]error
    78  
    79  	rateLimit chan struct{} // processing semaphore
    80  	log       eventlog.EventLogger
    81  
    82  	proc process.Process
    83  	sync.RWMutex
    84  }
    85  
    86  func newQueryRunner(q *dhtQuery) *dhtQueryRunner {
    87  	proc := process.WithParent(process.Background())
    88  	ctx := ctxproc.OnClosingContext(proc)
    89  	return &dhtQueryRunner{
    90  		query:          q,
    91  		peersToQuery:   queue.NewChanQueue(ctx, queue.NewXORDistancePQ(q.key)),
    92  		peersRemaining: todoctr.NewSyncCounter(),
    93  		peersSeen:      pset.New(),
    94  		rateLimit:      make(chan struct{}, q.concurrency),
    95  		proc:           proc,
    96  	}
    97  }
    98  
    99  func (r *dhtQueryRunner) Run(ctx context.Context, peers []peer.ID) (*dhtQueryResult, error) {
   100  	r.log = log
   101  
   102  	if len(peers) == 0 {
   103  		log.Warning("Running query with no peers!")
   104  		return nil, nil
   105  	}
   106  
   107  	// setup concurrency rate limiting
   108  	for i := 0; i < r.query.concurrency; i++ {
   109  		r.rateLimit <- struct{}{}
   110  	}
   111  
   112  	// add all the peers we got first.
   113  	for _, p := range peers {
   114  		r.addPeerToQuery(p)
   115  	}
   116  
   117  	// go do this thing.
   118  	// do it as a child proc to make sure Run exits
   119  	// ONLY AFTER spawn workers has exited.
   120  	r.proc.Go(r.spawnWorkers)
   121  
   122  	// so workers are working.
   123  
   124  	// wait until they're done.
   125  	err := routing.ErrNotFound
   126  
   127  	// now, if the context finishes, close the proc.
   128  	// we have to do it here because the logic before is setup, which
   129  	// should run without closing the proc.
   130  	ctxproc.CloseAfterContext(r.proc, ctx)
   131  
   132  	select {
   133  	case <-r.peersRemaining.Done():
   134  		r.proc.Close()
   135  		r.RLock()
   136  		defer r.RUnlock()
   137  
   138  		err = routing.ErrNotFound
   139  
   140  		// if every query to every peer failed, something must be very wrong.
   141  		if len(r.errs) > 0 && len(r.errs) == r.peersSeen.Size() {
   142  			log.Debugf("query errs: %s", r.errs)
   143  			err = r.errs[0]
   144  		}
   145  
   146  	case <-r.proc.Closed():
   147  		r.RLock()
   148  		defer r.RUnlock()
   149  		err = context.DeadlineExceeded
   150  	}
   151  
   152  	if r.result != nil && r.result.success {
   153  		return r.result, nil
   154  	}
   155  
   156  	return nil, err
   157  }
   158  
   159  func (r *dhtQueryRunner) addPeerToQuery(next peer.ID) {
   160  	// if new peer is ourselves...
   161  	if next == r.query.dht.self {
   162  		r.log.Debug("addPeerToQuery skip self")
   163  		return
   164  	}
   165  
   166  	if !r.peersSeen.TryAdd(next) {
   167  		return
   168  	}
   169  
   170  	r.peersRemaining.Increment(1)
   171  	select {
   172  	case r.peersToQuery.EnqChan <- next:
   173  	case <-r.proc.Closing():
   174  	}
   175  }
   176  
   177  func (r *dhtQueryRunner) spawnWorkers(proc process.Process) {
   178  	for {
   179  
   180  		select {
   181  		case <-r.peersRemaining.Done():
   182  			return
   183  
   184  		case <-r.proc.Closing():
   185  			return
   186  
   187  		case p, more := <-r.peersToQuery.DeqChan:
   188  			if !more {
   189  				return // channel closed.
   190  			}
   191  
   192  			// do it as a child func to make sure Run exits
   193  			// ONLY AFTER spawn workers has exited.
   194  			proc.Go(func(proc process.Process) {
   195  				r.queryPeer(proc, p)
   196  			})
   197  		}
   198  	}
   199  }
   200  
   201  func (r *dhtQueryRunner) queryPeer(proc process.Process, p peer.ID) {
   202  	// make sure we rate limit concurrency.
   203  	select {
   204  	case <-r.rateLimit:
   205  	case <-proc.Closing():
   206  		r.peersRemaining.Decrement(1)
   207  		return
   208  	}
   209  
   210  	// ok let's do this!
   211  
   212  	// create a context from our proc.
   213  	ctx := ctxproc.OnClosingContext(proc)
   214  
   215  	// make sure we do this when we exit
   216  	defer func() {
   217  		// signal we're done proccessing peer p
   218  		r.peersRemaining.Decrement(1)
   219  		r.rateLimit <- struct{}{}
   220  	}()
   221  
   222  	// make sure we're connected to the peer.
   223  	// FIXME abstract away into the network layer
   224  	if conns := r.query.dht.host.Network().ConnsToPeer(p); len(conns) == 0 {
   225  		log.Infof("not connected. dialing.")
   226  		// while we dial, we do not take up a rate limit. this is to allow
   227  		// forward progress during potentially very high latency dials.
   228  		r.rateLimit <- struct{}{}
   229  
   230  		pi := peer.PeerInfo{ID: p}
   231  
   232  		if err := r.query.dht.host.Connect(ctx, pi); err != nil {
   233  			log.Debugf("Error connecting: %s", err)
   234  
   235  			notif.PublishQueryEvent(ctx, &notif.QueryEvent{
   236  				Type:  notif.QueryError,
   237  				Extra: err.Error(),
   238  			})
   239  
   240  			r.Lock()
   241  			r.errs = append(r.errs, err)
   242  			r.Unlock()
   243  			<-r.rateLimit // need to grab it again, as we deferred.
   244  			return
   245  		}
   246  		<-r.rateLimit // need to grab it again, as we deferred.
   247  		log.Debugf("connected. dial success.")
   248  	}
   249  
   250  	// finally, run the query against this peer
   251  	res, err := r.query.qfunc(ctx, p)
   252  
   253  	if err != nil {
   254  		log.Debugf("ERROR worker for: %v %v", p, err)
   255  		r.Lock()
   256  		r.errs = append(r.errs, err)
   257  		r.Unlock()
   258  
   259  	} else if res.success {
   260  		log.Debugf("SUCCESS worker for: %v %s", p, res)
   261  		r.Lock()
   262  		r.result = res
   263  		r.Unlock()
   264  		go r.proc.Close() // signal to everyone that we're done.
   265  		// must be async, as we're one of the children, and Close blocks.
   266  
   267  	} else if len(res.closerPeers) > 0 {
   268  		log.Debugf("PEERS CLOSER -- worker for: %v (%d closer peers)", p, len(res.closerPeers))
   269  		for _, next := range res.closerPeers {
   270  			if next.ID == r.query.dht.self { // dont add self.
   271  				log.Debugf("PEERS CLOSER -- worker for: %v found self", p)
   272  				continue
   273  			}
   274  
   275  			// add their addresses to the dialer's peerstore
   276  			r.query.dht.peerstore.AddAddrs(next.ID, next.Addrs, peer.TempAddrTTL)
   277  			r.addPeerToQuery(next.ID)
   278  			log.Debugf("PEERS CLOSER -- worker for: %v added %v (%v)", p, next.ID, next.Addrs)
   279  		}
   280  	} else {
   281  		log.Debugf("QUERY worker for: %v - not found, and no closer peers.", p)
   282  	}
   283  }