From 35d20408bd0aa1d9fcd13076822cc695d50e0424 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Fri, 13 Sep 2024 00:13:11 +0900 Subject: [PATCH] =?UTF-8?q?change:=20Rust=20API=E3=81=AE=E8=84=B1Tokio?= =?UTF-8?q?=E3=82=92=E8=A1=8C=E3=81=84=E3=80=81=E3=83=A2=E3=82=B8=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E5=90=8D=E3=82=92`tokio`=E2=86=92`:nonblocki?= =?UTF-8?q?ng`=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 22 +++ Cargo.toml | 2 + crates/voicevox_core/Cargo.toml | 5 +- .../src/__internal/doctest_fixtures.rs | 12 +- crates/voicevox_core/src/asyncs.rs | 6 +- crates/voicevox_core/src/devices.rs | 4 +- .../src/engine/full_context_label.rs | 4 +- crates/voicevox_core/src/engine/open_jtalk.rs | 28 +++- .../src/infer/runtimes/onnxruntime.rs | 38 +++-- crates/voicevox_core/src/lib.rs | 2 +- crates/voicevox_core/src/nonblocking.rs | 25 ++++ crates/voicevox_core/src/status.rs | 4 +- crates/voicevox_core/src/synthesizer.rs | 138 ++++++++++-------- crates/voicevox_core/src/task.rs | 14 +- crates/voicevox_core/src/test_util.rs | 2 +- crates/voicevox_core/src/tokio.rs | 13 -- crates/voicevox_core/src/user_dict/dict.rs | 22 ++- crates/voicevox_core/src/voice_model.rs | 9 +- crates/voicevox_core_python_api/src/lib.rs | 48 +++--- 19 files changed, 257 insertions(+), 141 deletions(-) create mode 100644 crates/voicevox_core/src/nonblocking.rs delete mode 100644 crates/voicevox_core/src/tokio.rs diff --git a/Cargo.lock b/Cargo.lock index 53117ce5c..defbbe093 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2767,6 +2767,26 @@ dependencies = [ "time", ] +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +dependencies = [ + "pollster-macro", +] + +[[package]] +name = "pollster-macro" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea78f0ef4193055a4b09814ce6bcb572ad1174d6023e2f00a9ea1a798d18d301" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.102", +] + [[package]] name = "portable-atomic" version = "0.3.19" @@ -4345,6 +4365,7 @@ dependencies = [ "anyhow", "async-fs", "async_zip", + "blocking", "camino", "const_format", "derive-getters", @@ -4366,6 +4387,7 @@ dependencies = [ "ndarray", "open_jtalk", "ouroboros", + "pollster", "pretty_assertions", "ref-cast", "regex", diff --git a/Cargo.toml b/Cargo.toml index 3a2fffb01..922c7ac09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ async-fs = "2.1.2" async_zip = "=0.0.16" bindgen = "0.69.4" binstall-tar = "0.4.39" +blocking = "1.6.1" bytes = "1.1.0" camino = "1.1.6" cargo_metadata = "0.18.1" @@ -57,6 +58,7 @@ octocrab = { version = "0.19.0", default-features = false } once_cell = "1.19.0" ouroboros = "0.18.0" parse-display = "0.8.2" +pollster = "0.3.0" pretty_assertions = "1.3.0" proc-macro2 = "1.0.69" pyo3 = "0.20.3" diff --git a/crates/voicevox_core/Cargo.toml b/crates/voicevox_core/Cargo.toml index 74feebb4b..e05b04c79 100644 --- a/crates/voicevox_core/Cargo.toml +++ b/crates/voicevox_core/Cargo.toml @@ -18,6 +18,7 @@ link-onnxruntime = [] anyhow.workspace = true async-fs.workspace = true async_zip = { workspace = true, features = ["deflate"] } +blocking.workspace = true camino.workspace = true const_format.workspace = true derive-getters.workspace = true @@ -27,7 +28,7 @@ duplicate.workspace = true easy-ext.workspace = true educe.workspace = true enum-map.workspace = true -fs-err = { workspace = true, features = ["tokio"] } +fs-err.workspace = true futures-io.workspace = true futures-lite.workspace = true futures-util = { workspace = true, features = ["io"] } @@ -46,7 +47,6 @@ smallvec.workspace = true strum = { workspace = true, features = ["derive"] } tempfile.workspace = true thiserror.workspace = true -tokio = { workspace = true, features = ["rt"] } # FIXME: feature-gateする tracing.workspace = true uuid = { workspace = true, features = ["v4", "serde"] } voicevox-ort = { workspace = true, features = ["download-binaries", "__init-for-voicevox"] } @@ -54,6 +54,7 @@ voicevox_core_macros = { path = "../voicevox_core_macros" } [dev-dependencies] heck.workspace = true +pollster = { workspace = true, features = ["macro"] } pretty_assertions.workspace = true rstest.workspace = true rstest_reuse.workspace = true diff --git a/crates/voicevox_core/src/__internal/doctest_fixtures.rs b/crates/voicevox_core/src/__internal/doctest_fixtures.rs index 8f45cba73..253bb8d6f 100644 --- a/crates/voicevox_core/src/__internal/doctest_fixtures.rs +++ b/crates/voicevox_core/src/__internal/doctest_fixtures.rs @@ -10,23 +10,23 @@ pub async fn synthesizer_with_sample_voice_model( OsString, >, open_jtalk_dic_dir: impl AsRef, -) -> anyhow::Result> { - let syntesizer = crate::tokio::Synthesizer::new( +) -> anyhow::Result> { + let syntesizer = crate::nonblocking::Synthesizer::new( #[cfg(feature = "load-onnxruntime")] - crate::tokio::Onnxruntime::load_once() + crate::nonblocking::Onnxruntime::load_once() .filename(onnxruntime_dylib_path) .exec() .await?, #[cfg(feature = "link-onnxruntime")] - crate::tokio::Onnxruntime::init_once().await?, - crate::tokio::OpenJtalk::new(open_jtalk_dic_dir).await?, + crate::nonblocking::Onnxruntime::init_once().await?, + crate::nonblocking::OpenJtalk::new(open_jtalk_dic_dir).await?, &InitializeOptions { acceleration_mode: AccelerationMode::Cpu, ..Default::default() }, )?; - let model = &crate::tokio::VoiceModel::from_path(voice_model_path).await?; + let model = &crate::nonblocking::VoiceModel::from_path(voice_model_path).await?; syntesizer.load_voice_model(model).await?; Ok(syntesizer) diff --git a/crates/voicevox_core/src/asyncs.rs b/crates/voicevox_core/src/asyncs.rs index 7bbabbb06..5f4d7fd21 100644 --- a/crates/voicevox_core/src/asyncs.rs +++ b/crates/voicevox_core/src/asyncs.rs @@ -11,8 +11,7 @@ //! に[`SingleTasked`]を用意している。 //! //! [ブロッキング版API]: crate::blocking -//! [非同期版API]: crate::tokio -//! [blocking]: https://docs.rs/crate/blocking +//! [非同期版API]: crate::nonblocking use std::{ io::{self, Read as _, Seek as _, SeekFrom}, @@ -71,8 +70,7 @@ impl Async for SingleTasked { /// /// [非同期版API]用。 /// -/// [blocking]: https://docs.rs/crate/blocking -/// [非同期版API]: crate::tokio +/// [非同期版API]: crate::nonblocking pub(crate) enum BlockingThreadPool {} impl Async for BlockingThreadPool { diff --git a/crates/voicevox_core/src/devices.rs b/crates/voicevox_core/src/devices.rs index 6c0e87d06..ebffcb360 100644 --- a/crates/voicevox_core/src/devices.rs +++ b/crates/voicevox_core/src/devices.rs @@ -52,9 +52,9 @@ fn test_gpu( /// しても`cuda`や`dml`は`true`を示しうる。 /// /// ``` -/// # #[tokio::main] +/// # #[pollster::main] /// # async fn main() -> anyhow::Result<()> { -/// use voicevox_core::{tokio::Onnxruntime, SupportedDevices}; +/// use voicevox_core::{nonblocking::Onnxruntime, SupportedDevices}; /// /// # voicevox_core::blocking::Onnxruntime::load_once() /// # .filename(if cfg!(windows) { diff --git a/crates/voicevox_core/src/engine/full_context_label.rs b/crates/voicevox_core/src/engine/full_context_label.rs index 92617a8c1..dab5cbae5 100644 --- a/crates/voicevox_core/src/engine/full_context_label.rs +++ b/crates/voicevox_core/src/engine/full_context_label.rs @@ -425,7 +425,7 @@ mod tests { #[apply(label_cases)] #[tokio::test] async fn open_jtalk(text: &str, labels: &[&str], _accent_phrase: &[AccentPhrase]) { - let open_jtalk = crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + let open_jtalk = crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(); assert_eq!(&open_jtalk.extract_fullcontext(text).unwrap(), labels); @@ -447,7 +447,7 @@ mod tests { #[apply(label_cases)] #[tokio::test] async fn extract_fullcontext(text: &str, _labels: &[&str], accent_phrase: &[AccentPhrase]) { - let open_jtalk = crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + let open_jtalk = crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(); assert_eq!( diff --git a/crates/voicevox_core/src/engine/open_jtalk.rs b/crates/voicevox_core/src/engine/open_jtalk.rs index 88d16f381..fb7f3ea59 100644 --- a/crates/voicevox_core/src/engine/open_jtalk.rs +++ b/crates/voicevox_core/src/engine/open_jtalk.rs @@ -1,3 +1,16 @@ +// TODO: `VoiceModel`のように、次のような設計にする。 +// +// ``` +// pub(crate) mod blocking { +// pub struct OpenJtalk(Inner); +// // … +// } +// pub(crate) mod nonblocking { +// pub struct OpenJtalk(Inner); +// // … +// } +// ``` + use ::open_jtalk::Text2MecabError; #[derive(thiserror::Error, Debug)] @@ -183,12 +196,19 @@ pub(crate) mod blocking { } } -pub(crate) mod tokio { +pub(crate) mod nonblocking { use camino::Utf8Path; use super::FullcontextExtractor; /// テキスト解析器としてのOpen JTalk。 + /// + /// # Performance + /// + /// [blocking]クレートにより動いている。詳しくは[`nonblocking`モジュールのドキュメント]を参照。 + /// + /// [blocking]: https://docs.rs/crate/blocking + /// [`nonblocking`モジュールのドキュメント]: crate::nonblocking #[derive(Clone)] pub struct OpenJtalk(super::blocking::OpenJtalk); @@ -206,7 +226,7 @@ pub(crate) mod tokio { /// この関数を呼び出した後にユーザー辞書を変更した場合は、再度この関数を呼ぶ必要がある。 pub async fn use_user_dict( &self, - user_dict: &crate::tokio::UserDict, + user_dict: &crate::nonblocking::UserDict, ) -> crate::result::Result<()> { let inner = self.0 .0.clone(); let words = user_dict.to_mecab_format(); @@ -325,7 +345,7 @@ mod tests { #[case] text: &str, #[case] expected: anyhow::Result>, ) { - let open_jtalk = super::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + let open_jtalk = super::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(); let result = open_jtalk.extract_fullcontext(text); @@ -339,7 +359,7 @@ mod tests { #[case] text: &str, #[case] expected: anyhow::Result>, ) { - let open_jtalk = super::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + let open_jtalk = super::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(); for _ in 0..10 { diff --git a/crates/voicevox_core/src/infer/runtimes/onnxruntime.rs b/crates/voicevox_core/src/infer/runtimes/onnxruntime.rs index 7d975f7f7..91e435701 100644 --- a/crates/voicevox_core/src/infer/runtimes/onnxruntime.rs +++ b/crates/voicevox_core/src/infer/runtimes/onnxruntime.rs @@ -1,3 +1,16 @@ +// TODO: `VoiceModel`のように、次のような設計にする。 +// +// ``` +// pub(crate) mod blocking { +// pub struct Onnxruntime(Inner); +// // … +// } +// pub(crate) mod nonblocking { +// pub struct Onnxruntime(Inner); +// // … +// } +// ``` + use std::{fmt::Debug, vec}; use anyhow::{anyhow, bail, ensure}; @@ -18,9 +31,6 @@ use super::super::{ OutputScalarKind, OutputTensor, ParamInfo, PushInputTensor, }; -// TODO: `trait AsyncRuntime`みたいなものを作って抽象化しながら同期版と非同期版に別個の役割を -// 持たせる -// (なぜそうしたいかの理由の一つとしては) impl InferenceRuntime for self::blocking::Onnxruntime { type Session = ort::Session; type RunContext<'a> = OnnxruntimeRunContext<'a>; @@ -254,7 +264,7 @@ pub(crate) mod blocking { /// # Rust APIにおけるインスタンスの共有 /// /// インスタンスは[voicevox-ort]側に作られる。Rustのクレートとしてこのライブラリを利用する場合、 - /// Tokio版APIやvoicevox-ortを利用する他クレートともインスタンスが共有される。 + /// 非同期版APIやvoicevox-ortを利用する他クレートともインスタンスが共有される。 /// #[cfg_attr(feature = "load-onnxruntime", doc = "```")] #[cfg_attr(not(feature = "load-onnxruntime"), doc = "```compile_fail")] @@ -268,7 +278,7 @@ pub(crate) mod blocking { /// # .exec()?; /// # } /// let ort1 = voicevox_core::blocking::Onnxruntime::load_once().exec()?; - /// let ort2 = another_lib::tokio::Onnxruntime::get().expect("`ort1`と同一のはず"); + /// let ort2 = another_lib::nonblocking::Onnxruntime::get().expect("`ort1`と同一のはず"); /// assert_eq!(ptr_addr(ort1), ptr_addr(ort2)); /// /// fn ptr_addr(obj: &impl Sized) -> usize { @@ -430,7 +440,7 @@ pub(crate) mod blocking { } } -pub(crate) mod tokio { +pub(crate) mod nonblocking { use ref_cast::{ref_cast_custom, RefCastCustom}; use crate::SupportedDevices; @@ -448,7 +458,7 @@ pub(crate) mod tokio { #[cfg_attr(not(feature = "load-onnxruntime"), doc = "```compile_fail")] /// # use voicevox_core as another_lib; /// # - /// # #[tokio::main] + /// # #[pollster::main] /// # async fn main() -> anyhow::Result<()> { /// # if cfg!(windows) { /// # // Windows\System32\onnxruntime.dllを回避 @@ -456,7 +466,9 @@ pub(crate) mod tokio { /// # .filename(test_util::ONNXRUNTIME_DYLIB_PATH) /// # .exec()?; /// # } - /// let ort1 = voicevox_core::tokio::Onnxruntime::load_once().exec().await?; + /// let ort1 = voicevox_core::nonblocking::Onnxruntime::load_once() + /// .exec() + /// .await?; /// let ort2 = another_lib::blocking::Onnxruntime::get().expect("`ort1`と同一のはず"); /// assert_eq!(ptr_addr(ort1), ptr_addr(ort2)); /// @@ -467,7 +479,13 @@ pub(crate) mod tokio { /// # } /// ``` /// + /// # Performance + /// + /// [blocking]クレートにより動いている。詳しくは[`nonblocking`モジュールのドキュメント]を参照。 + /// /// [voicevox-ort]: https://github.com/VOICEVOX/ort + /// [blocking]: https://docs.rs/crate/blocking + /// [`nonblocking`モジュールのドキュメント]: crate::nonblocking #[derive(Debug, RefCastCustom)] #[repr(transparent)] pub struct Onnxruntime(pub(crate) super::blocking::Onnxruntime); @@ -584,11 +602,11 @@ mod tests { assert_eq!( super::blocking::Onnxruntime::LIB_NAME, - super::tokio::Onnxruntime::LIB_NAME, + super::nonblocking::Onnxruntime::LIB_NAME, ); assert_eq!( super::blocking::Onnxruntime::LIB_VERSION, - super::tokio::Onnxruntime::LIB_VERSION, + super::nonblocking::Onnxruntime::LIB_VERSION, ); } diff --git a/crates/voicevox_core/src/lib.rs b/crates/voicevox_core/src/lib.rs index dad702cc6..c5ab200d7 100644 --- a/crates/voicevox_core/src/lib.rs +++ b/crates/voicevox_core/src/lib.rs @@ -69,7 +69,7 @@ mod voice_model; pub mod __internal; pub mod blocking; -pub mod tokio; +pub mod nonblocking; #[cfg(test)] mod test_util; diff --git a/crates/voicevox_core/src/nonblocking.rs b/crates/voicevox_core/src/nonblocking.rs new file mode 100644 index 000000000..501a44d04 --- /dev/null +++ b/crates/voicevox_core/src/nonblocking.rs @@ -0,0 +1,25 @@ +//! 非同期版API。 +//! +//! # Performance +//! +//! これらは[blocking]クレートにより動いている。特定の非同期ランタイムを必要とせず、[pollster]など +//! でも動かすことができる。 +//! +//! スレッドプールおよびエグゼキュータはblockingクレートに依存するすべてのプログラム間で共有される。 +//! スレッドプールのサイズは、blockingクレートの説明にある通り`$BLOCKING_MAX_THREADS`で調整すること +//! ができる。 +//! +//! [blocking]: https://docs.rs/crate/blocking +//! [pollster]: https://docs.rs/crate/pollster + +pub use crate::{ + engine::open_jtalk::nonblocking::OpenJtalk, + infer::runtimes::onnxruntime::nonblocking::Onnxruntime, synthesizer::nonblocking::Synthesizer, + user_dict::dict::nonblocking::UserDict, voice_model::nonblocking::VoiceModel, +}; + +pub mod onnxruntime { + #[cfg(feature = "load-onnxruntime")] + #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] + pub use crate::infer::runtimes::onnxruntime::nonblocking::LoadOnce; +} diff --git a/crates/voicevox_core/src/status.rs b/crates/voicevox_core/src/status.rs index 419be52f5..5103e060e 100644 --- a/crates/voicevox_core/src/status.rs +++ b/crates/voicevox_core/src/status.rs @@ -408,7 +408,7 @@ mod tests { talk: enum_map!(_ => InferenceSessionOptions::new(0, DeviceSpec::Cpu)), }, ); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModel::sample().await.unwrap(); let model_contents = &model.read_inference_models().await.unwrap(); let result = status.insert_model(model.header(), model_contents); assert_debug_fmt_eq!(Ok(()), result); @@ -424,7 +424,7 @@ mod tests { talk: enum_map!(_ => InferenceSessionOptions::new(0, DeviceSpec::Cpu)), }, ); - let vvm = &crate::tokio::VoiceModel::sample().await.unwrap(); + let vvm = &crate::nonblocking::VoiceModel::sample().await.unwrap(); let model_header = vvm.header(); let model_contents = &vvm.read_inference_models().await.unwrap(); assert!( diff --git a/crates/voicevox_core/src/synthesizer.rs b/crates/voicevox_core/src/synthesizer.rs index 3b9642c8a..7a1bb2ab8 100644 --- a/crates/voicevox_core/src/synthesizer.rs +++ b/crates/voicevox_core/src/synthesizer.rs @@ -1,7 +1,20 @@ -/// [`blocking::Synthesizer::synthesis`]および[`tokio::Synthesizer::synthesis`]のオプション。 +// TODO: `VoiceModel`のように、次のような設計にする。 +// +// ``` +// pub(crate) mod blocking { +// pub struct Synthesizer(Inner); +// // … +// } +// pub(crate) mod nonblocking { +// pub struct Synthesizer(Inner); +// // … +// } +// ``` + +/// [`blocking::Synthesizer::synthesis`]および[`nonblocking::Synthesizer::synthesis`]のオプション。 /// /// [`blocking::Synthesizer::synthesis`]: blocking::Synthesizer::synthesis -/// [`tokio::Synthesizer::synthesis`]: tokio::Synthesizer::synthesis +/// [`nonblocking::Synthesizer::synthesis`]: nonblocking::Synthesizer::synthesis #[derive(Clone)] pub struct SynthesisOptions { pub enable_interrogative_upspeak: bool, @@ -21,10 +34,10 @@ impl From<&TtsOptions> for SynthesisOptions { } } -/// [`blocking::Synthesizer::tts`]および[`tokio::Synthesizer::tts`]のオプション。 +/// [`blocking::Synthesizer::tts`]および[`nonblocking::Synthesizer::tts`]のオプション。 /// /// [`blocking::Synthesizer::tts`]: blocking::Synthesizer::tts -/// [`tokio::Synthesizer::tts`]: tokio::Synthesizer::tts +/// [`nonblocking::Synthesizer::tts`]: nonblocking::Synthesizer::tts #[derive(Clone)] pub struct TtsOptions { pub enable_interrogative_upspeak: bool, @@ -56,10 +69,10 @@ pub enum AccelerationMode { Gpu, } -/// [`blocking::Synthesizer::new`]および[`tokio::Synthesizer::new`]のオプション。 +/// [`blocking::Synthesizer::new`]および[`nonblocking::Synthesizer::new`]のオプション。 /// /// [`blocking::Synthesizer::new`]: blocking::Synthesizer::new -/// [`tokio::Synthesizer::new`]: tokio::Synthesizer::new +/// [`nonblocking::Synthesizer::new`]: nonblocking::Synthesizer::new #[derive(Default)] pub struct InitializeOptions { pub acceleration_mode: AccelerationMode, @@ -67,7 +80,7 @@ pub struct InitializeOptions { } pub(crate) mod blocking { - // FIXME: ここのdocのコードブロックはasync版のものなので、`tokio`モジュールの方に移した上で、 + // FIXME: ここのdocのコードブロックはasync版のものなので、`nonblocking`モジュールの方に移した上で、 // (ブロッキング版をpublic APIにするならの話ではあるが)ブロッキング版はブロッキング版でコード例 // を用意する @@ -113,7 +126,7 @@ pub(crate) mod blocking { /// #[cfg_attr(feature = "load-onnxruntime", doc = "```")] #[cfg_attr(not(feature = "load-onnxruntime"), doc = "```compile_fail")] - /// # #[tokio::main] + /// # #[pollster::main] /// # async fn main() -> anyhow::Result<()> { /// # use test_util::{ONNXRUNTIME_DYLIB_PATH, OPEN_JTALK_DIC_DIR}; /// # @@ -122,7 +135,7 @@ pub(crate) mod blocking { /// use std::sync::Arc; /// /// use voicevox_core::{ - /// tokio::{Onnxruntime, OpenJtalk, Synthesizer}, + /// nonblocking::{Onnxruntime, OpenJtalk, Synthesizer}, /// AccelerationMode, InitializeOptions, /// }; /// @@ -466,7 +479,7 @@ pub(crate) mod blocking { /// # Example /// /// ``` - /// # #[tokio::main] + /// # #[pollster::main] /// # async fn main() -> anyhow::Result<()> { /// # let synthesizer = /// # voicevox_core::__internal::doctest_fixtures::synthesizer_with_sample_voice_model( @@ -685,7 +698,7 @@ pub(crate) mod blocking { /// # Example /// /// ``` - /// # #[tokio::main] + /// # #[pollster::main] /// # async fn main() -> anyhow::Result<()> { /// # let synthesizer = /// # voicevox_core::__internal::doctest_fixtures::synthesizer_with_sample_voice_model( @@ -729,7 +742,7 @@ pub(crate) mod blocking { /// # Example /// /// ``` - /// # #[tokio::main] + /// # #[pollster::main] /// # async fn main() -> anyhow::Result<()> { /// # let synthesizer = /// # voicevox_core::__internal::doctest_fixtures::synthesizer_with_sample_voice_model( @@ -762,7 +775,7 @@ pub(crate) mod blocking { /// # Examples /// /// ``` - /// # #[tokio::main] + /// # #[pollster::main] /// # async fn main() -> anyhow::Result<()> { /// # let synthesizer = /// # voicevox_core::__internal::doctest_fixtures::synthesizer_with_sample_voice_model( @@ -1127,7 +1140,7 @@ pub(crate) mod blocking { } } -pub(crate) mod tokio { +pub(crate) mod nonblocking { use std::sync::Arc; use crate::{ @@ -1138,13 +1151,20 @@ pub(crate) mod tokio { use super::{InitializeOptions, TtsOptions}; /// 音声シンセサイザ。 + /// + /// # Performance + /// + /// [blocking]クレートにより動いている。詳しくは[`nonblocking`モジュールのドキュメント]を参照。 + /// + /// [blocking]: https://docs.rs/crate/blocking + /// [`nonblocking`モジュールのドキュメント]: crate::nonblocking #[derive(Clone)] pub struct Synthesizer(pub(super) Arc>); // FIXME: docを書く impl self::Synthesizer { pub fn new( - onnxruntime: &'static crate::tokio::Onnxruntime, + onnxruntime: &'static crate::nonblocking::Onnxruntime, open_jtalk: O, options: &InitializeOptions, ) -> Result { @@ -1153,15 +1173,15 @@ pub(crate) mod tokio { .map(Self) } - pub fn onnxruntime(&self) -> &'static crate::tokio::Onnxruntime { - crate::tokio::Onnxruntime::from_blocking(self.0.onnxruntime()) + pub fn onnxruntime(&self) -> &'static crate::nonblocking::Onnxruntime { + crate::nonblocking::Onnxruntime::from_blocking(self.0.onnxruntime()) } pub fn is_gpu_mode(&self) -> bool { self.0.is_gpu_mode() } - pub async fn load_voice_model(&self, model: &crate::tokio::VoiceModel) -> Result<()> { + pub async fn load_voice_model(&self, model: &crate::nonblocking::VoiceModel) -> Result<()> { let model_bytes = &model.read_inference_models().await?; self.0.status.insert_model(model.header(), model_bytes) } @@ -1318,8 +1338,8 @@ mod tests { #[case(Ok(()))] #[tokio::test] async fn load_model_works(#[case] expected_result_at_initialized: Result<()>) { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::Onnxruntime::from_test_util_data() + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() .await .unwrap(), (), @@ -1331,7 +1351,7 @@ mod tests { .unwrap(); let result = syntesizer - .load_voice_model(&crate::tokio::VoiceModel::sample().await.unwrap()) + .load_voice_model(&crate::nonblocking::VoiceModel::sample().await.unwrap()) .await; assert_debug_fmt_eq!( @@ -1344,8 +1364,8 @@ mod tests { #[rstest] #[tokio::test] async fn is_use_gpu_works() { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::Onnxruntime::from_test_util_data() + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() .await .unwrap(), (), @@ -1363,8 +1383,8 @@ mod tests { #[tokio::test] async fn is_loaded_model_by_style_id_works(#[case] style_id: u32, #[case] expected: bool) { let style_id = StyleId::new(style_id); - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::Onnxruntime::from_test_util_data() + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() .await .unwrap(), (), @@ -1379,7 +1399,7 @@ mod tests { "expected is_model_loaded to return false, but got true", ); syntesizer - .load_voice_model(&crate::tokio::VoiceModel::sample().await.unwrap()) + .load_voice_model(&crate::nonblocking::VoiceModel::sample().await.unwrap()) .await .unwrap(); @@ -1394,8 +1414,8 @@ mod tests { #[rstest] #[tokio::test] async fn predict_duration_works() { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::Onnxruntime::from_test_util_data() + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() .await .unwrap(), (), @@ -1407,7 +1427,7 @@ mod tests { .unwrap(); syntesizer - .load_voice_model(&crate::tokio::VoiceModel::sample().await.unwrap()) + .load_voice_model(&crate::nonblocking::VoiceModel::sample().await.unwrap()) .await .unwrap(); @@ -1428,8 +1448,8 @@ mod tests { #[rstest] #[tokio::test] async fn predict_intonation_works() { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::Onnxruntime::from_test_util_data() + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() .await .unwrap(), (), @@ -1440,7 +1460,7 @@ mod tests { ) .unwrap(); syntesizer - .load_voice_model(&crate::tokio::VoiceModel::sample().await.unwrap()) + .load_voice_model(&crate::nonblocking::VoiceModel::sample().await.unwrap()) .await .unwrap(); @@ -1470,8 +1490,8 @@ mod tests { #[rstest] #[tokio::test] async fn decode_works() { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::Onnxruntime::from_test_util_data() + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() .await .unwrap(), (), @@ -1482,7 +1502,7 @@ mod tests { ) .unwrap(); syntesizer - .load_voice_model(&crate::tokio::VoiceModel::sample().await.unwrap()) + .load_voice_model(&crate::nonblocking::VoiceModel::sample().await.unwrap()) .await .unwrap(); @@ -1565,11 +1585,11 @@ mod tests { #[case] expected_text_consonant_vowel_data: &TextConsonantVowelData, #[case] expected_kana_text: &str, ) { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::Onnxruntime::from_test_util_data() + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() .await .unwrap(), - crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(), &InitializeOptions { @@ -1579,7 +1599,7 @@ mod tests { ) .unwrap(); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModel::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); let query = match input { @@ -1636,11 +1656,11 @@ mod tests { #[case] input: Input, #[case] expected_text_consonant_vowel_data: &TextConsonantVowelData, ) { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::Onnxruntime::from_test_util_data() + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() .await .unwrap(), - crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(), &InitializeOptions { @@ -1650,7 +1670,7 @@ mod tests { ) .unwrap(); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModel::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = match input { @@ -1704,11 +1724,11 @@ mod tests { #[rstest] #[tokio::test] async fn create_accent_phrases_works_for_japanese_commas_and_periods() { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::Onnxruntime::from_test_util_data() + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() .await .unwrap(), - crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(), &InitializeOptions { @@ -1718,7 +1738,7 @@ mod tests { ) .unwrap(); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModel::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer @@ -1767,11 +1787,11 @@ mod tests { #[rstest] #[tokio::test] async fn mora_length_works() { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::Onnxruntime::from_test_util_data() + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() .await .unwrap(), - crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(), &InitializeOptions { @@ -1781,7 +1801,7 @@ mod tests { ) .unwrap(); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModel::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer @@ -1808,11 +1828,11 @@ mod tests { #[rstest] #[tokio::test] async fn mora_pitch_works() { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::Onnxruntime::from_test_util_data() + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() .await .unwrap(), - crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(), &InitializeOptions { @@ -1822,7 +1842,7 @@ mod tests { ) .unwrap(); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModel::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer @@ -1849,11 +1869,11 @@ mod tests { #[rstest] #[tokio::test] async fn mora_data_works() { - let syntesizer = super::tokio::Synthesizer::new( - crate::tokio::Onnxruntime::from_test_util_data() + let syntesizer = super::nonblocking::Synthesizer::new( + crate::nonblocking::Onnxruntime::from_test_util_data() .await .unwrap(), - crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR) + crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR) .await .unwrap(), &InitializeOptions { @@ -1863,7 +1883,7 @@ mod tests { ) .unwrap(); - let model = &crate::tokio::VoiceModel::sample().await.unwrap(); + let model = &crate::nonblocking::VoiceModel::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer diff --git a/crates/voicevox_core/src/task.rs b/crates/voicevox_core/src/task.rs index 951e3c19e..233c0de85 100644 --- a/crates/voicevox_core/src/task.rs +++ b/crates/voicevox_core/src/task.rs @@ -1,16 +1,6 @@ -use std::panic; +// TODO: `Async::unblock`として取り回す /// ブロッキング操作を非同期化する。 -/// -/// # Panics -/// -/// - `f`がパニックした場合、パニックがそのままunwindされる。 -/// - tokioのランタイムの都合で`f`の実行が"cancel"された場合パニックする。 pub(crate) async fn asyncify R + Send + 'static, R: Send + 'static>(f: F) -> R { - tokio::task::spawn_blocking(f) - .await - .unwrap_or_else(|err| match err.try_into_panic() { - Ok(panic) => panic::resume_unwind(panic), - Err(err) => panic!("{err}"), // FIXME: エラーとして回収する - }) + blocking::unblock(f).await } diff --git a/crates/voicevox_core/src/test_util.rs b/crates/voicevox_core/src/test_util.rs index 5b97f21fc..f92c4ee0c 100644 --- a/crates/voicevox_core/src/test_util.rs +++ b/crates/voicevox_core/src/test_util.rs @@ -2,7 +2,7 @@ use ::test_util::SAMPLE_VOICE_MODEL_FILE_PATH; use crate::Result; -impl crate::tokio::VoiceModel { +impl crate::nonblocking::VoiceModel { pub(crate) async fn sample() -> Result { Self::from_path(SAMPLE_VOICE_MODEL_FILE_PATH).await } diff --git a/crates/voicevox_core/src/tokio.rs b/crates/voicevox_core/src/tokio.rs deleted file mode 100644 index 1e2fabada..000000000 --- a/crates/voicevox_core/src/tokio.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Tokio版API。 - -pub use crate::{ - engine::open_jtalk::tokio::OpenJtalk, infer::runtimes::onnxruntime::tokio::Onnxruntime, - synthesizer::tokio::Synthesizer, user_dict::dict::tokio::UserDict, - voice_model::tokio::VoiceModel, -}; - -pub mod onnxruntime { - #[cfg(feature = "load-onnxruntime")] - #[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))] - pub use crate::infer::runtimes::onnxruntime::tokio::LoadOnce; -} diff --git a/crates/voicevox_core/src/user_dict/dict.rs b/crates/voicevox_core/src/user_dict/dict.rs index 6997620f0..13c30540d 100644 --- a/crates/voicevox_core/src/user_dict/dict.rs +++ b/crates/voicevox_core/src/user_dict/dict.rs @@ -1,3 +1,16 @@ +// TODO: `VoiceModel`のように、次のような設計にする。 +// +// ``` +// pub(crate) mod blocking { +// pub struct UserDict(Inner); +// // … +// } +// pub(crate) mod nonblocking { +// pub struct UserDict(Inner); +// // … +// } +// ``` + pub(crate) mod blocking { use indexmap::IndexMap; use itertools::join; @@ -102,7 +115,7 @@ pub(crate) mod blocking { } } -pub(crate) mod tokio { +pub(crate) mod nonblocking { use std::sync::Arc; use indexmap::IndexMap; @@ -115,6 +128,13 @@ pub(crate) mod tokio { /// ユーザー辞書。 /// /// 単語はJSONとの相互変換のために挿入された順序を保つ。 + /// + /// # Performance + /// + /// [blocking]クレートにより動いている。詳しくは[`nonblocking`モジュールのドキュメント]を参照。 + /// + /// [blocking]: https://docs.rs/crate/blocking + /// [`nonblocking`モジュールのドキュメント]: crate::nonblocking #[derive(Debug, Default)] pub struct UserDict(Arc); diff --git a/crates/voicevox_core/src/voice_model.rs b/crates/voicevox_core/src/voice_model.rs index ac49d2cdb..48c541439 100644 --- a/crates/voicevox_core/src/voice_model.rs +++ b/crates/voicevox_core/src/voice_model.rs @@ -434,7 +434,7 @@ pub(crate) mod blocking { } } -pub(crate) mod tokio { +pub(crate) mod nonblocking { use std::path::Path; use crate::{ @@ -447,6 +447,13 @@ pub(crate) mod tokio { /// 音声モデル。 /// /// VVMファイルと対応する。 + /// + /// # Performance + /// + /// [blocking]クレートにより動いている。詳しくは[`nonblocking`モジュールのドキュメント]を参照。 + /// + /// [blocking]: https://docs.rs/crate/blocking + /// [`nonblocking`モジュールのドキュメント]: crate::nonblocking pub struct VoiceModel(Inner); impl self::VoiceModel { diff --git a/crates/voicevox_core_python_api/src/lib.rs b/crates/voicevox_core_python_api/src/lib.rs index b4aa65c9b..c09fafdc8 100644 --- a/crates/voicevox_core_python_api/src/lib.rs +++ b/crates/voicevox_core_python_api/src/lib.rs @@ -662,7 +662,7 @@ mod asyncio { #[pyclass] #[derive(Clone)] pub(crate) struct VoiceModel { - model: Arc, + model: Arc, } #[pymethods] @@ -670,7 +670,7 @@ mod asyncio { #[staticmethod] fn from_path(py: Python<'_>, path: PathBuf) -> PyResult<&PyAny> { pyo3_asyncio::tokio::future_into_py(py, async move { - let model = voicevox_core::tokio::VoiceModel::from_path(path).await; + let model = voicevox_core::nonblocking::VoiceModel::from_path(path).await; let model = Python::with_gil(|py| model.into_py_result(py))?.into(); Ok(Self { model }) }) @@ -693,33 +693,36 @@ mod asyncio { #[pyclass] #[derive(Clone)] - pub(crate) struct Onnxruntime(&'static voicevox_core::tokio::Onnxruntime); + pub(crate) struct Onnxruntime(&'static voicevox_core::nonblocking::Onnxruntime); #[pymethods] impl Onnxruntime { #[classattr] - const LIB_NAME: &'static str = voicevox_core::tokio::Onnxruntime::LIB_NAME; + const LIB_NAME: &'static str = voicevox_core::nonblocking::Onnxruntime::LIB_NAME; #[classattr] - const LIB_VERSION: &'static str = voicevox_core::tokio::Onnxruntime::LIB_VERSION; + const LIB_VERSION: &'static str = voicevox_core::nonblocking::Onnxruntime::LIB_VERSION; #[classattr] const LIB_VERSIONED_FILENAME: &'static str = - voicevox_core::tokio::Onnxruntime::LIB_VERSIONED_FILENAME; + voicevox_core::nonblocking::Onnxruntime::LIB_VERSIONED_FILENAME; #[classattr] const LIB_UNVERSIONED_FILENAME: &'static str = - voicevox_core::tokio::Onnxruntime::LIB_UNVERSIONED_FILENAME; + voicevox_core::nonblocking::Onnxruntime::LIB_UNVERSIONED_FILENAME; #[staticmethod] fn get(py: Python<'_>) -> PyResult>> { - let result = ONNXRUNTIME.get_or_try_init(|| { - match voicevox_core::tokio::Onnxruntime::get().map(|o| Py::new(py, Self(o))) { - Some(Ok(this)) => Ok(this), - Some(Err(err)) => Err(Some(err)), - None => Err(None), - } - }); + let result = + ONNXRUNTIME.get_or_try_init( + || match voicevox_core::nonblocking::Onnxruntime::get() + .map(|o| Py::new(py, Self(o))) + { + Some(Ok(this)) => Ok(this), + Some(Err(err)) => Err(Some(err)), + None => Err(None), + }, + ); match result { Ok(this) => Ok(Some(this.clone())), @@ -732,7 +735,7 @@ mod asyncio { #[pyo3(signature = (*, filename = Self::LIB_VERSIONED_FILENAME.into()))] fn load_once(filename: OsString, py: Python<'_>) -> PyResult<&PyAny> { pyo3_asyncio::tokio::future_into_py(py, async move { - let inner = voicevox_core::tokio::Onnxruntime::load_once() + let inner = voicevox_core::nonblocking::Onnxruntime::load_once() .filename(filename) .exec() .await; @@ -756,7 +759,7 @@ mod asyncio { #[pyclass] #[derive(Clone)] pub(crate) struct OpenJtalk { - open_jtalk: voicevox_core::tokio::OpenJtalk, + open_jtalk: voicevox_core::nonblocking::OpenJtalk, } #[pymethods] @@ -769,7 +772,8 @@ mod asyncio { py: Python<'_>, ) -> PyResult<&PyAny> { pyo3_asyncio::tokio::future_into_py(py, async move { - let open_jtalk = voicevox_core::tokio::OpenJtalk::new(open_jtalk_dict_dir).await; + let open_jtalk = + voicevox_core::nonblocking::OpenJtalk::new(open_jtalk_dict_dir).await; let open_jtalk = Python::with_gil(|py| open_jtalk.into_py_result(py))?; Ok(Self { open_jtalk }) }) @@ -787,8 +791,10 @@ mod asyncio { #[pyclass] pub(crate) struct Synthesizer { - synthesizer: - Closable, Self>, + synthesizer: Closable< + voicevox_core::nonblocking::Synthesizer, + Self, + >, } #[pymethods] @@ -807,7 +813,7 @@ mod asyncio { acceleration_mode: AccelerationMode, cpu_num_threads: u16, ) -> PyResult { - let synthesizer = voicevox_core::tokio::Synthesizer::new( + let synthesizer = voicevox_core::nonblocking::Synthesizer::new( onnxruntime.0, open_jtalk.open_jtalk.clone(), &InitializeOptions { @@ -1146,7 +1152,7 @@ mod asyncio { #[pyclass] #[derive(Default, Debug, Clone)] pub(crate) struct UserDict { - dict: Arc, + dict: Arc, } #[pymethods]