操作
バグ #479
完了バグ #474: コードベース健全性: メモリリーク・重大バグの一括調査と修正
並行処理レース・無制限キャッシュの是正
バグ #479:
並行処理レース・無制限キャッシュの是正
説明
現象¶
複数スレッドが同期なしで共有可変状態にアクセスし、可視性不足・レース・CME・スレッド未終了の温床がある。再現条件が状況依存なため、実装着手時に各項を再検証する。
対象箇所¶
-
AbstractAudioDriver 並列ロード
src/bms/player/beatoraja/audio/AbstractAudioDriver.java:410-
BoundedParallelExecutor(:440)の並列ロードでlocalSlice[note.getWav()]への check-then-create レース + 非スレッドセーフな libgdxArray.add。タスクキー(wavid)とスライス処理キー(note.getWav())が別 ID になり得るため同一要素を複数スレッドが触る。スライス音源欠落/AIOOBE の温床。
-
-
AbstractResult/MusicResult の非 volatile 共有
src/bms/player/beatoraja/result/AbstractResult.java:20,35,39/MusicResult.java- IR 送信ワーカースレッド(
startVirtual("beatoraja-ir-music-result", ...))が非 volatile なstate/ranking/rankingOffsetを書き、描画スレッドが同期なしで読む。IR 結果が描画に反映されない/古い state のまま遷移等。
- IR 送信ワーカースレッド(
-
PortAudioDriver
src/bms/player/beatoraja/audio/PortAudioDriver.java:29,193,246-249- ミキサー停止フラグ
stopが非 volatile(スレッド未終了でスレッド+オーディオストリームリークの恐れ)。:249は最大 1 秒の CPU スピンウェイト。
- ミキサー停止フラグ
-
BitmapFontCache
src/bms/player/beatoraja/skin/BitmapFontCache.java:11- static
HashMapが無制限増加・エビクションなし、かつスキン読込スレッドと描画スレッドから並行アクセスで CME/破損リスク。
- static
参考:StreamController の busy-wait src/bms/player/beatoraja/stream/StreamController.java:70
¶
-
while (!pipeBuffer.ready());で CPU 1 コアをスピン消費(資源浪費、機能バグではない)。
修正方針¶
-
stop/stateの volatile 化。localSliceへの並列アクセスを同期 or per-key ロック化。BitmapFontCacheをConcurrentHashMap+ 上限/解放に。rankingの差し替え/更新はロック。スピンウェイトは適切な待機に置換。 - 影響範囲が広いため、必要に応じて個別チケットに分割して慎重に対応。
検証¶
- 並列ロードを繰り返してもスライス音源欠落/AIOOBE が出ない。
- IR 結果がリザルトに反映される。
- 終了時にオーディオスレッドが確実に停止する。
エージェント ちゃん さんが6日前に更新
- ステータス を 未着手 から 実装中 に変更
- 進捗率 を 0 から 10 に変更
実装に着手しました。
- ブランチ:
feature/479 - 対応方針: 項目2(AbstractResult/MusicResultのstate/ranking/rankingOffset)・項目3(PortAudioDriverのstop+スピンウェイト)・項目4(BitmapFontCacheの並行/上限)・参考(StreamControllerのビジーウェイト)を是正。項目1(並列ロードのlocalSlice)は再検証で実レースなし(wavidで排他分割+awaitTermination可視性担保)と判明したため、コメントで根拠を文書化しスキップ。
エージェント ちゃん さんが6日前に更新
- ステータス を 実装中 から 完了 に変更
- 進捗率 を 10 から 100 に変更
対応が完了しました。
概要¶
複数スレッドが同期なしで共有可変状態にアクセスしていた箱所の可視性・レース・スレッドリーク・無制限キャッシュを是正。
変更点¶
-
AbstractResult: IRワーカースレッドが書き描画スレッドが読む
state/ranking/rankingOffsetを volatile 化。stateへの volatile 書き込み(updateScore 完了後のSTATE_IR_FINISHED)が ranking 内部変更を publish するフェンスとして機能することを Javadoc に明記。→ IR 結果がリザルトに反映されない/古い state のまま遷移する問題を解消。 -
PortAudioDriver: ミキサー停止フラグ
stopを volatile 化し、dispose()の最大1秒 CPU スピンウェイトをmixer.join(1000)に置換。→ 終了時のスレッド/オーディオストリームリークを解消。 -
BitmapFontCache: 非同期
HashMapをsynchronizedMap+ access-orderLinkedHashMap(LRU・上限512)に置換し CME/破損を是正。エビクションはマップ参照解放のみで dispose しない(共有フォントの use-after-dispose 回避)。 -
StreamController:
while(!pipeBuffer.ready());のビジーウェイトを sleep 付き待機に置換。 - 項目1(AbstractAudioDriver 並列ロード)は再検証の結果実レースなし(map が note.getWav() でキー化され localSlice インデックスが単一タスクの排他所有、かつ awaitTermination が可視性バリア)と判明し未変更。
- 変更ファイル:
AbstractResult.java/PortAudioDriver.java/BitmapFontCache.java/StreamController.java+ 新規BitmapFontCacheTest.java(計5件)
テスト¶
-
.\gradlew.bat test: 成功(BUILD SUCCESSFUL、全スイート green、新規 LRU エビクションテスト含む)
関連 MR¶
- !154 (feature/479 → develop)
操作