func (l *UnixListener) CloseFD() error {
if l == nil || l.fd == nil {
return syscall.EINVAL
}
return l.fd.Close()
}
package main
import ( "fmt" "net" "os" "os/signal" "sync" "time"
"github.com/coreos/go-systemd/activation" )
// ...
// Close listeners, got from external process, or not. // If we close them, first execution will end without error, // but all subsequent executions (with unix domain sockets) will fail with // error "dial unix /tmp/activation-test-1.sk: no such file or directory" // (because it is deleted in UnixListener.Close() during first execution). // If we don't close them, then process will hangs up. const closeListeners = false
func main() {
//...
listeners, err := activation.Listeners(true) if err != nil { panic(err) }
if !closeListeners { listeners[0] = &noCloseListener{listeners[0]} listeners[1] = &noCloseListener{listeners[1]} }
// ...
go func() { c := make(chan os.Signal, 1) signal.Notify(c) <-c // os.Exit(1) // not suitable, as we have cleanup and graceful shutdown logic listeners[0].Close() // usual approach - pointless here listeners[1].Close() // usual approach - pointless here // todo: what should we put here??? }()
var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() serve(listeners[0], func(c net.Conn) { defer c.Close() c.Write([]byte("Hello world")) listeners[0].Close() }) }() wg.Add(1) go func() { defer wg.Done() serve(listeners[1], func(c net.Conn) { defer c.Close() c.Write([]byte("Goodbye world")) listeners[1].Close() }) }() wg.Wait()
return }
// simplified http.Serve() func serve(l net.Listener, serveConn func(c net.Conn)) error { defer l.Close() for { c, e := l.Accept() if e != nil { if ne, ok := e.(net.Error); ok && ne.Temporary() { time.Sleep(5 * time.Millisecond) continue } return e } go serveConn(c) } }
// no-close listener wrapper type noCloseListener struct { net.Listener }
func (l *noCloseListener) Close() error { switch l.Listener.(type) { case *net.UnixListener: {
return nil //return l.Listener.(*net.UnixListener).CloseFD() // there is no such method in the standard lib, it's my "extension" } } return l.Listener.Close() }
// How many times to invoke child process.
// If we fork child process only once, than tests for unix domain
// sockets will pass even if child process deletes socket's file.
// If we fork child process several times, then all subsequent forks will
// terminate with error "dial unix /tmp/activation-test-1.sk: no such file or directory".
const forksNumber = 2
func TestUnixListeners(t *testing.T) {
exec.Command("go", "build", "-o", "../examples/activation/listen", "../examples/activation/listen.go").Run()
l1, err := net.Listen("unix", "/tmp/activation-test-1.sk")
if err != nil {
t.Fatalf(err.Error())
}
defer l1.Close()
l2, err := net.Listen("unix", "/tmp/activation-test-2.sk")
if err != nil {
t.Fatalf(err.Error())
}
defer l2.Close()
t1 := l1.(*net.UnixListener)
t2 := l2.(*net.UnixListener)
f1, _ := t1.File()
defer f1.Close()
f2, _ := t2.File()
defer f2.Close()
for i := 0; i < forksNumber; i++ {
cmd := exec.Command("../examples/activation/listen")
cmd.ExtraFiles = []*os.File{
f1,
f2,
}
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1")
r1, err := net.Dial("unix", "/tmp/activation-test-1.sk")
if err != nil {
t.Fatalf(err.Error())
}
defer r1.Close()
r1.Write([]byte("Hi"))
r2, err := net.Dial("unix", "/tmp/activation-test-2.sk")
if err != nil {
t.Fatalf(err.Error())
}
defer r2.Close()
r2.Write([]byte("Hi"))
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
if err := cmd.Start(); err != nil {
println(string(b.Bytes()))
t.Fatalf(err.Error())
}
go func() {
<-time.NewTimer(time.Second * 5).C
p := cmd.Process
if err := p.Signal(syscall.SIGTERM); err != nil {
println("Cannot terminate process")
println(err.Error())
}
<-time.NewTimer(time.Second * 5).C
if err := p.Kill(); err != nil {
println("Cannot kill process")
println(err.Error())
}
}()
if err := cmd.Wait(); err != nil {
println(string(b.Bytes()))
t.Fatalf(err.Error())
}
correctStringWrittenNet(t, r1, "Hello world")
correctStringWrittenNet(t, r2, "Goodbye world")
}
func (l *UnixListener) CloseFD() error {
if l == nil || l.fd == nil {
return syscall.EINVAL
}
return l.fd.Close()
}