Commit 9454558e authored by Jeromy Johnson's avatar Jeromy Johnson

Merge pull request #2817 from ipfs/feat/dht-reuse-stream

reuse streams for dht messaging
parents 84a9fb45 bd93c313
...@@ -58,6 +58,9 @@ type IpfsDHT struct { ...@@ -58,6 +58,9 @@ type IpfsDHT struct {
ctx context.Context ctx context.Context
proc goprocess.Process proc goprocess.Process
strmap map[peer.ID]*messageSender
smlk sync.Mutex
} }
// NewDHT creates a new DHT object with the given peer as the 'local' host // NewDHT creates a new DHT object with the given peer as the 'local' host
...@@ -77,6 +80,7 @@ func NewDHT(ctx context.Context, h host.Host, dstore ds.Datastore) *IpfsDHT { ...@@ -77,6 +80,7 @@ func NewDHT(ctx context.Context, h host.Host, dstore ds.Datastore) *IpfsDHT {
return nil return nil
}) })
dht.strmap = make(map[peer.ID]*messageSender)
dht.ctx = ctx dht.ctx = ctx
h.SetStreamHandler(ProtocolDHT, dht.handleNewStream) h.SetStreamHandler(ProtocolDHT, dht.handleNewStream)
......
package dht package dht
import ( import (
"errors" "sync"
"time" "time"
pb "github.com/ipfs/go-ipfs/routing/dht/pb" pb "github.com/ipfs/go-ipfs/routing/dht/pb"
...@@ -27,40 +27,42 @@ func (dht *IpfsDHT) handleNewMessage(s inet.Stream) { ...@@ -27,40 +27,42 @@ func (dht *IpfsDHT) handleNewMessage(s inet.Stream) {
w := ggio.NewDelimitedWriter(cw) w := ggio.NewDelimitedWriter(cw)
mPeer := s.Conn().RemotePeer() mPeer := s.Conn().RemotePeer()
// receive msg for {
pmes := new(pb.Message) // receive msg
if err := r.ReadMsg(pmes); err != nil { pmes := new(pb.Message)
log.Debugf("Error unmarshaling data: %s", err) if err := r.ReadMsg(pmes); err != nil {
return log.Debugf("Error unmarshaling data: %s", err)
} return
}
// update the peer (on valid msgs only)
dht.updateFromMessage(ctx, mPeer, pmes) // update the peer (on valid msgs only)
dht.updateFromMessage(ctx, mPeer, pmes)
// get handler for this msg type.
handler := dht.handlerForMsgType(pmes.GetType()) // get handler for this msg type.
if handler == nil { handler := dht.handlerForMsgType(pmes.GetType())
log.Debug("got back nil handler from handlerForMsgType") if handler == nil {
return log.Debug("got back nil handler from handlerForMsgType")
} return
}
// dispatch handler.
rpmes, err := handler(ctx, mPeer, pmes) // dispatch handler.
if err != nil { rpmes, err := handler(ctx, mPeer, pmes)
log.Debugf("handle message error: %s", err) if err != nil {
return log.Debugf("handle message error: %s", err)
} return
}
// if nil response, return it before serializing
if rpmes == nil { // if nil response, return it before serializing
log.Debug("Got back nil response from request.") if rpmes == nil {
return log.Debug("Got back nil response from request.")
} continue
}
// send out response msg
if err := w.WriteMsg(rpmes); err != nil { // send out response msg
log.Debugf("send response error: %s", err) if err := w.WriteMsg(rpmes); err != nil {
return log.Debugf("send response error: %s", err)
return
}
} }
return return
...@@ -70,32 +72,14 @@ func (dht *IpfsDHT) handleNewMessage(s inet.Stream) { ...@@ -70,32 +72,14 @@ func (dht *IpfsDHT) handleNewMessage(s inet.Stream) {
// measure the RTT for latency measurements. // measure the RTT for latency measurements.
func (dht *IpfsDHT) sendRequest(ctx context.Context, p peer.ID, pmes *pb.Message) (*pb.Message, error) { func (dht *IpfsDHT) sendRequest(ctx context.Context, p peer.ID, pmes *pb.Message) (*pb.Message, error) {
log.Debugf("%s DHT starting stream", dht.self) ms := dht.messageSenderForPeer(p)
s, err := dht.host.NewStream(ctx, ProtocolDHT, p)
if err != nil {
return nil, err
}
defer s.Close()
cr := ctxio.NewReader(ctx, s) // ok to use. we defer close stream in this func
cw := ctxio.NewWriter(ctx, s) // ok to use. we defer close stream in this func
r := ggio.NewDelimitedReader(cr, inet.MessageSizeMax)
w := ggio.NewDelimitedWriter(cw)
start := time.Now() start := time.Now()
if err := w.WriteMsg(pmes); err != nil { rpmes, err := ms.SendRequest(ctx, pmes)
return nil, err if err != nil {
}
log.Event(ctx, "dhtSentMessage", dht.self, p, pmes)
rpmes := new(pb.Message)
if err := r.ReadMsg(rpmes); err != nil {
return nil, err return nil, err
} }
if rpmes == nil {
return nil, errors.New("no response to request")
}
// update the peer (on valid msgs only) // update the peer (on valid msgs only)
dht.updateFromMessage(ctx, p, rpmes) dht.updateFromMessage(ctx, p, rpmes)
...@@ -108,24 +92,138 @@ func (dht *IpfsDHT) sendRequest(ctx context.Context, p peer.ID, pmes *pb.Message ...@@ -108,24 +92,138 @@ func (dht *IpfsDHT) sendRequest(ctx context.Context, p peer.ID, pmes *pb.Message
// sendMessage sends out a message // sendMessage sends out a message
func (dht *IpfsDHT) sendMessage(ctx context.Context, p peer.ID, pmes *pb.Message) error { func (dht *IpfsDHT) sendMessage(ctx context.Context, p peer.ID, pmes *pb.Message) error {
log.Debugf("%s DHT starting stream", dht.self) ms := dht.messageSenderForPeer(p)
s, err := dht.host.NewStream(ctx, ProtocolDHT, p)
if err := ms.SendMessage(ctx, pmes); err != nil {
return err
}
log.Event(ctx, "dhtSentMessage", dht.self, p, pmes)
return nil
}
func (dht *IpfsDHT) updateFromMessage(ctx context.Context, p peer.ID, mes *pb.Message) error {
dht.Update(ctx, p)
return nil
}
func (dht *IpfsDHT) messageSenderForPeer(p peer.ID) *messageSender {
dht.smlk.Lock()
defer dht.smlk.Unlock()
ms, ok := dht.strmap[p]
if !ok {
ms = dht.newMessageSender(p)
dht.strmap[p] = ms
}
return ms
}
type messageSender struct {
s inet.Stream
r ggio.ReadCloser
w ggio.WriteCloser
lk sync.Mutex
p peer.ID
dht *IpfsDHT
singleMes int
}
func (dht *IpfsDHT) newMessageSender(p peer.ID) *messageSender {
return &messageSender{p: p, dht: dht}
}
func (ms *messageSender) prep() error {
if ms.s != nil {
return nil
}
nstr, err := ms.dht.host.NewStream(ms.dht.ctx, ProtocolDHT, ms.p)
if err != nil { if err != nil {
return err return err
} }
defer s.Close()
cw := ctxio.NewWriter(ctx, s) // ok to use. we defer close stream in this func ms.r = ggio.NewDelimitedReader(nstr, inet.MessageSizeMax)
w := ggio.NewDelimitedWriter(cw) ms.w = ggio.NewDelimitedWriter(nstr)
ms.s = nstr
return nil
}
// streamReuseTries is the number of times we will try to reuse a stream to a
// given peer before giving up and reverting to the old one-message-per-stream
// behaviour.
const streamReuseTries = 3
if err := w.WriteMsg(pmes); err != nil { func (ms *messageSender) SendMessage(ctx context.Context, pmes *pb.Message) error {
ms.lk.Lock()
defer ms.lk.Unlock()
if err := ms.prep(); err != nil {
return err return err
} }
log.Event(ctx, "dhtSentMessage", dht.self, p, pmes)
if err := ms.writeMessage(pmes); err != nil {
return err
}
if ms.singleMes > streamReuseTries {
ms.s.Close()
ms.s = nil
}
return nil return nil
} }
func (dht *IpfsDHT) updateFromMessage(ctx context.Context, p peer.ID, mes *pb.Message) error { func (ms *messageSender) writeMessage(pmes *pb.Message) error {
dht.Update(ctx, p) err := ms.w.WriteMsg(pmes)
if err != nil {
// If the other side isnt expecting us to be reusing streams, we're gonna
// end up erroring here. To make sure things work seamlessly, lets retry once
// before continuing
log.Infof("error writing message: ", err)
ms.s.Close()
ms.s = nil
if err := ms.prep(); err != nil {
return err
}
if err := ms.w.WriteMsg(pmes); err != nil {
return err
}
// keep track of this happening. If it happens a few times, its
// likely we can assume the otherside will never support stream reuse
ms.singleMes++
}
return nil return nil
} }
func (ms *messageSender) SendRequest(ctx context.Context, pmes *pb.Message) (*pb.Message, error) {
ms.lk.Lock()
defer ms.lk.Unlock()
if err := ms.prep(); err != nil {
return nil, err
}
if err := ms.writeMessage(pmes); err != nil {
return nil, err
}
log.Event(ctx, "dhtSentMessage", ms.dht.self, ms.p, pmes)
mes := new(pb.Message)
if err := ms.r.ReadMsg(mes); err != nil {
ms.s.Close()
ms.s = nil
return nil, err
}
if ms.singleMes > streamReuseTries {
ms.s.Close()
ms.s = nil
}
return mes, nil
}
...@@ -2,7 +2,6 @@ package dht ...@@ -2,7 +2,6 @@ package dht
import ( import (
"io" "io"
"io/ioutil"
"math/rand" "math/rand"
"testing" "testing"
"time" "time"
...@@ -40,8 +39,7 @@ func TestGetFailures(t *testing.T) { ...@@ -40,8 +39,7 @@ func TestGetFailures(t *testing.T) {
// Reply with failures to every message // Reply with failures to every message
hosts[1].SetStreamHandler(ProtocolDHT, func(s inet.Stream) { hosts[1].SetStreamHandler(ProtocolDHT, func(s inet.Stream) {
defer s.Close() s.Close()
io.Copy(ioutil.Discard, s)
}) })
// This one should time out // This one should time out
...@@ -51,7 +49,7 @@ func TestGetFailures(t *testing.T) { ...@@ -51,7 +49,7 @@ func TestGetFailures(t *testing.T) {
err = merr[0] err = merr[0]
} }
if err.Error() != "process closing" { if err != io.EOF {
t.Fatal("Got different error than we expected", err) t.Fatal("Got different error than we expected", err)
} }
} else { } else {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment