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, ¬if.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, ¬if.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, ¬if.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 }