進行中の操作をキャンセルする

Goのcontext.Contextを使って、進行中の操作を管理できます。Contextは標準的なGoのデータ値で、それが表す全体的な操作がキャンセルされ、もはや不要になったかどうかを報告できます。context.Contextをアプリケーション内の関数呼び出しやサービス全体に渡すことで、処理が不要になったときに、それらは早期に作業を停止してエラーを返すことができます。Contextの詳細については、Go Concurrency Patterns: Contextを参照してください。

たとえば、次のようなことをしたい場合があります。

  • 完了に時間がかかりすぎるデータベース操作を含む、長時間実行される操作を終了する。
  • クライアントが接続を閉じるなど、他の場所からのキャンセル要求を伝播する。

Go開発者向けの多くのAPIには、Context引数を取るメソッドが含まれており、アプリケーション全体でContextを簡単に使用できるようになっています。

タイムアウト後のデータベース操作のキャンセル

Contextを使用して、操作がキャンセルされるタイムアウトまたは期限を設定できます。タイムアウトまたは期限付きのContextを導出するには、context.WithTimeoutまたはcontext.WithDeadlineを呼び出します。

次のタイムアウト例のコードは、Contextを導出し、それをsql.DBQueryContextメソッドに渡します。

func QueryWithTimeout(ctx context.Context) {
    // Create a Context with a timeout.
    queryCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    // Pass the timeout Context with a query.
    rows, err := db.QueryContext(queryCtx, "SELECT * FROM album")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    // Handle returned rows.
}

この例でqueryCtxctxから導出されているように、あるコンテキストが外側のコンテキストから導出されている場合、外側のコンテキストがキャンセルされると、導出されたコンテキストも自動的にキャンセルされます。たとえば、HTTPサーバーでは、http.Request.Contextメソッドはリクエストに関連付けられたコンテキストを返します。そのコンテキストは、HTTPクライアントが切断するか、HTTPリクエストをキャンセルした場合(HTTP/2で可能)にキャンセルされます。上記のQueryWithTimeoutにHTTPリクエストのコンテキストを渡すと、全体的なHTTPリクエストがキャンセルされた場合、またはクエリに5秒以上かかった場合のどちらでも、データベースクエリが早期に停止します。

注: タイムアウトまたは期限付きで新しいContextを作成したときに返されるcancel関数の呼び出しは、常にdeferしてください。これにより、囲んでいる関数が終了したときに、新しいContextが保持していたリソースが解放されます。また、queryCtxもキャンセルされますが、関数が返される時点では、queryCtxを使用しているものは何もありません。