1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-06-26 23:53:19 +08:00

stream back diagnostic responses as they are received

This commit is contained in:
Jeromy
2015-01-18 04:53:02 +00:00
committed by Juan Batiz-Benet
parent f6278735ef
commit 65b657e0ea

View File

@ -4,11 +4,9 @@
package diagnostics package diagnostics
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"sync" "sync"
"time" "time"
@ -33,6 +31,8 @@ var log = util.Logger("diagnostics")
// ProtocolDiag is the diagnostics protocol.ID // ProtocolDiag is the diagnostics protocol.ID
var ProtocolDiag protocol.ID = "/ipfs/diagnostics" var ProtocolDiag protocol.ID = "/ipfs/diagnostics"
var ErrAlreadyRunning = errors.New("diagnostic with that ID already running")
const ResponseTimeout = time.Second * 10 const ResponseTimeout = time.Second * 10
const HopTimeoutDecrement = time.Second * 2 const HopTimeoutDecrement = time.Second * 2
@ -159,86 +159,56 @@ func (d *Diagnostics) GetDiagnostic(timeout time.Duration) ([]*DiagInfo, error)
return nil, fmt.Errorf("diagnostic from peers err: %s", err) return nil, fmt.Errorf("diagnostic from peers err: %s", err)
} }
var out []*DiagInfo
di := d.getDiagInfo() di := d.getDiagInfo()
out = append(out, di) out := []*DiagInfo{di}
for _, dpi := range dpeers { for dpi := range dpeers {
out = appendDiagnostics(out, dpi) out = append(out, dpi)
} }
return out, nil return out, nil
} }
func appendDiagnostics(cur []*DiagInfo, data []byte) []*DiagInfo { func decodeDiagJson(data []byte) (*DiagInfo, error) {
buf := bytes.NewBuffer(data)
dec := json.NewDecoder(buf)
for {
di := new(DiagInfo) di := new(DiagInfo)
err := dec.Decode(di) err := json.Unmarshal(data, di)
if err != nil {
if err != io.EOF {
log.Errorf("error decoding DiagInfo: %v", err)
}
break
}
cur = append(cur, di)
}
return cur
}
func (d *Diagnostics) getDiagnosticFromPeers(ctx context.Context, peers map[peer.ID]int, pmes *pb.Message) ([][]byte, error) {
timeout := pmes.GetTimeoutDuration()
if timeout < 1 {
return nil, fmt.Errorf("timeout too short: %s", timeout)
}
ctx, _ = context.WithTimeout(ctx, timeout)
respdata := make(chan []byte)
sendcount := 0
for p, _ := range peers {
log.Debugf("Sending diagnostic request to peer: %s", p)
sendcount++
go func(p peer.ID) {
out, err := d.getDiagnosticFromPeer(ctx, p, pmes)
if err != nil {
log.Errorf("getDiagnostic error: %v", err)
respdata <- nil
return
}
respdata <- out
}(p)
}
outall := make([][]byte, 0, len(peers))
for i := 0; i < sendcount; i++ {
out := <-respdata
outall = append(outall, out)
}
return outall, nil
}
// TODO: this method no longer needed.
func (d *Diagnostics) getDiagnosticFromPeer(ctx context.Context, p peer.ID, mes *pb.Message) ([]byte, error) {
rpmes, err := d.sendRequest(ctx, p, mes)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return rpmes.GetData(), nil
return di, nil
} }
func newMessage(diagID string) *pb.Message { func (d *Diagnostics) getDiagnosticFromPeers(ctx context.Context, peers map[peer.ID]int, pmes *pb.Message) (<-chan *DiagInfo, error) {
pmes := new(pb.Message) respdata := make(chan *DiagInfo)
pmes.DiagID = proto.String(diagID) wg := sync.WaitGroup{}
return pmes for p, _ := range peers {
wg.Add(1)
log.Debugf("Sending diagnostic request to peer: %s", p)
go func(p peer.ID) {
defer wg.Done()
out, err := d.getDiagnosticFromPeer(ctx, p, pmes)
if err != nil {
log.Errorf("Error getting diagnostic from %s: %s", p, err)
return
}
for d := range out {
respdata <- d
}
}(p)
}
go func() {
wg.Wait()
close(respdata)
}()
return respdata, nil
} }
func (d *Diagnostics) sendRequest(ctx context.Context, p peer.ID, pmes *pb.Message) (*pb.Message, error) { func (d *Diagnostics) getDiagnosticFromPeer(ctx context.Context, p peer.ID, pmes *pb.Message) (<-chan *DiagInfo, error) {
s, err := d.host.NewStream(ProtocolDiag, p) s, err := d.host.NewStream(ProtocolDiag, p)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer s.Close()
cr := ctxutil.NewReader(ctx, s) // ok to use. we defer close stream in this func cr := ctxutil.NewReader(ctx, s) // ok to use. we defer close stream in this func
cw := ctxutil.NewWriter(ctx, s) // ok to use. we defer close stream in this func cw := ctxutil.NewWriter(ctx, s) // ok to use. we defer close stream in this func
@ -251,51 +221,57 @@ func (d *Diagnostics) sendRequest(ctx context.Context, p peer.ID, pmes *pb.Messa
return nil, err return nil, err
} }
rpmes := new(pb.Message) out := make(chan *DiagInfo)
if err := r.ReadMsg(rpmes); err != nil { go func() {
return nil, err
}
if rpmes == nil {
return nil, errors.New("no response to request")
}
defer func() {
close(out)
s.Close()
rtt := time.Since(start) rtt := time.Since(start)
log.Infof("diagnostic request took: %s", rtt.String()) log.Infof("diagnostic request took: %s", rtt.String())
return rpmes, nil }()
for {
rpmes := new(pb.Message)
if err := r.ReadMsg(rpmes); err != nil {
log.Errorf("Error reading diagnostic from stream: %s", err)
return
}
if rpmes == nil {
log.Error("Got no response back from diag request.")
return
}
di, err := decodeDiagJson(rpmes.GetData())
if err != nil {
log.Error(err)
return
}
select {
case out <- di:
case <-ctx.Done():
return
}
}
}()
return out, nil
} }
func (d *Diagnostics) handleDiagnostic(p peer.ID, pmes *pb.Message) (*pb.Message, error) { func newMessage(diagID string) *pb.Message {
log.Debugf("HandleDiagnostic from %s for id = %s", p, util.Key(pmes.GetDiagID()).B58String()) pmes := new(pb.Message)
resp := newMessage(pmes.GetDiagID()) pmes.DiagID = proto.String(diagID)
return pmes
// Make sure we havent already handled this request to prevent loops
d.diagLock.Lock()
_, found := d.diagMap[pmes.GetDiagID()]
if found {
d.diagLock.Unlock()
return resp, nil
}
d.diagMap[pmes.GetDiagID()] = time.Now()
d.diagLock.Unlock()
di := d.getDiagInfo()
resp.Data = di.Marshal()
dpeers, err := d.getDiagnosticFromPeers(context.TODO(), d.getPeers(), pmes)
if err != nil {
log.Errorf("diagnostic from peers err: %s", err)
} else {
for _, b := range dpeers {
resp.Data = append(resp.Data, b...) // concatenate them all.
}
}
return resp, nil
} }
func (d *Diagnostics) HandleMessage(ctx context.Context, s inet.Stream) error { func (d *Diagnostics) HandleMessage(ctx context.Context, s inet.Stream) error {
r := ggio.NewDelimitedReader(s, 32768) // maxsize cr := ctxutil.NewReader(ctx, s)
w := ggio.NewDelimitedWriter(s) cw := ctxutil.NewWriter(ctx, s)
r := ggio.NewDelimitedReader(cr, inet.MessageSizeMax) // maxsize
w := ggio.NewDelimitedWriter(cw)
// deserialize msg // deserialize msg
pmes := new(pb.Message) pmes := new(pb.Message)
@ -308,25 +284,51 @@ func (d *Diagnostics) HandleMessage(ctx context.Context, s inet.Stream) error {
log.Infof("[peer: %s] Got message from [%s]\n", log.Infof("[peer: %s] Got message from [%s]\n",
d.self.Pretty(), s.Conn().RemotePeer()) d.self.Pretty(), s.Conn().RemotePeer())
// dispatch handler. // Make sure we havent already handled this request to prevent loops
p := s.Conn().RemotePeer() if err := d.startDiag(pmes.GetDiagID()); err != nil {
rpmes, err := d.handleDiagnostic(p, pmes) return nil
}
resp := newMessage(pmes.GetDiagID())
resp.Data = d.getDiagInfo().Marshal()
if err := w.WriteMsg(resp); err != nil {
log.Errorf("Failed to write protobuf message over stream: %s", err)
return err
}
timeout := pmes.GetTimeoutDuration()
if timeout < HopTimeoutDecrement {
return fmt.Errorf("timeout too short: %s", timeout)
}
ctx, _ = context.WithTimeout(ctx, timeout)
pmes.SetTimeoutDuration(timeout - HopTimeoutDecrement)
dpeers, err := d.getDiagnosticFromPeers(ctx, d.getPeers(), pmes)
if err != nil { if err != nil {
log.Errorf("handleDiagnostic error: %s", err) log.Errorf("diagnostic from peers err: %s", err)
return nil return err
}
for b := range dpeers {
resp := newMessage(pmes.GetDiagID())
resp.Data = b.Marshal()
if err := w.WriteMsg(resp); err != nil {
log.Errorf("Failed to write protobuf message over stream: %s", err)
return err
}
} }
// if nil response, return it before serializing
if rpmes == nil {
return nil return nil
} }
// serialize + send response msg func (d *Diagnostics) startDiag(id string) error {
if err := w.WriteMsg(rpmes); err != nil { d.diagLock.Lock()
log.Errorf("Failed to encode protobuf message: %v", err) _, found := d.diagMap[id]
return nil if found {
d.diagLock.Unlock()
return ErrAlreadyRunning
} }
d.diagMap[id] = time.Now()
d.diagLock.Unlock()
return nil return nil
} }