backend_test.mdを更新・mainloop.mdを更新・test_client.mdを追加: /set/data/* の実行時取得とキャッシュ挙動を文書化して動的値選択の理由を追記、mainloopの start() 簡素化(メインループ維持、KeyboardInterruptでstop)と Watchdog(/run/feed_watchdog) 連携を明記、status=348ログ取り扱い・初期化待機・Watchdog運用などドキュメント整形と説明強化

This commit is contained in:
misyaguziya
2025-10-29 11:51:04 +09:00
parent 30f5b44168
commit 8e73469e41
3 changed files with 253 additions and 19 deletions

View File

@@ -9,9 +9,11 @@
### 1. グローバル変数
#### `run_mapping` (dict)
フロントエンドへの通知用エンドポイントマッピング。Controllerが `run()` コールバックを通じてフロントエンドに状態変化を通知する際に使用。
**主要なエンドポイント:**
- `/run/enable_translation` - 翻訳機能の有効/無効状態
- `/run/transcription_mic_message` - マイク音声認識結果
- `/run/transcription_speaker_message` - スピーカー音声認識結果
@@ -19,11 +21,14 @@
- `/run/initialization_complete` - 初期化完了通知
#### `mapping` (dict)
フロントエンドからのリクエストを処理する関数マッピング。各エンドポイントに対して:
- `status`: ロック状態True: 処理可能, False: ロック中)
- `variable`: 実行する Controller メソッド
**エンドポイント分類:**
- `/get/data/*` - 設定値の取得(初期化時に使用)
- `/set/data/*` - 設定値の更新
- `/set/enable/*` - 機能の有効化
@@ -31,6 +36,7 @@
- `/run/*` - アクション実行(メッセージ送信、ダウンロード等)
#### `init_mapping` (dict)
初期化時に実行される `/get/data/*` エンドポイントのサブセット。アプリケーション起動時に全設定値をフロントエンドに送信するために使用。
### 2. Mainクラス
@@ -38,11 +44,13 @@
#### コンストラクタ `__init__(controller_instance, mapping_data, worker_count)`
**パラメータ:**
- `controller_instance`: Controller インスタンス
- `mapping_data`: エンドポイントマッピング辞書
- `worker_count`: ハンドラワーカースレッド数(デフォルト: 3
**初期化処理:**
1. リクエストキュー (`Queue[Tuple[str, Any]]`) の作成
2. 停止イベント (`Event`) の作成
3. エンドポイント別 Lock の生成:
@@ -50,6 +58,7 @@
- 同一機能の有効化/無効化リクエストが競合しないよう排他制御
**正規化ロジックの例:**
```python
"/set/enable/translation" "/lock/set/translation"
"/set/disable/translation" "/lock/set/translation"
@@ -61,6 +70,7 @@
**責務:** stdin から JSON リクエストを読み取り、キューに投入
**処理フロー:**
1. `sys.stdin.readline()` でブロッキング読み取り
2. JSON パース (`json.loads()`)
3. エンドポイントとデータを抽出
@@ -69,6 +79,7 @@
6. キューに投入 `self.queue.put((endpoint, data))`
**エラー処理:**
- JSON パースエラー: ログ出力して継続
- EOF 到達: 0.1秒待機して再試行
- その他の例外: `errorLogging()` でトレースバック記録
@@ -80,6 +91,7 @@
**責務:** キューからリクエストを取り出し、適切なロックを取得して処理
**処理フロー:**
1. キューから `(endpoint, data)` を取得0.5秒タイムアウト)
2. エンドポイントを正規化キーに変換
3. 対応する Lock を取得試行(非ブロッキング)
@@ -89,10 +101,12 @@
5. レスポンスを stdout に出力 (`printResponse()`)
**排他制御の意義:**
- 例: 翻訳機能の有効化中に無効化リクエストが来た場合、無効化は待機
- 異なる機能のリクエストは並列実行可能
**再キューロジック:**
- status == 423 (Locked): 0.1秒待機して再キュー
- これにより、初期化中の設定変更リクエストが適切にリトライされる
@@ -103,6 +117,7 @@
**責務:** 実際のビジネスロジック実行
**処理フロー:**
1. `mapping` から対応するハンドラを取得
2. エンドポイントが存在しない → status 404
3. ハンドラの `status` が False → status 423 (Locked)
@@ -111,15 +126,33 @@
6. status と result を抽出して返却
**エラー処理:**
- 例外発生時: `errorLogging()` でトレースバック記録、status 500 を返却
#### `start()` / `stop(wait)` メソッド
#### `start()` / `stop(wait)` メソッド2025-10 仕様)
**start():**
- `startReceiver()` - stdin 読み取りスレッド起動
- `startHandler()` - ハンドラワーカースレッド起動
コミット `5ce281e` によりシンプルな「メインループ維持」機能のみになりました。`startReceiver()` / `startHandler()``start()` 内では呼ばれません。呼び出し側(`__main__` ブロックや外部プロセスマネージャ)で必要なスレッド起動した後、`start()` を呼び出して以下の挙動を提供します。
```python
def start(self) -> None:
try:
while not self._stop_event.is_set():
time.sleep(1)
except KeyboardInterrupt:
self.stop()
```
ポイント:
- メインスレッドを 1 秒スリープしつつ存続させ、Ctrl+CKeyboardInterruptで安全に停止へ遷移。
- 既存の receiver / handler スレッド二重起動のリスクを除去。
- ライフサイクル明確化: スレッド起動責務を `start()` から分離しテスト・拡張容易性向上。
従来仕様ドキュメントの「`start()` がスレッドを直接起動する」記述は廃止済みであり、最新コードでは不要です。
**stop(wait):**
- `_stop_event.set()` - 全スレッドに停止シグナル送信
- 各スレッドを `join(timeout=remaining)` で待機(最大 `wait` 秒)
@@ -131,16 +164,22 @@
2. `startReceiver()` - stdin リスニング開始
3. `startHandler()` - リクエスト処理開始
4. **Watchdog 設定:**
- `controller.setWatchdogCallback(main_instance.stop)`
- `controller.setWatchdogCallback(main_instance.stop)`
- Watchdog がタイムアウトした場合にプロセス全体を停止
5. **Controller 初期化:**
- `controller.init()`
- `controller.init()`
- Model の遅延初期化、デバイス列挙、ネットワーク接続チェック
- `init_mapping` のすべてのエンドポイントを実行して初期設定をフロントエンドに送信
6. **マッピングのアンロック:**
- すべての `mapping[key]["status"]` を True に設定
- これにより初期化中だった機能が利用可能になる
7. `main_instance.start()` - 実質的には何もしない(既に起動済み)
7. `main_instance.start()` - メインループ維持のみreceiver / handler は事前に起動済み)
### Watchdog エンドポイント連携
バックエンドはクライアント側から `/run/feed_watchdog` が一定間隔(例: 30 秒)で送信される前提の設計が可能です。テストクライアント(`test_client.py`)は初期化完了後にバックグラウンドスレッドでこのエンドポイントを自動送信し、内部監視(`controller` 側の Watchdog コールバック)タイムアウト防止を行います。
ログ上は通常レスポンス非取得fire & forgetとなるため、負荷低減のためにハートビート頻度調整が可能です。
## 並列処理とスレッドセーフティ
@@ -182,6 +221,7 @@
```
**フィールド:**
- `endpoint`: 実行するエンドポイント(必須)
- `data`: パラメータオプション、Base64 エンコード)
@@ -196,12 +236,13 @@
```
**フィールド:**
- `status`: HTTP ステータスコード相当
- 200: 成功
- 400: バリデーションエラー
- 404: 無効なエンドポイント
- 423: ロック中(リトライされる)
- 500: 内部エラー
- 200: 成功
- 400: バリデーションエラー
- 404: 無効なエンドポイント
- 423: ロック中(リトライされる)
- 500: 内部エラー
- `endpoint`: リクエストされたエンドポイント
- `result`: 処理結果(型はエンドポイントに依存)
@@ -218,52 +259,62 @@
## エラーハンドリング
### 1. JSON パースエラー
- **発生箇所:** `receiver()``json.loads()`
- **処理:** `errorLogging()` でトレースバック記録、リクエストをスキップ
### 2. ハンドラ実行エラー
- **発生箇所:** `_call_handler()``handler["variable"](data)`
- **処理:**
- `errorLogging()` でトレースバック記録
- status 500 と "Internal error" を返却
- プロセスは継続
- **処理:**
- `errorLogging()` でトレースバック記録
- status 500 と "Internal error" を返却
- プロセスは継続
### 3. JSON シリアライズエラー
- **発生箇所:** `printResponse()``json.dumps()`
- **処理:**
- エラーログに詳細を記録
- フォールバック JSON を出力status 500
- プロセスは継続
- エラーログに詳細を記録
- フォールバック JSON を出力status 500
- プロセスは継続
### 4. EOF (stdin 終了)
- **発生箇所:** `receiver()``readline()`
- **処理:** 0.1秒待機して再試行(フロントエンドの再起動待ち)
## パフォーマンス最適化
### 1. 複数ワーカースレッド
- デフォルト3スレッドで並列処理
- CPU バウンドな処理(翻訳、文字起こし)を効率化
### 2. 非ブロッキングロック
- ロック競合時に即座に再キュー
- スレッドのブロッキング時間を最小化
### 3. 処理安定化待機
- 各ハンドラ実行後に 0.2秒待機
- 連続リクエストによる競合状態を回避
## 制限事項
### 1. 初期化中の制限
- `mapping[key]["status"] = False` の間はリクエストが 423 でリトライされる
- 初期化完了まで最大数秒のレイテンシが発生
### 2. stdin の単方向性
- stdin → キュー → ハンドラの一方向フロー
- 複数のフロントエンドからの同時接続は非対応
### 3. シリアル実行の保証
- 同一エンドポイントのリクエストは排他的に実行されるが、
- 異なるエンドポイントは並列実行される可能性がある
- 依存関係のある操作は呼び出し側で順序制御が必要
@@ -294,14 +345,17 @@
## 今後の拡張性
### 1. 双方向通信
- WebSocket への移行でリアルタイム通知を改善
- stdin/stdout は互換性のため維持
### 2. 動的ワーカー数調整
- キューの深さに応じてスレッド数を自動調整
- CPU 負荷に応じた適応的なスケーリング
### 3. 優先度キュー
- 重要なリクエスト(エラー通知等)を優先処理
- `queue.PriorityQueue` への移行
@@ -315,6 +369,7 @@
## コーディング規約
本ファイルは以下の規約に従う:
- PEP 8 スタイルガイド
- 型ヒント (`typing` モジュール)
- Docstring は Google スタイル
@@ -323,6 +378,7 @@
## テストシナリオ
### 1. 基本動作テスト
```python
# stdin に JSON を送信
echo '{"endpoint": "/get/data/version", "data": null}' | python mainloop.py
@@ -330,14 +386,17 @@ echo '{"endpoint": "/get/data/version", "data": null}' | python mainloop.py
```
### 2. 並列リクエストテスト
- 複数の設定変更リクエストを同時送信
- すべてが正常に処理されることを確認
### 3. ロック競合テスト
- 翻訳の有効化と無効化を連続送信
- 両方が排他的に実行されることを確認
### 4. エラー回復テスト
- 不正なJSON、無効なエンドポイント、不正なデータを送信
- プロセスがクラッシュせずエラーレスポンスを返すことを確認