如何在Go守护进程中重新启动自身?

huangapple go评论92阅读模式
英文:

How to restart itself in Go daemon process?

问题

我使用go-daemon库来分叉进程并在后台运行它。在http处理程序内执行更新后,我需要重新启动守护进程。

处理程序的代码如下:

func httpUpdate(w http.ResponseWriter, req *http.Request) {
    if !isPost(req.Method) {
        http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
        return
    }

    if checkAuth(req) != 200 {
        http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
        return
    }

    log.Println("INFO: Update request, checking for update...")
    var err = doUpdate(UPDATE_URL, nil, false)
    if !isError(err) {
        log.Println("INFO: Update successful, exit")
        var system = RealSystem{}
        system.Run(fmt.Sprintf("(sleep 0.3s && %s start >/test/extra.log)&disown", appFilename()))
        system.Exit(0)

        return
    }

    w.WriteHeader(http.StatusNoContent)
}

如果doUpdate()成功替换了可执行文件,则返回nilRealSystem只是exec.Commandos.Exit()的包装器。appFilename()是可执行文件的名称。启动应用程序的命令是/path/to/app start

我看到新进程启动了,但执行Context::Reborn()时出现EOF错误。看起来一些作为实现细节的内部管道出现了EOF错误(可能是...)。

可能的原因是什么?或者有没有更好的方法来实现这个?

目前,一切都在Docker容器中的e2e测试的“上下文”中进行,如果有关系的话。我花了几个小时试图让它工作,但没有成功。

英文:

I use go-daemon library to fork process and run it in background. And I need to restart the daemon process after update performed from within http handler.

The handler code is

func httpUpdate(w http.ResponseWriter, req *http.Request) {
	if !isPost(req.Method) {
		http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
		return
	}

	if checkAuth(req) != 200 {
		http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		return
	}

	log.Println("INFO: Update request, checking for update...")
	var err = doUpdate(UPDATE_URL, nil, false)
	if !isError(err) {
		log.Println("INFO: Update successful, exit")
		var system = RealSystem{}
		system.Run(fmt.Sprintf("(sleep 0.3s && %s start &> /test/extra.log)&disown", appFilename()))
		system.Exit(0)

		return
	}

	w.WriteHeader(http.StatusNoContent)
}

doUpdate() returns nil if successfully replaced the executable file. RealSystem is just wrapper for exec.Command and os.Exit(). appFilename() is the executable file name. The command to start app is /path/to/app start.

I see that new process starts, but executing Context::Reborn() fails with EOF error. Looks like some intrinsic pipes used as implementation details fail with EOF (may be...).

What would be the reason? Or may be there is a better way of doing that?

For now everything happens inside docker container in the "context" of e2e test if it matters. I spent hours trying to make it work but with no success.

答案1

得分: 1

我假设你是指重新启动当前正在运行的Go二进制文件。你可以在基于Unix的系统中使用系统调用,而在Windows中可以使用exec.Command

func RestartSelf() error {
    self, err := osext.Executable()
    if err != nil {
        return err
    }
    args := os.Args
    env := os.Environ()
    // Windows不支持exec系统调用。
    if runtime.GOOS == "windows" {
        cmd := exec.Command(self, args[1:]...)
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        cmd.Stdin = os.Stdin
        cmd.Env = env
        err := cmd.Run()
        if err == nil {
            os.Exit(0)
        }
        return err
    }
    return syscall.Exec(self, args, env)
}

希望对你有帮助!

英文:

I assume you mean restarting the currently running Go binary. You can use a syscall for unix-based systems, and use an exec.Command for Windows.

func RestartSelf() error {
	self, err := osext.Executable()
	if err != nil {
		return err
	}
	args := os.Args
	env := os.Environ()
	// Windows does not support exec syscall.
	if runtime.GOOS == "windows" {
		cmd := exec.Command(self, args[1:]...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		cmd.Env = env
		err := cmd.Run()
		if err == nil {
			os.Exit(0)
		}
		return err
	}
	return syscall.Exec(self, args, env)
}

答案2

得分: 0

问题是特定于该库。从子进程中生成新的 self 实例对于系统来说不是问题,但对于该库来说是个问题。

要实现这一点,需要执行类似以下的操作。
注意将 _GO_DAEMON=0 变量设置为零。这使得库遵循父进程的控制流程。

var cmd = exec.Command("bash", "-c", fmt.Sprintf("sleep 0.5s; _GO_DAEMON=0 %s start", appFilename()))
var err = cmd.Start()

此外,还需要对原始库进行一些小的更改。这是分支

英文:

The issue is specific to the library. Spawn new self instance from within child process is not a problem for the system, but for that library.

To achieve this it's necessary to execute something like that.
Note the _GO_DAEMON=0 variable set to zero. This makes library follow parent control flow.

var cmd = exec.Command("bash", "-c", fmt.Sprintf("sleep 0.5s; _GO_DAEMON=0 %s start", appFilename()))
var err = cmd.Start()

Also it was necessary to make small changes to the original library. Here is the fork.

huangapple
  • 本文由 发表于 2021年7月1日 05:03:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/68201595.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定