post Image
Goでデーモンを作るにはどうするのが良い?

少し前にAdvent Calendarのネタswiftfsと言うツールを作ったのですが、Goでデーモンプログラムをどう書けば良いのかよくわからなかった。以下の記事を参考にしたけど、そもそもfork()しちゃいかんとなると、なかなか難しいですね。

Goでデーモンを実装する

で、swiftfsは以下のような実装にしました。syscall使うとWindowsとかで動かなくなってしまうとか、そもそも長いとか、あまり良い実装ではないように思います。Go的にもっとい良い方法があったら教えて下さい。コード全文は末尾とGistにあります。

親プロセス、子プロセス(デーモン本体)共通

func main() {
    var child *bool = flag.Bool("child", false, "Run as a child process")
    flag.Parse()

fork()できないので、親プロセス、子プロセスの判別をflagで行います。コマンドに–childが付いていれば子プロセスと判断。

親プロセス

    // 子プロセスとのパイプを作っておく
    r, w, err := os.Pipe()
    if err != nil {
        return err
    }

    cmd := exec.Command(os.Args[0], args...)
    cmd.ExtraFiles = []*os.File{w}
    cmd.Stderr = os.Stderr
    cmd.Stdout = os.Stdout

パイプを使って親プロセスと子プロセスの間でプロセス間通信をします。cmd.ExtraFilesに適当なos.File(この場合はw)を渡しておくと、実行したプロセス側のファイルディスクリプタに割り当てられます。割り当てられるのは3番からです。

子プロセスの起動完了を親側で待ったり、初期化処理でエラーになったことを親側で知るためにこうしました。親側では適切な終了コードとともにプログラムを終了できます。そして

    if err = cmd.Start(); err != nil {
        return err
    }

cmd.Start()で子プロセスを起動します。argsに–childをセットしてあるので、起動されたプロセスはchildMain()が実行されます。


    // パイプから子プロセスの起動状態を取得する
    var status int = DAEMON_START
    go func() {
        buf := make([]byte, 1)
        r.Read(buf)

        if int(buf[0]) == DAEMON_SUCCESS {
            status = int(buf[0])
        } else {
            status = DAEMON_FAIL
        }
    }()

    // 子プロセスの起動を30秒待つ
    i := 0
    for i < 60 {
        if status != DAEMON_START {
            break
        }
        time.Sleep(500 * time.Millisecond)
        i++
    }

    // 親プロセス終了
    if status == DAEMON_SUCCESS {
        return nil
    } else {
        return fmt.Errorf("Child failed to start")
    }

パイプの読み込み側(変数r)には子プロセス側から何かしら状態が返ってきます。それを待つgoroutineを作り、ステータスの変更を待ちます。もし子プロセスがシグナルなどを受け取って異常終了しても、タイムアウトして適切に(エラーで)終了できるようにしてます。

子プロセス


func childMain() {
    // 初期化処理があればここに
    var err error

    // 子プロセスの起動状態を親プロセスに通知する
    pipe := os.NewFile(uintptr(3), "pipe")
    if pipe != nil {
        defer pipe.Close()
        if err == nil {
            pipe.Write([]byte{DAEMON_SUCCESS})
        } else {
            pipe.Write([]byte{DAEMON_FAIL})
        }
    }

これはサンプルなので初期化処理は省いています。初期化が終わった後、子プロセス側はファイルディスクリプタ3番に自身の起動状態を書き込みます。ここは単純にint型で状態を表しています。

コード全文

package main

import (
    "flag"
    "fmt"
    "log"
    "os"
    "os/exec"
    "os/signal"
    "syscall"
    "time"
)

const (
    DAEMON_START = 1 + iota
    DAEMON_SUCCESS
    DAEMON_FAIL
)

func main() {
    var child *bool = flag.Bool("child", false, "Run as a child process")
    flag.Parse()

    if !*child {
        // parent
        if err := parentMain(); err != nil {
            log.Fatalf("Error occurred [%v]", err)
            os.Exit(1)
        } else {
            os.Exit(0)
        }

    } else {
        // child
        childMain()
    }
}

func parentMain() (err error) {
    args := []string{"--child"}
    args = append(args, os.Args[1:]...)

    // 子プロセスとのパイプを作っておく
    r, w, err := os.Pipe()
    if err != nil {
        return err
    }

    cmd := exec.Command(os.Args[0], args...)
    cmd.ExtraFiles = []*os.File{w}
    cmd.Stderr = os.Stderr
    cmd.Stdout = os.Stdout
    if err = cmd.Start(); err != nil {
        return err
    }

    // パイプから子プロセスの起動状態を取得する
    var status int = DAEMON_START
    go func() {
        buf := make([]byte, 1)
        r.Read(buf)

        if int(buf[0]) == DAEMON_SUCCESS {
            status = int(buf[0])
        } else {
            status = DAEMON_FAIL
        }
    }()

    // 子プロセスの起動を30秒待つ
    i := 0
    for i < 60 {
        if status != DAEMON_START {
            break
        }
        time.Sleep(500 * time.Millisecond)
        i++
    }

    // 親プロセス終了
    if status == DAEMON_SUCCESS {
        return nil
    } else {
        return fmt.Errorf("Child failed to start")
    }
}

func childMain() {
    // 初期化処理があればここに
    var err error

    // 子プロセスの起動状態を親プロセスに通知する
    pipe := os.NewFile(uintptr(3), "pipe")
    if pipe != nil {
        defer pipe.Close()
        if err == nil {
            pipe.Write([]byte{DAEMON_SUCCESS})
        } else {
            pipe.Write([]byte{DAEMON_FAIL})
        }
    }

    // SIGCHILDを無視する
    signal.Ignore(syscall.SIGCHLD)

    // STDOUT, STDIN, STDERRをクローズ
    syscall.Close(0)
    syscall.Close(1)
    syscall.Close(2)

    // プロセスグループリーダーになる
    syscall.Setsid()

    // Umaskをクリア
    syscall.Umask(022)

    // / にchdirする
    syscall.Chdir("/")

    // main loop
    for {
        time.Sleep(1000 * time.Millisecond)
    }
}

『 Go 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。