I want to run a go
server at linux
based system, it happened in soe cases that i found the same port is busy with another app, so i want to kill the running process at that port, and run my server instead, so I wrote the below code:
func main() {
host := "127.0.0.1"
port := "8070"
server := http.Server{
Addr: "127.0.0.1:8070",
}
http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))
ln, err := net.Listen("tcp", ":" port)
if err != nil {
fmt.Fprintf(os.Stderr, "Can't listen on port %q: %s \n", port, err)
// kill the running process at this port
_, err := exec.Command("fuser", "-k", "8070/tcp").Output()
if err != nil {
fmt.Printf("Failed to kill process at Port %q\n", port)
} else {
fmt.Printf("TCP Port %q is available\n", port)
server.ListenAndServe()
}
} else {
ln.Close()
server.ListenAndServe()
}
}
I was able to get the response TCP Port 8070 is available
whihc means there was another running process and it had been killed, but my app is closed directly without running my server at the same port which had been already closed!
hajsf@AIS-DM-YOUSEF-L:~/myapp$ go run myapp
Can't listen on port "8070": listen tcp :8070: bind: address already in use
TCP Port "8070" is available
hajsf@AIS-DM-YOUSEF-L:~/myapp$
In the origional terminal (the old instance of the app0 I got;
hajsf@AIS-DM-YOUSEF-L:~/myapp$ go run myapp
signal: killed
hajsf@AIS-DM-YOUSEF-L:~/myapp$
CodePudding user response:
As you can see in the response How to kill a process running on particular port in Linux?
Your command _, err := exec.Command("fuser", "-k", "8070/tcp").Output()
kill the process but doesn't cleanup the resource ie: port listening.
The port is put into TIME_WAIT state after the parent process is killed.
And you need to wait some time your OS/Kernel cleanup the port/socket
A better alternative is to handle the kill sigint from fuser
and do a graceful shutdown
package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"time"
)
func runServer(host, port string) {
server := http.Server{
Addr: host ":" port,
}
http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))
go func() {
if err := server.ListenAndServe(); err != nil {
}
}()
// Setting up signal capturing
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
// Waiting for SIGINT (kill -2)
<-stop
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
// handle err
}
}
func main() {
host := "127.0.0.1"
port := "8070"
ln, err := net.Listen("tcp", host ":" port)
if err != nil {
fmt.Fprintf(os.Stderr, "Can't listen on port %q: %s \n", port, err)
// kill the running process at this port
cmd := exec.Command("fuser", "-k", "-2", "8070/tcp")
fmt.Println("wait")
err = cmd.Run()
if err != nil {
fmt.Printf("Failed to kill process at Port %q\n", port)
} else {
fmt.Printf("TCP Port %q is available\n", port)
runServer(host, port)
}
} else {
ln.Close()
runServer(host, port)
}
}
CodePudding user response:
worked well on CentOS 7, but same issue with a Ubuntu server. @MoiioM 's answer is fine. But if another app is not the golang app itself, here is another way: set a SO_REUSEADDR flag on the socket
package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/exec"
"syscall"
"golang.org/x/sys/unix"
)
func reusePort(network, address string, conn syscall.RawConn) error {
return conn.Control(func(descriptor uintptr) {
syscall.SetsockoptInt(int(descriptor), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
})
}
func main() {
port := "8070"
server := http.Server{}
http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))
ln, err := net.Listen("tcp", ":" port)
if err != nil {
fmt.Fprintf(os.Stderr, "Can't listen on port %q: %s \n", port, err)
// kill the running process at this port
_, err := exec.Command("fuser", "-k", "8070/tcp").Output()
if err != nil {
fmt.Printf("Failed to kill process at Port %q\n", port)
} else {
fmt.Printf("TCP Port %q is available\n", port)
config := &net.ListenConfig{Control: reusePort}
listener, err := config.Listen(context.Background(), "tcp", ":" port)
if err != nil {
panic(err)
}
if err := server.Serve(listener); err != nil {
panic(err)
}
}
} else {
if err := server.Serve(ln); err != nil {
panic(err)
}
}
}