ipld-eth-server/vendor/github.com/ipfs/go-bitswap/messagequeue/messagequeue.go

274 lines
6.6 KiB
Go

package messagequeue
import (
"context"
"sync"
"time"
bsmsg "github.com/ipfs/go-bitswap/message"
bsnet "github.com/ipfs/go-bitswap/network"
wantlist "github.com/ipfs/go-bitswap/wantlist"
logging "github.com/ipfs/go-log"
peer "github.com/libp2p/go-libp2p-peer"
)
var log = logging.Logger("bitswap")
const (
defaultRebroadcastInterval = 30 * time.Second
maxRetries = 10
)
// MessageNetwork is any network that can connect peers and generate a message
// sender.
type MessageNetwork interface {
ConnectTo(context.Context, peer.ID) error
NewMessageSender(context.Context, peer.ID) (bsnet.MessageSender, error)
}
// MessageQueue implements queue of want messages to send to peers.
type MessageQueue struct {
ctx context.Context
p peer.ID
network MessageNetwork
outgoingWork chan struct{}
done chan struct{}
// do not touch out of run loop
wl *wantlist.SessionTrackedWantlist
nextMessage bsmsg.BitSwapMessage
nextMessageLk sync.RWMutex
sender bsnet.MessageSender
rebroadcastIntervalLk sync.RWMutex
rebroadcastInterval time.Duration
rebroadcastTimer *time.Timer
}
// New creats a new MessageQueue.
func New(ctx context.Context, p peer.ID, network MessageNetwork) *MessageQueue {
return &MessageQueue{
ctx: ctx,
wl: wantlist.NewSessionTrackedWantlist(),
network: network,
p: p,
outgoingWork: make(chan struct{}, 1),
done: make(chan struct{}),
rebroadcastInterval: defaultRebroadcastInterval,
}
}
// AddMessage adds new entries to an outgoing message for a given session.
func (mq *MessageQueue) AddMessage(entries []bsmsg.Entry, ses uint64) {
if !mq.addEntries(entries, ses) {
return
}
select {
case mq.outgoingWork <- struct{}{}:
default:
}
}
// AddWantlist adds a complete session tracked want list to a message queue
func (mq *MessageQueue) AddWantlist(initialWants *wantlist.SessionTrackedWantlist) {
initialWants.CopyWants(mq.wl)
mq.addWantlist()
}
// SetRebroadcastInterval sets a new interval on which to rebroadcast the full wantlist
func (mq *MessageQueue) SetRebroadcastInterval(delay time.Duration) {
mq.rebroadcastIntervalLk.Lock()
mq.rebroadcastInterval = delay
if mq.rebroadcastTimer != nil {
mq.rebroadcastTimer.Reset(delay)
}
mq.rebroadcastIntervalLk.Unlock()
}
// Startup starts the processing of messages, and creates an initial message
// based on the given initial wantlist.
func (mq *MessageQueue) Startup() {
mq.rebroadcastIntervalLk.RLock()
mq.rebroadcastTimer = time.NewTimer(mq.rebroadcastInterval)
mq.rebroadcastIntervalLk.RUnlock()
go mq.runQueue()
}
// Shutdown stops the processing of messages for a message queue.
func (mq *MessageQueue) Shutdown() {
close(mq.done)
}
func (mq *MessageQueue) runQueue() {
for {
select {
case <-mq.rebroadcastTimer.C:
mq.rebroadcastWantlist()
case <-mq.outgoingWork:
mq.sendMessage()
case <-mq.done:
if mq.sender != nil {
mq.sender.Close()
}
return
case <-mq.ctx.Done():
if mq.sender != nil {
mq.sender.Reset()
}
return
}
}
}
func (mq *MessageQueue) addWantlist() {
mq.nextMessageLk.Lock()
defer mq.nextMessageLk.Unlock()
if mq.wl.Len() > 0 {
if mq.nextMessage == nil {
mq.nextMessage = bsmsg.New(false)
}
for _, e := range mq.wl.Entries() {
mq.nextMessage.AddEntry(e.Cid, e.Priority)
}
select {
case mq.outgoingWork <- struct{}{}:
default:
}
}
}
func (mq *MessageQueue) rebroadcastWantlist() {
mq.rebroadcastIntervalLk.RLock()
mq.rebroadcastTimer.Reset(mq.rebroadcastInterval)
mq.rebroadcastIntervalLk.RUnlock()
mq.addWantlist()
}
func (mq *MessageQueue) addEntries(entries []bsmsg.Entry, ses uint64) bool {
var work bool
mq.nextMessageLk.Lock()
defer mq.nextMessageLk.Unlock()
// if we have no message held allocate a new one
if mq.nextMessage == nil {
mq.nextMessage = bsmsg.New(false)
}
for _, e := range entries {
if e.Cancel {
if mq.wl.Remove(e.Cid, ses) {
work = true
mq.nextMessage.Cancel(e.Cid)
}
} else {
if mq.wl.Add(e.Cid, e.Priority, ses) {
work = true
mq.nextMessage.AddEntry(e.Cid, e.Priority)
}
}
}
return work
}
func (mq *MessageQueue) extractOutgoingMessage() bsmsg.BitSwapMessage {
// grab outgoing message
mq.nextMessageLk.Lock()
message := mq.nextMessage
mq.nextMessage = nil
mq.nextMessageLk.Unlock()
return message
}
func (mq *MessageQueue) sendMessage() {
message := mq.extractOutgoingMessage()
if message == nil || message.Empty() {
return
}
err := mq.initializeSender()
if err != nil {
log.Infof("cant open message sender to peer %s: %s", mq.p, err)
// TODO: cant connect, what now?
return
}
for i := 0; i < maxRetries; i++ { // try to send this message until we fail.
if mq.attemptSendAndRecovery(message) {
return
}
}
}
func (mq *MessageQueue) initializeSender() error {
if mq.sender != nil {
return nil
}
nsender, err := openSender(mq.ctx, mq.network, mq.p)
if err != nil {
return err
}
mq.sender = nsender
return nil
}
func (mq *MessageQueue) attemptSendAndRecovery(message bsmsg.BitSwapMessage) bool {
err := mq.sender.SendMsg(mq.ctx, message)
if err == nil {
return true
}
log.Infof("bitswap send error: %s", err)
mq.sender.Reset()
mq.sender = nil
select {
case <-mq.done:
return true
case <-mq.ctx.Done():
return true
case <-time.After(time.Millisecond * 100):
// wait 100ms in case disconnect notifications are still propogating
log.Warning("SendMsg errored but neither 'done' nor context.Done() were set")
}
err = mq.initializeSender()
if err != nil {
log.Infof("couldnt open sender again after SendMsg(%s) failed: %s", mq.p, err)
// TODO(why): what do we do now?
// I think the *right* answer is to probably put the message we're
// trying to send back, and then return to waiting for new work or
// a disconnect.
return true
}
// TODO: Is this the same instance for the remote peer?
// If its not, we should resend our entire wantlist to them
/*
if mq.sender.InstanceID() != mq.lastSeenInstanceID {
wlm = mq.getFullWantlistMessage()
}
*/
return false
}
func openSender(ctx context.Context, network MessageNetwork, p peer.ID) (bsnet.MessageSender, error) {
// allow ten minutes for connections this includes looking them up in the
// dht dialing them, and handshaking
conctx, cancel := context.WithTimeout(ctx, time.Minute*10)
defer cancel()
err := network.ConnectTo(conctx, p)
if err != nil {
return nil, err
}
nsender, err := network.NewMessageSender(ctx, p)
if err != nil {
return nil, err
}
return nsender, nil
}