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