Go言語におけるSignalのハンドリングについて

golangには、Linuxのシグナルをハンドリングする機能がある。 具体的には、os/signalパッケージ/syscallパッケージとchannel機能を使う。

func main() {
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh,
        syscall.SIGTERM,
        syscall.SIGINT)

    switch(sigCh) {
        case syscall.SIGTERM:
            fmt.Println("SIGTERM")
        case syscall.SIGINT:
            fmt.Println("SIGINT")
    }
}

ただし、signalはlinux/unixにおける概念であり、WindowsでのCTRL^Cはキャッチできない。パッケージもsyscallだし。

Windowsに対応させるには、os.Interruptを使う (参考:signal - The Go Programming Language

func main() {
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh,
        syscall.SIGTERM,
        syscall.SIGINT,
        os.Intterupt)

    switch(sigCh) {
        case syscall.SIGTERM:
            fmt.Println("SIGTERM")
        case syscall.SIGINT:
            fmt.Println("SIGINT")
        case os.Interrupt:
            fmt.Println("Interrupt")
    }
}

これで対象のシグナルが発生すると、sigChにメッセージが通知されるようになる。 なお、sigChはchannelのため、読み出しを行うと、メッセージングされるまでブロッキングが発生する。

通常は何かを実行中のバックグランドでキャッチしたいはずなので、goroutineにする。ただし、goroutineはGCされないため、終了しないとリークしてしまうらしい(Go言語(Golang) はまりどころと解決策)。

なので、別途チャンネルを使って停止できるようにする。 このとき、ちゃんとgoroutineが終了するように、sync.WaitGroupを使って同期を行う

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "sync"
)

func main() {
    wg := sync.WaitGroup{}
    doneCh := make(chan struct{}, 1)

    wg.Add(1)
    go func() {
        defer wg.Done()

        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh,
            syscall.SIGTERM,
            syscall.SIGINT,
            os.Intterupt)
        defer signal.Stop(sigCh)

        for {
            select {
            case sig := <-sigCh:
                switch(sig) {
                case syscall.SIGTERM:
                    fmt.Println("SIGTERM")
                case syscall.SIGINT:
                    fmt.Println("SIGINT")
                case os.Interrupt:
                    fmt.Println("Interrupt")
                }
            case <-doneCh:
                fmt.Println("Done")
                return
            }
        }
    }()

    // 何かする

    // 終了
    doneCh <- struct{}{}
    wg.Wait()
}

context.Contextと組み合わせれば、CTRL^Cを受けてキャンセルを行ったりできる。

ただその場合、生成されたcontext.CancelFunc()を、シグナルハンドラのgoroutineで直接呼び出すか、一層channelをはさむべきなのか、今一つ判断がつかない。

Visual Studio Codeはmsys2版gitに対応していない

Visual Studio Codeはgitと連携可能で、 Preferenceで、gitのパスを指定して利用します。

ですが、msys2版のgitのパスを指定しても正常に動作しません。

以下のissueによって知ったのですが、msys2のgitは、 git rev-parse --show-toplevelを実行すると、/から始まるPOSIXシェル環境のパスを返します。 vscodeは当然Windows空間であるため、正常に動作しない、ということのようです。

$ cd ~/dotfiles
$ git rev-parse --show-toplevel
/home/User/dotfiles

github.com

というわけで、Chocolateyでportable版のgitを導入してvscodeに渡すようにしました。 PATHを通さないかぎり競合はしないでしょう。

choco install git.commandline

chocolateyではportable版は%ChocolateyInstall%\lib\(package name)インストロールされるようです。

{
    // Path to the git executable
    "git.path": "%ChocolateyInstall%\\lib\\git.commandline\\tools\\bin\\git.exe"
}

Cygwinにgit ver 1.8.3.4を導入する

Cygwinに付属するgitのバージョンは1.7.9。 理由不明ですが、長いことアップデートされていません。

一方、Cygwin Portsでは、現在version .1.8.3.4のgitがサポートされています。 別に古いバージョンでも良いっちゃ良いのですが、git difftoolの--dir-diff という超絶便利機能が、ver 1.7.11からしかサポートされていないという 悲しい状況のため、これを導入します。

git ver.1.8.3.4の導入

setup.exeを-Kオプション付きで起動します。setup.exeのミラーサイトを追加するオプションの模様

cygstart -- /path/to/setup-x86.exe -K http://cygwinports.org/ports.gpg

いつものようにポチポチと勧め、Choose Download Site(s)まで来たら、User URL欄に、ftp://ftp.cygwinports.org/pub/cygwinportsを入力してAddします。

これで、導入できるパッケージ一覧にCygwin Portsの内容が反映されるようになります。

git-completionの導入

同様に、git-completionも導入できます。 どうも、ver 1.7.9以降で、__git_ps1を表示させるためのシェルスクリプトが、git-prompt.shという形式で分割されたようです。 が、何故かこのファイルが含まれていません。仕方がないので、gitのソースから取り出しましょう。git ver1.8.3.4を、sourceにもチェックを入れて導入し直します。

/usr/src/git-1.8.3.4.tar.gz

に、ソースコードがインストールされるため、これを解答し、インストールします。

tar zxvf /usr/src/git-1.8.3.4.tar.gz
cp git-1.8.3.4/contrib/completion/git-prompt.sh /etc/bash_completion.d/

contribには、他にも色々な機能が詰まっているみたいなので、導入してみるのもいいかもしれません。

__git_ps1用の修正

.bashrc or .bash_profileを新環境に合わせて修正します。

git difftool --dir-diffをWinmergeに対応させる

git difftoolにWinmergeを利用するようにします。

以下のようなスクリプトを何処か(~/bin/等)にインストールし、.gitconfigで、スクリプトを呼び出すように修正します。

これでgit difftool --dir-diffが使えるようになりました。

参考文献

PlantUML用のマクロ作りました

テキストからUMLを自動生成するPlantUMLというツールが有ります

テキストベースなので、既存コードを解析しながらシーケンス図を起こす時など、コピペで作れるのでとても便利なのですが (開発時の資料が殆ど残っていないファッキンな環境なのですよ)、 PlantUMLのシーケンス図は、ちょっと凝ったことをしようとすると、とても見通しが悪くなってしまい困りものです。

具体的には、LifeLineの活性/非活性をコマメに行おうとすると、「あれ、今何段目だったっけ」みたいなことが頻繁に起こります。

なので、見通しの良くなるような定義ファイルを作りました。

リポジトリ

使い方

上記リポジトリをcloneしてきて、

  • 作成するUMLと同じフォルダ内に定義ファイルを置く

  • plantumlのinclude pathを通す

java -Dplantuml.include.path="path/to/repo" -jar plantuml.jar test.txt

どちらかの方法で導入し、

上記のように、プログラム言語っぽく記述すると、以下の様なシーケンス図が生成されます。

f:id:cad-san:20140302154853p:plain

簡単な解説

するほどのものでもないですが。。。

利用しているのは、以下の2つのファイルです。2つ目はただのskin定義なので、直接は関係ありません。

  • sequence_macro.h … マクロの定義
  • sequence_skin.h … 見た目を変更するファイル

UMLファイルの行頭で、includeします。

!include "sequence_macro.h"

定義は現状、同期呼び出し用のものだけです。

  • func(送信元, 送信先, メッセージ)

送信元から、送信先メッセージを送信し、一時的に送信先を活性状態にします。

  • func_begin(送信元, 送信先, メッセージ)
  • func_end(送信元, 送信先)

送信元から、送信先メッセージを送信し、送信先を活性状態にします。 送信先が非活性状態になるまで、別途操作が必要な場合に利用します。 func_end()で非活性状態になります。

  • local_func(オブジェクト, メッセージ)

自分自身にメッセージを送ります。

  • local_func_begin(送信元, 送信先, メッセージ)
  • local_func_end(送信元, 送信先)

自分自身にメッセージを送ります。非活性状態になるまで、別途操作が必要な場合に利用します。

終わりに

利用する事により、大分スッキリさせることが出来ました。 が、ヤリクチが微妙に頭悪い感じなんで、もっといい方法があれば誰か教えてください

CppUTestを使うGitHub上のリポジトリをTravis CIと連携させる方法

折角テスティングフレームワークを使っているのだから、CIツールも利用すべき、ということで、調べました。

前準備 : CppUTestを利用したリポジトリをGitHubに公開する

基本的には、ここで記載した内容を、make 一発で出来るようにすればOKです。 私の使ってるリポジトリでは、以下のようにしています。

  • CppUTestは、リポジトリに含まずsubmodule化する
  • MakefileWorker.mkを使いテスト用の実行ファイルを作成するMakefileは専用に切り出す(ex. MakefileCppUTest.mk )
  • プロジェクト自体のMakefileで、CppUTestのビルドを行ったあと、上記Makefileを呼び出すようにする(ex. Makefile )

1. Travis CIと連携する

ブラウザから、GitHubとTravis CI間で連携するための設定を行います。

  1. Travis CIにログインする
  2. リポジトリ一覧から対象のリポジトリのスイッチをONにする

私はこちらを参考にしました。非常にわかりやすいです。

2. Travis CI用の設定ファイルを書く

リポジトリ直下に.travis.ymlという名前の設定ファイルを設置します。

…素っ気ない設定ファイルですね。 Travis CIはテストの成否をscriptに書かれたコマンドの終了ステータスで判断しているようです。(0だと成功、それ以外だと失敗) CppUTestのMakefileWorker.mkはビルドと共にテストを実行してくれるので、これで十分のようです。

この状態で、commitをpushすると、Travis CIがhockしてビルドを実行してくれます。

CppUTestをインストールせずに利用する方法

CppUTestがver. 3.4になって、automakeによるインストールを行う用に修正された関係で、ver. 3.3以前とは、インストールせずに使う方法が変わっていました。

前提

ここで、CppUTestをシステムにインストールせず利用する、というのは、 libCppUTest.a 及び libCppUTestExt.a を自前でビルドし、 MakefileWorker.mkを利用して、テストプログラムにリンクさせるという方法です。

もしかしたら他にやり方あるのかもしれませんが(automake利用するとか)、浅学にして存じません。何方か知っていたら教えたください。

cpputestライブラリのビルド

Makefile_using_MakefileWorkerというファイルを利用してビルドします。

 $ git://github.com/cpputest/cpputest.git
 $ cd cpputest
 $ make -f Makefile_using_MakefileWorker

CppUMockを利用する場合は、libCppUTestExt.aのビルドが必要です

 $ make -f Makefile_using_MakefileWorker extensions

ver. 3.3以前は、リポジトリルートでmakeすれば良かったのですが、前述のとおり、automakeの関係で、ビルド方法が変わりました。

プロダクトコード&テストコードとのリンク

CppUTesetは、MakefileWorker.mkを使うと簡単にテスト用の実行ファイルを作成することができます。

以下の様な感じでMakefileを記述し、MakefileWorker.mkをインポートしてあげるとビルドから実行までやってくれます。

実際にテストコードを書く方法等は、CppUTestのマニュアルを参照したください。

注意点など

PROJECT_HOME_DIRの注意点

厳密に言うと、SRC_DIRS TEST_SRC_DIRS MOCKS_SRC_DIRS についての注意点です。

Makefileworker.mkを利用すると、ビルド時に、オブジェクトファイルがobjs以下に作成されますが、このとき、実際に生成される場所が

  • objs/$(SRC_DIRS)
  • objs/$(TEST_SRC_DIRS)
  • objs/$(MOCKS_SRC_DIRS)

になります。ここで、PROJECT_HOME_DIR..を指定してしまうと、プロダクトコードのオブジェクトが、例えば objs/../src/hoge.o に展開されてしまいます。

最悪、プロダクトコードと混じってしまうので、.を利用するのが無難でしょう。

プロダクトコードのライブラリはテスト用

Makefileworker.mkを利用すると、プロダクトコードを固めて lib(COMPONENT_NAME).a という静的ライブラリにビルドしてくれますが、これはそのままでは利用できません。

以前書きましたが、CppUTestでは、メモリリークを検出するために、new() delete() 等をマクロを用いて展開しているため、 CppUTestライブラリを利用しないとリンク時エラーになってしまいます。

リリース用のプロダクトコードは、別途makefileを記載する必要があります。

終わりに

もしかして、Makefileworker.mkはもうメンテされていない?