// RateLimited returns a rate limited Notifier. only limit goroutines // will be spawned. If limit is zero, no rate limiting happens. This // is the same as `Notifier{}`. func RateLimited(limit int) Notifier { n := Notifier{} if limit > 0 { n.lim = ratelimit.NewRateLimiter(process.Background(), limit) } return n }
func (bs *Bitswap) provideWorker(px process.Process) { limiter := ratelimit.NewRateLimiter(px, provideWorkerMax) limitedGoProvide := func(k key.Key, wid int) { ev := logging.LoggableMap{"ID": wid} limiter.LimitedGo(func(px process.Process) { ctx := procctx.OnClosingContext(px) // derive ctx from px defer log.EventBegin(ctx, "Bitswap.ProvideWorker.Work", ev, &k).Done() ctx, cancel := context.WithTimeout(ctx, provideTimeout) // timeout ctx defer cancel() if err := bs.network.Provide(ctx, k); err != nil { log.Error(err) } }) } // worker spawner, reads from bs.provideKeys until it closes, spawning a // _ratelimited_ number of workers to handle each key. limiter.Go(func(px process.Process) { for wid := 2; ; wid++ { ev := logging.LoggableMap{"ID": 1} log.Event(procctx.OnClosingContext(px), "Bitswap.ProvideWorker.Loop", ev) select { case <-px.Closing(): return case k, ok := <-bs.provideKeys: if !ok { log.Debug("provideKeys channel closed") return } limitedGoProvide(k, wid) } } }) }
func (s *Swarm) dialAddrs(ctx context.Context, d *conn.Dialer, p peer.ID, remoteAddrs []ma.Multiaddr) (conn.Conn, error) { // try to connect to one of the peer's known addresses. // we dial concurrently to each of the addresses, which: // * makes the process faster overall // * attempts to get the fastest connection available. // * mitigates the waste of trying bad addresses log.Debugf("%s swarm dialing %s %s", s.local, p, remoteAddrs) ctx, cancel := context.WithCancel(ctx) defer cancel() // cancel work when we exit func foundConn := make(chan struct{}) conns := make(chan conn.Conn, len(remoteAddrs)) errs := make(chan error, len(remoteAddrs)) // dialSingleAddr is used in the rate-limited async thing below. dialSingleAddr := func(addr ma.Multiaddr) { connC, err := s.dialAddr(ctx, d, p, addr) // check parent still wants our results select { case <-foundConn: if connC != nil { connC.Close() } return default: } if err != nil { errs <- err } else if connC == nil { errs <- fmt.Errorf("failed to dial %s %s", p, addr) } else { conns <- connC } } // this whole thing is in a goroutine so we can use foundConn // to end early. go func() { // rate limiting just in case. at most 10 addrs at once. limiter := ratelimit.NewRateLimiter(process.Background(), 10) limiter.Go(func(worker process.Process) { // permute addrs so we try different sets first each time. for _, i := range rand.Perm(len(remoteAddrs)) { select { case <-foundConn: // if one of them succeeded already break case <-worker.Closing(): // our context was cancelled break default: } workerAddr := remoteAddrs[i] // shadow variable to avoid race limiter.LimitedGo(func(worker process.Process) { dialSingleAddr(workerAddr) }) } }) processctx.CloseAfterContext(limiter, ctx) }() // wair fot the results. exitErr := fmt.Errorf("failed to dial %s", p) for i := 0; i < len(remoteAddrs); i++ { select { case exitErr = <-errs: // log.Debug("dial error: ", exitErr) case connC := <-conns: // take the first + return asap close(foundConn) return connC, nil } } return nil, exitErr }