0

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$ 

enter image description here

Hasan A Yousef
  • 22,789
  • 24
  • 132
  • 203

3 Answers3

1

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)
        }
    }
}
emptyhua
  • 6,634
  • 10
  • 11
1

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)
    }
}

MoiioM
  • 1,914
  • 1
  • 10
  • 16
  • Hi, your solution worked fine with me, but I found another solution, can you have a look at my answer and share your thoughts, thanks – Hasan A Yousef Oct 31 '21 at 20:56
0

I was able to solve it with panic ... recover as below:

package main

import (
    "fmt"
    "net"
    "net/http"
    "onsen/resources"
    "onsen/routes"
    "os"
    "os/exec"
)

func main() {
    http.Handle("/webUI/", http.StripPrefix("/webUI/", http.FileServer(http.FS(resources.WebUI))))
    http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))

    for key, value := range routes.Urls() {
        http.HandleFunc(key, value)
    }
    runServer()
}
func runServer() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
            runServer()
        }
    }()

    host := "127.0.0.1"
    port := "8070"

    server := http.Server{
        Addr: fmt.Sprintf("%v:%v", host, port),
    }

    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 {
            panic(fmt.Sprintf("Failed to kill process at Port %q\n", port))
        } else {
            fmt.Printf("TCP Port %q is available\n", port)
            fmt.Println("server started...")
            if err := server.Serve(ln); err != nil {
                panic(err)
            }
        }
    } else {
        fmt.Println("server started...")
        if err := server.Serve(ln); err != nil {
            panic(err)
        }
    }
}

And got the output as below:

Hasan A Yousef
  • 22,789
  • 24
  • 132
  • 203
  • this way has the same effect with a loop. it's not efficient, closed port may take up to 4 minutes to release as metioned in https://stackoverflow.com/questions/5106674/error-address-already-in-use-while-binding-socket-with-address-but-the-port-num#answer-5106755 – emptyhua Oct 31 '21 at 23:15
  • Like @emptyhua already said it's not efficient, and not the `idiomatic way` to resolve your issue. – MoiioM Nov 02 '21 at 08:27