BLOGブログ

2020.11.25UE4/ C++

[UE4] ThreadHeartBeat で無限ループを検知する

執筆バージョン: Unreal Engine 4.25

こんにちは!みなさん、ハートビート、してますか?
本日は ThreadHeartBeat の紹介です。

さて、突然ですが、Blueprint で以下のようなループ処理を書いて実行してみましょう。


すると、ループ処理は中止されて以下のようなワーニング&ログが表示されます。


これは、意図しない無限ループ処理を検知し、修正しやすくするための仕組みです。
Blueprint のスタックトレースが表示されるため、どこで無限ループになってしまっているかが分かりますね。
ちなみに、無限ループとして扱われるループ回数は、
[Project Settings] の [Maximum Loop Iteration Count] で変更することができます。

それでは、C++ でこんなコードを書いて実行してみましょう。

この関数を実行すると無限ループが発生し、アプリが固まります。
そして、ログにはなにも出力されません。
もしこのような不具合が発生した場合、デバッガ環境があればデバッガで追うことができますが、
パッケージでの実行だと調査が難しくなります。

というわけで、ThreadHeartBeat を使ってみましょう!

ThreadHeartBeat とは、メインスレッド(GameThread)などのスレッドが停止していないか
監視するための、UnrealEngine の仕組みです。

メインスレッドから定期的に HeartBeat 通知を受け取ることで、
メインスレッドが正常に動いていることを確認します。
この確認は、メインスレッドとは別の監視スレッドが行います。

一定時間 HeartBeat を受け取らなかった場合はメインスレッドが停止しているとみなし、
スタックトレースを出力してアプリを異常停止させます(※)。
なお、このような監視の仕組みは、”WatchDogTimer” と呼ばれたりします。

メインスレッドに対して ThreadHeartBeat を使うには、(プロジェクトフォルダ)/Config/DefaultEngine.ini に
以下のように “HangDuration” という項目を追加し、秒数を指定します。

HangDuration = 60.0 なら、メインスレッドが60秒連続して停止すると異常とみなします。
HangDuration = 0.0 の場合は、ThreadHeartBeat が無効化されます。
設定できる最小値は5秒ですが、開発中は、まだ最適化していない重い処理で
一時的に時間がかかることもあるかと思いますので、HangDuration 値をやや長めに設定しておくことをオススメします。

今回は上記の通り HangDuration 値を設定し、先ほどのコードを呼び出すサンプルを作成します。

なお、ThreadHeartBeat の監視開始処理よりも、パッケージ起動時レベルの BeginPlay の方が先に呼ばれているようなので
BeginPlay 直後に Delay を挟んでいます。
(この辺ややこしいですね…)

では、パッケージを作成して実行してみましょう。
作成前に、[Project Settings] -> [Packaging] の[Include Debug Files] をONにするのも忘れずに。

見事、ログにスタックトレースが出力されました。
スタックトレースがあるのとないのとでは、調査のしやすさは雲泥の差ですね。
ぜひご活用ください。

(※)プラットフォームなどの実行環境によって若干動作が異なります。
また、Debug ビルドやエディタ実行時は、動作が重いことから無効化されているようです。
詳細は、エンジンの ThreadHeartBeat.h/.cpp のソースコードをご覧ください。