Go 1.7 から本体に入った context パッケージは便利、というより今や必須の道具です。以下のように書くことで、一定時間で処理をキャンセルできたりします。
func slowOperationWithTimeout(ctx context.Context) (Result, error) { ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() // releases resources if slowOperation completes before timeout elapses return slowOperation(ctx) }
defer cancel()
とありますが、このようにしないとリソースリークするので context 使うときはこう書くのがパターンです。
さて、以下のプログラムですが、返ってくるデータが小さいうちは問題なく動きますが、大きなデータになると resp.Body
の読み込みで必ずエラーが返るようになります。
func doRequest(ctx context.Context, ...) *http.Response { ctx, cancel := context.WithTimeout(ctx, 5 * time.Second) defer cancel() req := http.NewRequest("GET", "http://...", nil) req = req.WithContext(ctx) resp, err := httpClient.Do(req) if err != nil { panic(err) } return resp } func main() { resp := doRequest(context.Background()) defer resp.Body.Close() data, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Println(string(data)) }
doRequest
から帰る時点で cancel されてしまっているため、その後の resp.Body からの読み込みが失敗するわけです。が、データが小さいうちは特にエラーにならずに成功してしまうため、気づきにくい罠です。
データが小さいうちは成功してしまう理由は、http クライアント内部で、レスポンスのヘッダを解析する時点で body のデータも読み込んで内部的にバッファしているためです。body が小さいうちはヘッダ解析処理時点ですべて読まれているため、cancel されても問題ないと。