VOICEVOX / open_jtalk-rs

BSD 3-Clause "New" or "Revised" License
10 stars 12 forks source link

dllアンロード後"char.bin"のロックが残る件について #17

Open miyabin1701 opened 9 months ago

miyabin1701 commented 9 months ago

内容

こんばんは、voicevox-core.dllをアンロードしても辞書ファイル"char.bin"のロックが 外れないので悩んでします・。

実行時の読み込まれてるdllリスト Base Size Path 0x00000000be220000 0x62000 R:\release\qtMeCabon.exe 0x0000000067600000 0xb5000 R:\release\portaudio_x64.dll 0x00000000cb360000 0x255000 R:\release\voicevox_core.dll 0x00000000ca6b0000 0x494000 R:\release\libmecab64.dll

dllをFreeLibraryでアンロードしました。 0x00000000be220000 0x62000 R:\release\qtMeCabon.exe 0x0000000067600000 0xb5000 R:\release\portaudio_x64.dll

 正常にdllはアンロードされましたが、mecabとvoicevox-openjtalk-mecabの開いている "char.bin"ファイルが開かれたままになっていて、辞書のビルドをしようとするとパーミッションエラーになってしまいます。 windows10になってからなのか?dllが開きっぱなしのファイルは親プロセスが終了するまでロックが解除されないようです。

 mecab側で試してみましたが、FreeLibraryの前に使っていたlattice,mecab,modelを 明示的にdestroyするとロックが外れることが分かりました。

mecab_lattice_destroy( lattice );
mecab_destroy( mecab );
mecab_model_destroy( model );
if ( FreeLibrary( hMecabDLL ) == 0 )

voicevox_finalize()の中で、mecabの上記後始末関数呼んで頂ければ助かります。 現在 voicevox_core-0.14.5 を使っています。よろしくお願いします。

Pros 良くなる点

dllをアンロード後にmecabの辞書がビルド可能になります。

Cons 悪くなる点

ソースの行数が少し増える?1行??

実現方法

voicevox_finalize呼び出し時にmecab_destroy( mecab );を呼ぶ

VOICEVOXのバージョン

-core 0.14.5

OSの種類/ディストリ/バージョン

その他

dllの実行中ロードやアンロードのテストコードを書くには時間がかかるでしょうから修正個所を教えていただければこちらでテストします。または、こちらのソースを先にGitHubにアップする方がよろしければそうします。

miyabin1701 commented 9 months ago

qtmeecabon-charbin009-APPrun-build char.binを開いているアプリはリソースモニターで確認できます。上記はmecabとvoicevoxがロックを外してない状態で、辞書をビルドしている(この時は別のフォルダーのchar.binを作成中で、後のステップでロックされているフォルダーの更新しようとしてこけます)際のものです。

PickledChair commented 9 months ago

ご報告ありがとうございます!

Discordでのやり取りの後、この件についてもう少し調べてみたのですが、自分の当初の予想とは異なる考えになりました。結論から言うと、問題は open_jtalk-rs の実装にはなく voicevox_core の方の問題のようでした。かつ、voicevox_coreの main ブランチではライブラリの設計変更に伴ってこの問題がすでに解決されている可能性が高そうでした。

問題が解決したバージョンのリリースにはまだ少し時間がかかると思います(次回リリースのバージョン 0.15 はハミング機能(project-s)の成果の投入が優先されています)。その後のバージョン 0.16 では解決しているのではないかと思います。

混乱させてしまっていたらすみません。以下でもう少し詳しく説明します。

Q. デストラクタで mecab_destroy 関数の呼び出しをするべきか

A. しなくても良い。というより呼び出しできない

OpenJTalk 版の MeCab には OpenJTalk 用の API が後付けされているようでした("for Open JTalk" と書かれています)。MeCab のオリジナルの API には "old mecab interface" と書かれています。

この2つの系統の API は別々のライフサイクルを持っているようです。非常に簡略に書くと以下の感じになりそうです:

両系統の API の間では扱う構造体に互換性がないので、両者を混ぜて使うことはできません。そして open_jtalk-rs では後者の OpenJTalk 版の API を用いています。したがって、デストラクタでは Mecab_clear のみを呼び出せば良いということになります。

Q. では、voicevox_core をアンロードしても辞書ファイルのロックが外れなかったのはなぜか?

A. voicevox_core の実装が、open_jtalk-rs のデストラクタを呼ばない書き方になっていたから

設計変更前の voicevox_core では、ライブラリの単一の状態を static 変数で保持していました。static 変数に束縛されているオブジェクトはプログラムの終了時であってもデストラクタが呼ばれません(Rust の static のドキュメントで以下のように説明されています。drop が Rust におけるデストラクタです)。

Static items do not call drop at the end of the program.

一方、新しい設計の voicevox_core では、OpenJTalk の機能は OpenJtalkRc という構造体を介して使うようになっています。この構造体は static 変数には束縛されず、ライブラリユーザーが API を介して明示的にリソースを管理するようになっているので、同じ問題は起きません。

Windows 向けのサンプルコードが以下にあります:

https://github.com/VOICEVOX/voicevox_core/blob/b9c953c56081d569b5dce4ed5aea3f6f79976ba6/example/cpp/windows/simple_tts/simple_tts.cpp#L37-L48

voicevox_open_jtalk_rc_new 関数でインスタンス作成、voicevox_open_jtalk_rc_delete 関数で削除処理をします。比較的早くに(まだその後も使われていそうなのに)voicevox_open_jtalk_rc_delete 関数を呼び出していて不思議に思うかもしれませんが、これはインスタンスが参照カウントで管理されているからです(Rc というのは reference counted のこと)。最初のインスタンス生成でカウント1、voicevox_synthesizer_newopen_jtalk を渡した時にカウント2になり、API 呼び出し側のスコープではその後 open_jtalk を使う予定がないので voicevox_open_jtalk_rc_delete でカウントをマイナス1しています。最終的には voicevox_synthesizer_delete 関数の呼び出しの時に、VoicevoxSynthesizer 構造体の破棄と同時にカウント0となり、リソースが解放されるはずです。

(余談ですが、ユーザー辞書 API も追加されたので、今後はユーザー独自の辞書作成・操作もサポートされるようになります。)

miyabin1701 commented 9 months ago

本文の方有難うございます。できれば0.14.5でロック解除できるとありがたいのですが。余談の方にコメントすると 拙作では、システム辞書を更新する方向で進めています。何故なら、辞書には登録されていても、適切に読めないことが多発しているからです。コーパスファイルに追記して、システム辞書をビルドしてmecabのmodelファイルを調整するしか適切に読めるように導く方法は無いと思います。これは、小説などの長文を読み上げさせようとしている場合の話ですが。主戦場を「あるじせんじょう」と読んだり、大規模を「おおきぼ」と読んだりします。mecabのmodelファイルはmecab-ipadic-2.7.0-20070801U8.modelしか見つからなかったのでそれを元に、読み損じをコーパスファイルに加筆しています。 ただ、どれだけの例をコーパスファイルに書けば「行った」が「いった」なのか「おこなった」なのかを区別できるようになるかどうかわかりません。左の文字が「に、へ」がいったで「を」がおこなった、「が」のときはどちらかわからない? ユーザ辞書登録はその語彙が一つの読み方しかない場合には有効かも知れません。 一番早いのはソースの文章をかなに変更することでしょうか・・・アクセントがつかないかもしれないけど

PickledChair commented 9 months ago

できれば0.14.5でロック解除できるとありがたいのですが

0.14 系は、0.15 が近日リリースされるためサポート対象外となると思います。一方、上述したように 0.15 でも問題は解決されていませんが、API の変更は(非公開 API が追加された以外は)ないと思うので、早期の修正が必要であれば 0.15 系で修正し、こちらをお使いいただくのが良いと思います(恐らく修正できるとは思うのですが、まだ修正方法を深くは検討していません)。

ただ、0.16 が早いうちにリリースされることになれば、これは問題解決済みのバージョンのはずなので 0.16 を待っていただくのが良い気もしています。

@Hiroshiba 0.15 系のサポート中に修正するか、0.16 系を待つかは 0.16 のリリース時期次第だと思うので、私には判断できませんでした(最近時間が取れず開発状況を完全にはキャッチアップできていなくてすみません)。こちらの判断をお願いしてもよろしいでしょうか?

余談の方にコメントすると 拙作では、システム辞書を更新する方向で進めています。

なるほどです。これであれば、確かにファイルロックの問題は致命的ですね。

Hiroshiba commented 9 months ago

@PickledChair 連絡ありがとうございます!! 最新版では解決済みなんですね!!よかった。

@miyabin1701 追従が大変かもなのですが、最新版をおすすめします!! どうしてもであればrelease-0.15ブランチにプルリクエストをお願いします 🙏 他にはまあプロセスごと分けちゃえるエンジンの方を使う手もあるかも・・・?