Golang:如何在二进制文件更新时自动重启进程?

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

Golang: How to auto-restart process when binary updated?

问题

我在本地构建了Go语言应用程序,然后使用scp命令将其传输到服务器上。我需要手动停止进程并重新启动。是否有一种方法可以在二进制文件更新时自动重新启动进程?

英文:

I built the golang app in local, then scp to server. I need to stop the process and restart manually. Is there any way to auto-restart the process when binary updated?

答案1

得分: 6

虽然通常最好使用类似daemontools或类似工具在进程外部实现此功能,但有些情况下您可能希望在程序内部完成。

在程序内部实现可能会有一些复杂性,取决于程序的特性,例如它可能打开的连接或文件等。

话虽如此,下面是一个在大多数情况下都能工作的实现示例:

package main

import (
	"log"
	"os"
	"syscall"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/kardianos/osext"
)

func setupWatcher() (chan struct{}, error) {
	file, err := osext.Executable()
	if err != nil {
		return nil, err
	}
	log.Printf("watching %q\n", file)
	w, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	done := make(chan struct{})
	go func() {
		for {
			select {
			case e := <-w.Events:
				log.Printf("watcher received: %+v", e)
				err := syscall.Exec(file, os.Args, os.Environ())
				if err != nil {
					log.Fatal(err)
				}
			case err := <-w.Errors:
				log.Printf("watcher error: %+v", err)
			case <-done:
				log.Print("watcher shutting down")
				return
			}
		}
	}()
	err = w.Add(file)
	if err != nil {
		return nil, err
	}
	return done, nil
}

func main() {
	log.Print("program starting")
	watcher, err := setupWatcher()
	if err != nil {
		// do something sensible
		log.Fatal(err)
	}
	// continue with app startup
	time.Sleep(100 * time.Minute) // just for testing
	// eventually you may need to end the watcher
	close(watcher) // this way you can
}

然后执行以下命令:

% go build main.go     
% ./main
2016/12/29 14:15:06 program starting
2016/12/29 14:15:06 watching "/home/plalloni/tmp/v/main"

在另一个终端窗口中运行连续的"go build main.go"命令("更新"正在运行的二进制文件),下面是它产生的输出:

2016/12/29 14:15:32 watcher received: "/home/plalloni/tmp/v/main": CHMOD
2016/12/29 14:15:32 program starting
2016/12/29 14:15:32 watching "/home/plalloni/tmp/v/main"
2016/12/29 14:15:38 watcher received: "/home/plalloni/tmp/v/main": CHMOD
2016/12/29 14:15:38 program starting
2016/12/29 14:15:38 watching "/home/plalloni/tmp/v/main"

希望对您有所帮助。

英文:

While this is generally better to be implemented off-process using something like daemontools or similar, there are some cases when you want/need it to be done inside your program.

Doing it inside your program can be tricky depending on the program characteristics such as connections or files it may have open, etc.

Having said that, here you have an implementation which would work in most cases:

package main
import (
&quot;log&quot;
&quot;os&quot;
&quot;syscall&quot;
&quot;time&quot;
&quot;github.com/fsnotify/fsnotify&quot;
&quot;github.com/kardianos/osext&quot;
)
func setupWatcher() (chan struct{}, error) {
file, err := osext.Executable()
if err != nil {
return nil, err
}
log.Printf(&quot;watching %q\n&quot;, file)
w, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
done := make(chan struct{})
go func() {
for {
select {
case e := &lt;-w.Events:
log.Printf(&quot;watcher received: %+v&quot;, e)
err := syscall.Exec(file, os.Args, os.Environ())
if err != nil {
log.Fatal(err)
}
case err := &lt;-w.Errors:
log.Printf(&quot;watcher error: %+v&quot;, err)
case &lt;-done:
log.Print(&quot;watcher shutting down&quot;)
return
}
}
}()
err = w.Add(file)
if err != nil {
return nil, err
}
return done, nil
}
func main() {
log.Print(&quot;program starting&quot;)
watcher, err := setupWatcher()
if err != nil {
// do something sensible
log.Fatal(err)
}
// continue with app startup
time.Sleep(100 * time.Minute) // just for testing
// eventually you may need to end the watcher
close(watcher) // this way you can
}

Then you do

% go build main.go     
% ./main
2016/12/29 14:15:06 program starting
2016/12/29 14:15:06 watching &quot;/home/plalloni/tmp/v/main&quot;

And here the output it produced after you run (in other terminal) some successive "go build main.go" (which "updates" the running binary).

2016/12/29 14:15:32 watcher received: &quot;/home/plalloni/tmp/v/main&quot;: CHMOD
2016/12/29 14:15:32 program starting
2016/12/29 14:15:32 watching &quot;/home/plalloni/tmp/v/main&quot;
2016/12/29 14:15:38 watcher received: &quot;/home/plalloni/tmp/v/main&quot;: CHMOD
2016/12/29 14:15:38 program starting
2016/12/29 14:15:38 watching &quot;/home/plalloni/tmp/v/main&quot;

Hope it helps.

答案2

得分: 3

你可以使用https://github.com/slayer/autorestart。

package main

import "github.com/slayer/autorestart"

func main() {
    autorestart.StartWatcher()
    http.ListenAndServe(":8080", nil) // 例如
}

英文:

You can use https://github.com/slayer/autorestart

package main

import &quot;github.com/slayer/autorestart&quot;

func main() {
    autorestart.StartWatcher()
    http.ListenAndServe(&quot;:8080&quot;, nil) // for example
}

答案3

得分: 2

需要复杂吗?你可以运行entr并在二进制文件更改时触发一个更新脚本。

http://entrproject.org/

例如:

echo 'binary_path' | entr script.sh &amp;
英文:

Does it need to be sophisticated? You could have entr running and trigger an updater script when the binary changes.

http://entrproject.org/

e.g.

echo &#39;binary_path&#39; | entr script.sh &amp;

答案4

得分: 1

我对这个案例有一个解决方案。

另请参阅。
https://github.com/narita-takeru/cmdrevive

示例

cmdrevive ./htmls/ &quot;.html$&quot; (应用程序) (参数)

所以,这个案例适用。

cmdrevive &quot;/(应用程序路径)&quot; &quot;(应用程序文件名)&quot; (应用程序完整路径) (参数)

如果在(应用程序路径)目录下更改了(应用程序文件名),则使用(参数)重新启动(应用程序完整路径)。

这个方案怎么样?

英文:

I have a resolution about this case.

See also.
https://github.com/narita-takeru/cmdrevive

example

cmdrevive ./htmls/ &quot;.html$&quot; (application) (arguments)

So, this case applicable.

cmdrevive &quot;/(app path)&quot; &quot;(app filename)&quot; (app full path) (arguments)

If (app filename) changed on (app path) directory, then restart (app full path) with (arguments).

How about this one?

huangapple
  • 本文由 发表于 2016年12月29日 16:49:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/41376092.html
匿名

发表评论

匿名网友

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

确定