2時に寝て3回ほど起きて7時に起きた。淡々と自分の機能開発をしていた。ある処理が失敗したときにローカルのファイルシステムに暗号化して書き込み、定期的にそのディレクトリを監視してファイルがあれば読み込んで復号化してリトライをするといった仕組みを実装した。とくに難しくなくすぐにできた。

晩ご飯に 大衆酒場 PING (ピン) というお店に行ってみた。1人でも入りやすく値段も安く食べ応えもあっても私のイメージする居酒屋さんの印象にぴったりでよかった。また出張したら行こうと思う。そろそろ出張でバテてきたので今日は夜にお仕事せずに晩ご飯食べてからもテレビをみながらだらだらしてた。

windows のサービス起動

準標準パッケージである golang.org/x/sys パッケージを使ってアプリケーションの起動と停止をサービス管理画面から操作できるようにする。サンプルコードは次の場所にある。

サービスから呼ばれたかどうかを判定をすることでエントリーポイントを切り替えられる。

inService, err := svc.IsWindowsService()
if inService && err == nil {
    if err := runService(serviceName, false); err != nil {
        log.Error("failed to run service", map[string]any{
            "err": err,
        })
        return
    }
}

そして、次のような Execute() メソッドをもつ windows サービスから呼ばれる構造体を定義して、そのメソッド内でサービス管理画面からのステータス変更に対応する状態遷移のコードを実装すればよい。ここではサービス開始してから stop/shutddown で停止するぐらいしか必要ないのでシンプルに実装した。一時停止や再開も必要ならもう少し複雑なコードになる。

type myService struct{}

func (m *myService) Execute(
	args []string, reqCh <-chan svc.ChangeRequest, statusCh chan<- svc.Status,
) (ssec bool, errno uint32) {
	ctx, cancel := context.WithCancel(context.Background())
	ch := startMyService(ctx, getConfig())
	statusCh <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}
	stop := false
	for {
		select {
		case c := <-reqCh:
			switch c.Cmd {
			case svc.Stop, svc.Shutdown:
				stop = true
				cancel()
				break
			}
		}
		if stop {
			break
		}
	}
	statusCh <- svc.Status{State: svc.StopPending}
	<-ch // wait until MyService would be completed
	return
}