github.com/holochain/holochain-proto@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/kad_query.go (about)

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