8000 wait for datapath device to be ready by rade · Pull Request #2117 · weaveworks/weave · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
This repository was archived by the owner on Jun 20, 2024. It is now read-only.

wait for datapath device to be ready #2117

Merged
merged 5 commits into from
Apr 1, 2016
Merged

wait for datapath device to be ready #2117

merged 5 commits into from
Apr 1, 2016

Conversation

rade
Copy link
Member
@rade rade commented Mar 30, 2016

so that when the router container restarts after a reboot it waits for the proxy to create the datapath device, rather than dying.

Fixes #2113.

rade added 4 commits March 30, 2016 14:01
@rade rade added this to the 1.5.0 milestone Mar 30, 2016
@rade
Copy link
Member Author
rade commented Mar 30, 2016

I've encountered hangs in random tests on CircleCI with this branch. Eventually I managed to reproduce that in a local test run and get a stack trace:

PC=0x51bbd8 m=0

goroutine 15 [syscall, 5 minutes]:
syscall.Syscall6(0x2d, 0x3, 0xc8212ae000, 0x10000, 0x0, 0xc821257258, 0xc821257254, 0x20, 0x20, 0xc129c0)
    /usr/local/go/src/syscall/asm_linux_amd64.s:44 +0x5 fp=0xc8212571a8 sp=0xc8212571a0
syscall.recvfrom(0x3, 0xc8212ae000, 0x10000, 0x10000, 0x0, 0xc821257258, 0xc821257254, 0x7f34778d1000, 0x0, 0x0)
    /usr/local/go/src/syscall/zsyscall_linux_amd64.go:1712 +0x91 fp=0xc821257200 sp=0xc8212571a8
syscall.Recvfrom(0x3, 0xc8212ae000, 0x10000, 0x10000, 0x0, 0xc8212612e0, 0x0, 0x0, 0x0, 0x0)
    /usr/local/go/src/syscall/syscall_unix.go:244 +0xb8 fp=0xc8212572d0 sp=0xc821257200
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*NetlinkSocket).recv(0xc821262ed0, 0xa00000000, 0x10, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/netlink.go:552 +0x70 fp=0xc8212574c8 sp=0xc8212572d0
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*NetlinkSocket).Receive(0xc821262ed0, 0xc821257558, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/netlink.go:575 +0x3c fp=0xc821257530 sp=0xc8212574c8
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*NetlinkSocket).Request(0xc821262ed0, 0xc821261320, 0x0, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/netlink.go:623 +0xad fp=0xc821257580 sp=0xc821257530
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.DatapathHandle.setVportUpcallPortId(0xc82126abe0, 0x6e, 0x890ad28700000000, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/vport.go:299 +0xd9 fp=0xc8212575b8 sp=0xc821257580
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*missVportConsumer).setVportUpcallPortId(0xc82126ac80, 0x0, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/packet.go:100 +0xee fp=0xc821257600 sp=0xc8212575b8
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.DatapathHandle.ConsumeMisses(0xc82126ab40, 0x6e, 0x7f34756021c8, 0xc8200702d0, 0x0, 0x0, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/packet.go:66 +0x4d2 fp=0xc821257738 sp=0xc821257600
github.com/weaveworks/weave/router.NewFastDatapath(0xc82000dd80, 0x1a7f, 0x0, 0x0, 0x0)
    github.com/weaveworks/weave/router/_obj/fastdp.go:125 +0x732 fp=0xc8212578c0 sp=0xc821257738
github.com/weaveworks/weave/prog/weaver.createOverlay(0x7ffcfc60dece, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1a7f, 0x8, 0x0, 0x0, ...)
    github.com/weaveworks/weave/prog/weaver/_test/_obj_test/main.go:420 +0x3a6 fp=0xc8212579c0 sp=0xc8212578c0
github.com/weaveworks/weave/prog/weaver.main()
    github.com/weaveworks/weave/prog/weaver/_test/_obj_test/main.go:233 +0x1e35 fp=0xc821257f40 sp=0xc8212579c0
github.com/weaveworks/weave/prog/weaver.TestMain(0xc820070240)
    /go/src/github.com/weaveworks/weave/prog/weaver/main_test.go:11 +0x19 fp=0xc821257f48 sp=0xc821257f40
testing.tRunner(0xc820070240, 0x13a47a0)
    /usr/local/go/src/testing/testing.go:456 +0x98 fp=0xc821257f80 sp=0xc821257f48
runtime.goexit()
    /usr/local/go/src/runtime/asm_amd64.s:1721 +0x1 fp=0xc821257f88 sp=0xc821257f80
created by testing.RunTests
    /usr/local/go/src/testing/testing.go:561 +0x86d

goroutine 1 [chan receive, 5 minutes]:
testing.RunTests(0xe8c958, 0x13a47a0, 0x1, 0x1, 0xc821211901)
    /usr/local/go/src/testing/testing.go:562 +0x8ad
testing.(*M).Run(0xc820036f08, 0x13d1660)
    /usr/local/go/src/testing/testing.go:494 +0x70
main.main()
    github.com/weaveworks/weave/prog/weaver/_test/_testmain.go:226 +0x25b

goroutine 17 [syscall, 5 minutes, locked to thread]:
runtime.goexit()
    /usr/local/go/src/runtime/asm_amd64.s:1721 +0x1

goroutine 5 [syscall, 5 minutes]:
os/signal.loop()
    /usr/local/go/src/os/signal/signal_unix.go:22 +0x18
created by os/signal.init.1
    /usr/local/go/src/os/signal/signal_unix.go:28 +0x37

goroutine 19 [chan send, 5 minutes]:
github.com/weaveworks/weave/vendor/github.com/vishvananda/netlink.LinkSubscribe.func2(0xc820016c00, 0xc821260560)
    /go/src/github.com/weaveworks/weave/vendor/github.com/vishvananda/netlink/link_linux.go:847 +0x2e6
created by github.com/weaveworks/weave/vendor/github.com/vishvananda/netlink.LinkSubscribe
    /go/src/github.com/weaveworks/weave/vendor/github.com/vishvananda/netlink/link_linux.go:850 +0x107

goroutine 20 [semacquire, 5 minutes]:
sync.runtime_Semacquire(0xc82126acbc)
    /usr/local/go/src/runtime/sema.go:43 +0x26
sync.(*Mutex).Lock(0xc82126acb8)
    /usr/local/go/src/sync/mutex.go:82 +0x1c4
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*missVportConsumer).setVportUpcallPortId(0xc82126ac80, 0x0, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/packet.go:93 +0x41
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*missVportConsumer).VportCreated(0xc82126ac80, 0xc80000006e, 0x7f3400000000, 0x7f3475602148, 0xc8212ea060, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/packet.go:109 +0x35
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*Dpif).consumeVportEvents.func1(0xc8212e8080, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/vport.go:353 +0x1bb
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*NetlinkSocket).consume.func1(0xc8212e8080, 0xc8212e8080, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/netlink.go:683 +0x17a
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*NetlinkSocket).Receive(0xc821262f60, 0xc820033f08, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/netlink.go:589 +0x107
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*NetlinkSocket).consume(0xc821262f60, 0x7f3475587030, 0xc82126ac80, 0xc820033f60)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/netlink.go:680 +0x57
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*Dpif).consumeVportEvents(0xc82126acd0, 0x7f34756021f8, 0xc82126ac80, 0x6e)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/vport.go:361 +0x9a
created by github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.DatapathHandle.ConsumeVportEvents
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/vport.go:330 +0x1c8

rax    0x2d
rbx    0xc82125e780
rcx    0x51bbda
rdx    0x10000
rdi    0x3
rsi    0xc8212ae000
rbp    0x0
rsp    0xc8212571a0
r8     0xc821257258
r9     0xc821257254
r10    0x0
r11    0x202
r12    0x2
r13    0xe86a30
r14    0x1
r15    0x8
rip    0x51bbd8
rflags 0x202
cs     0x33
fs     0x0
gs     0x0

So the fastdp code is stuck waiting for a response to a netlink request.

Note that something else is stuck too: a vishvananda/netlink.LinkSubscribe is blocking on a channel send.

That is due to the atrocious termination handling in that code. Our code calls LinkSubscribe with a done channel, and does indeed close that. That should close the netlink socket, but the receiver loop goroutine is not going to notice that until it hits Receive, which it won't if it's stuck in a channel send.

What i don't understand is why that causes the fastdp netlink connection to block. @dpw any clues?

@rade rade self-assigned this Mar 30, 2016
@rade
Copy link
Member Author
rade commented Mar 31, 2016

Both @dpw and i have managed to reproduce this with a simple

$ for i in $(seq 1 100) ; do echo "# $i #"; weave reset ; weave launch-router ; done

in <20 iterations.

I then ran the same after applying the following netlink patch:

diff --git a/link_linux.go b/link_linux.go
index 3aa9124..62e385d 100644
--- a/link_linux.go
+++ b/link_linux.go
@@ -844,7 +844,11 @@ func LinkSubscribe(ch chan<- LinkUpdate, done <-chan struct
                                if err != nil {
                                        return
                                }
-                               ch <- LinkUpdate{IfInfomsg: *ifmsg, Link: link}
+                               select {
+                               case ch <- LinkUpdate{IfInfomsg: *ifmsg, Link: l
+                               case <-done:
+                                       return
+                               }
                        }
                }
        }()

Alas that too stopped after ~40 iterations, this time with

PC=0x46e919 m=0

goroutine 0 [idle]:
runtime.epollwait(0x7fff00000008, 0x7fffa61a2dd0, 0xffffffff00000080, 0x0, 0xffffffff, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
    /usr/local/go/src/runtime/sys_linux_amd64.s:420 +0x19
runtime.netpoll(0x137b901, 0x0)
    /usr/local/go/src/runtime/netpoll_epoll.go:68 +0x94
runtime.findrunnable(0xc820016000, 0x0)
    /usr/local/go/src/runtime/proc1.go:1520 +0x598
runtime.schedule()
    /usr/local/go/src/runtime/proc1.go:1647 +0x267
runtime.park_m(0xc8204c5200)
    /usr/local/go/src/runtime/proc1.go:1706 +0x18b
runtime.mcall(0x7fffa61a34e0)
    /usr/local/go/src/runtime/asm_amd64.s:204 +0x5b

goroutine 1 [syscall, 1 minutes]:
syscall.Syscall6(0x2d, 0x3, 0xc822316000, 0x10000, 0x0, 0xc8212b91d8, 0xc8212b91d4, 0x0, 0x20, 0xbe26e0)
    /usr/local/go/src/syscall/asm_linux_amd64.s:44 +0x5
syscall.recvfrom(0x3, 0xc822316000, 0x10000, 0x10000, 0x0, 0xc8212b91d8, 0xc8212b91d4, 0x7fe923dfb000, 0x0, 0x0)
    /usr/local/go/src/syscall/zsyscall_linux_amd64.go:1712 +0x91
syscall.Recvfrom(0x3, 0xc822316000, 0x10000, 0x10000, 0x0, 0x45f830, 0x0, 0x0, 0x0, 0x0)
    /usr/local/go/src/syscall/syscall_unix.go:244 +0xb8
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*NetlinkSocket).recv(0xc8212ada70, 0xa00000000, 0x10, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/netlink.go:552 +0x70
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*NetlinkSocket).Receive(0xc8212ada70, 0xc8212b94d8, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/netlink.go:575 +0x3c
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*NetlinkSocket).Request(0xc8212ada70, 0xc8212ab020, 0x0, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/netlink.go:623 +0xad
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.DatapathHandle.setVportUpcallPortId(0xc8212b0690, 0x2fa, 0xffff8c1300000000, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/vport.go:299 +0xd9
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*missVportConsumer).setVportUpcallPortId(0xc8212b0730, 0x0, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/packet.go:100 +0xee
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.DatapathHandle.ConsumeMisses(0xc8212b05f0, 0x2fa, 0x7fe920b681a0, 0xc822312000, 0x0, 0x0, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/packet.go:66 +0x4d2
github.com/weaveworks/weave/router.NewFastDatapath(0xc8212c4ec0, 0x1a7f, 0x0, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/router/fastdp.go:122 +0x58c
main.createOverlay(0x7fffa61a3f01, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1a7f, 0x8, 0x0, 0x0, ...)
    /go/src/github.com/weaveworks/weave/prog/weaver/main.go:353 +0x4dd
main.main()
    /go/src/github.com/weaveworks/weave/prog/weaver/main.go:206 +0x1c92

goroutine 17 [syscall, 1 minutes, locked to thread]:
runtime.goexit()
    /usr/local/go/src/runtime/asm_amd64.s:1721 +0x1

goroutine 5 [syscall, 1 minutes]:
os/signal.loop()
    /usr/local/go/src/os/signal/signal_unix.go:22 +0x18
created by os/signal.init.1
    /usr/local/go/src/os/signal/signal_unix.go:28 +0x37

goroutine 9 [semacquire, 1 minutes]:
sync.runtime_Semacquire(0xc8212b076c)
    /usr/local/go/src/runtime/sema.go:43 +0x26
sync.(*Mutex).Lock(0xc8212b0768)
    /usr/local/go/src/sync/mutex.go:82 +0x1c4
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*missVportConsumer).setVportUpcallPortId(0xc8212b0730, 0x0, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/packet.go:93 +0x41
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*missVportConsumer).VportCreated(0xc8212b0730, 0xc8000002fa, 0x7fe900000000, 0x7fe920b68120, 0xc8212bc2a0, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/packet.go:109 +0x35
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*Dpif).consumeVportEvents.func1(0xc8212ba100, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/vport.go:353 +0x1bb
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*NetlinkSocket).consume.func1(0xc8212ba100, 0xc8212ba100, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/netlink.go:683 +0x17a
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*NetlinkSocket).Receive(0xc8212adb00, 0xc820037f08, 0x0, 0x0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/netlink.go:589 +0x107
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*NetlinkSocket).consume(0xc8212adb00, 0x7fe920bd1090, 0xc8212b0730, 0xc820037f60)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/netlink.go:680 +0x57
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.(*Dpif).consumeVportEvents(0xc8212b0780, 0x7fe920b681d0, 0xc8212b0730, 0x2fa)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/vport.go:361 +0x9a
created by github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp.DatapathHandle.ConsumeVportEvents
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-odp/odp/vport.go:330 +0x1c8

goroutine 10 [select]:
github.com/weaveworks/weave/vendor/github.com/weaveworks/go-checkpoint.CheckInterval.func1(0x13a52453c000, 0xc8212d2000, 0xe4ede8, 0xc8212700c0)
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-checkpoint/checkpoint.go:206 +0x130
created by github.com/weaveworks/weave/vendor/github.com/weaveworks/go-checkpoint.CheckInterval
    /go/src/github.com/weaveworks/weave/vendor/github.com/weaveworks/go-checkpoint/checkpoint.go:214 +0xb7

rax    0xfffffffffffffffc
rbx    0xffffffff
rcx    0xffffffffffffffff
rdx    0x80
rdi    0x8
rsi    0x7fffa61a2dd0
rbp    0x137c400
rsp    0x7fffa61a2d90
r8     0x137c400
r9     0x0
r10    0xffffffff
r11    0x246
r12    0x2c
r13    0xe4938c
r14    0x0
r15    0x8
rip    0x46e919
rflags 0x246
cs     0x33
fs     0x0
gs     0x0

Note that this is free of any vishvanada/netlink.

@rade
Copy link
Member Author
rade commented Mar 31, 2016

I have hacked netlink.LinkSubscribe to bits, but kept get 8000 ting explosions in either the fastdp code (bad file descriptor) or vishvanada code (slice OOB in nl parsing), or getting stuck as above; even after ensuring that the subscription fd is closed before proceeding to the fastdp code.

NB: Closing the fd does not appear to terminate the nl socket Receive().

Once I disabled the nl reading code in LinkSubscribe altogether, everything worked fine. But that's obviously not something we can do for real.

@rade
Copy link
Member Author
rade commented Mar 31, 2016

Because The Linux Journal says

The application is responsible for picking a unique 32-bit integer to fill in nl_pid

just for the fun of it I made the nl_pid unique in vishvanada and go-odp with

diff --git a/nl/nl_linux.go b/nl/nl_linux.go
index e3afb5c..a1e4b0c 100644
--- a/nl/nl_linux.go
+++ b/nl/nl_linux.go
@@ -5,6 +5,7 @@ import (
        "bytes"
        "encoding/binary"
        "fmt"
+       "math/rand"
        "net"
        "sync/atomic"
        "syscall"
@@ -308,6 +309,7 @@ func Subscribe(protocol int, groups ...uint) (*NetlinkSocket
                fd: fd,
        }
        s.lsa.Family = syscall.AF_NETLINK
+       s.lsa.Pid = rand.Uint32()

        for _, g := range groups {
                s.lsa.Groups |= (1 << (g - 1))

and

diff --git a/odp/netlink.go b/odp/netlink.go
index d89fcad..adb4458 100644
--- a/odp/netlink.go
+++ b/odp/netlink.go
@@ -2,6 +2,7 @@ package odp

 import (
        "fmt"
+       "math/rand"
        "reflect"
        "sync/atomic"
        "syscall"
@@ -39,7 +40,7 @@ func OpenNetlinkSocket(protocol int) (*NetlinkSocket, error) {
                return nil, err
        }

-       addr := syscall.SockaddrNetlink{Family: syscall.AF_NETLINK}
+       addr := syscall.SockaddrNetlink{Family: syscall.AF_NETLINK, Pid: rand.Ui
        if err := syscall.Bind(fd, &addr); err != nil {
                return nil, err
        }

But it is still getting stuck in the same place as before.

@rade
Copy link
Member Author
rade commented Mar 31, 2016

Here is the simplest fix that "solves" the problem:

diff --git a/net/if.go b/net/if.go
index f107433..d73c1a9 100644
--- a/net/if.go
+++ b/net/if.go
@@ -19,9 +19,7 @@ func EnsureInterface(ifaceName string) (*net.Interface, error)

 func ensureInterface(ifaceName string) (*net.Interface, error) {
        ch := make(chan netlink.LinkUpdate)
-       done := make(chan struct{})
-       defer close(done)
-       if err := netlink.LinkSubscribe(ch, done); err != nil {
+       if err := netlink.LinkSubscribe(ch, nil); err != nil {
                return nil, err
        }
        // check for currently-existing interface after subscribing, to avoid ra

i.e. simply don't close the netlink socket.

Something really weird is going on when that socket is closed. As I mentioned above, the recvfrom is firmly stuck, even after closure. I have confirmed that by literally making the recvfrom syscall and then closing the socket from another goroutine.

@rade
Copy link
Member Author
rade commented Mar 31, 2016

This stripped down version of vishvanada's LinkSubscribe...

func LinkSubscribe(ch chan<- LinkUpdate, _ <-chan struct{}) error {
    s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK)
    if err != nil {
        return err<
8000
/span>
    }
    fd := s.GetFd()
    s.Close()
    rb := make([]byte, syscall.Getpagesize())
//  done := make(chan struct{})
    go func() {
        for {
            _, _, err = syscall.Recvfrom(fd, rb, 0)
            if err != nil {
//              close(done)
                return
            }
        }
    }()
//  <-done
    return nil
}

causes the subsequent call to the stdlib's net.InterfaceByName to block. Removing the comments makes everything work.

I guess the problem might occur if the fd gets reused before the goroutine hits the first Recvfrom.

@dpw
Copy link
Contributor
dpw commented Apr 1, 2016

I've confirmed with a C test program that closing a netlink socket does not cause a recvfrom to abort (with the close and recvfrom in separate threads). The recvfrom will continue to wait for a message on the netlink socket. (Of course, a subsequent recvfrom on the closed fd will usually result in EBADF. But in-progress ones continue after the close.)

This is clearly not what the LinkSubscribe code expects.

So my guess is that the issues (both the interactions with go-odp and perhaps the http one too) result from the following sequence of events:

  • vishvananda's netlink blocks on recvfrom in LinkSubscribe.
  • Via the done channel, the netlink socket used in LinkSubscribe gets closed. But that recvfrom continues.
  • Something else (e.g. go-odp) reuses that fd
  • A netlink interface event (or whatever) comes in, and causes the recvfrom to finish. But the loop in LinkSubscribe means that it now does another recvfrom on the fd that it no longer owns.
  • Thus LinkSubscribe sits there stealing data from the fd, confusing whatever code really owns the fd.

This is consistent with what I saw when looking at the go-odp hang. Is it consistent with the rest of the evidence?

If this is correct, I think we can come up with a fix for LinkSubscribe.

@rade
Copy link
Member Author
rade commented Apr 1, 2016

Is it consistent with the rest of the evidence?

Yes, that is correct. There are a few other permutations. In particular, the subscription go routine may never even have gotten as far as the first recvfrom before the fd gets closed and re-used.

There is no way for us to tell whether the goroutine has reached the first recvfrom.

The only approach I can think of that might work is to follow your suggestion from yesterday to somehow inject data that causes the recvfrom to proceed after we have closed the socket. i.e. the sequence would be

  1. close socket - causing any subsequent recvfrom, prior to fd reuse, to error
  2. inject data - causing any in-progress recvfrom to proceed
  3. wait for subscription goroutine to terminate - ensuring that there won't be another recvfrom

@rade
Copy link
Member Author
rade commented Apr 1, 2016

The slightly less gross alternative is for the main thread to signal the subscription goroutine when we want it to terminate, and for that goroutine to close the socket when it does. The only downside is that the fd remains in use until some data is received.

Uh oh!

There was an error while loading. Please reload this page.

@dpw
Copy link
Contributor
dpw commented Apr 1, 2016
  1. close socket - causing any subsequent recvfrom, prior to fd reuse, to error
  2. inject data - causing any in-progress recvfrom to proceed
  3. wait for subscription goroutine to terminate - ensuring that there won't be another recvfrom

It's probably hard to achieve 2 after closing the socket in 1.

The slightly less gross alternative is for the main thread to signal the subscription goroutine when we want it to terminate, and for that goroutine to close the socket when it does. The only downside is that the fd remains in use until some data is received.

Yes, think this is the starting point.

Then, as a refinement, the done listener thread can also write a request to the socket that is guaranteed to produce a response. E.g. an RTM_GETLINK request with NLM_F_DUMP set (as LinkList does). The response will force the subscription goroutine to leave recvfrom, and so close the socket promptly.

@rade
Copy link
Member Author
rade commented Apr 1, 2016

The response will force the subscription goroutine to leave recvfrom

Because that goroutine is not expecting that and hence failing to decode it?

@rade
Copy link
Member Author
rade commented Apr 1, 2016

btw, there is a bunch of similar code in vishvanada, i.e. different functions to subscribe to different types of events. They are all broken in a similar way, so if we are contemplating submitting a patch, we really ought to fix them all.

@rade
Copy link
Member Author
rade commented Apr 1, 2016

I have opened #2120 for the fd re-use issue and in this PR added a workaround, namely not closing the subscription.

@rade rade removed their assignment Apr 1, 2016
defer close(done)
if err := netlink.LinkSubscribe(ch, done); err != nil {
// NB: We do not supply (and eventually close) a 'done' channel
// here since that can cause incorrect file descriptor re-usage. See

This comment was marked as abuse.

This comment was marked as abuse.

Closing a subscription can cause the associated fd to be re-used while
the subscription goroutine is also still trying to use it. Which can
have disastrous consequences.

So as a work around we don't close subscriptions. This means we leak
an fd, some buffer space, and goroutine. The latter will eventually
block in a channel send of a subscription event, since nothing is left
reading ont the other end. So at least it won't be consuming cycles.
@rade rade force-pushed the 2113-wait-for-dp branch from 5812334 to 495a35a Compare April 1, 2016 10:07
@bboreham bboreham merged commit b7d49cd into master Apr 1, 2016
@rade rade deleted the 2113-wait-for-dp branch April 16, 2016 09:58
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0