More resilient broadcast handling (fixes #1907)
My theory is that some error condition on the socket results in it blocking for writes, which maybe also blocks reads... This separates the two into separate services with their own socket, with restarts and retries as appropriates on write timeouts and read/write errors. It should be more robust, hopefully, but I have a hard time testing the actual error conditions...
This commit is contained in:
parent
885d050e5f
commit
297769ef57
@ -6,31 +6,51 @@
|
|||||||
|
|
||||||
package beacon
|
package beacon
|
||||||
|
|
||||||
import "net"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/thejerf/suture"
|
||||||
|
)
|
||||||
|
|
||||||
type Broadcast struct {
|
type Broadcast struct {
|
||||||
conn *net.UDPConn
|
*suture.Supervisor
|
||||||
port int
|
port int
|
||||||
inbox chan []byte
|
inbox chan []byte
|
||||||
outbox chan recv
|
outbox chan recv
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBroadcast(port int) (*Broadcast, error) {
|
func NewBroadcast(port int) *Broadcast {
|
||||||
conn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: port})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b := &Broadcast{
|
b := &Broadcast{
|
||||||
conn: conn,
|
Supervisor: suture.New("broadcastBeacon", suture.Spec{
|
||||||
|
// Don't retry too frenetically: an error to open a socket or
|
||||||
|
// whatever is usually something that is either permanent or takes
|
||||||
|
// a while to get solved...
|
||||||
|
FailureThreshold: 2,
|
||||||
|
FailureBackoff: 60 * time.Second,
|
||||||
|
// Only log restarts in debug mode.
|
||||||
|
Log: func(line string) {
|
||||||
|
if debug {
|
||||||
|
l.Debugln(line)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
port: port,
|
port: port,
|
||||||
inbox: make(chan []byte),
|
inbox: make(chan []byte),
|
||||||
outbox: make(chan recv, 16),
|
outbox: make(chan recv, 16),
|
||||||
}
|
}
|
||||||
|
|
||||||
go genericReader(b.conn, b.outbox)
|
b.Add(&broadcastReader{
|
||||||
go b.writer()
|
port: port,
|
||||||
|
outbox: b.outbox,
|
||||||
|
})
|
||||||
|
b.Add(&broadcastWriter{
|
||||||
|
port: port,
|
||||||
|
inbox: b.inbox,
|
||||||
|
})
|
||||||
|
|
||||||
return b, nil
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Broadcast) Send(data []byte) {
|
func (b *Broadcast) Send(data []byte) {
|
||||||
@ -42,13 +62,37 @@ func (b *Broadcast) Recv() ([]byte, net.Addr) {
|
|||||||
return recv.data, recv.src
|
return recv.data, recv.src
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Broadcast) writer() {
|
type broadcastWriter struct {
|
||||||
for bs := range b.inbox {
|
port int
|
||||||
|
inbox chan []byte
|
||||||
|
conn *net.UDPConn
|
||||||
|
failed bool // Have we already logged a failure reason?
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *broadcastWriter) Serve() {
|
||||||
|
if debug {
|
||||||
|
l.Debugln(w, "starting")
|
||||||
|
defer l.Debugln(w, "stopping")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
w.conn, err = net.ListenUDP("udp4", nil)
|
||||||
|
if err != nil {
|
||||||
|
if !w.failed {
|
||||||
|
l.Warnln("Local discovery over IPv4 unavailable:", err)
|
||||||
|
w.failed = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer w.conn.Close()
|
||||||
|
|
||||||
|
w.failed = false
|
||||||
|
|
||||||
|
for bs := range w.inbox {
|
||||||
addrs, err := net.InterfaceAddrs()
|
addrs, err := net.InterfaceAddrs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugln("Broadcast: interface addresses:", err)
|
l.Debugln("Local discovery (broadcast writer):", err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -71,13 +115,27 @@ func (b *Broadcast) writer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range dsts {
|
for _, ip := range dsts {
|
||||||
dst := &net.UDPAddr{IP: ip, Port: b.port}
|
dst := &net.UDPAddr{IP: ip, Port: w.port}
|
||||||
|
|
||||||
_, err := b.conn.WriteTo(bs, dst)
|
w.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||||
if err != nil {
|
_, err := w.conn.WriteTo(bs, dst)
|
||||||
|
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||||
|
// Write timeouts should not happen. We treat it as a fatal
|
||||||
|
// error on the socket.
|
||||||
|
l.Infoln("Local discovery (broadcast writer):", err)
|
||||||
|
w.failed = true
|
||||||
|
return
|
||||||
|
} else if err, ok := err.(net.Error); ok && err.Temporary() {
|
||||||
|
// A transient error. Lets hope for better luck in the future.
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugln(err)
|
l.Debugln(err)
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
// Some other error that we don't expect. Bail and retry.
|
||||||
|
l.Infoln("Local discovery (broadcast writer):", err)
|
||||||
|
w.failed = true
|
||||||
|
return
|
||||||
} else if debug {
|
} else if debug {
|
||||||
l.Debugf("sent %d bytes to %s", len(bs), dst)
|
l.Debugf("sent %d bytes to %s", len(bs), dst)
|
||||||
}
|
}
|
||||||
@ -85,6 +143,76 @@ func (b *Broadcast) writer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *broadcastWriter) Stop() {
|
||||||
|
w.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *broadcastWriter) String() string {
|
||||||
|
return fmt.Sprintf("broadcastWriter@%p", w)
|
||||||
|
}
|
||||||
|
|
||||||
|
type broadcastReader struct {
|
||||||
|
port int
|
||||||
|
outbox chan recv
|
||||||
|
conn *net.UDPConn
|
||||||
|
failed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *broadcastReader) Serve() {
|
||||||
|
if debug {
|
||||||
|
l.Debugln(r, "starting")
|
||||||
|
defer l.Debugln(r, "stopping")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
r.conn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: r.port})
|
||||||
|
if err != nil {
|
||||||
|
if !r.failed {
|
||||||
|
l.Warnln("Local discovery over IPv4 unavailable:", err)
|
||||||
|
r.failed = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer r.conn.Close()
|
||||||
|
|
||||||
|
bs := make([]byte, 65536)
|
||||||
|
for {
|
||||||
|
n, addr, err := r.conn.ReadFrom(bs)
|
||||||
|
if err != nil {
|
||||||
|
if !r.failed {
|
||||||
|
l.Infoln("Local discovery (broadcast reader):", err)
|
||||||
|
r.failed = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.failed = false
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
l.Debugf("recv %d bytes from %s", n, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := make([]byte, n)
|
||||||
|
copy(c, bs)
|
||||||
|
select {
|
||||||
|
case r.outbox <- recv{c, addr}:
|
||||||
|
default:
|
||||||
|
if debug {
|
||||||
|
l.Debugln("dropping message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *broadcastReader) Stop() {
|
||||||
|
r.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *broadcastReader) String() string {
|
||||||
|
return fmt.Sprintf("broadcastReader@%p", r)
|
||||||
|
}
|
||||||
|
|
||||||
func bcast(ip *net.IPNet) *net.IPNet {
|
func bcast(ip *net.IPNet) *net.IPNet {
|
||||||
var bc = &net.IPNet{}
|
var bc = &net.IPNet{}
|
||||||
bc.IP = make([]byte, len(ip.IP))
|
bc.IP = make([]byte, len(ip.IP))
|
||||||
|
|||||||
@ -86,17 +86,10 @@ func (d *Discoverer) StartLocal(localPort int, localMCAddr string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Discoverer) startLocalIPv4Broadcasts(localPort int) {
|
func (d *Discoverer) startLocalIPv4Broadcasts(localPort int) {
|
||||||
bb, err := beacon.NewBroadcast(localPort)
|
bb := beacon.NewBroadcast(localPort)
|
||||||
if err != nil {
|
|
||||||
if debug {
|
|
||||||
l.Debugln("discover: Start local v4:", err)
|
|
||||||
}
|
|
||||||
l.Infoln("Local discovery over IPv4 unavailable")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
d.beacons = append(d.beacons, bb)
|
d.beacons = append(d.beacons, bb)
|
||||||
go d.recvAnnouncements(bb)
|
go d.recvAnnouncements(bb)
|
||||||
|
bb.ServeBackground()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Discoverer) startLocalIPv6Multicasts(localMCAddr string) {
|
func (d *Discoverer) startLocalIPv6Multicasts(localMCAddr string) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user