mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-22 10:57:42 +08:00
this PR greatly speeds up providing and add. (1) Instead of idling workers, we move to a ratelimiter-based worker. We put this max at 512, so that means _up to_ 512 goroutines. This is very small load on the node, as each worker is providing to the dht, which means mostly waiting. It DOES put a large load on the DHT. but i want to try this out for a while and see if it's a problem. We can decide later if it is a problem for the network (nothing stops anyone from re-compiling, but the defaults of course matter). (2) We add a buffer size for provideKeys, which means that we block the add process much less. this is a very cheap buffer, as it only stores keys (it may be even cheaper with a lock + ring buffer instead of a channel...). This makes add blazing fast-- it was being rate limited by providing. Add should not be ratelimited by providing (much, if any) as the user wants to just store the stuff in the local node's repo. This buffer is initially set to 4096, which means: 4096 * keysize (~258 bytes + go overhead) ~ 1-1.5MB this buffer only last a few sec to mins, and is an ok thing to do for the sake of very fast adds. (this could be a configurable paramter, certainly for low-mem footprint use cases). At the moment this is not much, compared to block sizes. (3) We make the providing EventBegin() + Done(), so that we can track how long a provide takes, and we can remove workers as they finish in bsdash and similar tools. License: MIT Signed-off-by: Juan Batiz-Benet <juan@benet.ai>
208 lines
5.2 KiB
Go
208 lines
5.2 KiB
Go
package bitswap
|
|
|
|
import (
|
|
"time"
|
|
|
|
process "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
|
|
ratelimit "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/ratelimit"
|
|
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
|
|
waitable "github.com/ipfs/go-ipfs/thirdparty/waitable"
|
|
|
|
key "github.com/ipfs/go-ipfs/blocks/key"
|
|
eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog"
|
|
)
|
|
|
|
var TaskWorkerCount = 8
|
|
|
|
func (bs *Bitswap) startWorkers(px process.Process, ctx context.Context) {
|
|
// Start up a worker to handle block requests this node is making
|
|
px.Go(func(px process.Process) {
|
|
bs.providerConnector(ctx)
|
|
})
|
|
|
|
// Start up workers to handle requests from other nodes for the data on this node
|
|
for i := 0; i < TaskWorkerCount; i++ {
|
|
i := i
|
|
px.Go(func(px process.Process) {
|
|
bs.taskWorker(ctx, i)
|
|
})
|
|
}
|
|
|
|
// Start up a worker to manage periodically resending our wantlist out to peers
|
|
px.Go(func(px process.Process) {
|
|
bs.rebroadcastWorker(ctx)
|
|
})
|
|
|
|
// Start up a worker to manage sending out provides messages
|
|
px.Go(func(px process.Process) {
|
|
bs.provideCollector(ctx)
|
|
})
|
|
|
|
// Spawn up multiple workers to handle incoming blocks
|
|
// consider increasing number if providing blocks bottlenecks
|
|
// file transfers
|
|
px.Go(bs.provideWorker)
|
|
}
|
|
|
|
func (bs *Bitswap) taskWorker(ctx context.Context, id int) {
|
|
idmap := eventlog.LoggableMap{"ID": id}
|
|
defer log.Info("bitswap task worker shutting down...")
|
|
for {
|
|
log.Event(ctx, "Bitswap.TaskWorker.Loop", idmap)
|
|
select {
|
|
case nextEnvelope := <-bs.engine.Outbox():
|
|
select {
|
|
case envelope, ok := <-nextEnvelope:
|
|
if !ok {
|
|
continue
|
|
}
|
|
log.Event(ctx, "Bitswap.TaskWorker.Work", eventlog.LoggableMap{
|
|
"ID": id,
|
|
"Target": envelope.Peer.Pretty(),
|
|
"Block": envelope.Block.Multihash.B58String(),
|
|
})
|
|
|
|
bs.wm.SendBlock(ctx, envelope)
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (bs *Bitswap) provideWorker(px process.Process) {
|
|
|
|
limiter := ratelimit.NewRateLimiter(px, provideWorkerMax)
|
|
|
|
limitedGoProvide := func(k key.Key, wid int) {
|
|
ev := eventlog.LoggableMap{"ID": wid}
|
|
limiter.LimitedGo(func(px process.Process) {
|
|
|
|
ctx := waitable.Context(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 := eventlog.LoggableMap{"ID": 1}
|
|
log.Event(waitable.Context(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 (bs *Bitswap) provideCollector(ctx context.Context) {
|
|
defer close(bs.provideKeys)
|
|
var toProvide []key.Key
|
|
var nextKey key.Key
|
|
var keysOut chan key.Key
|
|
|
|
for {
|
|
select {
|
|
case blk, ok := <-bs.newBlocks:
|
|
if !ok {
|
|
log.Debug("newBlocks channel closed")
|
|
return
|
|
}
|
|
if keysOut == nil {
|
|
nextKey = blk.Key()
|
|
keysOut = bs.provideKeys
|
|
} else {
|
|
toProvide = append(toProvide, blk.Key())
|
|
}
|
|
case keysOut <- nextKey:
|
|
if len(toProvide) > 0 {
|
|
nextKey = toProvide[0]
|
|
toProvide = toProvide[1:]
|
|
} else {
|
|
keysOut = nil
|
|
}
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// connects to providers for the given keys
|
|
func (bs *Bitswap) providerConnector(parent context.Context) {
|
|
defer log.Info("bitswap client worker shutting down...")
|
|
|
|
for {
|
|
log.Event(parent, "Bitswap.ProviderConnector.Loop")
|
|
select {
|
|
case req := <-bs.findKeys:
|
|
keys := req.keys
|
|
if len(keys) == 0 {
|
|
log.Warning("Received batch request for zero blocks")
|
|
continue
|
|
}
|
|
log.Event(parent, "Bitswap.ProviderConnector.Work", eventlog.LoggableMap{"Keys": keys})
|
|
|
|
// NB: Optimization. Assumes that providers of key[0] are likely to
|
|
// be able to provide for all keys. This currently holds true in most
|
|
// every situation. Later, this assumption may not hold as true.
|
|
child, cancel := context.WithTimeout(req.ctx, providerRequestTimeout)
|
|
providers := bs.network.FindProvidersAsync(child, keys[0], maxProvidersPerRequest)
|
|
for p := range providers {
|
|
go bs.network.ConnectTo(req.ctx, p)
|
|
}
|
|
cancel()
|
|
|
|
case <-parent.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (bs *Bitswap) rebroadcastWorker(parent context.Context) {
|
|
ctx, cancel := context.WithCancel(parent)
|
|
defer cancel()
|
|
|
|
broadcastSignal := time.NewTicker(rebroadcastDelay.Get())
|
|
defer broadcastSignal.Stop()
|
|
|
|
tick := time.NewTicker(10 * time.Second)
|
|
defer tick.Stop()
|
|
|
|
for {
|
|
log.Event(ctx, "Bitswap.Rebroadcast.idle")
|
|
select {
|
|
case <-tick.C:
|
|
n := bs.wm.wl.Len()
|
|
if n > 0 {
|
|
log.Debug(n, "keys in bitswap wantlist")
|
|
}
|
|
case <-broadcastSignal.C: // resend unfulfilled wantlist keys
|
|
log.Event(ctx, "Bitswap.Rebroadcast.active")
|
|
entries := bs.wm.wl.Entries()
|
|
if len(entries) > 0 {
|
|
bs.connectToProviders(ctx, entries)
|
|
}
|
|
case <-parent.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|