This is an automated email from the ASF dual-hosted git repository.

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git


The following commit(s) were added to refs/heads/master by this push:
     new fadf702  feat: kill the process holding the port on startup
fadf702 is described below

commit fadf702449cabf760b22500fcd5b355ca23a8207
Author: Yang Luo <[email protected]>
AuthorDate: Tue Aug 18 22:25:22 2026 +0800

    feat: kill the process holding the port on startup
---
 conf/app.conf   |  7 +++--
 main.go         | 16 +++++++++++
 util/process.go | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 104 insertions(+), 2 deletions(-)

diff --git a/conf/app.conf b/conf/app.conf
index 6c218ac..e00c500 100644
--- a/conf/app.conf
+++ b/conf/app.conf
@@ -1,5 +1,6 @@
 appname = casbin-gateway
-; Port of the management UI and the REST API.
+; Port of the management UI and the REST API. Whatever is already listening on
+; it is killed on startup, so a previous run does not have to be stopped by 
hand.
 httpport = 17000
 runmode = dev
 SessionOn = true
@@ -20,7 +21,9 @@ redisEndpoint =
 ; The reverse-proxy gateway, i.e. the WAF itself. It is off by default because
 ; ports 80 and 443 usually belong to something else on the host, and binding
 ; them needs root on Linux and macOS. Sites and rules can still be configured
-; while it is off, but nothing is proxied until it is turned on.
+; while it is off, but nothing is proxied until it is turned on. Turning it on
+; also means the two ports below are taken over on startup, killing whatever
+; holds them, so do not point them at a web server that has to keep running.
 gatewayEnabled = false
 gatewayHttpPort = 80
 gatewayHttpsPort = 443
diff --git a/main.go b/main.go
index 181d8ee..7a6f887 100644
--- a/main.go
+++ b/main.go
@@ -92,6 +92,22 @@ func main() {
 
        port := conf.GetHttpPort()
 
+       // A previous run still holding one of these ports would keep this one 
from
+       // starting, so it is stopped first. The gateway ports come before the
+       // management port because service.Start() binds them first.
+       stopPorts := []int{}
+       if conf.IsGatewayEnabled() {
+               stopPorts = append(stopPorts, conf.GetGatewayHttpPort(), 
conf.GetGatewayHttpsPort())
+       }
+       stopPorts = append(stopPorts, port)
+       for _, stopPort := range stopPorts {
+               if err := util.StopOldInstance(stopPort); err != nil {
+                       // The bind below reports the conflict in full, so a 
failed kill only
+                       // needs a note here and never stops the startup by 
itself.
+                       fmt.Printf("Casbin Gateway: could not free port %d: 
%v\n", stopPort, err)
+               }
+       }
+
        service.PrintStartupSummary()
 
        // beego.Run() binds the management port itself and reports a conflict 
as a
diff --git a/util/process.go b/util/process.go
new file mode 100644
index 0000000..ea3ff0a
--- /dev/null
+++ b/util/process.go
@@ -0,0 +1,83 @@
+// Copyright 2026 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package util
+
+import (
+       "fmt"
+       "os"
+       "time"
+)
+
+const (
+       // portReleaseTimeout bounds the wait for a killed process to let go of 
its
+       // port. The kill is asynchronous, so the port stays busy for a short 
while
+       // after the process itself is gone.
+       portReleaseTimeout = 3 * time.Second
+       // portReleasePollInterval is how often the port is retried while 
waiting.
+       portReleasePollInterval = 100 * time.Millisecond
+)
+
+// StopOldInstance kills whatever process is listening on the port, so that a
+// restart never has to wait for the previous run to be shut down by hand.
+//
+// This takes the port for Casbin Gateway unconditionally: an unrelated program
+// that happens to be listening there is killed just the same, so the ports in
+// conf/app.conf have to be ones this machine really is allowed to hand over.
+// Only sockets in the LISTEN state count, so a process that merely holds a
+// connection to a remote port with the same number is never touched.
+func StopOldInstance(port int) error {
+       pid := findListenerPid(port)
+       if pid <= 0 || pid == os.Getpid() {
+               return nil
+       }
+
+       process, err := os.FindProcess(pid)
+       if err != nil {
+               return err
+       }
+
+       name := findProcessName(pid)
+
+       err = process.Kill()
+       if err != nil {
+               return err
+       }
+
+       if name == "" {
+               fmt.Printf("Casbin Gateway: stopped pid %d, which was holding 
port %d\n", pid, port)
+       } else {
+               fmt.Printf("Casbin Gateway: stopped %s (pid %d), which was 
holding port %d\n", name, pid, port)
+       }
+
+       return waitForPortRelease(port)
+}
+
+// waitForPortRelease blocks until the port can be bound again, because a 
killed
+// process releases it a moment after it disappears. It gives up quietly on
+// timeout and leaves the caller's own bind to produce the error.
+func waitForPortRelease(port int) error {
+       deadline := time.Now().Add(portReleaseTimeout)
+       for {
+               if err := CheckPortAvailable(port); err == nil {
+                       return nil
+               }
+
+               if time.Now().After(deadline) {
+                       return nil
+               }
+
+               time.Sleep(portReleasePollInterval)
+       }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to