読者です 読者をやめる 読者になる 読者になる

at kaneshin

Free space for me.

iOSDC で LT してきました! - Generative Programming in Swift

iOSDC iOS Golang Swift

※写真ありがとうございます!

Generative Programming in Swift // Speaker Deck

Generative Programming とは?

生成的プログラミング(英: Generative programming)とは、ジェネリックなクラス、プロトタイプベース、テンプレート、アスペクト、コード生成などの技法を使ってソースコード作成を自動化し、プログラマの生産性を向上させるプログラミングのスタイルである。ソフトウェアコンポーネントなどのコード再利用の技法とも連携して使用される。

自動プログラミング - Wikipedia

会場には Generative Programming を実施した方がほとんどいないようでしたが、 Generics も Compile 時のことを考えれば C++ のテンプレートと似ていて生成的プログラミングの一種となります。なので、知らず知らずのうちに Generative Programming は大体の Swift 経験者なら経験しています!

今回の LT ではコード生成 (Code Generation) の方をお話したかったので、自分が Swift でクライアントアプリを作るときに使用している ishkawa/APIKit を元にコード生成をするデモコードを前日の夜にちゃちゃっと作ってみました。

github.com

kaneshin/genkit

まだ、デモ程度の実装しかしていないです。

まず、JSON Schema を用意しておき、それを元に genkit を使用します。使用方法は generate.go に書いてありますが

go run /path/to/genkit/cmd/genkit/main.go /path/to/genkit/cmd/genkit/gen.go -path=./Sources -output=apikit_gen.swift ./api.json

の様に使用します。コマンドとしてインストールしててもOKです。

go get github.com/kaneshin/genkit/cmd/genkit
genkit -path=./Sources -output=apikit_gen.swift ./api.json

これを実行すると、 apikit_gen.swift が生成されます。ただ、 Swift には gofmt に相当するものが無いのでコードのインデントが崩れています。

(swiftformat で探してみると案外あるんですね。LT 前に知りたかった…)

github.com

github.com

Swift Code Generators

ちなみに、他にも Generator は存在しています。

github.com

おわりに

別の LT で BaseViewController の話がありましたが、コード生成前提でコードを設計すると、神クラスのような煩雑なコードをつくり上げるようなことがなくなる気がしているので、コード生成はかなりおすすめです。 社内プロジェクトでもコード生成は結構使っているので、実装者はビジネスロジックを考えることに集中することができます。本当におすすめです。


lestrrat さんも builderscon を作っているので、是非参考にすると良いかもです!

golang の channel を使って Dispatcher-Worker を作り goroutine 爆発させないようにする

Golang

golang で処理の高速化をするために goroutine/channel を使って並行処理にし、待ち時間を無駄にしないようにするのは言葉で表すのは簡単ですが、実際にパターンとして落としこむためには経験が必要だなと思うので、今回 Dispatcher-Worker として Job Queue を golang で実装する方法を紹介したいと思います。

この記事は mattn さんの Big Sky :: golang の channel を使ったテクニックあれこれ の次のステップとして読むことをオススメします。

mattn.kaoriya.net

golang で作成したアプリケーションで多くのリクエストをアプリケーションが送受信する必要がある場合、高速に捌くために並行処理にして非同期化を図る場合を想定しています。

今回は get という関数でHTTPリクエストを実行して取得したデータのサイズとそのときの goroutine の数を出力するようにしています。

func get(url string) {
    resp, err := http.DefaultClient.Get(url)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("Goroutine:%d, URL:%s (%d bytes)", runtime.NumGoroutine(), url, len(body))
}

非同期処理への単純アプローチ

TL;DR https://gist.github.com/kaneshin/c589b958592b4b685accc48249bf4b41

「並行処理するためには go をつければいいんですよね?」という感覚で下記のように実装してしまうと、大規模アプリケーションでは即座に破綻します。 goroutine の数が処理の数に応じて上昇するためです。

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            get(fmt.Sprintf("http://placehold.it/%dx%d", i, i))
        }(i)
    }
    wg.Wait()
}

これのログを見てみると、for文のカウンターに依存して goroutine の数が増えているのがわかります。

...
2016/08/18 17:05:27 Goroutine:104, URL:http://placehold.it/31x31 (2843 bytes)
2016/08/18 17:05:27 Goroutine:101, URL:http://placehold.it/32x32 (2901 bytes)
2016/08/18 17:05:27 Goroutine:98, URL:http://placehold.it/17x17 (2879 bytes) 
2016/08/18 17:05:27 Goroutine:95, URL:http://placehold.it/83x83 (3495 bytes) 
2016/08/18 17:05:27 Goroutine:92, URL:http://placehold.it/57x57 (3197 bytes) 
2016/08/18 17:05:27 Goroutine:89, URL:http://placehold.it/25x25 (2923 bytes) 
2016/08/18 17:05:27 Goroutine:86, URL:http://placehold.it/95x95 (3586 bytes) 
...

今回の Dispatcher-Worker ではこの goroutine を worker の数で制限できるような方針です。

Dispatcher-Worker でのアプローチ

TL;DR: https://gist.github.com/kaneshin/69bd13c7b57ba8bac84fb4de0098b5fc

Dispatcher-Worker のイメージは下記の通りで、dispatcher の方で Idle 状態の worker にキューイングされているメッセージを渡します。

f:id:laplus-knsn:20160818181940p:plain

Dispatcher と Worker

dispatcher ではメッセージのキューイングと Idle 状態の worker を管理できるようにします。

type (
    // Dispatcher represents a management workers.
    Dispatcher struct {
        pool    chan *worker     // Idle 状態の worker の受け入れ先
        queue   chan interface{} // メッセージの受け入れ先
        workers []*worker
        wg      sync.WaitGroup   // 非同期処理の待機用
        quit    chan struct{}
    }
)

worker は処理するメッセージを受け取れるようにします。

type (
    // worker represents the worker that executes the job.
    worker struct {
        dispatcher *Dispatcher
        data       chan interface{} // 受け取ったメッセージの受信先
        quit       chan struct{}
    }
)

Dispatcher と Worker の起動

Dispatcher と Worker は起動させてからはメッセージを受け取り次第勝手に処理を実行するようになります。非同期処理の終了を待機できるようにしておくように sync.WaitGroup もうまくハンドリングします。

Dispatcher はメッセージがキューイングされたとき、 Idle になっている worker にメッセージを送信します。

// Start starts the specified dispatcher but does not wait for it to complete.
func (d *Dispatcher) Start() {
    for _, w := range d.workers {
        w.start()
    }

    go func() {
        for {
            select {
            // メッセージがキューイングされた場合、 v にメッセージを設定
            case v := <-d.queue:
                (<-d.pool).data <- v // 下記の2行と同じ意味
                // worker := <-d.pool // d.pool から Idle の worker がpoolingされるまで待機
                // worker.data <- v // worker.data に メッセージ v を送信

            case <-d.quit:
                return
            }
        }
    }()
}

worker は自身が Idle になり次第自身をプーリングすることと、メッセージを受信したら処理を実施します。

func (w *worker) start() {
    go func() {
        for {
            // dispatcher の pool に自身を送信する(Idle状態を示す)
            w.dispatcher.pool <- w

            select {
            // メッセージがキューイングされた場合、 v にメッセージを設定
            case v := <-w.data:
                if str, ok := v.(string); ok {
                    // get 関数でHTTPリクエスト
                    get(str)
                }

                // WaitGroupのカウントダウン
                w.dispatcher.wg.Done()

            case <-w.quit:
                return
            }
        }
    }()
}

メッセージのキューイング

外からのメッセージをキューイングするのは Dispatcher だけですので、 Dispatcher にエンキュー用の関数を用意します。

// Add adds a given value to the queue of the dispatcher.
func (d *Dispatcher) Add(v interface{}) {
    // キューイングされた場合に処理を待機するために WaitGroup をカウントアップ
    d.wg.Add(1)
    d.queue <- v
}

Dispatcher の待機

非同期処理が終わる前に main の実行を終了してしまった場合、キューイングしたメッセージが処理されないで終了してしまいます。そうならないように Wait 関数を用意します。

// Wait waits for the dispatcher to exit. It must have been started by Start.
func (d *Dispatcher) Wait() {
    d.wg.Wait()
}

単純に sync.WaitGroup の Wait 関数で待機するだけです。

Dispatcher と Worker の初期化と実行

ここまで実装が完了すれば後は初期化を行い、実行するのみです。

const (
    maxWorkers = 3
    maxQueues  = 10000
)

// NewDispatcher returns a pointer of Dispatcher.
func NewDispatcher() *Dispatcher {
    // dispatcher の初期化
    d := &Dispatcher{
        pool:  make(chan *worker, maxWorkers),    // capacity は用意する worker の数
        queue: make(chan interface{}, maxQueues), // capacity はメッセージをキューイングする数
        quit:  make(chan struct{}),
    }

    // worker の初期化
    d.workers = make([]*worker, cap(d.pool))
    for i := 0; i < cap(d.pool); i++ {
        w := worker{
            dispatcher: d,
            data:       make(chan interface{}), // worker でキューイングする場合は capacity を2以上
            quit:       make(chan struct{}),
        }
        d.workers[i] = &w
    }
    return d
}

func main() {
    d := NewDispatcher()

    d.Start()
    for i := 0; i < 100; i++ {
        url := fmt.Sprintf("http://placehold.it/%dx%d", i, i)
        d.Add(url)
    }
    d.Wait()
}

これを実行してみると、 goroutine の数が for文のカウンターではなく、 worker の数に依存しているのがわかります。

...
2016/08/18 18:54:06 Goroutine:22, URL:http://placehold.it/77x77 (3321 bytes)   
2016/08/18 18:54:06 Goroutine:22, URL:http://placehold.it/78x78 (3431 bytes)   
2016/08/18 18:54:06 Goroutine:20, URL:http://placehold.it/79x79 (3427 bytes)   
2016/08/18 18:54:06 Goroutine:16, URL:http://placehold.it/81x81 (3296 bytes)   
2016/08/18 18:54:06 Goroutine:16, URL:http://placehold.it/80x80 (3478 bytes)   
...

コード全体はこちらにあります。

今回の worker はシンプルに実装しているので、worker 側でキューイングすることはしていないですが、延長で実装が可能なので是非試してみて下さい。

おわりに

mattn さんも Big Sky :: golang の channel を使ったテクニックあれこれ の中で話していますが、 goroutine と channel の可能性はかなりあると思っており、今回の一例も単なるベースに過ぎずこれ以上の実装はまだまだ可能です。慣れないと使い処に困る goroutine と channel かもしれませんが、使いこなすことが出来れば実装の視野も広がると思います。是非、この夏に goroutine/channel を使いこなせるようになりましょう。

Golangの標準入力をインタラクティブかパイプで入力を受け取る

Golang

Go言語でコマンドラインツールを作るときに入力を受け取るインターフェースでオプションや標準入力で受け付けることはあると思いますが、パイプで渡すことも考慮されているとクールなツールになるなと思っています。

標準入力の受け取り

それぞれの実装方法は簡単です。

インタラクティブ

var stdin string
fmt.Scan(&stdin)
fmt.Println(stdin)

インタラクティブに標準入力からデータを受け取るには fmt.Scan で入力待ちをします。このとき入力した値が渡した変数に格納されます。

パイプ

body, err := ioutil.ReadAll(os.Stdin)
fmt.Println(string(body))

パイプで渡ってきたものは os.Stdin というファイルディスクリプタにデータが入っているので、ここから取得します。

インタラクティブかパイプを判定する

パイプでファイルディスクリプタが渡ってきた場合はそのままそのファイルディスクリプタからデータを取得すれば良いので、インタラクティブな入力待ちは必要ありません。 そんなときは syscall パッケージを利用します。

syscallパッケージ

const ioctlReadTermios = 0x5401  // syscall.TCGETS

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal() bool {
    fd := syscall.Stdin
    var termios syscall.Termios
    _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
    return err == 0
}

上記の IsTerminal 関数が true で返ってくる場合、インタラクティブにデータを受け取る場合になります。処理としては、/dev/stdin のファイルディスクリプタ値である syscall.Stdin が読み込みをしているかを判定しています。

ただ、この実装は Linux に依存しており、他のPlatformで利用するには ioctlReadTermios の値を適宜変更しなければいけません。 これを自分で実装するのはちょい面倒なので、既に実装がされているものを利用します。

terminalパッケージ

golang.orgに golang.org/x/crypto/ssh/terminal というパッケージが存在していて、ここにあるIsTerminalという関数が先ほどの各Platformの要件を満たしています。

go get -u golang.org/x/crypto/ssh/terminal
import "golang.org/x/crypto/ssh/terminal"

func main() {
    // ...
    if terminal.IsTerminal(syscall.Stdin) {
        // Do something ...
    }
    // ...
}

全体の実装

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "syscall"

    "golang.org/x/crypto/ssh/terminal"
)

func main() {
    if terminal.IsTerminal(syscall.Stdin) {
        // Execute: go run main.go
        fmt.Print("Type something then press the enter key: ")
        var stdin string
        fmt.Scan(&stdin)
        fmt.Printf("Result: %s\n", stdin)
        return
    }

    // Execute: echo "foo" | go run main.go
    body, err := ioutil.ReadAll(os.Stdin)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Result: %s\n", string(body))
}

Ansible: "unsupported parameter for module: gather_subset"

Ansible

Ansible 2.1を動かしたところ、下記のようなエラーが発生…

[kaneshin@casper-01] ~/local/src/github.com/kaneshin/kamimai (master)
 ( ^o^) < ansible-playbook -C misc/playbook/localhost.yml

PLAY [127.0.0.1] ***************************************************************

TASK [setup] *******************************************************************
fatal: [127.0.0.1]: FAILED! => {"changed": false, "failed": true, "msg": "unsupported parameter for module: gather_subset"}

NO MORE HOSTS LEFT *************************************************************
        to retry, use: --limit @misc/playbook/localhost.retry

PLAY RECAP *********************************************************************
127.0.0.1                  : ok=0    changed=0    unreachable=0    failed=1   

Pythonが2.7.9だとダメなのかと思い、2.7.11にしてみたり、pipでansibleを再インストールしてみたりしたが一向に直らず。 諦めたくなってきたころにGoogle グループにあった回答をしっかり読んでみたら

「aptでインストールしたansbileがあったからそれを削除した。そしたら直った。」とあったので、実際にaptでもインストールしていたことが確認できたのでaptからremoveしたら直った。

[kaneshin@casper-01] ~/local/src/github.com/kaneshin/kamimai (master)
 ( ^o^) < ansible-playbook -C misc/playbook/localhost.yml

PLAY [127.0.0.1] ***************************************************************

TASK [setup] *******************************************************************
ok: [127.0.0.1]

TASK [golang/lint : install golint] ********************************************
skipping: [127.0.0.1]

PLAY RECAP *********************************************************************
127.0.0.1                  : ok=1    changed=0    unreachable=0    failed=0   

地味にハマった…(40分くらい)

GopherCon@Denver 2016に参戦する

Golang Event

DenverにてGopherCon 2016が7/11~7/13の三日間で開催されるのに行く予定です。

ほぼ一ヶ月後には本場(?)のGopherたちと濃い期間を過ごすと思うと非常に楽しみ。

f:id:laplus-knsn:20160608014447p:plain

ただ、飛行機代とホテル代でかなりの銭が飛んだので、見合った成果を持ち帰りたいなと思っています。

日本人で参加する方いるのかな。

Go言語で構造体の関数を動的に変更する

Go言語の構造体に関数を定義したあとに、動的に処理を変更することはreflectを使わない限りできないです。 ただ、スタブやモックとして関数の挿げ替えを行いたい場合や、実装を動的に変更したいという特質な要件を持つ人もたまにはいるでしょう。

そんなときはC言語っぽい考えで実装をしてしまいましょう。

TL;DR

type Foo struct {
    stringFunc func(Foo) string
}

func (f Foo) String() string {
    if f.stringFunc == nil {
        return ""
    }
    return f.stringFunc(f)
}

The Go Playground

構造体のフィールドに関数

C言語の復讐

C言語オブジェクト指向っぽいものが流行っていた時は下記のように書くことがあったかと思います。

#include <stdio.h>
#include <stdlib.h>

struct foo {
    char *name;
    void (*func)(struct foo*);
};

void foo_func(struct foo* f)
{
    printf("@%s\n", f->name);
}

int main(int argc, char* argv[])
{
    struct foo* f = (struct foo*)malloc(sizeof(struct foo));
    f->name = "kaneshin";
    f->func = foo_func;
    f->func(f);
    return 0;
}

関数のポインタ変数を構造体のメンバーに定義しておき、そこに対してApplyするような実装になっています。

Go言語で実装

このC言語の特徴をGo言語でもうまく使ってしまおうということです。(関数オーバーヘッド?なにそれおいしいの?)

package main

import "fmt"

type User struct {
    name     string
    stringer func(User) string
}

func (u User) String() string {
    return u.stringer(u)
}

Userという構造体のフィールドにstringerという関数を作っています。引数に自身を渡すのはC言語の方を理解している人なら当たり前にわかると思いますが、呼び出し先ではどのような構造体の値からの呼び出しなのかが判断つかないので、呼び出し時にセットします。 これはGo言語でも同じなので気をつけてください。

func printer(f fmt.Stringer) {
    fmt.Printf("Stringer: %s\n", f.String())
}

func main() {
    u := User{name: "kaneshin"}

    // @<user.name>
    u.stringer = func(u User) string {
        return fmt.Sprintf("@%s:", u.name)
    }
    printer(u)

    // Hello <user.name>
    u.stringer = func(u User) string {
        return fmt.Sprintf("Hello %s", u.name)
    }
    printer(u)

}

Goの方ではfmtパッケージのStringer型 (interface) を満たすようしています。 上記のmainの中身を見てもらうとわかるかと思いますが、String()の中で呼ばれているstringerをここで設定しています。

おわりに

こういう実装をしていくと、定義が別の関数内に入ってしまったりするので、すべてのコードがテスタブルなコードを書くようにするにはまた一工夫必要であったりします。 また、関数が設定されていない場合はBAD ACCESSになるので気をつけてください。

この実装方法に慣れてしまって動的に変更したくなったとしても、用法・用量は守りましょう。

f:id:laplus-knsn:20160601011624p:plain

Goで書いたWebをUnix domain socketで公開する方法

Golang

Unix domain socketをGoのWebアプリケーションで使用する方法です。tcpによるリッスンの情報はたくさんありますが、ソケットを介した方法があまりなかったので紹介します。

TL;DR

成果物は→kaneshin/playground/go/unixsocket

Unix domain socketの置き場

特に用意していない場合に、ソケットファイルの置き場を作成します。 /tmpを経由するのはセキュリティの都合上よろしくないので/var/runを経由するようにします。 /var/runへの追加はsystemdの機能でテンポラリなディレクトリを作成するようにします。まずは/etc/tmpfiles.dに設定ファイルを追加しておきます。

$ cat /etc/tmpfiles.d/gopher.conf
d /var/run/gopher 0755 [UID] [GID] -

/var/run/gopherというディレクトリを作成するようにしています。 ※[UID], [GID]は各自で設定してください。

設定ファイルを登録して、再起動をかけます。

$ systemd-tmpfiles --create /etc/tmpfiles.d/gopher.conf
$ systemctl daemon-reload

これでUnix domain socketの置く場所作成は完了です。

nginx configuration

ほぼ定型文です。今回は/var/run/gopher/go.sockをリッスンします。

upstream backend {
    server unix:/var/run/gopher/go.sock;
}

server {
    listen      80;
    server_name go.example.com;
    root        /var/www/html;

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://backend/;
    }
}         

アプリケーションコード

GoでUnix domain socketをリッスンするのは非常に簡単です。

func main() {
    mux := http.NewServeMux()

    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "<h1>It works!</h1>\n")
    })

    mux.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
        b, err := json.MarshalIndent(r.Header, "", "  ")
        if err != nil {
            fmt.Fprintf(w, err.Error())
        } else {
            w.Header().Add("Content-Type", "application/json")
            fmt.Fprintf(w, "%v\n", string(b))
        }
    })

net/httpが持つ ServerMux にハンドラを登録したあと、ソケットを介してリッスンします。

   listener, err := net.Listen("unix", sock)
    if err != nil {
        fmt.Fprintln(os.Stderr, err.Error())
        os.Exit(1)
    }
    defer func() {
        if err := listener.Close(); err != nil {
            log.Println("Error:", err.Error())
        }
    }()
    shutdown(listener)
    if err := http.Serve(listener, mux); err != nil {
        fmt.Fprintln(os.Stderr, err.Error())
        os.Exit(1)
    }
}

割り込みで終了のシグナルが来た場合にリスナーをクローズ (listener.Close()) するようにしておきます。

func shutdown(listener net.Listener) {
    c := make(chan os.Signal, 2)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        s := <-c
        log.Println("Got signal:", s)
        if err := listener.Close(); err != nil {
            log.Println("Error:", err.Error())
        }
        os.Exit(1)
    }()
}

クローズしないとソケットファイルが生き残り続けます。

起動

go buildなどでバイナリにして起動していただければそのまま動くと思います。 502 Bad Gatewayの文字が現れたときはエラーログを見てもらえればいいのですが、大抵はパーミッションによるエラーだと思います。

パーミッションの解決方法(例)

www-dataユーザで実行することを仮定すると

$ cat /etc/tmpfiles.d/gopher.conf
d /var/run/gopher 0755 www-data www-data -

としてsystemdへ登録しなおし、その後にgo buildで作成したバイナリをwww-dataで起動してあげればそのまま動くはずです。

$ go build -o /tmp/bin
$ sudo -u www-data /tmp/bin

おわりに

今回のコードは kaneshin/playground/go/unixsocket にあります。

毎回、「リッスン」を使用する度に思うのですが、「リッスン」は既に動詞なのに「リッスンする」という言葉を使用しないと日本語では伝わりにくいですよね。