FNiagaraScriptExecutionContextBase::ExecuteInternal_Legacy()でクラッシュ

お世話になっております。

開発チームとも協議いたしましたので、ご報告いたします。

まず、元投稿において頂戴したご質問について改めてご回答申し上げます。

> FNiagaraScriptExecutionContextBase::ExecuteInternal_Legacy()内で

> FScopeLock Lock(&ExecData.OptimizationTask.Lock);

> の記述があります。

>

> こちらの変数のLockはここでしか見当たらなかったので、ここが並列に実行される可能性があるのかなと見受けられます。

> こちらの認識はあっているでしょうか?

はい、その認識で間違いございません。

複数のスレッドがこの関数を同時に実行した場合、ExecData.OptimizationTask.State が IsValid() であれば、各スレッドが if 文に入り、そのうちの1スレッドが FScopeLock を取得してバイトコードの最適化結果を適用(ApplyFinishedOptimization())します。その他のスレッドはロックで待機し、最適化済みコードが適用済みであれば何もせずに進むというのが、想定されている動作です。

> その場合Lockのスコープが狭く、別スレッドで実行中の同じ FNiagaraScriptExecutionContextBase::ExecuteInternal_Legacy() で ByteCode / OptimizedByteCode の中身が書き換えられるのでは?と考えています。

こちらは開発チーム側としては想定していない振る舞いで、発生しうるシナリオに心当たりがないとのことです。クラッシュシナリオとしては、

if (ExecData.OptimizationTask.State.IsValid()) { FScopeLock Lock(&ExecData.OptimizationTask.Lock); if (ExecData.OptimizationTask.State.IsValid()) { ExecData.ApplyFinishedOptimization(Script->GetVMExecutableDataCompilationId(), ExecData.OptimizationTask.State); ExecData.OptimizationTask.State.Reset(); } }こちらのコードブロックの最初のifチェックの時点で、あるスレッドのExecData.OptimizationTask.StateオブジェクトがValidではなく、別のスレッドではValidだったということが考えられるのですが、そのような状況自体が想定外(発生しないはず)であり、再現条件の手がかりが欲しいとのことでした。

(ロックの範囲が狭いことが問題なのではなく、このような想定外の状況がなぜ起きているのか、ということのほうが問題という認識です)

また、

> UNiagaraScriptにFCriticalSectionを追加して、FNiagaraScriptExecutionContextBase::ExecuteInternal_Legacy()全体をLockすると発生しなくなります。

ScriptオブジェクトのクリティカルセクションでExecuteInternal_Legacy()全体をLockすると発生しなくなったということは、何らかの不整合の発生がExecuteInternal_Legacy()で実行されるコードの範囲内で起こっていることになりますが、コードをチェックした限りでは、当方でも不審な点を見つけることができておりません。

UE5.4では、社内外で他の報告例を見つけられていないことも気になっております。何か思い当たることがありましたら、情報提供をいただけますと幸いです。

※このスレッドをPrivateに切り替えることも可能ですので、必要に応じてご連絡ください。

なお、UE5.6 ではこの Legacy VM が完全に削除されており、新型VM(UE5.4 時点ではExecuteInternal_Experimental()経由の実行パス)に一本化されました。そのため、今後の流れとしては、ExecuteInternal_Legacy()だけの問題であればローカルパッチで対応、ExecuteInternal_Legacy()の外側に問題がある(新型VMでも問題がある)場合はエンジン側で対応、という形になるかと存じます。

以上、よろしくお願いいたします。