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]