プロジェクト

全般

プロフィール

バグ #479

完了

バグ #474: コードベース健全性: メモリリーク・重大バグの一括調査と修正

並行処理レース・無制限キャッシュの是正

バグ #479: 並行処理レース・無制限キャッシュの是正

エージェント ちゃん さんが7日前に追加. 6日前に更新.

ステータス:
完了
優先度:
通常
担当者:
対象バージョン:
開始日:
2026/06/29
期日:
進捗率:

100%

予定工数:

説明

現象

複数スレッドが同期なしで共有可変状態にアクセスし、可視性不足・レース・CME・スレッド未終了の温床がある。再現条件が状況依存なため、実装着手時に各項を再検証する。

対象箇所

  1. AbstractAudioDriver 並列ロード src/bms/player/beatoraja/audio/AbstractAudioDriver.java:410
    • BoundedParallelExecutor(:440)の並列ロードで localSlice[note.getWav()] への check-then-create レース + 非スレッドセーフな libgdx Array.add。タスクキー(wavid)とスライス処理キー(note.getWav())が別 ID になり得るため同一要素を複数スレッドが触る。スライス音源欠落/AIOOBE の温床。
  2. 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 のまま遷移等。
  3. PortAudioDriver src/bms/player/beatoraja/audio/PortAudioDriver.java:29,193,246-249
    • ミキサー停止フラグ stop が非 volatile(スレッド未終了でスレッド+オーディオストリームリークの恐れ)。:249 は最大 1 秒の CPU スピンウェイト。
  4. BitmapFontCache src/bms/player/beatoraja/skin/BitmapFontCache.java:11
    • static HashMap が無制限増加・エビクションなし、かつスキン読込スレッドと描画スレッドから並行アクセスで CME/破損リスク。

参考:StreamController の busy-wait src/bms/player/beatoraja/stream/StreamController.java:70

  • while (!pipeBuffer.ready()); で CPU 1 コアをスピン消費(資源浪費、機能バグではない)。

修正方針

  • stop/state の volatile 化。localSlice への並列アクセスを同期 or per-key ロック化。BitmapFontCacheConcurrentHashMap + 上限/解放に。ranking の差し替え/更新はロック。スピンウェイトは適切な待機に置換。
  • 影響範囲が広いため、必要に応じて個別チケットに分割して慎重に対応。

検証

  • 並列ロードを繰り返してもスライス音源欠落/AIOOBE が出ない。
  • IR 結果がリザルトに反映される。
  • 終了時にオーディオスレッドが確実に停止する。

エージェント ちゃん さんが6日前に更新 操作 #1

  • ステータス未着手 から 実装中 に変更
  • 進捗率0 から 10 に変更

実装に着手しました。

  • ブランチ: feature/479
  • 対応方針: 項目2(AbstractResult/MusicResultのstate/ranking/rankingOffset)・項目3(PortAudioDriverのstop+スピンウェイト)・項目4(BitmapFontCacheの並行/上限)・参考(StreamControllerのビジーウェイト)を是正。項目1(並列ロードのlocalSlice)は再検証で実レースなし(wavidで排他分割+awaitTermination可視性担保)と判明したため、コメントで根拠を文書化しスキップ。

エージェント ちゃん さんが6日前に更新 操作 #2

  • ステータス実装中 から 完了 に変更
  • 進捗率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: 非同期 HashMapsynchronizedMap + access-order LinkedHashMap(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)

他の形式にエクスポート: PDF Atom