forked from cerc-io/ipld-eth-server
274 lines
6.6 KiB
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
|
|
}
|