詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

チャンネルを使って、決まった数のリソースをgoroutine間で共有するパターン

生成が重いリソース(や重い処理の実行権)を goroutine 間で共有し使いまわすようなパターンです。よく知られていて名前がついていそうだけど、ぐぐっても分からなかったので書いておく。

コネクションプールに近い感じで、最初にリソースを生成したあと、それらを大事に取り回します。リソースが空いてなかったら goroutine は待つことにする。sync.Pool は「プールにあったら使うけど、なかったら新しく作る」くらいの感じなので、ちょっとスタンスが違う。

チャンネルによる実装は簡単で、以下のエントリにも書いたセマフォを応用すればよい。

ざっくりと書いてみた例がこちら: https://play.golang.org/p/QWAXsA_89Y

チャンネルによるセマフォの実装は、「バッファありチャンネルに何か(struct{})を挿入できた goroutine が実行の権利を持つ」というものでしたが、今回は「バッファありチャンネルからリソースを取得できた goroutine がリソースの権利を持つ」というふうになっています。

最初にリソースのプール(チャンネル)を作り:

pool := make(chan *worker, 5)
for i := 0; i < 5; i++ {
    pool <- &worker{i}
}

各 gorountine では、リソースをプールから取得。リソースを使い終わったらプールに戻します。プールに戻すまではリソースを占有できていることが保証されています。

    case w := <-pool:
        w.work()
        pool <- w
    }

リソースを取得しようとするとき、空いているものがなければブロックするので、セマフォの上位版ということになりますね。

最近のGoプロジェクトのMakefile

最近は仕事でも新しくGoのプロジェクトをイチからはじめることが増えてきて、コピペ元が欲しくなるので、スナップショットとして残しておきます。とくに Go でウェブアプリケーションを書くような場合を想定していて、npm エコシステムにも乗っていきます。

大まかな方針としては、

  • self-contained である
  • グローバルな環境を汚染しない
  • コマンド一発で開発環境が再現できる

……というところを目指します。

motemen/prchecklist がこれを達成しているつもりなので、以下、これを例に見ていきます。

依存ライブラリは dep なり何かしらのツールと Go 標準の vendoring で管理すればよい一方、そのツール自体であったり、他の開発中に必要なツール(golint とか gobump とか)であったりのインストールをどうするかという話。

npm であれば devDependencies というやつでうまいことやってくれますが、現行の Go の依存管理ツールではデファクトがない、というか依存管理ツールのデファクトが(少なくとも自分の中では)まだ定まってないので、これを Makefile でやる必要がある。

かといって依存ツールを単純に go get してはグローバルな環境を汚染してしまうことになるので、npm を真似て Go 製ツールのバイナリを .bin/ 以下に置くことにします。以下のように GOBIN 環境変数を指定すれば、バイナリを作業ディレクトリ以下にインストールできます*1

.PHONY: setup-go
setup-go:
   GOBIN=$(abspath .bin) go get -v \
        gobin.cc/go-bindata \
        gobin.cc/reflex \
        gobin.cc/mockgen \
        github.com/motemen/go-generate-jsschema/cmd/gojsschemagen

gobin.cc を使えばすっきり。

最初に make setup-go しなくてもうまいこと動くように、依存関係をきちんと書いてやります。

.PHONY: test
test: lib/web/web_mock_test.go
    ...

lib/web/web_mock_test.go: lib/web/web.go .bin/mockgen
    .bin/mockgen -package web -source $< GitHubGateway > $@

.bin/%: Makefile
    @$(MAKE) setup-go
    @touch $@

テストがモックのソースコードに依存し、それがさらに .bin/mockgen に依存するように宣言しています。そして .bin/ 以下のファイルは先ほどの setup-go タスクで生成するよう指定します。.bin/% の依存先として Makefile を書いているのは、go get するパッケージの一覧が Makefile に書いてあるから。

ここに書いたのとまったく同じ方法で npm パッケージ向けの設定も書けますが、その場合は

node_modules/.bin/%: package.json

のように書けばいいわけです。

このようにして、gonpm または yarn さえ入っていればだいたい再現可能な開発環境が作れました(だいたいというのは、Go ツールに非互換な変更が入ったときに壊れる、という懸念があるよね、というところです)。

余談になるけれど、Go ソースコードの watch & build にこれまでよく entr を使っていて、これが大変便利なんですが、パッケージマネージャによるユニバーサルなインストール方法というのがなく、このエントリに書いたような再現可能な環境を作るために Go 製の reflex を使ってみました。

ls **/*.go | entr -r sh -c '...'

reflex -r '\.go\z' -s -- sh -c '...'

になる感じで、汎用的に使えるツールです。わりとよさそう。

*1:$GOPATH/pkg は汚染されますが。これは自分にとっては問題にならない

prchecklist でリリース Pull Request のチェックフローをスムーズに行う

背景

GitHub を使った開発では、

  • master ブランチがいつでも本番に出せる状態として、
  • master から切った develop ブランチを開発のベースとし、
  • 各フィーチャは develop から切って develop にマージし、
  • リリースのタイミングで develop を master にマージ、リリース

……という流れを pull request ベースで行うのがよくあるパターンのひとつだと思います。リリースの際、ステージングや QA という名前のついた本番前環境でそれぞれの機能が正しく動いているか確認するのもよくあるフローです。

このチェックを pull request 本文のチェックボックスを使って行おう、というアイデアを実装したのが git-pr-release で、もともと id:hitode909 がチーム向けにこしらえたものをパクった汎用化したものでした。この仕組はとても便利で、いまも社内で使われているのですが、GitHub 上のチェックボックスを利用するのはすこし困るところがありました:

  • (最近はないけど)Firefox で挙動がおかしいことがあった
  • 誰がチェックしたかわからない
  • 複数人でチェックできない
  • チェックが完了したかどうか、リリース担当者が確認する必要がある

などなど。GitHub の機能追加でチェックボックスまわりが今後リッチになっていく可能性は十分ありますが、それがいつのことかは当然わかりません。チームのプロセスの進歩のためには、自分たちで拡張可能な仕組みにする必要があります。

prchecklist

そういうわけでこのチェックリストをウェブアプリケーション化したのが prchecklist です。

GitHub - motemen/prchecklist: Provides checklists based on release PR

git-pr-release と同様に、複数のフィーチャ PR をまとめて master にマージする「リリース PR」に対してチェックリストを生成します(pull request の作成はおこないません)。チェックリストは、リポジトリへのアクセス権限のある GitHub ユーザによってチェックされ、設定によっては Slack への通知も行えます。

動作を大づかみするには、Heroku にデプロイしているデモを見るのが早そうです。サンプルのリリース PR である https://github.com/motemen/test-repository/pull/2 のチェックリストが 、prchecklist 上では https://prchecklist.herokuapp.com/motemen/test-repository/pull/2 になります(GitHub における URL のパスが prchecklist のパスに対応しています)。ログイン必須で右上から GitHub ログインする必要がありますが、repo 権限が必要なのでちょっと厳しいかも……。という人はあとの説明をみて、手元で立てて確かめてみてください。

f:id:motemen:20170926092823p:plain

設定

先のデモでは、qa と production の2ステージが選択できるようになっていました(右上のドロップダウン)。このような設定は、リポジトリ直下の prchecklist.yml でカスタマイズできます。

例はステージの設定のみですが、通知を設定することもできて、以下はチェックリストのアイテムがチェックされた場合と、チェックリストがすべてチェックされた場合に別々の Slack チャンネルに通知するような例です。

stages:
  - qa
  - production
notification:
  events:
    on_check:
      - ch_check
    on_complete:
      - ch_complete
  channels:
    ch_complete:
      url: https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX
    ch_check:
      url: https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX

通知は、こんな感じのが来ます。

f:id:motemen:20170926093024p:plain

ビルド/デプロイ

https://github.com/motemen/prchecklist/releases にビルド済みのバイナリをアップロードしています。go get github.com/motemen/prchecklist/cmd/prchecklist または git clone して make でも取得可能です。

利用するには GitHub にアプリケーションを登録する必要があります(-github-client-id-github-client-secret)。また、チェック情報の保存に別途ストレージが必要で、デフォルトでは Bolt を利用しますが、-datasource redis://u:<password>@<host> のような形で Redis も指定可能です。

% prchecklist -h
Usage of prchecklist:
  -behind-proxy
        prchecklist is behind a reverse proxy (PRCHECKLIST_BEHIND_PROXY)
  -datasource string
        database source name (default "bolt:./prchecklist.db")
  -github-client-id string
        GitHub client ID (GITHUB_CLIENT_ID)
  -github-client-secret string
        GitHub client secret (GITHUB_CLIENT_SECRET)
  -github-domain string
        GitHub domain (GITHUB_DOMAIN) (default "github.com")
  -licenses
        show license notifications
  -listen address
        address to listen (default "localhost:8080")
  -session-secret string
        session secret (PRCHECKLIST_SESSION_SECRET)
  -version
        show version information

おわり

便利なウェブアプリ、prchecklist の紹介でした。

今のところ社内でも順調に使われています。git-pr-release と組み合わせるとより便利でしょう。

ためしに Gitter ルームも用意してみました。make develop で開発環境も簡単に立ち上がりますし、PR ウェルカムです!

はてなで一緒に働きませんか?