mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
This pull request improves the reliability and safety of server restart and reload operations by ensuring the correct retrieval of the current executable path and process arguments. It replaces direct usage of `os.Args[0]` with a more robust approach using `gfile.SelfPath()`, and adds error handling for cases where the executable path cannot be determined. Additionally, it introduces a helper function to safely obtain process arguments, and removes an unnecessary import. **Executable Path Handling and Error Checking:** - Replaced usage of `os.Args[0]` with `gfile.SelfPath()` throughout the server admin and process management code to reliably determine the current executable path; added checks and error responses when the path cannot be determined. [[1]](diffhunk://#diff-0d174b149c56c4aa7ffeba2be94d16dc1b8000933f1f2a2e6bf011acdad3272fL122-R134) [[2]](diffhunk://#diff-0d174b149c56c4aa7ffeba2be94d16dc1b8000933f1f2a2e6bf011acdad3272fL168-R184) [[3]](diffhunk://#diff-3b4265be7ef0335b832dacfc2fc7ddc0f9dfae5b81340ff57d2b6a526c60d9e1L62-R65) - Added early returns in initialization functions if `os.Args` is empty, preventing potential panics or misbehavior when the argument list is missing. [[1]](diffhunk://#diff-0aa99f033274ea60b9c466ae4fc98d0816ec13781d8ec787fb3ef106a49a79ecR35-R37) [[2]](diffhunk://#diff-5782fa47aa858b8e8358fd50353b050ee30418b7844b36e313e9c6d01188c092R47-R49) **Process Argument Handling:** - Introduced the `getCurrentProcessArgs()` helper function to safely return process arguments (excluding the program name), ensuring correct behavior even if no arguments are provided. Updated process creation calls to use this helper. **Code Cleanup:** - Removed an unused import of the `os` package from `ghttp_server_admin.go`. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
209 lines
5.4 KiB
Go
209 lines
5.4 KiB
Go
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
// If a copy of the MIT was not distributed with this file,
|
|
// You can obtain one at https://github.com/gogf/gf.
|
|
|
|
package gdebug
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
maxCallerDepth = 1000
|
|
stackFilterKey = "/debug/gdebug/gdebug"
|
|
)
|
|
|
|
var (
|
|
goRootForFilter = runtime.GOROOT() // goRootForFilter is used for stack filtering purpose.
|
|
binaryVersion = "" // The version of current running binary(uint64 hex).
|
|
binaryVersionMd5 = "" // The version of current running binary(MD5).
|
|
selfPath = "" // Current running binary absolute path.
|
|
)
|
|
|
|
func init() {
|
|
if goRootForFilter != "" {
|
|
goRootForFilter = strings.ReplaceAll(goRootForFilter, "\\", "/")
|
|
}
|
|
if len(os.Args) == 0 {
|
|
return
|
|
}
|
|
// Initialize internal package variable: selfPath.
|
|
selfPath, _ = exec.LookPath(os.Args[0])
|
|
if selfPath != "" {
|
|
selfPath, _ = filepath.Abs(selfPath)
|
|
}
|
|
if selfPath == "" {
|
|
selfPath, _ = filepath.Abs(os.Args[0])
|
|
}
|
|
}
|
|
|
|
// Caller returns the function name and the absolute file path along with its line
|
|
// number of the caller.
|
|
func Caller(skip ...int) (function string, path string, line int) {
|
|
return CallerWithFilter(nil, skip...)
|
|
}
|
|
|
|
// CallerWithFilter returns the function name and the absolute file path along with
|
|
// its line number of the caller.
|
|
//
|
|
// The parameter `filters` is used to filter the path of the caller.
|
|
func CallerWithFilter(filters []string, skip ...int) (function string, path string, line int) {
|
|
var (
|
|
number = 0
|
|
ok = true
|
|
)
|
|
if len(skip) > 0 {
|
|
number = skip[0]
|
|
}
|
|
pc, file, line, start := callerFromIndex(filters)
|
|
if start != -1 {
|
|
for i := start + number; i < maxCallerDepth; i++ {
|
|
if i != start {
|
|
pc, file, line, ok = runtime.Caller(i)
|
|
}
|
|
if ok {
|
|
if filterFileByFilters(file, filters) {
|
|
continue
|
|
}
|
|
function = ""
|
|
if fn := runtime.FuncForPC(pc); fn == nil {
|
|
function = "unknown"
|
|
} else {
|
|
function = fn.Name()
|
|
}
|
|
return function, file, line
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return "", "", -1
|
|
}
|
|
|
|
// callerFromIndex returns the caller position and according information exclusive of the
|
|
// debug package.
|
|
//
|
|
// VERY NOTE THAT, the returned index value should be `index - 1` as the caller's start point.
|
|
func callerFromIndex(filters []string) (pc uintptr, file string, line int, index int) {
|
|
var ok bool
|
|
for index = 0; index < maxCallerDepth; index++ {
|
|
if pc, file, line, ok = runtime.Caller(index); ok {
|
|
if filterFileByFilters(file, filters) {
|
|
continue
|
|
}
|
|
if index > 0 {
|
|
index--
|
|
}
|
|
return
|
|
}
|
|
}
|
|
return 0, "", -1, -1
|
|
}
|
|
|
|
func filterFileByFilters(file string, filters []string) (filtered bool) {
|
|
// Filter empty file.
|
|
if file == "" {
|
|
return true
|
|
}
|
|
// Filter gdebug package callings.
|
|
if strings.Contains(file, stackFilterKey) {
|
|
return true
|
|
}
|
|
for _, filter := range filters {
|
|
if filter != "" && strings.Contains(file, filter) {
|
|
return true
|
|
}
|
|
}
|
|
// GOROOT filter.
|
|
if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
|
|
// https://github.com/gogf/gf/issues/2047
|
|
fileSeparator := file[len(goRootForFilter)]
|
|
if fileSeparator == filepath.Separator || fileSeparator == '\\' || fileSeparator == '/' {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// CallerPackage returns the package name of the caller.
|
|
func CallerPackage() string {
|
|
function, _, _ := Caller()
|
|
// it defines a new internal function to retrieve the package name from caller function name,
|
|
// which is for unit testing purpose for core logic of this function.
|
|
return getPackageFromCallerFunction(function)
|
|
}
|
|
|
|
func getPackageFromCallerFunction(function string) string {
|
|
indexSplit := strings.LastIndexByte(function, '/')
|
|
if indexSplit == -1 {
|
|
return function[:strings.IndexByte(function, '.')]
|
|
}
|
|
var (
|
|
leftPart = function[:indexSplit+1]
|
|
rightPart = function[indexSplit+1:]
|
|
indexDot = strings.IndexByte(rightPart, '.')
|
|
)
|
|
if indexDot >= 0 {
|
|
rightPart = rightPart[:indexDot]
|
|
}
|
|
return leftPart + rightPart
|
|
}
|
|
|
|
// CallerFunction returns the function name of the caller.
|
|
func CallerFunction() string {
|
|
function, _, _ := Caller()
|
|
function = function[strings.LastIndexByte(function, '/')+1:]
|
|
function = function[strings.IndexByte(function, '.')+1:]
|
|
return function
|
|
}
|
|
|
|
// CallerFilePath returns the file path of the caller.
|
|
func CallerFilePath() string {
|
|
_, path, _ := Caller()
|
|
return path
|
|
}
|
|
|
|
// CallerDirectory returns the directory of the caller.
|
|
func CallerDirectory() string {
|
|
_, path, _ := Caller()
|
|
return filepath.Dir(path)
|
|
}
|
|
|
|
// CallerFileLine returns the file path along with the line number of the caller.
|
|
func CallerFileLine() string {
|
|
_, path, line := Caller()
|
|
return fmt.Sprintf(`%s:%d`, path, line)
|
|
}
|
|
|
|
// CallerFileLineShort returns the file name along with the line number of the caller.
|
|
func CallerFileLineShort() string {
|
|
_, path, line := Caller()
|
|
return fmt.Sprintf(`%s:%d`, filepath.Base(path), line)
|
|
}
|
|
|
|
// FuncPath returns the complete function path of given `f`.
|
|
func FuncPath(f any) string {
|
|
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
|
}
|
|
|
|
// FuncName returns the function name of given `f`.
|
|
func FuncName(f any) string {
|
|
path := FuncPath(f)
|
|
if path == "" {
|
|
return ""
|
|
}
|
|
index := strings.LastIndexByte(path, '/')
|
|
if index < 0 {
|
|
index = strings.LastIndexByte(path, '\\')
|
|
}
|
|
return path[index+1:]
|
|
}
|