You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go应用重启脚本无法终止当前进程问题求助

解决Go应用版本升级时旧进程无法终止的问题

问题根源分析

  • 日志文件句柄未释放:当前进程打开日志文件后未调用Close(),持有的文件句柄可能干扰进程终止流程;
  • 进程阻塞未主动退出:旧进程启动重启脚本后进入长时间sleep,虽taskkill /F理论上可强制终止,但Go runtime的阻塞状态可能导致信号处理异常;
  • 子进程与父进程资源关联:重启脚本作为旧进程启动的子进程,继承了部分资源,可能影响taskkill的执行效果。

解决方案

1. 修复日志文件句柄泄漏

修改initVar()函数,确保日志文件句柄被正确关闭,并刷新日志缓冲区:

import (
    "fmt"
    "log"
    "os"
    "path/filepath"
)

func initVar() {
    executable, err := os.Executable()
    if err != nil {
        panic(err)
    }
    CurPath = executable
    CurParent = filepath.Dir(CurPath)
    CurName = filepath.Base(CurPath)
    CurExt = filepath.Ext(CurName)

    // 修正文件权限为更合理的0644
    lf, err := os.OpenFile(LogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        panic(err)
    }
    defer lf.Close() // 添加defer确保进程退出时关闭句柄
    Log = log.New(lf, "", log.LstdFlags)
}

func renameCur() {
    if err := os.Rename(CurPath, RenamePath(CurName)); err != nil {
        panic(err)
    }
    logMsg := fmt.Sprintf("Rename Current Running Process Executable File")
    Log.Println(logMsg)
    // 刷新日志到磁盘,避免缓冲区未写入
    if f, ok := Log.Writer().(*os.File); ok {
        f.Sync()
    }
}

func copyNew() {
    cf, err := os.Create(CurPath)
    if err != nil {
        panic(err)
    }
    defer cf.Close()
    nf, err := os.Open(NewVersionPath(CurName))
    if err != nil {
        panic(err)
    }
    defer nf.Close()
    if _, err = io.Copy(cf, nf); err != nil {
        panic(err)
    }
    logMsg := fmt.Sprintf("Copy New Version Executable And Replace Current Path")
    Log.Println(logMsg)
    if f, ok := Log.Writer().(*os.File); ok {
        f.Sync()
    }
}

2. 让旧进程主动退出,避免阻塞

修改restart()函数,启动重启脚本后立即主动退出旧进程,同时设置子进程独立运行:

import (
    "os"
    "os/exec"
    "strconv"
    "syscall"
)

func restart() {
    cmd := exec.Command(CmdPath(), strconv.Itoa(os.Getpid()), CurPath)
    // 创建独立进程组,避免子进程继承父进程资源
    cmd.SysProcAttr = &syscall.SysProcAttr{
        CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
    }
    err := cmd.Start()
    if err != nil {
        Log.Println("Restart Application Failure:", err)
        // 启动失败时强制退出
        os.Exit(1)
    }
    // 主动退出旧进程,无需等待sleep
    os.Exit(0)
}

3. 优化restart.cmd脚本,确保进程终止生效

更新批处理脚本,添加重试机制和进程存在性检查:

@echo off
setlocal enabledelayedexpansion

set "pid=%1"
set "path=%2"
set "len=2"

set "v_args="
set "count=0"
for %%a in (%*) do (
    if !count! geq %len% (
        set v_args=!v_args! %%a
    )
    set /A count+=1
)

set "v_args=%v_args:~1%"

:: 检查PID是否存在
tasklist /FI "PID eq %pid%" | findstr /I "%pid%" >nul
if not errorlevel 1 (
    :: 强制终止进程,重试一次确保成功
    taskkill /F /PID %pid% >nul 2>&1
    if errorlevel 1 (
        timeout /t 1 /nobreak >nul
        taskkill /F /PID %pid% >nul 2>&1
    )
)

:: 后台启动新版本,避免控制台窗口残留
start /B "" "%path%" %v_args%

:: 脚本立即退出
exit /B 0

4. 替代方案:直接在Go代码中终止旧进程

无需依赖批处理脚本的taskkill,直接在Go代码中启动新版本后主动终止旧进程:

import (
    "os"
    "os/exec"
    "syscall"
)

func restart() {
    // 直接启动新版本应用
    cmd := exec.Command(CurPath)
    err := cmd.Start()
    if err != nil {
        Log.Println("Restart Application Failure:", err)
        os.Exit(1)
    }
    // 强制终止当前进程
    err = syscall.Kill(os.Getpid(), syscall.SIGTERM)
    if err != nil {
        os.Exit(1)
    }
}

内容的提问来源于stack exchange,提问作者Emiya Elien

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 22:00:54