Block diagram
bitswap data sturcure & initialization
// Bitswap instances implement the bitswap protocol.
type Bitswap struct {
// the wantlist tracks global wants for bitswap
wm *bswm.WantManager
// the provider query manager manages requests to find providers
pqm *bspqm.ProviderQueryManager
// the engine is the bit of logic that decides who to send which blocks to
engine *decision.Engine
// network delivers messages on behalf of the session
network bsnet.BitSwapNetwork
// blockstore is the local database
// NB: ensure threadsafety
blockstore blockstore.Blockstore
// manages channels of outgoing blocks for sessions
notif notifications.PubSub
// newBlocks is a channel for newly added blocks to be provided to the
// network. blocks pushed down this channel get buffered and fed to the
// provideKeys channel later on to avoid too much network activity
newBlocks chan cid.Cid
// provideKeys directly feeds provide workers
provideKeys chan cid.Cid
// the sessionmanager manages tracking sessions
sm *bssm.SessionManager
...
}
wm -> wantManager
sm -> SessionManager
pqm -> ProviderQueryManager
When initializing bitswap, wm, pqm, bs.workers, decisionEngine workers will be started as well. Moreover, provideCollector and ProvideWorker will be started when ProviderEnable is true.
bs.wm.Startup()
bs.pqm.Startup()
network.SetDelegate(bs)
// Start up bitswaps async worker routines
bs.startWorkers(ctx, px)
engine.StartWorkers(ctx, px)
// bind the context and process.
// do it over here to avoid closing before all setup is done.
go func() {
<-px.Closing() // process closes first
cancelFunc()
notif.Shutdown()
}()
bitswap Worker
Waiting for bs.engine.Outbox(), then extracting Message/Blocks, finnally bs.sendBlocks(ctx, envelope)…
func (bs *Bitswap) taskWorker(ctx context.Context, id int) {
for {
select {
case nextEnvelope := <-bs.engine.Outbox():
select {
case envelope, ok := <-nextEnvelope:
if !ok {
continue
}
// update the BS ledger to reflect sent message
// TODO: Should only track *useful* messages in ledger
outgoing := bsmsg.New(false)
for _, block := range envelope.Message.Blocks() {
outgoing.AddBlock(block)
}
bs.engine.MessageSent(envelope.Peer, outgoing)
case <-ctx.Done():
return
}
case <-ctx.Done():
return
}
}
}
WantManager
Simple, the task of wm is shown below. It will take message from wm/wantMessages.
func (wm *WantManager) run() {
// NOTE: Do not open any streams or connections from anywhere in this
// event loop. Really, just don't do anything likely to block.
for {
select {
case message := <-wm.wantMessages:
message.handle(wm)
case <-wm.ctx.Done():
return
}
}
}
Availble messages:
- wantSet
- currentBroadcastWantsMessage
- currentWantsMessage
- wantCountMessage
- connectedMessage
- disconnectedMessage
Providing
- ProvideCollector
- ProvideWorker
SessionManager & Session
SessionManager is responsible for managing session’s lifecycle, NewSession/removeSession.
A session will be created by bitswap.NewSession(), in case that we do some block requests.
// Session holds state for an individual bitswap transfer operation.
// This allows bitswap to make smarter decisions about who to send wantlist
// info to, and who to request blocks from.
type Session struct {
// dependencies
ctx context.Context
wm WantManager
pm PeerManager
srs RequestSplitter
sw sessionWants
// channels
incoming chan op
latencyReqs chan chan time.Duration
tickDelayReqs chan time.Duration
// do not touch outside run loop
idleTick *time.Timer
periodicSearchTimer *time.Timer
baseTickDelay time.Duration
latTotal time.Duration
fetchcnt int
consecutiveTicks int
initialSearchDelay time.Duration
periodicSearchDelay delay.D
// identifiers
notif notifications.PubSub
uuid logging.Loggable
id uint64
}
type SessionManager struct {
ctx context.Context
sessionFactory SessionFactory
peerManagerFactory PeerManagerFactory
requestSplitterFactory RequestSplitterFactory
notif notifications.PubSub
// Sessions
sessLk sync.RWMutex
sessions []sesTrk
// Session Index
sessIDLk sync.Mutex
sessID uint64
}
peerManagerFactory & requestSplitterFactory are used in NewSession. So, whenever a session is created, a peerManager and a requestSplitter will be created as well. All together, session, peerMgr and splitter will grouped as a tracked Session.
Also, a routine is created to safe guard the context cancellation.
// NewSession initializes a session with the given context, and adds to the
// session manager.
func (sm *SessionManager) NewSession(ctx context.Context,
provSearchDelay time.Duration,
rebroadcastDelay delay.D) exchange.Fetcher {
id := sm.GetNextSessionID()
sessionctx, cancel := context.WithCancel(ctx)
pm := sm.peerManagerFactory(sessionctx, id)
srs := sm.requestSplitterFactory(sessionctx)
session := sm.sessionFactory(sessionctx, id, pm, srs, sm.notif, provSearchDelay, rebroadcastDelay)
tracked := sesTrk{session, pm, srs}
sm.sessLk.Lock()
sm.sessions = append(sm.sessions, tracked)
sm.sessLk.Unlock()
go func() {
defer cancel()
select {
case <-sm.ctx.Done():
sm.removeSession(tracked)
case <-ctx.Done():
sm.removeSession(tracked)
}
}()
return session
}
SessionPeerManager
A session will create a PeerManager for managing peers.
// SessionPeerManager tracks and manages peers for a session, and provides
// the best ones to the session
type SessionPeerManager struct {
ctx context.Context
tagger PeerTagger
providerFinder PeerProviderFinder
tag string
id uint64
peerMessages chan peerMessage
// do not touch outside of run loop
activePeers map[peer.ID]*peerData
unoptimizedPeersArr []peer.ID
optimizedPeersArr []peer.ID
broadcastLatency *latencyTracker
timeoutDuration time.Duration
}
SessionPeerManager will start a loop to receiving message:
func (spm *SessionPeerManager) run(ctx context.Context) {
for {
select {
case pm := <-spm.peerMessages:
pm.handle(spm)
case <-ctx.Done():
spm.handleShutdown()
return
}
}
}
Message types:
- peerFoundMessage
- peerResponseMessage
- peerRequestMessage
- getPeersMessage
- cancelMessage
- peerTimeoutMessage
- broadcastTimeoutMessage
- setTimeoutMessage
peerManagerFactory & requestSplitterFactory
Actually these two look like this:
sessionPeerManagerFactory := func(ctx context.Context, id uint64) bssession.PeerManager {
return bsspm.New(ctx, id, network.ConnectionManager(), pqm)
}
sessionRequestSplitterFactory := func(ctx context.Context) bssession.RequestSplitter {
return bssrs.New(ctx)
}