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
相关产品推荐
相关产品推荐

