Azure Functions の関数にタイムアウトを設定する(C#)

Azure Functions の関数にタイムアウトを設定する方法を試してみました
不明点や疑問点などあればぜひコメントいただければ嬉しいです
# タイムアウト期間のデフォルト値と最大値
関数のタイムアウト期間は、[Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-scale#timeout) に記載があります
まとめるとこんな感じです (2021/21/21 時点)

| ホスティング プラン | ランタイムバージョン | デフォルト (分) | 最大値 (分) |
|:-----------|:------------|:------------:|:------------:|
| 従量課金    |1.x, 2.x, 3.x | 5            | 10           |
| Premium    | 1.x          | 無制限* ※[動作保証 60 分](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-host-json#functiontimeout)　   | 無制限* ※動作保証 60 分       |
| Premium    | 2.x, 3.x     | 30           | 無制限* ※動作保証 60 分       |
| App Service (専用) | 1.x  | 無制限*        | 無制限*       |
| App Service (専用) | 2.x, 3.x | 30        | 無制限*       |


:::note warn
*NOTE*
タイムアウト期間を無制に設定することは可能ですが、パフォーマンスの観点から上限の設定が推奨されています。詳細は以下"Azure Functions のベストプラクティス"を参照ください。
https://docs.microsoft.com/ja-jp/azure/azure-functions/performance-reliability#avoid-long-running-functions
:::

なお、HTTP Trigger の関数を使用する際に最大で 230 秒以内(Linuxは240秒)に処理が終了しなければ、クライアントにリクエストタイムアウトが返却されてしまうことに注意が必要です。 Azure Functions の前段には Load Balancer があり、タイムアウト時間 (240 秒) が設定されています。リクエストタイムアウトが発生する場合には、最大で 230 秒以内(Linuxは240秒)に Azure Functions での処理を完了させてレスポンスを返却するまたは、Durable Functions や Web Job の使用を検討してください。

図でイメージするとこんな感じ:
client (ブラウザーなど) --> Load Balancer (240 秒のタイムアウト) --> Azure Functions (230 秒のタイムアウト※Linuxは240秒)

https://docs.microsoft.com/ja-jp/azure/app-service/faq-availability-performance-application-issues#230------------------------

# すべての関数にタイムアウトを設定する
**host.json** ファイルの [**"functionTimeout" プロパティ**](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-host-json#functiontimeout)を使用して Azure Functions 内の関数全体にタイムアウトを設定できます。

"functionTimeout" プロパティの値は、`[-][d.]hh:mm:ss[.fffffff]`形式 (Timespan オブジェクト) で入力します。
例えば、タイムアウトを 5 分に設定する場合は、以下のようになります。

```json:host.json
{
    "functionTimeout": "00:05:00"
}
```

タイムアウト期間の入力形式の詳細は[標準 TimeSpan 書式指定文字列](https://docs.microsoft.com/ja-jp/dotnet/standard/base-types/standard-timespan-format-strings#the-constant-c-format-specifier)を参照ください。

## 試してみる
検証環境は以下の通り
**■ 環境**
Azure Functions 3.x
プラン: Premium
言語: C# 
ランタイム: .NET Core 3.1
OS: Windows
開発環境: Visual Studio Code

**■ サンプルコード**
本検証ではタイマートリガーの関数を使用します。

```csharp:TimerTrigger1.cs
public static class TimerTrigger1
{
    [FunctionName("TimerTrigger1")]
    public static void Run([TimerTrigger("0 */5 * * * *")]TimerInfo myTimer, ILogger log)
    {
        log.LogInformation($"C# TimerTrigger1 function started at: {DateTime.Now}");
        Thread.Sleep(TimeSpan.FromDays(1));
        log.LogInformation("C# TimerTrigger1 function completed.");
    }
}
```

###  1 ) host.json ファイルにタイムアウト期間を設定
Azure Functions のプロジェクトルートに配置されている host.json ファイルの "functionTimeout" プロパティに `"00:00:05"` (5 秒) を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256703/6d9f0244-94f5-e263-98dc-b8a114e6d0db.png)

### 2 ) タイムアウトの発生を確認
Azure上へデプロイ後、[Azure ポータルの[コードとテスト]ブレードから関数のテストを実行](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-scheduled-function#test-the-function)します。
テスト実行後、関数の実行ログを確認すると、関数を実行した時刻のおおよそ 5 秒後にタイムアウトが発生しています。

2021-12-21T04:50:**00**.027 Executing 'TimerTrigger1' (Reason='Timer fired at 2021-12-21T04:50:00.0144829+00:00', Id=3249ddef-XXX)
2021-12-21T04:50:**05**.057 Timeout value of 00:00:05 exceeded by function 'TimerTrigger1' (Id: '3249ddef-XXX'). Initiating cancellation.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256703/3621b220-9e0c-2413-0944-f49cac3ec555.png)


:::note info
関数の実行ログは %HOME%\LogFiles\Application\Functions\function フォルダー配下の log ファイルからも確認できます。(2021/12/21 現在)
※確認方法は、高度なツール (Kudu サービス) を参照してください
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-how-to-use-azure-function-app-settings?tabs=portal#advanced-tools-kudu
:::

## スロットごとにタイムアウト期間を変更したい
Azure Functions のスロットごとにタイムアウト期間を変更したい場合に、host.json ファイルにタイムアウト期間を直接書き込んでしまうと、デプロイ時にスロットごとに host.json を書き換えねばならず手間になることがあります。
そんな時はアプリケーション設定を使用します。

アプリケーション設定は、スロットごとに設定することができ、かつ、[host.json の値を上書きする機能](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-host-json#override-hostjson-values)があるため、これを利用してスロットごとにタイムアウト期間を設定します。
具体的には、タイムアウト期間を 7 秒で設定したい場合には、以下のようにアプリケーション設定に登録します。

名前: AzureFunctionsJobHost__functionTimeout
値: タイムアウトを発生させる期間 (例: 00:00:07)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256703/c0d782e8-190a-bb3c-81a5-bd502c258ba4.png)

試しに、スロットごとに異なるタイムアウト期間をアプリケーション設定に登録し、関数を動作させてみると、それぞれ想定した期間でタイムアウトが発生しています。

**■ PRO スロット**
アプリケーション設定に以下を追加して、関数の実行開始後、8 秒後にタイムアウトが発生
名前: AzureFunctionsJobHost__functionTimeout
値: 00:00:08
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256703/18a767c5-daf3-b85c-b41b-63fffcba8487.png)

**■ STG スロット**
アプリケーション設定に以下を追加して、関数の実行開始後、7 秒後にタイムアウトが発生
名前: AzureFunctionsJobHost__functionTimeout
値: 00:00:07
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256703/973d2701-227d-248b-ff70-28e54171ab62.png)


# 関数ごとにタイムアウトを設定する (※VS・VS Code で開発している場合)
Visual Studio・Visual Studio Code を使用して関数を開発している場合に、メソッドやクラスに[Timeout 属性](https://docs.microsoft.com/ja-jp/azure/app-service/webjobs-sdk-how-to#timeout-attribute)を使用して関数ごとにタイムアウト期間を設定できます。例えば、タイムアウトを 15 秒に設定する場合は、以下のようになります。

```csharp:TimeoutAttributeSample.cs
[Timeout("00:00:15")]
public static async Task TimeoutJob(
    [QueueTrigger("testqueue2")] string message,
    CancellationToken token,
    TextWriter log)
{
    await log.WriteLineAsync("Job starting");
    await Task.Delay(TimeSpan.FromDays(1), token);
    await log.WriteLineAsync("Job completed");
}
```

:::note warn
*NOTE*
Timeout 属性を使用してメソッドやクラスにタイムアウト期間を設定する場合は、メソッドは CancellationToken を使用してタイムアウトを発生させるため、引数に CancellationToken を渡してください。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-dotnet-class-library?tabs=v2%2Ccmd#cancellation-tokens
:::

CancellationToken が必要となる理由について詳しくは、[Microsoft Azure team のエンジニア (Benjamin Perkins) のブログ](https://www.thebestcsharpprogrammerintheworld.com/2021/07/05/a-cancellation-token-an-azure-function-and-managing-disruption/
)の [Why use Cancellation Tokens] セクションを参照ください。

## 試してみる
検証環境は以下の通り
**■ 環境**
Azure Functions 3.x
プラン: Premium
言語: C# 
ランタイム: .NET Core 3.1
OS: Windows
開発環境: Visual Studio Code

**■ サンプルコード**
本検証ではタイマートリガーの関数を使用します。

```csharp:TimerTrigger2.cs
public static class TimerTrigger2
{
    [FunctionName("TimerTrigger2")]
    [Timeout("00:00:08")]
    public static void Run([TimerTrigger("0 45 * * * *")]TimerInfo myTimer, ILogger log, CancellationToken token)
    {
        log.LogInformation($"C# TimerTrigger2 function started at: {DateTime.Now}");
        Thread.Sleep(TimeSpan.FromDays(1));
        log.LogInformation("C# TimerTrigger2 function completed.");
    }
}
```

###  1 ) Timeout 属性を設定
上記サンプルコードに記載のように、Timeout 属性と CancellationToken を Run メソッドに設定します。
サンプルコードでは、タイムアウト期間を`"00:00:08"` (8 秒) としています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256703/e509489b-7e6a-e040-d863-efe38cad3c85.png)


### 2 ) タイムアウトの発生を確認
Azure上へデプロイ後、[Azure ポータルの[コードとテスト]ブレードから関数のテストを実行](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-scheduled-function#test-the-function)します。
テスト実行後、関数の実行ログを確認すると、関数を実行した時刻のおおよそ 8 秒後にタイムアウトが発生しています。

2021-12-21T06:49:**29**.781 Executing 'TimerTrigger2' (Reason='This function was programmatically called via the host APIs.', Id=4397ae93-XXX)
2021-12-21T06:49:**37**.803 Timeout value of 00:00:08 exceeded by function 'TimerTrigger2' (Id: '4397ae93-XXX'). Initiating cancellation.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256703/b1367d91-d4ff-f341-2c09-5bd119a74790.png)

# 最後に
今回は、host.json ファイルと Timeout 属性を使用したタイムアウト期間の設定方法をご紹介いたしました。

クラウドサービスは日々進化しているので、仕様変更等により、この記事に記載している方法で今後も必ず実施できるとは限りません。そのため、実際に Azure Functions の関数にタイムアウト期間を設定する際には、必ず Microsoft Docs 等の公式ドキュメントを確認の上でお試しください。

また、少しでも参考になれば嬉しいです。

Azure Functions はかなり奥が深いので、またいろいろ試して記事にできればと思います。
ありがとうございました！

# 参考
- [Azure Functions の概要](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-overview)
- [Azure Functions のホスティング オプション](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-scale)
- [Azure Functions 2.x 以降の host.json のリファレンス](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-host-json)
- [Durable Functions とは](https://docs.microsoft.com/ja-jp/azure/azure-functions/durable/durable-functions-overview?tabs=csharp)
- [Azure App Service で Web ジョブを使用してバックグラウンド タスクを実行する](https://docs.microsoft.com/ja-jp/azure/app-service/webjobs-create)
- [アクセス ログ ファイル](https://docs.microsoft.com/ja-jp/azure/app-service/troubleshoot-diagnostic-logs#access-log-files)
- [Class 5 Status Codes](https://docs.microsoft.com/ja-jp/archive/blogs/waws/troubleshooting-azure-app-service-apps-using-web-server-logs#class-5-status-codes)