Browse Source

Fix GC race in bind with Einhorn

This fixes a race condition between package bind and the garbage
collector, where if the garbage collector ran between einhornInit and
einhornBind, bind would fatal with the error "dup: bad file descriptor"

The core of the bug is that Go's os.File uses runtime.SetFinalizer to
register a callback to close the underlying file descriptor an os.File
points at when the os.File itself is being garbage collected. However,
the Einhorn initialization code in bind, in the process of ensuring that
every Einhorn-passed socket had CloseOnExec set on it, allocated
os.File's pointing at each of these passed file descriptors, but did not
keep references to these os.File's, allowing them to be garbage
collected. Subsequently, if you attempted to bind one of these sockets,
you'd find that it was no longer open.

This is the simplest fix to the bug, which is to only allocate an
os.File when we actually attempt to bind the socket. Note that there's
still a race condition here if you attempt to bind the same file
descriptor twice, since a GC between the two binds will likely cause the
file to be collected. Fortunately, that one can be worked around by
simply not allowing such silly behavior :). Another patch that makes
this more clear will follow.

Closes #29.
Carl Jackson 12 years ago
parent
commit
a54c913a6a
1 changed files with 5 additions and 5 deletions
  1. +5
    -5
      bind/einhorn.go

+ 5
- 5
bind/einhorn.go View File

@ -38,8 +38,7 @@ func einhornInit() {
// Prevent einhorn's fds from leaking to our children // Prevent einhorn's fds from leaking to our children
for i := 0; i < einhornNumFds; i++ { for i := 0; i < einhornNumFds; i++ {
fd := int(einhornFd(i).Fd())
syscall.CloseOnExec(fd)
syscall.CloseOnExec(einhornFdMap(i))
} }
} }
@ -47,13 +46,13 @@ func usingEinhorn() bool {
return einhornNumFds > 0 return einhornNumFds > 0
} }
func einhornFd(n int) *os.File {
func einhornFdMap(n int) int {
name := fmt.Sprintf("EINHORN_FD_%d", n) name := fmt.Sprintf("EINHORN_FD_%d", n)
fno, err := envInt(name) fno, err := envInt(name)
if err != nil { if err != nil {
log.Fatal(einhornErr) log.Fatal(einhornErr)
} }
return os.NewFile(uintptr(fno), name)
return fno
} }
func einhornBind(n int) (net.Listener, error) { func einhornBind(n int) (net.Listener, error) {
@ -64,7 +63,8 @@ func einhornBind(n int) (net.Listener, error) {
return nil, fmt.Errorf(tooBigErr, n, einhornNumFds) return nil, fmt.Errorf(tooBigErr, n, einhornNumFds)
} }
f := einhornFd(n)
fno := einhornFdMap(n)
f := os.NewFile(uintptr(fno), fmt.Sprintf("einhorn@%d", n))
return net.FileListener(f) return net.FileListener(f)
} }


Loading…
Cancel
Save