Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

複数プロセス利用時にユーザー辞書を活用するためのpyopenjtalk別プロセス化 #89

Merged
merged 12 commits into from
Mar 8, 2024

Conversation

kale4eat
Copy link

@kale4eat kale4eat commented Mar 5, 2024

概要

#71 の対策として、pyopenjtalk別プロセス化とプロセス間通信を導入します。

仕様

コード面

pyopenjtalk_workerというパッケージにまとめました。
従来のpyopenjtalk使用箇所はtextパッケージの中のモジュールのみだったので
この中に配置しました。

import pyopenjtalk

from (相対的な位置関係) import pyopenjtalk_worker as pyopenjtalk
に置き換えることでコード変更を最小限にとどめます。
また、importした後に初期化処理initializeを呼ぶようにします。
後述のサーバー起動を-mオプションによる__main__.pyを実行することにより
実現する関係上、必要です。
(呼ばずに振る舞いを切り替えられれば良かったのですが
どちらの場合も__init__.pyが呼ばれ、
importと-mオプションの分岐方法がわかりませんでした)

プロセス間通信

TCPソケット通信により、行います。
クライアント: importしたプロセス
サーバー: 初期化処理によって起動したプロセス
importした後の初期化処理は以下の流れです。

  1. まだサーバーが起動していなければ起動します。
  2. その後、クライアントとして接続します。

処理速度やソケットの枯渇を考慮して、接続を保持し続けます。
終了時には接続が切断されるようにします。
また、自プロセスがサーバーに接続している最後のプロセスの場合、
サーバーに停止要求を送り、サーバーを終了します。
(必ずしもサーバーを起動したプロセスが終了させるとは限らない)

主な確認内容

  1. server_editor.pyを起動

  2. webui_train.pyを起動

  3. esd.list にうまく読めない単語を含むようなデータを準備する
    webuiでStep 3: 書き起こしファイルの前処理を行いesd.list.cleanedの音素を記録しておく
    Ex. GNU
    image

  4. エディターでユーザー辞書登録する
    確認: エラーが出ずに終了すること
    Ex. 読み: グヌウで登録
    image
    image

  5. webuiで再度、Step 3: 書き起こしファイルの前処理を行う
    確認: エラーが出ずに終了すること

  6. esd.list.cleaned を確認
    確認: 音素がユーザー辞書登録した読みに対応するものになっていること
    image

  7. エディターを終了する

  8. webuiを終了する
    確認: サーバープロセスが残っていないこと
    Windows: タスクマネージャーでpythonがないこと
    Linux: ps -aux | grep python でないこと

その他、server_fastapi.pyの方も確認しました。

  • 辞書登録前後で、辞書に従って合成音声の読み方が変化すること

検討事項

  • ポート番号を仮に7861に設定
    値の妥当性やYAMLファイルによる設定変更機能の必要性

@OzoneAsai
Copy link

Gradioのapp.py(app.bat)はどうなりますか?
gradioのshareはよく使うもので...

@kale4eat
Copy link
Author

kale4eat commented Mar 5, 2024

@OzoneAsai
app.pyも同様です。他のUIと同時に起動した場合のユーザー辞書の問題をクリアできます。

@litagin02
ただ、app.pyも動作確認する中で、別の問題を確認しました。
それはCtrl + Cでプログラムを終了した場合、
別プロセスのサーバーが道連れにされてしまうことです。
申し訳ございません。
もしかしたらOSによって違うかもしれません。
再現性を確認し、対策を検討します。

Minor Correction:
* logging
* declaration of terminate
Minor Correction:
* logging
* change status return type
Add signal handling for when the process is killed
@kale4eat
Copy link
Author

kale4eat commented Mar 6, 2024

追加コミット修正内容

  • Ctrl + Cが押された場合に、サーバープロセスが道連れにならないように修正しました。
  • Linux環境ではpkillによって強制終了された場合にterminateが呼ばれるようにしました。
  • サーバー側のエラー処理を強化しました。
    具体的にはタスクマネージャーによって接続が切断されたときの例外をキャッチし、サーバー側が終了しないようにしました。

備考:
調査したところ、どうやらWindowsのタスクマネージャーでは
このシグナルハンドリングは機能しないようです。

上記対応により、サーバープロセスは完全に独立し、
一部クライアントのプロセスで予期しないエラーが起きた場合も
他のクライアントとの接続は維持されるようになります。

惜しむらくはMacの環境で動作確認できないことです。
subprocess.PopenはMacとLinuxで同じはずです。

何かご質問等、ありましたらよろしくお願いいたします。

Comment on lines 106 to 108
with open("signal_handler.txt", mode="w") as f:

pass
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここはpassにしていますがログか何かを書き込む予定でしょうか?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

デバッグの痕跡が混入してしまったようです。
申し訳ございません。
削除いたします。

@litagin02
Copy link
Owner

ソケット通信を使うやり方に詳しくないので、いくつか質問させてください。

  • これはクラウド環境(主にGoogle colabノートブック)での動作でも正常に機能するようになっていますでしょうか?手元でcolabで試すとうまくいきましたが、動作を想定したコードかが気になっています。
  • Windowsでバッチファイル経由で立ち上げ、終了の際にCtrl + Cでなくコマンドプロンプトを閉じる方法で終了すると、起動しているpyopenjtalkのサーバーは終了されるようになっていますでしょうか?(どこらへんでそういう処理が行われているのか教えていただけたら幸いです)

@kale4eat
Copy link
Author

kale4eat commented Mar 6, 2024

Google Colab

こちらは想定していませんでした。
OSという点では問題ないと思います。
ColabマシンはUbuntu 22.04であり、自身の動作確認したPCと同じです。

Colabなどクラウド環境特有の挙動がどうかというところですが
内部のソケット通信は通常、制限されていないので大丈夫そうです。
(Colabでは簡単なソケット通信を使うスクリプトを
別プロセスで実行できることを確認)

コマンドプロンプトを閉じられた場合

その点が抜けていました。申し訳ございません。
確認したところ、ダメなようです。
色々調査したところ、とくにWindowsでは
プログラム側から終了を察知して、適切にリソースを解放するには限界があるようです。
やはり、環境、起動や終了などパターンが複数あるので網羅するのは難しそうです。

対策案

サーバーの方で一定時間(30秒など)、
クライアントが誰もいない状態が続いた時には終了させるようにしたいと思います。
終了までのタイムラグはありますが、残り続けることはなくなります。
明日、作業いたします。
よろしくお願いいたします。

summarize the except statement at disconnection
@kale4eat
Copy link
Author

kale4eat commented Mar 7, 2024

リッスンしているポートを監視して、サーバーの挙動を確認しました。

  • クライアントとなるアプリがいなくなったら30秒後に終了することを確認
  • 他にクライアントがいる場合には終了しないことを確認

ご説明遅くなりましたが、
デバッグ詳細のTRACEログについては
common.log.pyにおいて levelを設定してください。
logger.add(SAFE_STDOUT, format=log_format, backtrace=True, diagnose=True, level="TRACE")

今回の対応は必要となる知識が多岐にわたるため
動作確認するにあたっても、一定の複雑さがあるか存じます。
文章で難しいところございましたら、
ご連絡いただければボイスチャンネルの方にもはせ参じます。
以上、よろしくお願いいたします。

@litagin02 litagin02 merged commit 5fda210 into litagin02:dev Mar 8, 2024
@litagin02
Copy link
Owner

確認しました(ログについても教えていただいてありがとうございます、助かりました)。
正直正確な動作や挙動を全部分かっているわけではないですが、期待通りにきちんと動いていて問題もなさそうで、かつ便利なので、マージして取り入れます。
(おそらく次のバージョンアップでmasterに取り込みます。)

自分ではできなかったことなので、本当にありがとうございました!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants