mainloopのスレッド管理を改善し、マルチワーカー化を実装。デバイス管理の初期化を遅延させ、エラーハンドリングを強化。ドキュメントを更新し、設定の変更点を明示化。

This commit is contained in:
misyaguziya
2025-10-13 08:28:27 +09:00
parent 0130792682
commit 914789c9cb
10 changed files with 328 additions and 86 deletions

View File

@@ -172,6 +172,15 @@ config.saveConfig('CUSTOM_SAVE', {'foo': 'bar'}, immediate_save=True)
- `saveConfig()` はデバウンスされるため、高頻度の設定変更では複数の変更がまとめて書き込まれます。即時書き込みが必要な操作(重要な鍵の更新など)は `immediate_save=True` を使ってください。
- `SELECTABLE_*` 系や `*_DICT` 系は初期化時に外部モジュール翻訳リソース、whisper_models、device_manager 等)から生成されます。これらが利用できない環境ではデフォルトが空になる可能性があります。
### 2025-10-13 の変更device_manager / config に関する挙動改善)
- `DeviceManager` のシングルトン生成時に軽量 `init()` を実行するようになりました。これにより、モジュールのインポート順序に依存して `config``SELECTED_*``NoDevice` のままになる問題が軽減されます(監視スレッドは自動起動しません)。
- `config.init_config()` はこれまで `device_manager._initialized` をチェックしていた箇所を見直し、`device_manager.getDefaultMicDevice()` / `getDefaultSpeakerDevice()` といったアクセサを利用して値を取得するように変更しました。アクセサは必要なら遅延初期化を行うため、`controller``config` のトップレベルインポート順に依存しません。
- 影響: 起動時に PyAudio 等の依存が利用可能であれば、起動中に実機デバイス名が `config` に反映される確率が高くなります。依存がない場合は従来どおり `NoDevice` にフォールバックします。
推奨運用:
- `controller.init()` でコールバック登録後、`mainloop` の起動シーケンスで `device_manager.startMonitoring()` を明示的に呼ぶと、起動後もデバイス変更がコールバック経由で確実に届きます(この呼び出しは任意です)。
## 推奨改善点(将来的なドキュメント/実装)
- 設定スキーマを JSON Schema で定義し、load 時の検証を明確化すると安全性が向上します。
- 設定変更イベントを発火する仕組みobserver パターンを導入すると、Controller/Model 側の再初期化処理をより明確に実装できます。

View File

@@ -5,6 +5,10 @@
- UI からのコマンドを受け取り、`model` の開始/停止、設定の変更、ダウンロードの開始、各種フラグの切り替え、進捗通知(`run` コールバック経由)を行います。
- 多くのメソッドは JSON 系の応答オブジェクトを返します: {"status": int, "result": Any}。副作用で `self.run(status, run_mapping[key], payload)` を呼び出して UI に通知します。
### mainloop のマルチワーカー化とカノニカルロックについて (2025-10-13)
- `mainloop.Main` はデフォルトで複数(デフォルト 3のハンドラワーカースレッドを動かすようになりました。これにより、モデルロードなどの重い操作で他のリクエストが待たされることが少なくなります。
- `/set/enable/<feature>``/set/disable/<feature>` のように同一機能の ON/OFF を切り替えるエンドポイントは、内部的にカノニカルロックキー(例: `/lock/set/<feature>`)に正規化してロック取得されます。これにより、遅い disable の処理が後から来て最終状態を書き換えてしまうレースが防がれます。
初期化とランタイムフック
- __init__() -> None
- フィールド: `init_mapping: dict`, `run_mapping: dict`, `run: Callable`, `device_access_status: bool`

View File

@@ -45,6 +45,17 @@ device_manager.forceUpdateAndSetMicDevices()
- Windows 固有のモジュールPyAudio paWASAPI, pycawに依存します。クロスプラットフォーム対応が必要な場合は別実装が必要です。
- 監視スレッドは永続的に動作するため、アプリケーション終了時は `stopMonitoring()` を呼んで安全に停止してください。
変更点2025-10-13:
- `DeviceManager` のシングルトン生成時(`__new__`)に軽量な `init()` を実行するようになりました。これによりモジュールのインポート順に依存せず、最小限の内部構造が常に確立されます(※監視スレッドは自動で起動しません)。
- `init()` は監視スレッドを開始しませんが、PyAudio が利用可能な場合に限りベストエフォートで一度だけ `update()` を呼び、起動時に可能な限り実機デバイス情報を埋めるようになりました(例外は握り潰して安全性を維持)。
- アクセサ (`getDefaultMicDevice()` / `getDefaultSpeakerDevice()` など) は遅延初期化を行い、呼び出し時に `init()` が動いていない場合は安全に初期化されるようになりました。これにより `controller``config` がトップレベルインポートで互いに依存している状況でも、`config` に正しいデバイス情報が入るようになります。
推奨起動シーケンス:
- `controller.init()` でコールバック登録が完了した直後に、`mainloop` の起動シーケンス中で明示的に `device_manager.startMonitoring()` を呼ぶことを推奨します。これにより以降のデバイス変更がコールバックを通じて確実に届きます。なお、`startMonitoring()` は任意で、軽量にしたい場合は呼ばなくても構いません(ただし動的変化は検出されません)。
ドキュメントにおける重要な注意:
- この変更は "import-time に重大な副作用を持たせない" という方針を維持しつつ、インポート順の違いによる初期化漏れを解消するために行われています。`init()` は監視スレッドを開始しないため、インポートだけでスレッドが走ることはありません。
## 詳細設計
目的: ローカルの入力マイクと出力ループバックから抽出されたスピーカーデバイスを列挙し、変更を監視してコールバックで通知する。Windows の WASAPI 等に依存。

View File

@@ -1,22 +1,28 @@
## mainloop モジュールsrc-python/mainloop.py
このドキュメントは `mainloop.py` の実装と、2025-10-09 に行ったリファクタの概要をまとめます。`mainloop` は標準入力から JSON を受け取り、`controller` のメソッドにルーティングして標準出力へ JSON で応答を返す小さなメインループです。
このドキュメントは `mainloop.py` の実装と、最近行ったリファクタの概要をまとめます。`mainloop` は標準入力から JSON を受け取り、`controller` のメソッドにルーティングして標準出力へ JSON で応答を返す小さなメインループです。
重要な変更点2025-10-09:
- `Main` クラスに `start()` / `stop()` を追加し、受信スレッドとハンドラスレッドのライフサイクル管理を明示化しました。
- `queue.get(timeout=...)` を使ってポーリング負荷を下げ、`_stop_event` による安全なシャットダウンを可能にしました。
- 標準入力の JSON パースエラーと一般例外のハンドリングを強化しました
- `startReceiver()` / `startHandler()` を使って個別にスレッドを起動することも可能です。
重要な変更点:
- 2025-10-09: `Main` クラスに `start()` / `stop()` を追加し、受信スレッドとハンドラスレッドのライフサイクル管理を明示化しました。`queue.get(timeout=...)` による安全なシャットダウンを可能にしています。
- 2025-10-13: ハンドラの振る舞いを改善しました(マルチワーカー化とロック正規化):
- マルチワーカー化: ハンドラ処理はデフォルトで複数ワーカー(例: 3 本で並列実行されます。これにより、1 つの重い処理が他のすべてのリクエストをブロックしてしまう問題を緩和します
- ロック正規化: `/set/enable/<feature>` `/set/disable/<feature>` のような on/off ペアは同一のロックキーに正規化され、同一機能の on と off が同時に別スレッドで実行されることを防ぎます。これにより、遅い方の処理結果が後から上書きして最終状態が意図しないものになる不具合を防止します。
クラス: Main
- __init__(controller_instance: Controller, mapping_data: dict) -> None
- __init__(controller_instance: Controller, mapping_data: dict, worker_count: int = 3) -> None
- `controller_instance`: `Controller` のインスタンス。
- `mapping_data`: `mainloop` 内で使用する `mapping`(エンドポイント -> ハンドラ情報)辞書。
- `worker_count`: ハンドラワーカー数(デフォルト 3。実行環境に応じて調整可能です。
- start() -> None
- 内部で `startReceiver()``startHandler()` を呼び、スレッドを起動します。
- 内部で `startReceiver()``startHandler()` を呼び、受信とハンドラのスレッドを起動します。
- stop(wait: float = 2.0) -> None
- シャットダウンシグナルをセットし、スレッド終了を待ちます(デフォルト 2 秒)。
動作の重要ポイント
- キュー運用: 受信した JSON は内部キューに入れられ、ハンドラワーカーが順次取り出して処理します。`queue.get(timeout=...)` を使っているため CPU 負荷を抑えつつ安全に停止できます。
- 同期応答設計: 各エンドポイントは基本的に呼び出し元に同期的に結果を返します(`handler` が戻り値としてステータスと結果を返す)。今回の変更でもこの設計は維持されています。
- 同一機能直列化: `/set/enable/X``/set/disable/X` のような on/off ペアは内部で同一の "ロックキー" に正規化され、同時に両方が実行されることを防ぎます。これにより、enable と disable が競合して遅い方が勝つ問題が解消されます。
使い方(例):
```python
@@ -29,15 +35,16 @@ main_instance.start()
main_instance.stop()
```
既存のスクリプト互換性:
- 既存コードが `startReceiver()``startHandler()` を直接呼んでいる場合、そのまま動作します。`start()` / `stop()` を使うと簡潔に起動 / 停止が行えます
確認手順(変更の検証):
1. バックエンドを起動しておく
2. UIテストスクリプトから `/set/enable/translation``/set/disable/translation` を高速に交互送信する(数十〜数百ミリ秒間隔で連打)。
3. ログ(`printLog` 出力)を確認し、同一機能の複数実行が同時に走っていないこと、最終状態が遅い方に常に上書きされないことを確認する。
4. 必要に応じて `worker_count` を増減して挙動を確認するPC リソースに応じて 1〜6 程度を推奨)。
注意点と推奨事項:
- `stop()` を呼ばないとバックグラウンドスレッドがデーモンであってもプロセス終了前にクリーンアップが不十分になる場合があります。アプリ終了時は `stop()` を呼ぶことを推奨します
- `queue.get(timeout=...)` を使うことで即時性よりも CPU 使用量の低減を優先しています。非常に低レイテンシが必要なケースでは timeout を短くしてください(ただし CPU 使用量に注意)
スクリプト連携:
- `mainloop.mapping``mainloop.run_mapping``scripts/print_mapping.py` などのツールから直接参照されます。mapping のキー/値を変更する場合はそれらのスクリプトも確認してください。
- `worker_count` を増やすと他のエンドポイントの並列処理性は上がりますが、controller/model 側で共有リソースGPU メモリやデバイスハンドルなどへの同時アクセスが許可されていない場合は、controller 側で機能単位のロック(例: translation_lockを追加してください
- このドキュメントの変更は `mainloop` の外側から見える挙動同期応答、ログ、ロックの方針を説明するものです。controller 内の処理自体は引き続き同期的に実行されます。必要があれば、enable 系の重い処理を非同期化して完了通知をイベントで返す設計UI 変更が必要)も検討してください
変更履歴:
- 2025-10-09: start/stop ライフサイクル、タイムアウト付きキュー取得、エラー処理強化を追加。
- 2025-10-13: マルチワーカー化(デフォルト 3と enable/disable のロック正規化を実装。これにより同一機能の on/off の同時実行を防止し、UI からの高速トグルで最終状態が遅い方に上書きされる問題を修正しました。