This repository was archived by the owner on Feb 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 112
Bitswap Refactor #1: Session Manager & Extract Want Manager #28
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
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") | ||
|
||
type MessageQueue struct { | ||
p peer.ID | ||
|
||
outlk sync.Mutex | ||
out bsmsg.BitSwapMessage | ||
network bsnet.BitSwapNetwork | ||
wl *wantlist.ThreadSafe | ||
|
||
sender bsnet.MessageSender | ||
|
||
refcnt int | ||
|
||
work chan struct{} | ||
done chan struct{} | ||
} | ||
|
||
func New(p peer.ID, network bsnet.BitSwapNetwork) *MessageQueue { | ||
return &MessageQueue{ | ||
done: make(chan struct{}), | ||
work: make(chan struct{}, 1), | ||
wl: wantlist.NewThreadSafe(), | ||
network: network, | ||
p: p, | ||
refcnt: 1, | ||
} | ||
} | ||
|
||
func (mq *MessageQueue) RefIncrement() { | ||
mq.refcnt++ | ||
} | ||
|
||
func (mq *MessageQueue) RefDecrement() bool { | ||
mq.refcnt-- | ||
return mq.refcnt > 0 | ||
} | ||
|
||
func (mq *MessageQueue) AddMessage(entries []*bsmsg.Entry, ses uint64) { | ||
var work bool | ||
mq.outlk.Lock() | ||
defer func() { | ||
mq.outlk.Unlock() | ||
if !work { | ||
return | ||
} | ||
select { | ||
case mq.work <- struct{}{}: | ||
default: | ||
} | ||
}() | ||
|
||
// if we have no message held allocate a new one | ||
if mq.out == nil { | ||
mq.out = bsmsg.New(false) | ||
} | ||
|
||
// TODO: add a msg.Combine(...) method | ||
// otherwise, combine the one we are holding with the | ||
// one passed in | ||
for _, e := range entries { | ||
if e.Cancel { | ||
if mq.wl.Remove(e.Cid, ses) { | ||
work = true | ||
mq.out.Cancel(e.Cid) | ||
} | ||
} else { | ||
if mq.wl.Add(e.Cid, e.Priority, ses) { | ||
work = true | ||
mq.out.AddEntry(e.Cid, e.Priority) | ||
} | ||
} | ||
} | ||
} | ||
|
||
func (mq *MessageQueue) Startup(ctx context.Context, initialEntries []*wantlist.Entry) { | ||
|
||
// new peer, we will want to give them our full wantlist | ||
fullwantlist := bsmsg.New(true) | ||
for _, e := range initialEntries { | ||
for k := range e.SesTrk { | ||
mq.wl.AddEntry(e, k) | ||
} | ||
fullwantlist.AddEntry(e.Cid, e.Priority) | ||
} | ||
mq.out = fullwantlist | ||
mq.work <- struct{}{} | ||
|
||
go mq.runQueue(ctx) | ||
} | ||
|
||
func (mq *MessageQueue) Shutdown() { | ||
close(mq.done) | ||
} | ||
func (mq *MessageQueue) runQueue(ctx context.Context) { | ||
for { | ||
select { | ||
case <-mq.work: // there is work to be done | ||
mq.doWork(ctx) | ||
case <-mq.done: | ||
if mq.sender != nil { | ||
mq.sender.Close() | ||
} | ||
return | ||
case <-ctx.Done(): | ||
if mq.sender != nil { | ||
mq.sender.Reset() | ||
} | ||
return | ||
} | ||
} | ||
} | ||
|
||
func (mq *MessageQueue) doWork(ctx context.Context) { | ||
// grab outgoing message | ||
mq.outlk.Lock() | ||
wlm := mq.out | ||
if wlm == nil || wlm.Empty() { | ||
mq.outlk.Unlock() | ||
return | ||
} | ||
mq.out = nil | ||
mq.outlk.Unlock() | ||
|
||
// NB: only open a stream if we actually have data to send | ||
if mq.sender == nil { | ||
err := mq.openSender(ctx) | ||
if err != nil { | ||
log.Infof("cant open message sender to peer %s: %s", mq.p, err) | ||
// TODO: cant connect, what now? | ||
return | ||
} | ||
} | ||
|
||
// send wantlist updates | ||
for { // try to send this message until we fail. | ||
err := mq.sender.SendMsg(ctx, wlm) | ||
if err == nil { | ||
return | ||
} | ||
|
||
log.Infof("bitswap send error: %s", err) | ||
mq.sender.Reset() | ||
mq.sender = nil | ||
|
||
select { | ||
case <-mq.done: | ||
return | ||
case <-ctx.Done(): | ||
return | ||
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.openSender(ctx) | ||
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 | ||
} | ||
|
||
// 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() | ||
} | ||
*/ | ||
} | ||
} | ||
|
||
func (mq *MessageQueue) openSender(ctx context.Context) 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 := mq.network.ConnectTo(conctx, mq.p) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
nsender, err := mq.network.NewMessageSender(ctx, mq.p) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
mq.sender = nsender | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having this cast is a bit funky. I get that this is to avoid a dependency issue but I wonder if there's a better way. Would it make sense to move sessions into their own package? That way, SessionManager could operate over concrete sessions.
Unfortunately, I don't think that'll work as Session appears to need access to bitswap internals. An alternative is to create a
Session
interface that exposes anInterestedIn
method.I'd be fine punting these questions till later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey!
Let's punt till later since two PR's forward Sessions end up in their own package, and this cast is gone :)