> Source: https://www.ymotongpoo.com/works/goblog-ja/context/


# Goの並行パターン：コンテキスト
[Go Concurrency Pattern: Context](https://blog.golang.org/context) by Sameer Ajmani

## はじめに
Goで書かれたサーバでは、サーバに来たリクエストはそれぞれそれ自身のゴルーチンで処理されます。
リクエストハンドラはしばしばデータベースやRPCサービスといったバックエンドにアクセスするために追加のゴルーチンを起動します。
リクエストの処理を行っているゴルーチンは、通常エンドユーザのアイデンティティや認可トークン、リクエストの期限などリクエスト固有の値へのアクセス権が必要です。
リクエストがキャンセルされたりタイムアウトした場合には、それらのゴルーチンが使っていたリソースをシステムが再度要求できるように、そのリクエストの処理を行っているすべてのゴルーチンは素早く終了すべきです。

Googleで私たちは、APIの境界をまたぐリクエスト固有の値やキャンセルのシグナル、期限などを、
あるリクエストの処理に関与するすべてのゴルーチンに渡すことを容易にする、 `context` パッケージというパッケージを開発しました。
パッケージは [golang.org/x/net/context](http://godoc.org/golang.org/x/net/context) に公開されています。 [^1]
この記事ではそのパッケージの使い方と実際に動作する例を紹介したいと思います。

[^1]: 訳註: 原文では `code.google.com/p/go.net/context` を参照していますが、現状に合わせてURLを変更しました。

## コンテキスト（Context）

`context` パッケージの核となっているのは `Context` 型です。

```go
// ContextはAPIの境界を越えて期限とキャンセルシグナルとリクエスト固有の値を保持します。
// メソッドは複数のゴルーチンから同時に呼び出されても安全です。
type Context interface {
    // Doneはこのコンテキストがキャンセルされたりタイムアウトした場合に閉じられます。
    Done() <-chan struct{}

    // ErrはDoneチャンネルが閉じた後なぜこのコンテキストがキャンセルされたかを知らせます。
    Err() error

    // Deadlineは設定されている場合にはいつこのContextがキャンセルされるかを返します。
    Deadline() (deadline time.Time, ok bool)

    // Valueはkeyに紐付いた値を返し、設定がない場合はnilを返します。
    Value(key interface{}) interface{}
}
```

（この説明は要約されたもので、 [godoc](http://godoc.org/golang.org/x/net/context) が正式なものです。）

`Done` メソッドは、 `Context` の代わりに動作する関数に対するキャンセルシグナルとして振る舞うチャンネルを返します。チャンネルが閉じられたときに、関数は処理を中断して戻るべきです。
`Err` メソッドはなぜその `Context` がキャンセルされたかを示すエラーを返します。
[パイプラインとキャンセル](https://blog.golang.org/pipelines) の記事では `Done` チャンネルのイディオムについてより詳細に議論しています。

`Context` には `Done` チャンネルが受信専用であるのと同様の理由で `Cancel` メソッドがあり *ません* 。
キャンセルシグナルを受け取る関数は通常シグナルを送る関数ではありません。
特に、親の操作が子の操作を行うためのゴルーチンを起動したときに、それらの子の操作が親の操作をキャンセルできるべきではありません。
代わりに `WithCancel` 関数（あとで説明します）で新しい `Context` の値をキャンセルする方法を提供します。

`Context` は複数のゴルーチンから同時に使われても安全です。コード内では1つの `Context` を任意の数のゴルーチンに渡し、その `Context` をキャンセルしてすべてのゴルーチンに伝えることができます。

`Deadline` は関数が処理を始めるべきかどうかを決定できるメソッドです。
もし残り時間が少なければ、起動する価値はありません。コードでは期限をI/O操作のタイムアウトとして利用することもあるでしょう。

`Value` によって `Context` がリクエスト固有のデータを運ぶことができます。
そのようなデータは複数のゴルーチンによって同時に利用されても安全です。

## 派生したコンテキスト

`context` パッケージでは既存のコンテキストから新しい `Context` の値を *派生する* 関数を提供しています。
これらの値は木構造になっていて、 `Context` がキャンセルされたときに、そこから派生した `Context` もすべてキャンセルされます。


`Background` はすべての `Context` 木構造の根になっていて、決してキャンセルされることはありません。

```go
// Background は空の Context を返します。
// そのコンテキストは決してキャンセルされることはなく、期限はなく、また値を持ちません。
// Backgroundは通常、main、init、テストの中で使われ、到着するリクエストの
// 最上位の Context として使われます。
func Background() Context
```

`WithCancel` と `WithTimeout` は派生した `Context` を返します。
この `Context` は親の `Context` よりも早くキャンセルされます。
到着するリクエストに紐付いた `Context` は通常リクエストハンドラが戻すとキャンセルされます。
`WithCancel` は複数のレプリカを使うときに冗長なリクエストをキャンセルするのにも便利です。
`WithTimeout` はバックエンドサーバーへのリクエストの期限を設定するのに便利です。

```go
// WithCancel は parent のコピーを返し、 parent.Done が閉じられた、
// またはキャンセルが呼ばれるとすぐに、その Done チャンネルが閉じられます。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

// CancelFunc は Context をキャンセルします。
type CancelFunc func()

// WithTimeout は parent のコピーを返し、 parent.Done が閉じられた、
// または timeout が過ぎるとすぐに、その Done チャンネルが閉じられます。
// Context の Deadline は 現在時刻+timeout か親の期限のどちらか早いほうに設定されます。
// もしタイマーがまだ動いていた場合、キャンセル関数はそのリソースを解放します。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
```

`WithValue` はリクエスト固有の値を `Context` に紐付ける方法を提供します。

```go
// WithValue は親のコピーを返し、そのValueメソッドがkeyに対しvalを返すようにします。
func WithValue(parent Context, key interface{}, val interface{}) Context
```

`context` パッケージの使い方を理解するには動く実例を通して見るのが最良でしょう。

## 例: Google ウェブ検索
一つの例は `/search?q=golang&timeout=1s` のようなURLを処理して「golang」という検索クエリを [Google Web Search API](https://developers.google.com/web-search/docs/) に投げて、
その結果を表示するようなHTTPサーバーです。 `timeout` パラメータはサーバーに指定時間が経過したら
リクエストをキャンセルするように伝えます。

コードは3つのパッケージに分かれています。

* [server](https://blog.golang.org/context/server/server.go) は `main` 関数と `/search` のハンドラを提供します。
* [userip](https://blog.golang.org/context/userip/userip.go) はリクエストからユーザーのIPアドレスを抜き出し、 `Context` に紐付ける関数を提供します。

* [google](https://blog.golang.org/context/google/google.go) はGoogleにクエリを送信する `Search` 関数を提供します。

## サーバーのプログラム
[server](https://blog.golang.org/context/server/server.go) のプログラムは `/search?q=golang` のようなリクエストを処理して `golang` という検索クエリによるGoogle検索の最初の結果いくつかを返します。サーバーでは `handleSearch` という関数を `/search` のエンドポイントとして登録しています。ハンドラは `ctx` という最初の `Context` を生成して、ハンドラが値を返すときにそれがキャンセルされるように設定します。
もしリクエストに `timeout` というURLパラメーターが含まれていたら、 `Context` は期限が来たら自動的にキャンセルされます。

```go
func handleSearch(w http.ResponseWriter, req *http.Request) {
    // ctx はこのハンドラの Context です。 cancel を呼ぶことで
    // ctx.Done チャンネルが閉じられます。これで、このハンドラからのリクエスト用の
    // キャンセルシグナルです。
    var (
        ctx    context.Context
        cancel context.CancelFunc
    )
    timeout, err := time.ParseDuration(req.FormValue("timeout"))
    if err == nil {
        // リクエストにはタイムアウトがあるので、期限が来たら自動的にキャンセルされる
        // コンテキストを作成します。
        ctx, cancel = context.WithTimeout(context.Background(), timeout)
    } else {
        ctx, cancel = context.WithCancel(context.Background())
    }
    defer cancel() // handleSearchが値を返したらすぐに ctx をキャンセルします。
```

ハンドラは検索クエリとクライアントのIPアドレスを取得します。

```go
    // 検索クエリをチェックします。
    query := req.FormValue("q")
    if query == "" {
        http.Error(w, "no query", http.StatusBadRequest)
        return
    }

    // 他のパッケージのコードから使えるように、ユーザーのIPアドレスをctxに保存します。
    userIP, err := userip.FromRequest(req)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    ctx = userip.NewContext(ctx, userIP)
```

このハンドラは `google.Search` を `ctx` と `query` を使って呼び出します。

```go
    // Google検索を実行して結果を表示します。
    start := time.Now()
    results, err := google.Search(ctx, query)
    elapsed := time.Since(start)
```

検索に成功したら、ハンドラは結果を返します。

```go
    if err := resultsTemplate.Execute(w, struct {
        Results          google.Results
        Timeout, Elapsed time.Duration
    }{
        Results: results,
        Timeout: timeout,
        Elapsed: elapsed,
    }); err != nil {
        log.Print(err)
        return
    }
```

## userip パッケージ
[userip](https://blog.golang.org/context/userip/userip.go) パッケージはリクエストからユーザーのIPアドレスを抜き出し、それを `Context` に紐付ける関数を提供します。 `Context` はキーと値の対応表を提供します。このとき、キーも値もともに `interface{}` 型です。
キーの型は同値性をサポートしなければならず、値の型は複数のゴルーチンから同時に使われても安全でなければなりません。
`userip` のようなパッケージはこの対応表の詳細を隠し、特定の `Context` の値に対して強く型付けされたアクセスを提供します。

キーの衝突を避けるために、 `userip` ではエクスポートされていない型である `key` を定義し、この型の値をコンテキストのキーとして使います。

```go
// key型は他のパッケージで定義されているコンテキストのキーと衝突しないよう、エクスポートされません。
type key int

// userIPkey はユーザーのIPアドレスのためのコンテキストのキーです。値がゼロの場合は任意の値となります。
// もしこのパッケージが他のコンテキストのキーであったなら、別の整数値になっていたことでしょう。
const userIPKey key = 0
```

`FromRequest` は `userIP` の値を `http.Request` から抜き出します。

```go
func FromRequest(req *http.Request) (net.IP, error) {
    ip, _, err := net.SplitHostPort(req.RemoteAddr)
    if err != nil {
        return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
    }
```

`NewContext` は与えられた `userIP` の値を持つ新しい `Context` を返します。

```go
func NewContext(ctx context.Context, userIP net.IP) context.Context {
    return context.WithValue(ctx, userIPKey, userIP)
}
```

`FromContext` は `Context` から `userIP` を抜き出します。

```go
func FromContext(ctx context.Context) (net.IP, bool) {
    // ctx.Value が nil を返す場合、 ctx はキーになる値を持っていません。
    // このとき net.IP 型のアサーションは ok=false を返します。
    userIP, ok := ctx.Value(userIPKey).(net.IP)
    return userIP, ok
}
```

## google パッケージ
[google.Search](https://blog.golang.org/context/google/google.go) 関数は [Google Web Search API](https://developers.google.com/web-search/docs/) に対してHTTPリクエストを送り、JSONエンコードされた結果をパースします。この関数は `Context` のパラメーター `ctx` を受け取り、もし `ctx.Done` が閉じられていたら、リクエストが実行中だったとしても、直ちに結果を返します。

Google Web Search APIのリクエストには、クエリパラメータとして検索クエリとユーザーのIPアドレスが含まれています。

```go
func Search(ctx context.Context, query string) (Results, error) {
    // Google Search API へのリクエストの準備
    req, err := http.NewRequest("GET", "https://ajax.googleapis.com/ajax/services/search/web?v=1.0", nil)
    if err != nil {
        return nil, err
    }
    q := req.URL.Query()
    q.Set("q", query)

    // ctx にユーザーのIPアドレスがあった場合、それをサーバーに転送します。
    // Google API はサーバーが起動したリクエストとエンドユーザーのリクエストを区別するために
    // ユーザーのIPアドレスを使います。
    if userIP, ok := userip.FromContext(ctx); ok {
        q.Set("userip", userIP.String())
    }
    req.URL.RawQuery = q.Encode()
```

`Search` はHTTPリクエストを発行するためにヘルパー関数 `httpDo` を使い、リクエストまたはレスポンスが処理中に `ctx.Done` が閉じられるとHTTPリクエストをキャンセルします。
`Search` はHTTPレスポンスを処理するために `httpDo` にクロージャーを渡します。

```go
    var results Results
    err = httpDo(ctx, req, func(resp *http.Response, err error) error {
        if err != nil {
            return err
        }
        defer resp.Body.Close()

        // JSON形式の検索結果をパースする。
        // https://developers.google.com/web-search/docs/#fonje
        var data struct {
            ResponseData struct {
                Results []struct {
                    TitleNoFormatting string
                    URL               string
                }
            }
        }
        if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
            return err
        }
        for _, res := range data.ResponseData.Results {
            results = append(results, Result{Title: res.TitleNoFormatting, URL: res.URL})
        }
        return nil
    })
    // httpDo は先ほど渡したクロージャーが結果を返すのを待つので、ここで結果を読み込んでも安全です。
    return results, err
```

`httpDo` 関数はHTTPリクエストを実行し、そのレスポンスを新しいゴルーチンで処理します。
`ctx.Done` が閉じられた場合にはゴルーチンが終了する前にリクエストをキャンセルします。

```go
func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error {
    // HTTPリクエストをゴルーチン内で実行し、レスポンスを f に渡す。
    tr := &http.Transport{}
    client := &http.Client{Transport: tr}
    c := make(chan error, 1)
    go func() { c <- f(client.Do(req)) }()
    select {
    case <-ctx.Done():
        tr.CancelRequest(req)
        <-c // f が値を返すのを待つ
        return ctx.Err()
    case err := <-c:
        return err
    }
}
```

## Context用のコードを適用する

多くのサーバーフレームワークがリクエスト固有の値を保持するためにパッケージと型を提供しています。
既存のフレームワークを使ったコードと `Context` パラメータを期待するコードとの架け橋として
`Context` インターフェースの新しい実装を定義できます。

たとえば、Gorillaの [github.com/gorilla/context](http://github.com/gorilla/context) パッケージは、HTTPリクエストからキーと値のペアへの対応表を提供することで、ハンドラがデータと受け取ったリクエストを紐付けることができるようになっています。 [gorilla.go](https://blog.golang.org/context/gorilla/gorilla.go) では、`Value` メソッドがGorillaパッケージ内の特定のHTTPリクエストに紐付いた値を返すような `Context` の実装を提供しています。

他のパッケージも `Context` と似たキャンセルの仕組みをサポートしています。たとえば [Tomb](http://godoc.org/gopkg.in/tomb.v2) では、 `Dying` チャンネルを閉じることでキャンセルシグナルを送る `Kill` メソッドを提供しています。
また `Tomb` は処理用のゴルーチンが終了するのを待つ、`sync.WaitGroup` に似たメソッドも提供しています。
[tomb.go](https://blog.golang.org/context/tomb/tomb.go) では、親の `Context` がキャンセルされる、もしくは与えられた `Tomb` が殺された場合にキャンセルされる `Context` の実装を提供しています。

## 結論
Googleでは、受信したリクエストから送信するリクエストに至るまでの呼び出し経路上にあるすべての関数で、Goプログラマが第1引数に `Context` パラメーターを渡すことを要求しています。
これによって、多くの異なるチームが開発したGoのコードがお互いに上手く動くようになっています。
`Context` によって期限とキャンセルをシンプルに制御できるようになり、またセキュリティ上の認証情報といった重要な値がGoのプログラム内を適切に通過することを確実にしています。

`Context` 上に構築したいサーバーフレームワークは、そのパッケージと `Context` パラメーターがあると期待されるパッケージ間の橋渡しをするような `Context` の実装を提供すべきでしょう。そうするとクライアントライブラリは `Context` を呼び出し元のコードから受け取るでしょう。リクエスト固有のデータとキャンセルに関する共通のインターフェースを構築することで、 `Context` はパッケージ開発者がスケーラブルなサービスを作るためにコードを共有することをより簡単にします。

By Sameer Ajmani

## あわせて読みたい

* [Go Concurrency Patterns: Pipelines and cancellation](http://blog.golang.org/pipelines)
  * [Goの並行パターン：パイプラインとキャンセル (Go Concurrency Pattern: Pipelines and cancellation)](../pipelines/)
* [Introducing the Go Race Detector](http://blog.golang.org/race-detector)
* [Advanced Go Concurrency Patterns](http://blog.golang.org/advanced-go-concurrency-patterns)
* [Concurrency is not parallelism](http://blog.golang.org/concurrency-is-not-parallelism)
* [Go videos from Google I/O 2012](http://blog.golang.org/go-videos-from-google-io-2012)
* [Go Concurrency Patterns: Timing out, moving on](http://blog.golang.org/go-concurrency-patterns-timing-out-and)
  * [Goの並行パターン：タイムアウトと進行](../go-concurrency-patterns-timing-out-and/)
* [Share Memory By Communicating](http://blog.golang.org/share-memory-by-communicating)
