OBS WebSocket 5 を Unity から叩いたら ClientWebSocket の同時送信で壊れた
Hello → Identify → Identified の認証フロー、倍々バックオフの再接続、そして await せずに並べた 2 つの要求が SendAsync の InvalidOperationException で再接続ループへ落ちた話。
何に困っていたか
24 時間配信の運用では「12 時間で別枠へ移す」ためのシーン切替と配信の停止・開始、そして「OBS 側で配信が落ちた」ことの検知を Unity ゲーム内から行いたい。OBS の操作が人手か外部ツール任せだと、Unity 側のスケジューラーや Remote 経由の指示と連動できない。
OBS Studio 28 以降に同梱の obs-websocket 5(ws://127.0.0.1:4455、rpcVersion 1)へ接続する Obs パッケージを作った。
認証と再接続の設計
接続後の流れは Hello(op 0)→ Identify(op 1)→ Identified(op 2)である。パスワードがある場合の認証文字列はこう計算する。
base64(sha256(base64(sha256(password + salt)) + challenge))
OBS は認証失敗時にエラー本文を返さず切断するので、Identify 送信後に応答なく切断されたら認証失敗と見なすという運用的な判定を明文化した。
再接続は 1 秒から倍々で最大 30 秒のバックオフ。Identified に到達したらリセットする。パスワード未設定は Failed(理由:パスワード未設定)にしてバックオフ後に再試行し、.env へ入れれば復帰できるようにした。
責務は 3 つに分けた。
McObsConnection:裏スレッドのTask.Runで handshake・requestIdによる要求と応答の対応付け・再接続ループを回す。McObsProtocol:opとdを解析する。IMcObsWebSocket:テストで差し替えられる WebSocket の抽象。
イベントは初期化時に捕捉した SynchronizationContext.Post でメインスレッドへ配送する。
何が起きたか
同時送信でソケットが壊れた。 SetCurrentSceneAsync と StartRecordAsync を await せず連続で呼ぶと、ClientWebSocket.SendAsync が同時実行されて InvalidOperationException を投げ、ソケットが壊れて再接続ループに入った。接続完了時に内部で走る初期取得(シーン一覧・配信状態・録画状態の 3 要求)とも重なる。.NET の ClientWebSocket は同時送信を許さない。非同期 API を並べた瞬間にこの前提が破られる。
キャンセルが効かない待機。 SendRequestAsync は応答待ちの間、引数の CancellationToken を見ていなかった。中止要求を出しても要求タイムアウト(既定 10 秒)まで解放されなかった。
テスト用のフェイクがスレッド安全でなかった。 受信スレッドからの追記とテストスレッドからの列挙で「Collection was modified」が出る不安定テストがあり、スナップショットを返す型に置き換えた。
どう直したか
- 送信は 1 件ずつ直列化し、同時
SendAsyncを構造的に起こせなくした。内部の初期取得も同じ排他路に乗せる。 - 応答待ちを
CancellationTokenで打ち切り、OperationCanceledExceptionを送出する。 - 導入チェックは、パスワードが任意なので Error ではなく Warning に留めた。
教訓
- 非同期 API を公開した時点で「
awaitせず連続呼び出しされる」ことは前提。 送信路は必ず直列化する。 - 自前の内部要求もユーザー呼び出しと競合する。 内部要求も同じ排他路に乗せる。
CancellationTokenを受け取って無視する API は、タイムアウトが長いほど害が大きい。- handshake の失敗をプロトコルで区別できない場合、運用的判定を明文化する。 復旧手順が書けるようになる。
- テストのフェイク自体がスレッド安全でないと、本体ではなくテストが落ちる。
対象外
obs-websocket 4 系、ソース・フィルター・音量・トランジション操作、OBS プロセスの起動と終了(Watchdog の責務)、LAN 上の別 PC の OBS は対象外にした。「12 時間で別枠へ移す」自動化そのものも、このパッケージは部品を提供するだけにしている。
Related Articles