diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac0578d..cd1de52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,8 +9,13 @@ on: branches: - main + +concurrency: + group: tests-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + env: - MSRV: "1.63" + MSRV: "1.76" RUST_BACKTRACE: 1 RUSTFLAGS: -Dwarnings @@ -50,8 +55,6 @@ jobs: name: "Windows GNU" channel: - "stable" - - "beta" - - "nightly" steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@master @@ -83,7 +86,6 @@ jobs: channel: - "stable" - "beta" - - "nightly" steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@master diff --git a/Cargo.lock b/Cargo.lock index 2cb1fb5..bc8b968 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "arrayref" @@ -54,20 +54,20 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -86,9 +86,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bincode" @@ -101,21 +101,15 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec", @@ -126,24 +120,21 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" [[package]] name = "cfg-if" @@ -157,8 +148,8 @@ version = "0.1.0" dependencies = [ "anyhow", "futures", + "iroh-quinn", "quic-rpc", - "quinn", "rustls", "serde", "tokio", @@ -180,9 +171,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -190,15 +181,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] @@ -238,35 +229,40 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.3.6" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "flume" -version = "0.10.14" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "futures-core", "futures-sink", "nanorand", - "pin-project", "spin 0.9.8", ] @@ -278,9 +274,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -291,11 +287,22 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-buffered" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de8419e65098e54c06f5ae8a130a79e8ba2e391ff995d260ca5d77ea72ab2fe3" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", +] + [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -303,15 +310,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -320,38 +327,51 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -367,9 +387,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -380,15 +400,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -405,15 +425,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -423,9 +443,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -434,9 +454,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -457,9 +477,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -472,7 +492,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", @@ -481,41 +501,86 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ - "autocfg", + "equivalent", "hashbrown", ] [[package]] name = "interprocess" -version = "2.0.0" -source = "git+https://github.com/kotauskas/interprocess?branch=main#77f471f7c7dc307300b3e3024a66de5835ba29a6" +version = "2.1.0" +source = "git+https://github.com/kotauskas/interprocess?branch=main#b79d3636152158f7a8977f8043188e89446916c5" dependencies = [ - "cfg-if", "futures-core", - "futures-io", - "futures-util", "libc", - "rustc_version", - "to_method", + "recvmsg", "tokio", - "winapi", + "widestring", + "windows-sys 0.52.0", +] + +[[package]] +name = "iroh-quinn" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b934380145fd5d53a583d01ae9500f4807efe6b0f0fe115c7be4afa2b35db99f" +dependencies = [ + "bytes", + "iroh-quinn-proto", + "iroh-quinn-udp", + "pin-project-lite", + "rustc-hash", + "rustls", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "iroh-quinn-proto" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f2656b322c7f6cf3eb95e632d1c0f2fa546841915b0270da581f918c70c4be" +dependencies = [ + "bytes", + "rand", + "ring 0.16.20", + "rustc-hash", + "rustls", + "rustls-native-certs", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "iroh-quinn-udp" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6679979a7271c24f9dae9622c0b4a543881508aa3a7396f55dfbaaa56f01c063" +dependencies = [ + "bytes", + "libc", + "socket2", + "tracing", + "windows-sys 0.48.0", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -528,21 +593,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -550,34 +615,34 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -601,30 +666,34 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -641,18 +710,18 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl-probe" @@ -666,11 +735,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -678,15 +753,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.5", ] [[package]] @@ -700,29 +775,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -744,16 +819,16 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] [[package]] name = "quic-rpc" -version = "0.6.1" +version = "0.9.0" dependencies = [ "anyhow", "async-stream", @@ -764,13 +839,17 @@ dependencies = [ "educe", "flume", "futures", + "futures-buffered", + "futures-lite", + "futures-sink", + "futures-util", "hex", "hyper", "interprocess", + "iroh-quinn", + "iroh-quinn-udp", "pin-project", "proc-macro2", - "quinn", - "quinn-udp", "rcgen", "rustls", "serde", @@ -783,59 +862,11 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "quinn" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" -dependencies = [ - "bytes", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "thiserror", - "tokio", - "tracing", -] - -[[package]] -name = "quinn-proto" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" -dependencies = [ - "bytes", - "rand", - "ring 0.16.20", - "rustc-hash", - "rustls", - "rustls-native-certs", - "slab", - "thiserror", - "tinyvec", - "tracing", -] - -[[package]] -name = "quinn-udp" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" -dependencies = [ - "bytes", - "libc", - "socket2 0.5.5", - "tracing", - "windows-sys", -] - [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -882,13 +913,19 @@ dependencies = [ "yasna", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -908,23 +945,24 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.5" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -943,25 +981,25 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.1", + "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.8" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.5", + "ring 0.17.8", "rustls-webpki", "sct", ] @@ -980,11 +1018,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", ] [[package]] @@ -993,17 +1031,17 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.5", + "ring 0.17.8", "untrusted 0.9.0", ] [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1018,17 +1056,17 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.5", + "ring 0.17.8", "untrusted 0.9.0", ] [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -1037,9 +1075,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -1047,28 +1085,28 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -1078,8 +1116,8 @@ dependencies = [ "anyhow", "async-stream", "futures", + "iroh-quinn", "quic-rpc", - "quinn", "rcgen", "rustls", "serde", @@ -1099,9 +1137,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -1117,28 +1155,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.4.10" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" -dependencies = [ - "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1169,9 +1197,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" dependencies = [ "proc-macro2", "quote", @@ -1180,35 +1208,34 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -1219,9 +1246,9 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -1229,11 +1256,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", + "num-conv", "powerfmt", "serde", "time-core", @@ -1260,17 +1288,11 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "to_method" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" - [[package]] name = "tokio" -version = "1.33.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -1280,20 +1302,20 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -1313,9 +1335,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", @@ -1323,7 +1345,6 @@ dependencies = [ "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -1352,7 +1373,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -1367,9 +1388,9 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", @@ -1378,9 +1399,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "nu-ansi-term", "sharded-slab", @@ -1392,9 +1413,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "types" @@ -1447,9 +1468,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1457,24 +1478,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1482,33 +1503,39 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "winapi" version = "0.3.9" @@ -1537,7 +1564,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", ] [[package]] @@ -1546,13 +1582,29 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -1561,42 +1613,90 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + [[package]] name = "yasna" version = "0.5.2" diff --git a/Cargo.toml b/Cargo.toml index 0f09f6c..3dc248b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quic-rpc" -version = "0.6.1" +version = "0.9.0" edition = "2021" authors = ["RĂ¼diger Klaehn "] keywords = ["api", "protocol", "network", "rpc"] @@ -10,27 +10,31 @@ repository = "https://github.com/n0-computer/quic-rpc" description = "A streaming rpc system based on quic" # Sadly this also needs to be updated in .github/workflows/ci.yml -rust-version = "1.65" +rust-version = "1.76" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bincode = { version = "1.3.3", optional = true } bytes = { version = "1", optional = true } -flume = { version = "0.10", optional = true } -futures = "0.3" +derive_more = "0.99.17" +flume = { version = "0.11", optional = true } +futures-lite = "2.3.0" +futures-sink = "0.3.30" +futures-util = { version = "0.3.30", features = ["sink"] } hyper = { version = "0.14.16", features = ["full"], optional = true } pin-project = "1" -quinn = { version = "0.10", optional = true } -serde = { version = "1.0.103" } -tokio = { version = "1", default-features = false, features = ["macros"] } +quinn = { package = "iroh-quinn", version = "0.10", optional = true } +serde = { version = "1.0.183", features = ["derive"] } +tokio = { version = "1", default-features = false, features = ["macros", "sync"] } tokio-serde = { version = "0.8", features = ["bincode"], optional = true } tokio-util = { version = "0.7", features = ["codec", "compat"], optional = true } tracing = "0.1" -quinn-udp = { version = "0.4.0", optional = true } +quinn-udp = { package = "iroh-quinn-udp", version = "0.4.0", optional = true } interprocess = { git = "https://github.com/kotauskas/interprocess", branch = "main", version = "2", features = ["tokio"], optional = true } hex = "0.4.3" blake3 = "1.4.1" +futures = { version = "0.3.30", optional = true } [dependencies.educe] # This is an unused dependency, it is needed to make the minimal @@ -39,28 +43,33 @@ blake3 = "1.4.1" version = "0.4.20" [dev-dependencies] -anyhow = "1" +anyhow = "1.0.73" async-stream = "0.3.3" -derive_more = "0.99.17" + serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } -quinn = "0.10" +quinn = { package = "iroh-quinn", version = "0.10" } rcgen = "0.10.0" rustls = "0.21" thousands = "0.2.0" tracing-subscriber = "0.3.16" tempfile = "3.5.0" proc-macro2 = "1.0.66" +futures-buffered = "0.2.4" [features] -hyper-transport = ["flume", "hyper", "bincode", "bytes"] +hyper-transport = ["flume", "hyper", "bincode", "bytes", "tokio-serde", "tokio-util"] quinn-transport = ["flume", "quinn", "bincode", "tokio-serde", "tokio-util"] flume-transport = ["flume"] -interprocess-transport = ["quinn-udp", "interprocess", "bytes", "tokio-util", "quinn-transport"] +interprocess-transport = ["quinn-udp", "interprocess", "bytes", "tokio-util", "quinn-transport", "quinn-flume-socket", "futures"] combined-transport = [] quinn-flume-socket = ["flume", "quinn", "quinn-udp", "bytes", "tokio-util"] macros = [] -default = ["quinn-flume-socket", "interprocess-transport"] +default = ["flume-transport", "interprocess-transport"] +futures = ["dep:futures"] + +[package.metadata.docs.rs] +all-features = true [[example]] name = "errors" @@ -72,8 +81,11 @@ required-features = ["flume-transport", "macros"] [[example]] name = "store" +required-features = ["flume-transport", "macros"] + +[[example]] +name = "modularize" required-features = ["flume-transport"] [workspace] members = ["examples/split/types", "examples/split/server", "examples/split/client"] - diff --git a/examples/errors.rs b/examples/errors.rs index 29f8d9e..26eb88b 100644 --- a/examples/errors.rs +++ b/examples/errors.rs @@ -55,9 +55,9 @@ impl Fs { #[tokio::main] async fn main() -> anyhow::Result<()> { let fs = Fs; - let (server, client) = quic_rpc::transport::flume::connection(1); - let client = RpcClient::::new(client); - let server = RpcServer::::new(server); + let (server, client) = quic_rpc::transport::flume::connection::(1); + let client = RpcClient::new(client); + let server = RpcServer::new(server); let handle = tokio::task::spawn(async move { for _ in 0..1 { let (req, chan) = server.accept().await?; diff --git a/examples/macro.rs b/examples/macro.rs index 1e46013..2188402 100644 --- a/examples/macro.rs +++ b/examples/macro.rs @@ -49,7 +49,8 @@ mod store_rpc { } use async_stream::stream; -use futures::{SinkExt, Stream, StreamExt}; +use futures_lite::{Stream, StreamExt}; +use futures_util::SinkExt; use quic_rpc::client::RpcClient; use quic_rpc::server::run_server_loop; use quic_rpc::transport::flume; @@ -104,7 +105,7 @@ create_store_dispatch!(Store, dispatch_store_request); #[tokio::main] async fn main() -> anyhow::Result<()> { - let (server, client) = flume::connection::(1); + let (server, client) = flume::connection::(1); let server_handle = tokio::task::spawn(async move { let target = Store; run_server_loop(StoreService, server, target, dispatch_store_request).await diff --git a/examples/modularize.rs b/examples/modularize.rs new file mode 100644 index 0000000..d55f1bf --- /dev/null +++ b/examples/modularize.rs @@ -0,0 +1,534 @@ +//! This example shows how an RPC service can be modularized, even between different crates. +//! +//! * `app` module is the top level. it composes `iroh` plus one handler of the app itself +//! * `iroh` module composes two other services, `calc` and `clock` +//! +//! The [`calc`] and [`clock`] modules both expose a [`quic_rpc::Service`] in a regular fashion. +//! They do not `use` anything from `super` or `app` so they could live in their own crates +//! unchanged. + +use anyhow::Result; +use futures_lite::StreamExt; +use futures_util::SinkExt; +use quic_rpc::{transport::flume, RpcClient, RpcServer, ServiceConnection, ServiceEndpoint}; +use tracing::warn; + +use app::AppService; + +#[tokio::main] +async fn main() -> Result<()> { + // Spawn an inmemory connection. + // Could use quic equally (all code in this example is generic over the transport) + let (server_conn, client_conn) = flume::connection::(1); + + // spawn the server + let handler = app::Handler::default(); + tokio::task::spawn(run_server(server_conn, handler)); + + // run a client demo + client_demo(client_conn).await?; + + Ok(()) +} + +async fn run_server>(server_conn: C, handler: app::Handler) { + let server = RpcServer::new(server_conn); + loop { + match server.accept().await { + Err(err) => warn!(?err, "server accept failed"), + Ok((req, chan)) => { + let handler = handler.clone(); + tokio::task::spawn(async move { + if let Err(err) = handler.handle_rpc_request(req, chan).await { + warn!(?err, "internal rpc error"); + } + }); + } + } + } +} +pub async fn client_demo>(conn: C) -> Result<()> { + let rpc_client = RpcClient::new(conn); + let client = app::Client::new(rpc_client.clone()); + + // call a method from the top-level app client + let res = client.app_version().await?; + println!("app_version: {res:?}"); + + // call a method from the wrapped iroh.calc client + let res = client.iroh.calc.add(40, 2).await?; + println!("iroh.calc.add: {res:?}"); + + // can also do "raw" calls without using the wrapped clients + let res = rpc_client + .clone() + .map::() + .map::() + .rpc(calc::AddRequest(19, 4)) + .await?; + println!("iroh.calc.add (raw): {res:?}"); + + let (mut sink, res) = rpc_client + .map::() + .map::() + .client_streaming(calc::SumRequest) + .await?; + sink.send(calc::SumUpdate(4)).await.unwrap(); + sink.send(calc::SumUpdate(8)).await.unwrap(); + sink.send(calc::SumUpdate(30)).await.unwrap(); + drop(sink); + let res = res.await?; + println!("iroh.calc.sum (raw): {res:?}"); + + // call a server-streaming method from the wrapped iroh.clock client + let mut stream = client.iroh.clock.tick().await?; + while let Some(tick) = stream.try_next().await? { + println!("iroh.clock.tick: {tick}"); + } + Ok(()) +} + +mod app { + //! This is the app-specific code. + //! + //! It composes all of `iroh` (which internally composes two other modules) and adds an + //! application specific RPC. + //! + //! It could also easily compose services from other crates or internal modules. + + use anyhow::Result; + use derive_more::{From, TryInto}; + use quic_rpc::{ + message::RpcMsg, server::RpcChannel, RpcClient, Service, ServiceConnection, ServiceEndpoint, + }; + use serde::{Deserialize, Serialize}; + + use super::iroh; + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Request { + Iroh(iroh::Request), + AppVersion(AppVersionRequest), + } + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Response { + Iroh(iroh::Response), + AppVersion(AppVersionResponse), + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct AppVersionRequest; + + impl RpcMsg for AppVersionRequest { + type Response = AppVersionResponse; + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct AppVersionResponse(pub String); + + #[derive(Copy, Clone, Debug)] + pub struct AppService; + impl Service for AppService { + type Req = Request; + type Res = Response; + } + + #[derive(Clone)] + pub struct Handler { + iroh: iroh::Handler, + app_version: String, + } + + impl Default for Handler { + fn default() -> Self { + Self { + iroh: iroh::Handler::default(), + app_version: "v0.1-alpha".to_string(), + } + } + } + + impl Handler { + pub async fn handle_rpc_request>( + self, + req: Request, + chan: RpcChannel, + ) -> Result<()> { + match req { + Request::Iroh(req) => self.iroh.handle_rpc_request(req, chan.map()).await?, + Request::AppVersion(req) => chan.rpc(req, self, Self::on_version).await?, + }; + Ok(()) + } + + pub async fn on_version(self, _req: AppVersionRequest) -> AppVersionResponse { + AppVersionResponse(self.app_version.clone()) + } + } + + #[derive(Debug, Clone)] + pub struct Client> { + pub iroh: iroh::Client, + client: RpcClient, + } + + impl Client + where + S: Service, + C: ServiceConnection, + { + pub fn new(client: RpcClient) -> Self { + Self { + iroh: iroh::Client::new(client.clone().map()), + client, + } + } + + pub async fn app_version(&self) -> Result { + let res = self.client.rpc(AppVersionRequest).await?; + Ok(res.0) + } + } +} + +mod iroh { + //! This module composes two sub-services. Think `iroh` crate which exposes services and + //! clients for iroh-bytes and iroh-gossip or so. + //! It uses only the `calc` and `clock` modules and nothing else. + + use anyhow::Result; + use derive_more::{From, TryInto}; + use quic_rpc::{server::RpcChannel, RpcClient, Service, ServiceConnection, ServiceEndpoint}; + use serde::{Deserialize, Serialize}; + + use super::{calc, clock}; + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Request { + Calc(calc::Request), + Clock(clock::Request), + } + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Response { + Calc(calc::Response), + Clock(clock::Response), + } + + #[derive(Copy, Clone, Debug)] + pub struct IrohService; + impl Service for IrohService { + type Req = Request; + type Res = Response; + } + + #[derive(Clone, Default)] + pub struct Handler { + calc: calc::Handler, + clock: clock::Handler, + } + + impl Handler { + pub async fn handle_rpc_request( + self, + req: Request, + chan: RpcChannel, + ) -> Result<()> + where + S: Service, + E: ServiceEndpoint, + { + match req { + Request::Calc(req) => self.calc.handle_rpc_request(req, chan.map()).await?, + Request::Clock(req) => self.clock.handle_rpc_request(req, chan.map()).await?, + } + Ok(()) + } + } + + #[derive(Debug, Clone)] + pub struct Client { + pub calc: calc::Client, + pub clock: clock::Client, + } + + impl Client + where + C: ServiceConnection, + S: Service, + { + pub fn new(client: RpcClient) -> Self { + Self { + calc: calc::Client::new(client.clone().map()), + clock: clock::Client::new(client.clone().map()), + } + } + } +} + +mod calc { + //! This is a library providing a service, and a client. E.g. iroh-bytes or iroh-hypermerge. + //! It does not use any `super` imports, it is completely decoupled. + + use anyhow::{bail, Result}; + use derive_more::{From, TryInto}; + use futures_lite::{Stream, StreamExt}; + use quic_rpc::{ + message::{ClientStreaming, ClientStreamingMsg, Msg, RpcMsg}, + server::RpcChannel, + RpcClient, Service, ServiceConnection, ServiceEndpoint, + }; + use serde::{Deserialize, Serialize}; + use std::fmt::Debug; + + #[derive(Debug, Serialize, Deserialize)] + pub struct AddRequest(pub i64, pub i64); + + impl RpcMsg for AddRequest { + type Response = AddResponse; + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct AddResponse(pub i64); + + #[derive(Debug, Serialize, Deserialize)] + pub struct SumRequest; + + #[derive(Debug, Serialize, Deserialize)] + pub struct SumUpdate(pub i64); + + impl Msg for SumRequest { + type Pattern = ClientStreaming; + } + + impl ClientStreamingMsg for SumRequest { + type Update = SumUpdate; + type Response = SumResponse; + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct SumResponse(pub i64); + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Request { + Add(AddRequest), + Sum(SumRequest), + SumUpdate(SumUpdate), + } + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Response { + Add(AddResponse), + Sum(SumResponse), + } + + #[derive(Copy, Clone, Debug)] + pub struct CalcService; + impl Service for CalcService { + type Req = Request; + type Res = Response; + } + + #[derive(Clone, Default)] + pub struct Handler; + + impl Handler { + pub async fn handle_rpc_request( + self, + req: Request, + chan: RpcChannel, + ) -> Result<()> + where + S: Service, + E: ServiceEndpoint, + { + match req { + Request::Add(req) => chan.rpc(req, self, Self::on_add).await?, + Request::Sum(req) => chan.client_streaming(req, self, Self::on_sum).await?, + Request::SumUpdate(_) => bail!("Unexpected update message at start of request"), + } + Ok(()) + } + + pub async fn on_add(self, req: AddRequest) -> AddResponse { + AddResponse(req.0 + req.1) + } + + pub async fn on_sum( + self, + _req: SumRequest, + updates: impl Stream, + ) -> SumResponse { + let mut sum = 0i64; + tokio::pin!(updates); + while let Some(SumUpdate(n)) = updates.next().await { + sum += n; + } + SumResponse(sum) + } + } + + #[derive(Debug, Clone)] + pub struct Client { + client: RpcClient, + } + + impl Client + where + C: ServiceConnection, + S: Service, + { + pub fn new(client: RpcClient) -> Self { + Self { client } + } + pub async fn add(&self, a: i64, b: i64) -> anyhow::Result { + let res = self.client.rpc(AddRequest(a, b)).await?; + Ok(res.0) + } + } +} + +mod clock { + //! This is a library providing a service, and a client. E.g. iroh-bytes or iroh-hypermerge. + //! It does not use any `super` imports, it is completely decoupled. + + use anyhow::Result; + use derive_more::{From, TryInto}; + use futures_lite::{stream::Boxed as BoxStream, Stream, StreamExt}; + use futures_util::TryStreamExt; + use quic_rpc::{ + message::{Msg, ServerStreaming, ServerStreamingMsg}, + server::RpcChannel, + RpcClient, Service, ServiceConnection, ServiceEndpoint, + }; + use serde::{Deserialize, Serialize}; + use std::{ + fmt::Debug, + sync::{Arc, RwLock}, + time::Duration, + }; + use tokio::sync::Notify; + + #[derive(Debug, Serialize, Deserialize)] + pub struct TickRequest; + + impl Msg for TickRequest { + type Pattern = ServerStreaming; + } + + impl ServerStreamingMsg for TickRequest { + type Response = TickResponse; + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct TickResponse { + tick: usize, + } + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Request { + Tick(TickRequest), + } + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Response { + Tick(TickResponse), + } + + #[derive(Copy, Clone, Debug)] + pub struct ClockService; + impl Service for ClockService { + type Req = Request; + type Res = Response; + } + + #[derive(Clone)] + pub struct Handler { + tick: Arc>, + ontick: Arc, + } + + impl Default for Handler { + fn default() -> Self { + Self::new(Duration::from_secs(1)) + } + } + + impl Handler { + pub fn new(tick_duration: Duration) -> Self { + let h = Handler { + tick: Default::default(), + ontick: Default::default(), + }; + let h2 = h.clone(); + tokio::task::spawn(async move { + loop { + tokio::time::sleep(tick_duration).await; + *h2.tick.write().unwrap() += 1; + h2.ontick.notify_waiters(); + } + }); + h + } + + pub async fn handle_rpc_request( + self, + req: Request, + chan: RpcChannel, + ) -> Result<()> + where + S: Service, + E: ServiceEndpoint, + { + match req { + Request::Tick(req) => chan.server_streaming(req, self, Self::on_tick).await?, + } + Ok(()) + } + + pub fn on_tick( + self, + req: TickRequest, + ) -> impl Stream + Send + 'static { + let (tx, rx) = flume::bounded(2); + tokio::task::spawn(async move { + if let Err(err) = self.on_tick0(req, tx).await { + tracing::warn!(?err, "on_tick RPC handler failed"); + } + }); + rx.into_stream() + } + + pub async fn on_tick0( + self, + _req: TickRequest, + tx: flume::Sender, + ) -> Result<()> { + loop { + let tick = *self.tick.read().unwrap(); + tx.send_async(TickResponse { tick }).await?; + self.ontick.notified().await; + } + } + } + + #[derive(Debug, Clone)] + pub struct Client { + client: RpcClient, + } + + impl Client + where + C: ServiceConnection, + S: Service, + { + pub fn new(client: RpcClient) -> Self { + Self { client } + } + pub async fn tick(&self) -> Result>> { + let res = self.client.server_streaming(TickRequest).await?; + Ok(res.map_ok(|r| r.tick).map_err(anyhow::Error::from).boxed()) + } + } +} diff --git a/examples/split/client/Cargo.toml b/examples/split/client/Cargo.toml index edcd431..244454d 100644 --- a/examples/split/client/Cargo.toml +++ b/examples/split/client/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" anyhow = "1.0.14" futures = "0.3.26" quic-rpc = { path = "../../..", features = ["quinn-transport", "macros"] } -quinn = "0.10" +quinn = { package = "iroh-quinn", version = "0.10" } rustls = { version = "0.21", features = ["dangerous_configuration"] } tracing-subscriber = "0.3.16" serde = { version = "1", features = ["derive"] } diff --git a/examples/split/client/src/main.rs b/examples/split/client/src/main.rs index 16738fd..fc4df83 100644 --- a/examples/split/client/src/main.rs +++ b/examples/split/client/src/main.rs @@ -1,5 +1,7 @@ +#![allow(unknown_lints, non_local_definitions)] use futures::sink::SinkExt; use futures::stream::StreamExt; +use quic_rpc::transport::quinn::QuinnConnection; use quic_rpc::RpcClient; use quinn::{ClientConfig, Endpoint}; use std::io; @@ -14,12 +16,9 @@ async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let server_addr: SocketAddr = "127.0.0.1:12345".parse()?; let endpoint = make_insecure_client_endpoint("0.0.0.0:0".parse()?)?; - let client = quic_rpc::transport::quinn::QuinnConnection::new( - endpoint, - server_addr, - "localhost".to_string(), - ); - let client = RpcClient::::new(client); + let client = + QuinnConnection::::new(endpoint, server_addr, "localhost".to_string()); + let client = RpcClient::new(client); // let mut client = ComputeClient(client); // a rpc call diff --git a/examples/split/server/Cargo.toml b/examples/split/server/Cargo.toml index 7dbdb51..71d9576 100644 --- a/examples/split/server/Cargo.toml +++ b/examples/split/server/Cargo.toml @@ -11,7 +11,7 @@ async-stream = "0.3.3" futures = "0.3.26" tracing-subscriber = "0.3.16" quic-rpc = { path = "../../..", features = ["quinn-transport", "macros"] } -quinn = "0.10" +quinn = { package = "iroh-quinn", version = "0.10" } rcgen = "0.10.0" rustls = "0.21" serde = { version = "1", features = ["derive"] } diff --git a/examples/split/server/src/main.rs b/examples/split/server/src/main.rs index e064395..90e1823 100644 --- a/examples/split/server/src/main.rs +++ b/examples/split/server/src/main.rs @@ -1,6 +1,7 @@ use async_stream::stream; use futures::stream::{Stream, StreamExt}; use quic_rpc::server::run_server_loop; +use quic_rpc::transport::quinn::QuinnServerEndpoint; use quinn::{Endpoint, ServerConfig}; use std::net::SocketAddr; use std::sync::Arc; @@ -61,7 +62,7 @@ async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let server_addr: SocketAddr = "127.0.0.1:12345".parse()?; let (server, _server_certs) = make_server_endpoint(server_addr)?; - let channel = quic_rpc::transport::quinn::QuinnServerEndpoint::new(server)?; + let channel = QuinnServerEndpoint::::new(server)?; let target = Compute; run_server_loop( ComputeService, diff --git a/examples/split/types/Cargo.toml b/examples/split/types/Cargo.toml index fd976ab..ef680c0 100644 --- a/examples/split/types/Cargo.toml +++ b/examples/split/types/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -derive_more = "0.99.17" futures = "0.3.26" quic-rpc = { path = "../../..", features = ["macros"] } serde = { version = "1", features = ["derive"] } +derive_more = "0.99.17" diff --git a/examples/store.rs b/examples/store.rs index dbbb182..c5cff9b 100644 --- a/examples/store.rs +++ b/examples/store.rs @@ -1,11 +1,12 @@ #![allow(clippy::enum_variant_names)] use async_stream::stream; use derive_more::{From, TryInto}; -use futures::{SinkExt, Stream, StreamExt}; +use futures_lite::{Stream, StreamExt}; +use futures_util::SinkExt; use quic_rpc::{ server::RpcServerError, transport::{flume, Connection, ServerEndpoint}, - Service, ServiceEndpoint, *, + *, }; use serde::{Deserialize, Serialize}; use std::{fmt::Debug, result}; @@ -183,7 +184,7 @@ async fn main() -> anyhow::Result<()> { } } - let (server, client) = flume::connection::(1); + let (server, client) = flume::connection::(1); let client = RpcClient::::new(client); let server = RpcServer::::new(server); let server_handle = tokio::task::spawn(server_future(server)); @@ -230,7 +231,13 @@ async fn main() -> anyhow::Result<()> { } async fn _main_unsugared() -> anyhow::Result<()> { - let (server, client) = flume::connection::(1); + #[derive(Clone, Debug)] + struct Service; + impl crate::Service for Service { + type Req = u64; + type Res = String; + } + let (server, client) = flume::connection::(1); let to_string_service = tokio::spawn(async move { let (mut send, mut recv) = server.accept_bi().await?; while let Some(item) = recv.next().await { diff --git a/src/client.rs b/src/client.rs index a5fb86f..537c747 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,38 +2,39 @@ //! //! The main entry point is [RpcClient]. use crate::{ - message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, - transport::ConnectionErrors, + map::{ChainedMapper, MapService, Mapper}, Service, ServiceConnection, }; -use futures::{ - future::BoxFuture, stream::BoxStream, FutureExt, Sink, SinkExt, Stream, StreamExt, TryFutureExt, -}; +use futures_lite::Stream; +use futures_sink::Sink; + use pin_project::pin_project; use std::{ - error, - fmt::{self, Debug}, + fmt::Debug, marker::PhantomData, pin::Pin, - result, + sync::Arc, task::{Context, Poll}, }; +/// Sync version of `future::stream::BoxStream`. +pub type BoxStreamSync<'a, T> = Pin + Send + Sync + 'a>>; + /// A client for a specific service /// /// This is a wrapper around a [ServiceConnection] that serves as the entry point /// for the client DSL. `S` is the service type, `C` is the substream source. #[derive(Debug)] -pub struct RpcClient { - source: C, - p: PhantomData, +pub struct RpcClient { + pub(crate) source: C, + pub(crate) map: Arc>, } -impl Clone for RpcClient { +impl Clone for RpcClient { fn clone(&self) -> Self { Self { source: self.source.clone(), - p: PhantomData, + map: Arc::clone(&self.map), } } } @@ -42,33 +43,49 @@ impl Clone for RpcClient { /// that support it, [crate::message::ClientStreaming] and [crate::message::BidiStreaming]. #[pin_project] #[derive(Debug)] -pub struct UpdateSink, T: Into>( - #[pin] C::SendSink, - PhantomData, -); - -impl, T: Into> Sink for UpdateSink { +pub struct UpdateSink( + #[pin] pub C::SendSink, + pub PhantomData, + pub Arc>, +) +where + S: Service, + SInner: Service, + C: ServiceConnection, + T: Into; + +impl Sink for UpdateSink +where + S: Service, + SInner: Service, + C: ServiceConnection, + T: Into, +{ type Error = C::SendError; fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_ready_unpin(cx) + self.project().0.poll_ready(cx) } fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - let req: S::Req = item.into(); - self.project().0.start_send_unpin(req) + let req = self.2.req_into_outer(item.into()); + self.project().0.start_send(req) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_flush_unpin(cx) + self.project().0.poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_close_unpin(cx) + self.project().0.poll_close(cx) } } -impl> RpcClient { +impl RpcClient +where + S: Service, + C: ServiceConnection, +{ /// Create a new rpc client for a specific [Service] given a compatible /// [ServiceConnection]. /// @@ -76,267 +93,59 @@ impl> RpcClient { pub fn new(source: C) -> Self { Self { source, - p: PhantomData, + map: Arc::new(Mapper::new()), } } +} +impl RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ /// Get the underlying connection pub fn into_inner(self) -> C { self.source } - /// RPC call to the server, single request, single response - pub async fn rpc(&self, msg: M) -> result::Result> - where - M: RpcMsg, - { - let msg = msg.into(); - let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; - send.send(msg).await.map_err(RpcClientError::::Send)?; - let res = recv - .next() - .await - .ok_or(RpcClientError::::EarlyClose)? - .map_err(RpcClientError::::RecvError)?; - // keep send alive until we have the answer - drop(send); - M::Response::try_from(res).map_err(|_| RpcClientError::DowncastError) - } - - /// Bidi call to the server, request opens a stream, response is a stream - pub async fn server_streaming( - &self, - msg: M, - ) -> result::Result< - BoxStream<'static, result::Result>>, - StreamingResponseError, - > - where - M: ServerStreamingMsg, - { - let msg = msg.into(); - let (mut send, recv) = self - .source - .open_bi() - .await - .map_err(StreamingResponseError::Open)?; - send.send(msg) - .map_err(StreamingResponseError::::Send) - .await?; - let recv = recv.map(move |x| match x { - Ok(x) => { - M::Response::try_from(x).map_err(|_| StreamingResponseItemError::DowncastError) - } - Err(e) => Err(StreamingResponseItemError::RecvError(e)), - }); - // keep send alive so the request on the server side does not get cancelled - let recv = DeferDrop(recv, send).boxed(); - Ok(recv) - } - - /// Call to the server that allows the client to stream, single response - pub async fn client_streaming( - &self, - msg: M, - ) -> result::Result< - ( - UpdateSink, - BoxFuture<'static, result::Result>>, - ), - ClientStreamingError, - > + /// Map this channel's service into an inner service. + /// + /// This method is available if the required bounds are upheld: + /// SNext::Req: Into + TryFrom, + /// SNext::Res: Into + TryFrom, + /// + /// Where SNext is the new service to map to and SInner is the current inner service. + /// + /// This method can be chained infintely. + pub fn map(self) -> RpcClient where - M: ClientStreamingMsg, + SNext: Service, + SNext::Req: Into + TryFrom, + SNext::Res: Into + TryFrom, { - let msg = msg.into(); - let (mut send, mut recv) = self - .source - .open_bi() - .await - .map_err(ClientStreamingError::Open)?; - send.send(msg).map_err(ClientStreamingError::Send).await?; - let send = UpdateSink::(send, PhantomData); - let recv = async move { - let item = recv - .next() - .await - .ok_or(ClientStreamingItemError::EarlyClose)?; - - match item { - Ok(x) => { - M::Response::try_from(x).map_err(|_| ClientStreamingItemError::DowncastError) - } - Err(e) => Err(ClientStreamingItemError::RecvError(e)), - } + let map = ChainedMapper::new(self.map); + RpcClient { + source: self.source, + map: Arc::new(map), } - .boxed(); - Ok((send, recv)) - } - - /// Bidi call to the server, request opens a stream, response is a stream - pub async fn bidi( - &self, - msg: M, - ) -> result::Result< - ( - UpdateSink, - BoxStream<'static, result::Result>>, - ), - BidiError, - > - where - M: BidiStreamingMsg, - { - let msg = msg.into(); - let (mut send, recv) = self.source.open_bi().await.map_err(BidiError::Open)?; - send.send(msg).await.map_err(BidiError::::Send)?; - let send = UpdateSink(send, PhantomData); - let recv = recv - .map(|x| match x { - Ok(x) => M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError), - Err(e) => Err(BidiItemError::RecvError(e)), - }) - .boxed(); - Ok((send, recv)) } } -impl> AsRef for RpcClient { +impl AsRef for RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ fn as_ref(&self) -> &C { &self.source } } -/// Client error. All client DSL methods return a `Result` with this error type. -#[derive(Debug)] -pub enum RpcClientError { - /// Unable to open a substream at all - Open(C::OpenError), - /// Unable to send the request to the server - Send(C::SendError), - /// Server closed the stream before sending a response - EarlyClose, - /// Unable to receive the response from the server - RecvError(C::RecvError), - /// Unexpected response from the server - DowncastError, -} - -impl fmt::Display for RpcClientError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for RpcClientError {} - -/// Server error when accepting a bidi request -#[derive(Debug)] -pub enum BidiError { - /// Unable to open a substream at all - Open(C::OpenError), - /// Unable to send the request to the server - Send(C::SendError), -} - -impl fmt::Display for BidiError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for BidiError {} - -/// Server error when receiving an item for a bidi request -#[derive(Debug)] -pub enum BidiItemError { - /// Unable to receive the response from the server - RecvError(C::RecvError), - /// Unexpected response from the server - DowncastError, -} - -impl fmt::Display for BidiItemError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for BidiItemError {} - -/// Server error when accepting a client streaming request -#[derive(Debug)] -pub enum ClientStreamingError { - /// Unable to open a substream at all - Open(C::OpenError), - /// Unable to send the request to the server - Send(C::SendError), -} - -impl fmt::Display for ClientStreamingError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for ClientStreamingError {} - -/// Server error when receiving an item for a client streaming request -#[derive(Debug)] -pub enum ClientStreamingItemError { - /// Connection was closed before receiving the first message - EarlyClose, - /// Unable to receive the response from the server - RecvError(C::RecvError), - /// Unexpected response from the server - DowncastError, -} - -impl fmt::Display for ClientStreamingItemError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for ClientStreamingItemError {} - -/// Server error when accepting a server streaming request -#[derive(Debug)] -pub enum StreamingResponseError { - /// Unable to open a substream at all - Open(C::OpenError), - /// Unable to send the request to the server - Send(C::SendError), -} - -impl fmt::Display for StreamingResponseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for StreamingResponseError {} - -/// Client error when handling responses from a server streaming request -#[derive(Debug)] -pub enum StreamingResponseItemError { - /// Unable to receive the response from the server - RecvError(S::RecvError), - /// Unexpected response from the server - DowncastError, -} - -impl fmt::Display for StreamingResponseItemError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for StreamingResponseItemError {} - /// Wrap a stream with an additional item that is kept alive until the stream is dropped #[pin_project] -struct DeferDrop(#[pin] S, X); +pub(crate) struct DeferDrop(#[pin] pub S, pub X); impl Stream for DeferDrop { type Item = S::Item; diff --git a/src/lib.rs b/src/lib.rs index 4195b3c..115feb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,7 @@ //! } //! //! // create a transport channel, here a memory channel for testing -//! let (server, client) = quic_rpc::transport::flume::connection::(1); +//! let (server, client) = quic_rpc::transport::flume::connection::(1); //! //! // client side //! // create the rpc client given the channel and the service type @@ -102,6 +102,9 @@ pub use client::RpcClient; pub use server::RpcServer; #[cfg(feature = "macros")] mod macros; +mod map; + +pub mod pattern; /// Requirements for a RPC message /// diff --git a/src/map.rs b/src/map.rs new file mode 100644 index 0000000..597ca93 --- /dev/null +++ b/src/map.rs @@ -0,0 +1,145 @@ +use std::{marker::PhantomData, sync::Arc}; + +use crate::Service; + +/// Convert requests and responses between an outer and an inner service. +/// +/// An "outer" service has request and response enums which wrap the requests and responses of an +/// "inner" service. This trait is implemented on the [`Mapper`] and [`ChainedMapper`] structs +/// to convert the requests and responses between the outer and inner services. +pub trait MapService: + std::fmt::Debug + Send + Sync + 'static +{ + /// Convert an inner request into the outer request. + fn req_into_outer(&self, req: SInner::Req) -> SOuter::Req; + + /// Convert an inner response into the outer response. + fn res_into_outer(&self, res: SInner::Res) -> SOuter::Res; + + /// Try to convert the outer request into the inner request. + /// + /// Returns an error if the request is not of the variant of the inner service. + fn req_try_into_inner(&self, req: SOuter::Req) -> Result; + + /// Try to convert the outer response into the inner request. + /// + /// Returns an error if the response is not of the variant of the inner service. + fn res_try_into_inner(&self, res: SOuter::Res) -> Result; +} + +/// Zero-sized struct to map between two services. +#[derive(Debug)] +pub struct Mapper(PhantomData, PhantomData); + +impl Mapper +where + SOuter: Service, + SInner: Service, + SInner::Req: Into + TryFrom, + SInner::Res: Into + TryFrom, +{ + /// Create a new mapper between `SOuter` and `SInner` services. + /// + /// This method is availalbe if the required bounds to convert between the outer and inner + /// request and response enums are met: + /// `SInner::Req: Into + TryFrom` + /// `SInner::Res: Into + TryFrom` + pub fn new() -> Self { + Self(PhantomData, PhantomData) + } +} + +impl MapService for Mapper +where + SOuter: Service, + SInner: Service, + SInner::Req: Into + TryFrom, + SInner::Res: Into + TryFrom, +{ + fn req_into_outer(&self, req: SInner::Req) -> SOuter::Req { + req.into() + } + + fn res_into_outer(&self, res: SInner::Res) -> SOuter::Res { + res.into() + } + + fn req_try_into_inner(&self, req: SOuter::Req) -> Result { + req.try_into().map_err(|_| ()) + } + + fn res_try_into_inner(&self, res: SOuter::Res) -> Result { + res.try_into().map_err(|_| ()) + } +} + +/// Map between an outer and an inner service with any number of intermediate services. +/// +/// This uses an `Arc` to contain an unlimited chain of [`Mapper`]s. +#[derive(Debug)] +pub struct ChainedMapper +where + SOuter: Service, + SMid: Service, + SInner: Service, + SInner::Req: Into + TryFrom, +{ + map1: Arc>, + map2: Mapper, +} + +impl ChainedMapper +where + SOuter: Service, + SMid: Service, + SInner: Service, + SInner::Req: Into + TryFrom, + SInner::Res: Into + TryFrom, +{ + /// Create a new [`ChainedMapper`] by appending a service `SInner` to the existing `dyn + /// MapService`. + /// + /// Usage example: + /// ```ignore + /// // S1 is a Service and impls the Into and TryFrom traits to map to S2 + /// // S2 is a Service and impls the Into and TryFrom traits to map to S3 + /// // S3 is also a Service + /// + /// let mapper: Mapper = Mapper::new(); + /// let mapper: Arc> = Arc::new(mapper); + /// let chained_mapper: ChainedMapper = ChainedMapper::new(mapper); + /// ``` + pub fn new(map1: Arc>) -> Self { + Self { + map1, + map2: Mapper::new(), + } + } +} + +impl MapService for ChainedMapper +where + SOuter: Service, + SMid: Service, + SInner: Service, + SInner::Req: Into + TryFrom, + SInner::Res: Into + TryFrom, +{ + fn req_into_outer(&self, req: SInner::Req) -> SOuter::Req { + let req = self.map2.req_into_outer(req); + self.map1.req_into_outer(req) + } + fn res_into_outer(&self, res: SInner::Res) -> SOuter::Res { + let res = self.map2.res_into_outer(res); + self.map1.res_into_outer(res) + } + fn req_try_into_inner(&self, req: SOuter::Req) -> Result { + let req = self.map1.req_try_into_inner(req)?; + self.map2.req_try_into_inner(req) + } + + fn res_try_into_inner(&self, res: SOuter::Res) -> Result { + let res = self.map1.res_try_into_inner(res)?; + self.map2.res_try_into_inner(res) + } +} diff --git a/src/message.rs b/src/message.rs index 83b6bee..d7c9caf 100644 --- a/src/message.rs +++ b/src/message.rs @@ -4,6 +4,11 @@ use crate::Service; use std::fmt::Debug; +pub use crate::pattern::bidi_streaming::{BidiStreaming, BidiStreamingMsg}; +pub use crate::pattern::client_streaming::{ClientStreaming, ClientStreamingMsg}; +pub use crate::pattern::rpc::{Rpc, RpcMsg}; +pub use crate::pattern::server_streaming::{ServerStreaming, ServerStreamingMsg}; + /// Declares the interaction pattern for a message and a service. /// /// For each server and each message, only one interaction pattern can be defined. @@ -12,59 +17,6 @@ pub trait Msg: Into + TryFrom + Send + 'static { type Pattern: InteractionPattern; } -/// Defines the response type for a rpc message. -/// -/// Since this is the most common interaction pattern, this also implements [Msg] for you -/// automatically, with the interaction pattern set to [Rpc]. This is to reduce boilerplate -/// when defining rpc messages. -pub trait RpcMsg: Msg { - /// The type for the response - /// - /// For requests that can produce errors, this can be set to [Result](std::result::Result). - type Response: Into + TryFrom + Send + 'static; -} - -/// We can only do this for one trait, so we do it for RpcMsg since it is the most common -impl, S: Service> Msg for T { - type Pattern = Rpc; -} - -/// Defines update type and response type for a client streaming message. -pub trait ClientStreamingMsg: Msg { - /// The type for request updates - /// - /// For a request that does not support updates, this can be safely set to any type, including - /// the message type itself. Any update for such a request will result in an error. - type Update: Into + TryFrom + Send + 'static; - - /// The type for the response - /// - /// For requests that can produce errors, this can be set to [Result](std::result::Result). - type Response: Into + TryFrom + Send + 'static; -} - -/// Defines response type for a server streaming message. -pub trait ServerStreamingMsg: Msg { - /// The type for the response - /// - /// For requests that can produce errors, this can be set to [Result](std::result::Result). - type Response: Into + TryFrom + Send + 'static; -} - -/// Defines update type and response type for a bidi streaming message. -pub trait BidiStreamingMsg: Msg { - /// The type for request updates - /// - /// For a request that does not support updates, this can be safely set to any type, including - /// the message type itself. Any update for such a request will result in an error. - type Update: Into + TryFrom + Send + 'static; - - /// The type for the response - /// - /// For requests that can produce errors, this can be set to [Result](std::result::Result). - type Response: Into + TryFrom + Send + 'static; -} - /// Trait defining interaction pattern. /// /// Currently there are 4 patterns: @@ -75,33 +27,3 @@ pub trait BidiStreamingMsg: Msg { /// /// You could define your own interaction patterns such as OneWay. pub trait InteractionPattern: Debug + Clone + Send + Sync + 'static {} - -/// Rpc interaction pattern -/// -/// There is only one request and one response. -#[derive(Debug, Clone, Copy)] -pub struct Rpc; -impl InteractionPattern for Rpc {} - -/// Client streaming interaction pattern -/// -/// After the initial request, the client can send updates, but there is only -/// one response. -#[derive(Debug, Clone, Copy)] -pub struct ClientStreaming; -impl InteractionPattern for ClientStreaming {} - -/// Server streaming interaction pattern -/// -/// After the initial request, the server can send a stream of responses. -#[derive(Debug, Clone, Copy)] -pub struct ServerStreaming; -impl InteractionPattern for ServerStreaming {} - -/// Bidirectional streaming interaction pattern -/// -/// After the initial request, the client can send updates and the server can -/// send responses. -#[derive(Debug, Clone, Copy)] -pub struct BidiStreaming; -impl InteractionPattern for BidiStreaming {} diff --git a/src/pattern/bidi_streaming.rs b/src/pattern/bidi_streaming.rs new file mode 100644 index 0000000..cb3e12f --- /dev/null +++ b/src/pattern/bidi_streaming.rs @@ -0,0 +1,156 @@ +//! Bidirectional stream interaction pattern. + +use futures_lite::{Stream, StreamExt}; +use futures_util::{FutureExt, SinkExt}; + +use crate::{ + client::{BoxStreamSync, UpdateSink}, + message::{InteractionPattern, Msg}, + server::{race2, RpcChannel, RpcServerError, UpdateStream}, + transport::ConnectionErrors, + RpcClient, Service, ServiceConnection, ServiceEndpoint, +}; + +use std::{ + error, + fmt::{self, Debug}, + marker::PhantomData, + result, + sync::Arc, +}; + +/// Bidirectional streaming interaction pattern +/// +/// After the initial request, the client can send updates and the server can +/// send responses. +#[derive(Debug, Clone, Copy)] +pub struct BidiStreaming; +impl InteractionPattern for BidiStreaming {} + +/// Defines update type and response type for a bidi streaming message. +pub trait BidiStreamingMsg: Msg { + /// The type for request updates + /// + /// For a request that does not support updates, this can be safely set to any type, including + /// the message type itself. Any update for such a request will result in an error. + type Update: Into + TryFrom + Send + 'static; + + /// The type for the response + /// + /// For requests that can produce errors, this can be set to [Result](std::result::Result). + type Response: Into + TryFrom + Send + 'static; +} + +/// Server error when accepting a bidi request +#[derive(Debug)] +pub enum Error { + /// Unable to open a substream at all + Open(C::OpenError), + /// Unable to send the request to the server + Send(C::SendError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for Error {} + +/// Server error when receiving an item for a bidi request +#[derive(Debug)] +pub enum ItemError { + /// Unable to receive the response from the server + RecvError(C::RecvError), + /// Unexpected response from the server + DowncastError, +} + +impl fmt::Display for ItemError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for ItemError {} + +impl RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ + /// Bidi call to the server, request opens a stream, response is a stream + pub async fn bidi( + &self, + msg: M, + ) -> result::Result< + ( + UpdateSink, + BoxStreamSync<'static, result::Result>>, + ), + Error, + > + where + M: BidiStreamingMsg, + { + let msg = self.map.req_into_outer(msg.into()); + let (mut send, recv) = self.source.open_bi().await.map_err(Error::Open)?; + send.send(msg).await.map_err(Error::::Send)?; + let send = UpdateSink(send, PhantomData, Arc::clone(&self.map)); + let map = Arc::clone(&self.map); + let recv = Box::pin(recv.map(move |x| match x { + Ok(x) => { + let x = map + .res_try_into_inner(x) + .map_err(|_| ItemError::DowncastError)?; + M::Response::try_from(x).map_err(|_| ItemError::DowncastError) + } + Err(e) => Err(ItemError::RecvError(e)), + })); + Ok((send, recv)) + } +} + +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, + SInner: Service, +{ + /// handle the message M using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + pub async fn bidi_streaming( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: BidiStreamingMsg, + F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, + Str: Stream + Send + 'static, + T: Send + 'static, + { + let Self { mut send, recv, .. } = self; + // downcast the updates + let (updates, read_error) = UpdateStream::new(recv, Arc::clone(&self.map)); + // get the response + let responses = f(target, req, updates); + race2(read_error.map(Err), async move { + tokio::pin!(responses); + while let Some(response) = responses.next().await { + // turn into a S::Res so we can send it + let response = self.map.res_into_outer(response.into()); + // send it and return the error if any + send.send(response) + .await + .map_err(RpcServerError::SendError)?; + } + Ok(()) + }) + .await + } +} diff --git a/src/pattern/client_streaming.rs b/src/pattern/client_streaming.rs new file mode 100644 index 0000000..ba26687 --- /dev/null +++ b/src/pattern/client_streaming.rs @@ -0,0 +1,156 @@ +//! Client streaming interaction pattern. + +use futures_lite::{future::Boxed, Future, StreamExt}; +use futures_util::{FutureExt, SinkExt, TryFutureExt}; + +use crate::{ + client::UpdateSink, + message::{InteractionPattern, Msg}, + server::{race2, RpcChannel, RpcServerError, UpdateStream}, + transport::ConnectionErrors, + RpcClient, Service, ServiceConnection, ServiceEndpoint, +}; + +use std::{ + error, + fmt::{self, Debug}, + marker::PhantomData, + result, + sync::Arc, +}; + +/// Client streaming interaction pattern +/// +/// After the initial request, the client can send updates, but there is only +/// one response. +#[derive(Debug, Clone, Copy)] +pub struct ClientStreaming; +impl InteractionPattern for ClientStreaming {} + +/// Defines update type and response type for a client streaming message. +pub trait ClientStreamingMsg: Msg { + /// The type for request updates + /// + /// For a request that does not support updates, this can be safely set to any type, including + /// the message type itself. Any update for such a request will result in an error. + type Update: Into + TryFrom + Send + 'static; + + /// The type for the response + /// + /// For requests that can produce errors, this can be set to [Result](std::result::Result). + type Response: Into + TryFrom + Send + 'static; +} + +/// Server error when accepting a client streaming request +#[derive(Debug)] +pub enum Error { + /// Unable to open a substream at all + Open(C::OpenError), + /// Unable to send the request to the server + Send(C::SendError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for Error {} + +/// Server error when receiving an item for a client streaming request +#[derive(Debug)] +pub enum ItemError { + /// Connection was closed before receiving the first message + EarlyClose, + /// Unable to receive the response from the server + RecvError(C::RecvError), + /// Unexpected response from the server + DowncastError, +} + +impl fmt::Display for ItemError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for ItemError {} + +impl RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ + /// Call to the server that allows the client to stream, single response + pub async fn client_streaming( + &self, + msg: M, + ) -> result::Result< + ( + UpdateSink, + Boxed>>, + ), + Error, + > + where + M: ClientStreamingMsg, + { + let msg = self.map.req_into_outer(msg.into()); + let (mut send, mut recv) = self.source.open_bi().await.map_err(Error::Open)?; + send.send(msg).map_err(Error::Send).await?; + let send = UpdateSink::(send, PhantomData, Arc::clone(&self.map)); + let map = Arc::clone(&self.map); + let recv = async move { + let item = recv.next().await.ok_or(ItemError::EarlyClose)?; + + match item { + Ok(x) => { + let x = map + .res_try_into_inner(x) + .map_err(|_| ItemError::DowncastError)?; + M::Response::try_from(x).map_err(|_| ItemError::DowncastError) + } + Err(e) => Err(ItemError::RecvError(e)), + } + } + .boxed(); + Ok((send, recv)) + } +} + +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, + SInner: Service, +{ + /// handle the message M using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + pub async fn client_streaming( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: ClientStreamingMsg, + F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, + Fut: Future + Send + 'static, + T: Send + 'static, + { + let Self { mut send, recv, .. } = self; + let (updates, read_error) = UpdateStream::new(recv, Arc::clone(&self.map)); + race2(read_error.map(Err), async move { + // get the response + let res = f(target, req, updates).await; + // turn into a S::Res so we can send it + let res = self.map.res_into_outer(res.into()); + // send it and return the error if any + send.send(res).await.map_err(RpcServerError::SendError) + }) + .await + } +} diff --git a/src/pattern/mod.rs b/src/pattern/mod.rs new file mode 100644 index 0000000..da2b879 --- /dev/null +++ b/src/pattern/mod.rs @@ -0,0 +1,11 @@ +//! Predefined interaction patterns. +//! +//! An interaction pattern can be as simple as an rpc call or something more +//! complex such as bidirectional streaming. +//! +//! Each pattern defines different associated message types for the interaction. +pub mod bidi_streaming; +pub mod client_streaming; +pub mod rpc; +pub mod server_streaming; +pub mod try_server_streaming; diff --git a/src/pattern/rpc.rs b/src/pattern/rpc.rs new file mode 100644 index 0000000..c07865e --- /dev/null +++ b/src/pattern/rpc.rs @@ -0,0 +1,160 @@ +//! RPC interaction pattern. + +use futures_lite::{Future, StreamExt}; +use futures_util::{FutureExt, SinkExt}; + +use crate::{ + message::{InteractionPattern, Msg}, + server::{race2, RpcChannel, RpcServerError}, + transport::ConnectionErrors, + RpcClient, Service, ServiceConnection, ServiceEndpoint, +}; + +use std::{ + error, + fmt::{self, Debug}, + result, +}; + +/// Rpc interaction pattern +/// +/// There is only one request and one response. +#[derive(Debug, Clone, Copy)] +pub struct Rpc; +impl InteractionPattern for Rpc {} + +/// Defines the response type for a rpc message. +/// +/// Since this is the most common interaction pattern, this also implements [Msg] for you +/// automatically, with the interaction pattern set to [Rpc]. This is to reduce boilerplate +/// when defining rpc messages. +pub trait RpcMsg: Msg { + /// The type for the response + /// + /// For requests that can produce errors, this can be set to [Result](std::result::Result). + type Response: Into + TryFrom + Send + 'static; +} + +/// We can only do this for one trait, so we do it for RpcMsg since it is the most common +impl, S: Service> Msg for T { + type Pattern = Rpc; +} +/// Client error. All client DSL methods return a `Result` with this error type. +#[derive(Debug)] +pub enum Error { + /// Unable to open a substream at all + Open(C::OpenError), + /// Unable to send the request to the server + Send(C::SendError), + /// Server closed the stream before sending a response + EarlyClose, + /// Unable to receive the response from the server + RecvError(C::RecvError), + /// Unexpected response from the server + DowncastError, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for Error {} + +impl RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ + /// RPC call to the server, single request, single response + pub async fn rpc(&self, msg: M) -> result::Result> + where + M: RpcMsg, + { + let msg = self.map.req_into_outer(msg.into()); + let (mut send, mut recv) = self.source.open_bi().await.map_err(Error::Open)?; + send.send(msg).await.map_err(Error::::Send)?; + let res = recv + .next() + .await + .ok_or(Error::::EarlyClose)? + .map_err(Error::::RecvError)?; + // keep send alive until we have the answer + drop(send); + let res = self + .map + .res_try_into_inner(res) + .map_err(|_| Error::DowncastError)?; + M::Response::try_from(res).map_err(|_| Error::DowncastError) + } +} + +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, + SInner: Service, +{ + /// handle the message of type `M` using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + pub async fn rpc( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: RpcMsg, + F: FnOnce(T, M) -> Fut, + Fut: Future, + T: Send + 'static, + { + let Self { + mut send, mut recv, .. + } = self; + // cancel if we get an update, no matter what it is + let cancel = recv + .next() + .map(|_| RpcServerError::UnexpectedUpdateMessage::); + // race the computation and the cancellation + race2(cancel.map(Err), async move { + // get the response + let res = f(target, req).await; + // turn into a S::Res so we can send it + let res = self.map.res_into_outer(res.into()); + // send it and return the error if any + send.send(res).await.map_err(RpcServerError::SendError) + }) + .await + } + + /// A rpc call that also maps the error from the user type to the wire type + /// + /// This is useful if you want to write your function with a convenient error type like anyhow::Error, + /// yet still use a serializable error type on the wire. + pub async fn rpc_map_err( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: RpcMsg>, + F: FnOnce(T, M) -> Fut, + Fut: Future>, + E2: From, + T: Send + 'static, + { + let fut = |target: T, msg: M| async move { + // call the inner fn + let res: Result = f(target, msg).await; + // convert the error type + let res: Result = res.map_err(E2::from); + res + }; + self.rpc(req, target, fut).await + } +} diff --git a/src/pattern/server_streaming.rs b/src/pattern/server_streaming.rs new file mode 100644 index 0000000..8fa7204 --- /dev/null +++ b/src/pattern/server_streaming.rs @@ -0,0 +1,148 @@ +//! Server streaming interaction pattern. + +use futures_lite::{Stream, StreamExt}; +use futures_util::{FutureExt, SinkExt, TryFutureExt}; + +use crate::{ + client::{BoxStreamSync, DeferDrop}, + message::{InteractionPattern, Msg}, + server::{race2, RpcChannel, RpcServerError}, + transport::ConnectionErrors, + RpcClient, Service, ServiceConnection, ServiceEndpoint, +}; + +use std::{ + error, + fmt::{self, Debug}, + result, + sync::Arc, +}; + +/// Server streaming interaction pattern +/// +/// After the initial request, the server can send a stream of responses. +#[derive(Debug, Clone, Copy)] +pub struct ServerStreaming; +impl InteractionPattern for ServerStreaming {} + +/// Defines response type for a server streaming message. +pub trait ServerStreamingMsg: Msg { + /// The type for the response + /// + /// For requests that can produce errors, this can be set to [Result](std::result::Result). + type Response: Into + TryFrom + Send + 'static; +} + +/// Server error when accepting a server streaming request +#[derive(Debug)] +pub enum Error { + /// Unable to open a substream at all + Open(C::OpenError), + /// Unable to send the request to the server + Send(C::SendError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for Error {} + +/// Client error when handling responses from a server streaming request +#[derive(Debug)] +pub enum ItemError { + /// Unable to receive the response from the server + RecvError(S::RecvError), + /// Unexpected response from the server + DowncastError, +} + +impl fmt::Display for ItemError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for ItemError {} + +impl RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ + /// Bidi call to the server, request opens a stream, response is a stream + pub async fn server_streaming( + &self, + msg: M, + ) -> result::Result>>, Error> + where + M: ServerStreamingMsg, + { + let msg = self.map.req_into_outer(msg.into()); + let (mut send, recv) = self.source.open_bi().await.map_err(Error::Open)?; + send.send(msg).map_err(Error::::Send).await?; + let map = Arc::clone(&self.map); + let recv = recv.map(move |x| match x { + Ok(x) => { + let x = map + .res_try_into_inner(x) + .map_err(|_| ItemError::DowncastError)?; + M::Response::try_from(x).map_err(|_| ItemError::DowncastError) + } + Err(e) => Err(ItemError::RecvError(e)), + }); + // keep send alive so the request on the server side does not get cancelled + let recv = Box::pin(DeferDrop(recv, send)); + Ok(recv) + } +} + +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, + SInner: Service, +{ + /// handle the message M using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + pub async fn server_streaming( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: ServerStreamingMsg, + F: FnOnce(T, M) -> Str + Send + 'static, + Str: Stream + Send + 'static, + T: Send + 'static, + { + let Self { + mut send, mut recv, .. + } = self; + // cancel if we get an update, no matter what it is + let cancel = recv + .next() + .map(|_| RpcServerError::UnexpectedUpdateMessage::); + // race the computation and the cancellation + race2(cancel.map(Err), async move { + // get the response + let responses = f(target, req); + tokio::pin!(responses); + while let Some(response) = responses.next().await { + // turn into a S::Res so we can send it + let response = self.map.res_into_outer(response.into()); + // send it and return the error if any + send.send(response) + .await + .map_err(RpcServerError::SendError)?; + } + Ok(()) + }) + .await + } +} diff --git a/src/pattern/try_server_streaming.rs b/src/pattern/try_server_streaming.rs new file mode 100644 index 0000000..88e275f --- /dev/null +++ b/src/pattern/try_server_streaming.rs @@ -0,0 +1,219 @@ +//! Fallible server streaming interaction pattern. + +use futures_lite::{Future, Stream, StreamExt}; +use futures_util::{FutureExt, SinkExt, TryFutureExt}; +use serde::{Deserialize, Serialize}; + +use crate::{ + client::{BoxStreamSync, DeferDrop}, + message::{InteractionPattern, Msg}, + server::{race2, RpcChannel, RpcServerError}, + transport::ConnectionErrors, + RpcClient, Service, ServiceConnection, ServiceEndpoint, +}; + +use std::{ + error, + fmt::{self, Debug}, + result, + sync::Arc, +}; + +/// A guard message to indicate that the stream has been created. +/// +/// This is so we can dinstinguish between an error creating the stream and +/// an error in the first item produced by the stream. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct StreamCreated; + +/// Fallible server streaming interaction pattern. +#[derive(Debug, Clone, Copy)] +pub struct TryServerStreaming; + +impl InteractionPattern for TryServerStreaming {} + +/// Same as ServerStreamingMsg, but with lazy stream creation and the error type explicitly defined. +pub trait TryServerStreamingMsg: Msg +where + result::Result: Into + TryFrom, + result::Result: Into + TryFrom, +{ + /// Error when creating the stream + type CreateError: Debug + Send + 'static; + + /// Error for stream items + type ItemError: Debug + Send + 'static; + + /// Successful response item + type Item: Send + 'static; +} + +/// Server error when accepting a server streaming request +/// +/// This combines network errors with application errors. Usually you don't +/// care about the exact nature of the error, but if you want to handle +/// application errors differently, you can match on this enum. +#[derive(Debug)] +pub enum Error { + /// Unable to open a substream at all + Open(C::OpenError), + /// Unable to send the request to the server + Send(C::SendError), + /// Error received when creating the stream + Recv(C::RecvError), + /// Connection was closed before receiving the first message + EarlyClose, + /// Unexpected response from the server + Downcast, + /// Application error + Application(E), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for Error {} + +/// Client error when handling responses from a server streaming request. +/// +/// This combines network errors with application errors. +#[derive(Debug)] +pub enum ItemError { + /// Unable to receive the response from the server + Recv(S::RecvError), + /// Unexpected response from the server + Downcast, + /// Application error + Application(E), +} + +impl fmt::Display for ItemError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for ItemError {} + +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, + SInner: Service, +{ + /// handle the message M using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + /// + /// Compared to [RpcChannel::server_streaming], with this method the stream creation is via + /// a function that returns a future that resolves to a stream. + pub async fn try_server_streaming( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: TryServerStreamingMsg, + std::result::Result: Into + TryFrom, + std::result::Result: + Into + TryFrom, + F: FnOnce(T, M) -> Fut + Send + 'static, + Fut: Future> + Send + 'static, + Str: Stream> + Send + 'static, + T: Send + 'static, + { + let Self { + mut send, mut recv, .. + } = self; + // cancel if we get an update, no matter what it is + let cancel = recv + .next() + .map(|_| RpcServerError::UnexpectedUpdateMessage::); + // race the computation and the cancellation + race2(cancel.map(Err), async move { + // get the response + let responses = match f(target, req).await { + Ok(responses) => { + // turn into a S::Res so we can send it + let response = self.map.res_into_outer(Ok(StreamCreated).into()); + // send it and return the error if any + send.send(response) + .await + .map_err(RpcServerError::SendError)?; + responses + } + Err(cause) => { + // turn into a S::Res so we can send it + let response = self.map.res_into_outer(Err(cause).into()); + // send it and return the error if any + send.send(response) + .await + .map_err(RpcServerError::SendError)?; + return Ok(()); + } + }; + tokio::pin!(responses); + while let Some(response) = responses.next().await { + // turn into a S::Res so we can send it + let response = self.map.res_into_outer(response.into()); + // send it and return the error if any + send.send(response) + .await + .map_err(RpcServerError::SendError)?; + } + Ok(()) + }) + .await + } +} + +impl RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ + /// Bidi call to the server, request opens a stream, response is a stream + pub async fn try_server_streaming( + &self, + msg: M, + ) -> result::Result< + BoxStreamSync<'static, Result>>, + Error, + > + where + M: TryServerStreamingMsg, + Result: Into + TryFrom, + Result: Into + TryFrom, + { + let msg = self.map.req_into_outer(msg.into()); + let (mut send, mut recv) = self.source.open_bi().await.map_err(Error::Open)?; + send.send(msg).map_err(Error::Send).await?; + let map = Arc::clone(&self.map); + let Some(initial) = recv.next().await else { + return Err(Error::EarlyClose); + }; + let initial = initial.map_err(Error::Recv)?; // initial response + let initial = map + .res_try_into_inner(initial) + .map_err(|_| Error::Downcast)?; + let initial = >::try_from(initial) + .map_err(|_| Error::Downcast)?; + let _ = initial.map_err(Error::Application)?; + let recv = recv.map(move |x| { + let x = x.map_err(ItemError::Recv)?; + let x = map.res_try_into_inner(x).map_err(|_| ItemError::Downcast)?; + let x = >::try_from(x) + .map_err(|_| ItemError::Downcast)?; + let x = x.map_err(ItemError::Application)?; + Ok(x) + }); + // keep send alive so the request on the server side does not get cancelled + let recv = Box::pin(DeferDrop(recv, send)); + Ok(recv) + } +} diff --git a/src/server.rs b/src/server.rs index 755e945..0e4e522 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,17 +2,26 @@ //! //! The main entry point is [RpcServer] use crate::{ - message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, + map::{ChainedMapper, MapService, Mapper}, transport::ConnectionErrors, Service, ServiceEndpoint, }; -use futures::{channel::oneshot, task, task::Poll, Future, FutureExt, SinkExt, Stream, StreamExt}; +use futures_lite::{Future, Stream, StreamExt}; use pin_project::pin_project; -use std::{error, fmt, fmt::Debug, marker::PhantomData, pin::Pin, result}; +use std::{ + error, + fmt::{self, Debug}, + marker::PhantomData, + pin::Pin, + result, + sync::Arc, + task::{self, Poll}, +}; +use tokio::sync::oneshot; /// A server channel for a specific service. /// -/// This is a wrapper around a [ServiceEndpoint](crate::ServiceEndpoint) that serves as the entry point for the server DSL. +/// This is a wrapper around a [ServiceEndpoint] that serves as the entry point for the server DSL. /// `S` is the service type, `C` is the channel type. #[derive(Debug)] pub struct RpcServer { @@ -54,187 +63,57 @@ impl> RpcServer { /// Sink and stream are independent, so you can take the channel apart and use /// them independently. #[derive(Debug)] -pub struct RpcChannel> { +pub struct RpcChannel, SInner: Service = S> { /// Sink to send responses to the client. pub send: C::SendSink, /// Stream to receive requests from the client. pub recv: C::RecvStream, - /// Phantom data to make the type parameter `S` non-instantiable. - p: PhantomData, + /// Mapper to map between S and S2 + pub map: Arc>, } -impl> RpcChannel { - /// Create a new channel from a sink and a stream. +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, +{ + /// Create a new RPC channel. pub fn new(send: C::SendSink, recv: C::RecvStream) -> Self { Self { send, recv, - p: PhantomData, + map: Arc::new(Mapper::new()), } } +} - /// handle the message of type `M` using the given function on the target object - /// - /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn rpc( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: RpcMsg, - F: FnOnce(T, M) -> Fut, - Fut: Future, - T: Send + 'static, - { - let Self { - mut send, mut recv, .. - } = self; - // cancel if we get an update, no matter what it is - let cancel = recv - .next() - .map(|_| RpcServerError::UnexpectedUpdateMessage::); - // race the computation and the cancellation - race2(cancel.map(Err), async move { - // get the response - let res = f(target, req).await; - // turn into a S::Res so we can send it - let res: S::Res = res.into(); - // send it and return the error if any - send.send(res).await.map_err(RpcServerError::SendError) - }) - .await - } - - /// handle the message M using the given function on the target object - /// - /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn client_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: ClientStreamingMsg, - F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, - Fut: Future + Send + 'static, - T: Send + 'static, - { - let Self { mut send, recv, .. } = self; - let (updates, read_error) = UpdateStream::new(recv); - race2(read_error.map(Err), async move { - // get the response - let res = f(target, req, updates).await; - // turn into a S::Res so we can send it - let res: S::Res = res.into(); - // send it and return the error if any - send.send(res).await.map_err(RpcServerError::SendError) - }) - .await - } - - /// handle the message M using the given function on the target object +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, + SInner: Service, +{ + /// Map this channel's service into an inner service. /// - /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn bidi_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: BidiStreamingMsg, - F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, - Str: Stream + Send + 'static, - T: Send + 'static, - { - let Self { mut send, recv, .. } = self; - // downcast the updates - let (updates, read_error) = UpdateStream::new(recv); - // get the response - let responses = f(target, req, updates); - race2(read_error.map(Err), async move { - tokio::pin!(responses); - while let Some(response) = responses.next().await { - // turn into a S::Res so we can send it - let response: S::Res = response.into(); - // send it and return the error if any - send.send(response) - .await - .map_err(RpcServerError::SendError)?; - } - Ok(()) - }) - .await - } - - /// handle the message M using the given function on the target object + /// This method is available if the required bounds are upheld: + /// SNext::Req: Into + TryFrom, + /// SNext::Res: Into + TryFrom, /// - /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn server_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: ServerStreamingMsg, - F: FnOnce(T, M) -> Str + Send + 'static, - Str: Stream + Send + 'static, - T: Send + 'static, - { - let Self { - mut send, mut recv, .. - } = self; - // cancel if we get an update, no matter what it is - let cancel = recv - .next() - .map(|_| RpcServerError::UnexpectedUpdateMessage::); - // race the computation and the cancellation - race2(cancel.map(Err), async move { - // get the response - let responses = f(target, req); - tokio::pin!(responses); - while let Some(response) = responses.next().await { - // turn into a S::Res so we can send it - let response: S::Res = response.into(); - // send it and return the error if any - send.send(response) - .await - .map_err(RpcServerError::SendError)?; - } - Ok(()) - }) - .await - } - - /// A rpc call that also maps the error from the user type to the wire type + /// Where SNext is the new service to map to and SInner is the current inner service. /// - /// This is useful if you want to write your function with a convenient error type like anyhow::Error, - /// yet still use a serializable error type on the wire. - pub async fn rpc_map_err( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> + /// This method can be chained infintely. + pub fn map(self) -> RpcChannel where - M: RpcMsg>, - F: FnOnce(T, M) -> Fut, - Fut: Future>, - E2: From, - T: Send + 'static, + SNext: Service, + SNext::Req: Into + TryFrom, + SNext::Res: Into + TryFrom, { - let fut = |target: T, msg: M| async move { - // call the inner fn - let res: Result = f(target, msg).await; - // convert the error type - let res: Result = res.map_err(E2::from); - res - }; - self.rpc(req, target, fut).await + let map = ChainedMapper::new(self.map); + RpcChannel { + send: self.send, + recv: self.recv, + map: Arc::new(map), + } } } @@ -284,40 +163,61 @@ impl> AsRef for RpcServer { /// cause a termination of the RPC call. #[pin_project] #[derive(Debug)] -pub struct UpdateStream, T>( +pub struct UpdateStream( #[pin] C::RecvStream, Option>>, PhantomData, -); + Arc>, +) +where + S: Service, + SInner: Service, + C: ServiceEndpoint; -impl, T> UpdateStream { - fn new(recv: C::RecvStream) -> (Self, UnwrapToPending>) { +impl UpdateStream +where + S: Service, + SInner: Service, + C: ServiceEndpoint, + T: TryFrom, +{ + pub(crate) fn new( + recv: C::RecvStream, + map: Arc>, + ) -> (Self, UnwrapToPending>) { let (error_send, error_recv) = oneshot::channel(); let error_recv = UnwrapToPending(error_recv); - (Self(recv, Some(error_send), PhantomData), error_recv) + (Self(recv, Some(error_send), PhantomData, map), error_recv) } } -impl, T> Stream for UpdateStream +impl Stream for UpdateStream where - T: TryFrom, + S: Service, + SInner: Service, + C: ServiceEndpoint, + T: TryFrom, { type Item = T; fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { let mut this = self.project(); - match this.0.poll_next_unpin(cx) { + match Pin::new(&mut this.0).poll_next(cx) { Poll::Ready(Some(msg)) => match msg { - Ok(msg) => match T::try_from(msg) { - Ok(msg) => Poll::Ready(Some(msg)), - Err(_cause) => { - // we were unable to downcast, so we need to send an error - if let Some(tx) = this.1.take() { - let _ = tx.send(RpcServerError::UnexpectedUpdateMessage); + Ok(msg) => { + let msg = this.3.req_try_into_inner(msg); + let msg = msg.and_then(|msg| T::try_from(msg).map_err(|_cause| ())); + match msg { + Ok(msg) => Poll::Ready(Some(msg)), + Err(_cause) => { + // we were unable to downcast, so we need to send an error + if let Some(tx) = this.1.take() { + let _ = tx.send(RpcServerError::UnexpectedUpdateMessage); + } + Poll::Pending } - Poll::Pending } - }, + } Err(cause) => { // we got a recv error, so return pending and send the error if let Some(tx) = this.1.take() { @@ -370,13 +270,13 @@ impl fmt::Display for RpcServerError { impl error::Error for RpcServerError {} /// Take an oneshot receiver and just return Pending the underlying future returns `Err(oneshot::Canceled)` -struct UnwrapToPending(oneshot::Receiver); +pub(crate) struct UnwrapToPending(oneshot::Receiver); impl Future for UnwrapToPending { type Output = T; fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { - match self.0.poll_unpin(cx) { + match Pin::new(&mut self.0).poll(cx) { Poll::Ready(Ok(x)) => Poll::Ready(x), Poll::Ready(Err(_)) => Poll::Pending, Poll::Pending => Poll::Pending, @@ -384,7 +284,7 @@ impl Future for UnwrapToPending { } } -async fn race2, B: Future>(f1: A, f2: B) -> T { +pub(crate) async fn race2, B: Future>(f1: A, f2: B) -> T { tokio::select! { x = f1 => x, x = f2 => x, diff --git a/src/transport/combined.rs b/src/transport/combined.rs index df0cedb..81a50f0 100644 --- a/src/transport/combined.rs +++ b/src/transport/combined.rs @@ -1,10 +1,9 @@ //! Transport that combines two other transports use super::{Connection, ConnectionCommon, ConnectionErrors, LocalAddr, ServerEndpoint}; -use crate::RpcMessage; -use futures::{ - future::{self, BoxFuture}, - FutureExt, Sink, Stream, TryFutureExt, -}; +use crate::{RpcMessage, Service}; +use futures_lite::{future::Boxed as BoxFuture, Stream}; +use futures_sink::Sink; +use futures_util::{FutureExt, TryFutureExt}; use pin_project::pin_project; use std::{ error, fmt, @@ -16,17 +15,17 @@ use std::{ }; /// A connection that combines two other connections -pub struct CombinedConnection { +pub struct CombinedConnection { /// First connection pub a: Option, /// Second connection pub b: Option, - /// Phantom data so we can have `In` and `Out` as type parameters - _p: PhantomData<(In, Out)>, + /// Phantom data so we can have `S` as type parameters + _p: PhantomData, } -impl, B: Connection, In: RpcMessage, Out: RpcMessage> - CombinedConnection +impl, B: Connection, S: Service> + CombinedConnection { /// Create a combined connection from two other connections /// @@ -39,9 +38,7 @@ impl, B: Connection, In: RpcMessage, Out: RpcMes } } } -impl Clone - for CombinedConnection -{ +impl Clone for CombinedConnection { fn clone(&self) -> Self { Self { a: self.a.clone(), @@ -51,9 +48,7 @@ impl Clone } } -impl Debug - for CombinedConnection -{ +impl Debug for CombinedConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CombinedConnection") .field("a", &self.a) @@ -63,19 +58,19 @@ impl Debug } /// An endpoint that combines two other endpoints -pub struct CombinedServerEndpoint { +pub struct CombinedServerEndpoint { /// First endpoint pub a: Option, /// Second endpoint pub b: Option, /// Local addresses from all endpoints local_addr: Vec, - /// Phantom data so we can have `In` and `Out` as type parameters - _p: PhantomData<(In, Out)>, + /// Phantom data so we can have `S` as type parameters + _p: PhantomData, } -impl, B: ServerEndpoint, In: RpcMessage, Out: RpcMessage> - CombinedServerEndpoint +impl, B: ServerEndpoint, S: Service> + CombinedServerEndpoint { /// Create a combined server endpoint from two other server endpoints /// @@ -105,9 +100,7 @@ impl, B: ServerEndpoint, In: RpcMessage, Out } } -impl Clone - for CombinedServerEndpoint -{ +impl Clone for CombinedServerEndpoint { fn clone(&self) -> Self { Self { a: self.a.clone(), @@ -118,9 +111,7 @@ impl Clone } } -impl Debug - for CombinedServerEndpoint -{ +impl Debug for CombinedServerEndpoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CombinedServerEndpoint") .field("a", &self.a) @@ -284,36 +275,36 @@ impl error::Error for AcceptBiError = - BoxFuture<'static, result::Result, self::OpenBiError>>; + BoxFuture, self::OpenBiError>>; /// Future returned by accept_bi pub type AcceptBiFuture = - BoxFuture<'static, result::Result, self::AcceptBiError>>; + BoxFuture, self::AcceptBiError>>; type Socket = ( self::SendSink, self::RecvStream, ); -impl ConnectionErrors - for CombinedConnection +impl ConnectionErrors + for CombinedConnection { type SendError = self::SendError; type RecvError = self::RecvError; type OpenError = self::OpenBiError; } -impl, B: Connection, In: RpcMessage, Out: RpcMessage> - ConnectionCommon for CombinedConnection +impl, B: Connection, S: Service> + ConnectionCommon for CombinedConnection { - type RecvStream = self::RecvStream; - type SendSink = self::SendSink; + type RecvStream = self::RecvStream; + type SendSink = self::SendSink; } -impl, B: Connection, In: RpcMessage, Out: RpcMessage> - Connection for CombinedConnection +impl, B: Connection, S: Service> + Connection for CombinedConnection { - fn open_bi(&self) -> OpenBiFuture { + fn open_bi(&self) -> OpenBiFuture { let this = self.clone(); async { // try a first, then b @@ -324,59 +315,59 @@ impl, B: Connection, In: RpcMessage, Out: RpcMes let (send, recv) = b.open_bi().await.map_err(OpenBiError::B)?; Ok((SendSink::B(send), RecvStream::B(recv))) } else { - future::err(OpenBiError::NoChannel).await + std::future::ready(Err(OpenBiError::NoChannel)).await } } .boxed() } - type OpenBiFut = OpenBiFuture; + type OpenBiFut = OpenBiFuture; } -impl ConnectionErrors - for CombinedServerEndpoint +impl ConnectionErrors + for CombinedServerEndpoint { type SendError = self::SendError; type RecvError = self::RecvError; type OpenError = self::AcceptBiError; } -impl, B: ServerEndpoint, In: RpcMessage, Out: RpcMessage> - ConnectionCommon for CombinedServerEndpoint +impl, B: ServerEndpoint, S: Service> + ConnectionCommon for CombinedServerEndpoint { - type RecvStream = self::RecvStream; - type SendSink = self::SendSink; + type RecvStream = self::RecvStream; + type SendSink = self::SendSink; } -impl, B: ServerEndpoint, In: RpcMessage, Out: RpcMessage> - ServerEndpoint for CombinedServerEndpoint +impl, B: ServerEndpoint, S: Service> + ServerEndpoint for CombinedServerEndpoint { - fn accept_bi(&self) -> AcceptBiFuture { + fn accept_bi(&self) -> AcceptBiFuture { let a_fut = if let Some(a) = &self.a { a.accept_bi() .map_ok(|(send, recv)| { ( - SendSink::::A(send), - RecvStream::::A(recv), + SendSink::::A(send), + RecvStream::::A(recv), ) }) .map_err(AcceptBiError::A) .left_future() } else { - future::pending().right_future() + std::future::pending().right_future() }; let b_fut = if let Some(b) = &self.b { b.accept_bi() .map_ok(|(send, recv)| { ( - SendSink::::B(send), - RecvStream::::B(recv), + SendSink::::B(send), + RecvStream::::B(recv), ) }) .map_err(AcceptBiError::B) .left_future() } else { - future::pending().right_future() + std::future::pending().right_future() }; async move { tokio::select! { @@ -387,7 +378,7 @@ impl, B: ServerEndpoint, In: RpcMessage, Out .boxed() } - type AcceptBiFut = AcceptBiFuture; + type AcceptBiFut = AcceptBiFuture; fn local_addr(&self) -> &[LocalAddr] { &self.local_addr @@ -404,13 +395,19 @@ mod tests { Connection, }; + #[derive(Clone, Debug)] + struct Service; + impl crate::Service for Service { + type Req = (); + type Res = (); + } + #[tokio::test] async fn open_empty_channel() { let channel = combined::CombinedConnection::< - flume::FlumeConnection<(), ()>, - flume::FlumeConnection<(), ()>, - (), - (), + flume::FlumeConnection, + flume::FlumeConnection, + Service, >::new(None, None); let res = channel.open_bi().await; assert!(matches!(res, Err(OpenBiError::NoChannel))); diff --git a/src/transport/flume.rs b/src/transport/flume.rs index 9c86843..154ec77 100644 --- a/src/transport/flume.rs +++ b/src/transport/flume.rs @@ -1,12 +1,14 @@ //! Memory transport implementation using [flume] //! //! [flume]: https://docs.rs/flume/ +use futures_lite::{Future, Stream}; +use futures_sink::Sink; + use crate::{ transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}, - RpcMessage, + RpcMessage, Service, }; use core::fmt; -use futures::{Future, FutureExt, Sink, SinkExt, Stream, StreamExt}; use std::{error, fmt::Display, marker::PhantomData, pin::Pin, result, task::Poll}; use super::ConnectionCommon; @@ -39,14 +41,14 @@ impl Sink for SendSink { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.0 - .poll_ready_unpin(cx) + Pin::new(&mut self.0) + .poll_ready(cx) .map_err(|_| SendError::ReceiverDropped) } fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - self.0 - .start_send_unpin(item) + Pin::new(&mut self.0) + .start_send(item) .map_err(|_| SendError::ReceiverDropped) } @@ -54,8 +56,8 @@ impl Sink for SendSink { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.0 - .poll_flush_unpin(cx) + Pin::new(&mut self.0) + .poll_flush(cx) .map_err(|_| SendError::ReceiverDropped) } @@ -63,8 +65,8 @@ impl Sink for SendSink { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.0 - .poll_close_unpin(cx) + Pin::new(&mut self.0) + .poll_close(cx) .map_err(|_| SendError::ReceiverDropped) } } @@ -85,7 +87,7 @@ impl Stream for RecvStream { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - match self.0.poll_next_unpin(cx) { + match Pin::new(&mut self.0).poll_next(cx) { Poll::Ready(Some(v)) => Poll::Ready(Some(Ok(v))), Poll::Ready(None) => Poll::Ready(None), Poll::Pending => Poll::Pending, @@ -98,11 +100,12 @@ impl error::Error for RecvError {} /// A flume based server endpoint. /// /// Created using [connection]. -pub struct FlumeServerEndpoint { - stream: flume::Receiver<(SendSink, RecvStream)>, +pub struct FlumeServerEndpoint { + #[allow(clippy::type_complexity)] + stream: flume::Receiver<(SendSink, RecvStream)>, } -impl Clone for FlumeServerEndpoint { +impl Clone for FlumeServerEndpoint { fn clone(&self) -> Self { Self { stream: self.stream.clone(), @@ -110,7 +113,7 @@ impl Clone for FlumeServerEndpoint { } } -impl fmt::Debug for FlumeServerEndpoint { +impl fmt::Debug for FlumeServerEndpoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FlumeServerEndpoint") .field("stream", &self.stream) @@ -118,7 +121,7 @@ impl fmt::Debug for FlumeServerEndpoint ConnectionErrors for FlumeServerEndpoint { +impl ConnectionErrors for FlumeServerEndpoint { type SendError = self::SendError; type RecvError = self::RecvError; @@ -156,7 +159,7 @@ impl Future for OpenBiFuture { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll { - match self.inner.poll_unpin(cx) { + match Pin::new(&mut self.inner).poll(cx) { Poll::Ready(Ok(())) => self .res .take() @@ -184,7 +187,7 @@ impl Future for AcceptBiFuture { type Output = result::Result<(SendSink, RecvStream), AcceptBiError>; fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - match self.wrapped.poll_unpin(cx) { + match Pin::new(&mut self.wrapped).poll(cx) { Poll::Ready(Ok((send, recv))) => Poll::Ready(Ok((send, recv))), Poll::Ready(Err(_)) => Poll::Ready(Err(AcceptBiError::RemoteDropped)), Poll::Pending => Poll::Pending, @@ -192,13 +195,13 @@ impl Future for AcceptBiFuture { } } -impl ConnectionCommon for FlumeServerEndpoint { - type SendSink = SendSink; - type RecvStream = RecvStream; +impl ConnectionCommon for FlumeServerEndpoint { + type SendSink = SendSink; + type RecvStream = RecvStream; } -impl ServerEndpoint for FlumeServerEndpoint { - type AcceptBiFut = AcceptBiFuture; +impl ServerEndpoint for FlumeServerEndpoint { + type AcceptBiFut = AcceptBiFuture; fn accept_bi(&self) -> Self::AcceptBiFut { AcceptBiFuture { @@ -212,7 +215,7 @@ impl ServerEndpoint for FlumeServerEnd } } -impl ConnectionErrors for FlumeConnection { +impl ConnectionErrors for FlumeConnection { type SendError = self::SendError; type RecvError = self::RecvError; @@ -220,17 +223,17 @@ impl ConnectionErrors for FlumeConnection ConnectionCommon for FlumeConnection { - type SendSink = SendSink; - type RecvStream = RecvStream; +impl ConnectionCommon for FlumeConnection { + type SendSink = SendSink; + type RecvStream = RecvStream; } -impl Connection for FlumeConnection { - type OpenBiFut = OpenBiFuture; +impl Connection for FlumeConnection { + type OpenBiFut = OpenBiFuture; fn open_bi(&self) -> Self::OpenBiFut { - let (local_send, remote_recv) = flume::bounded::(128); - let (remote_send, local_recv) = flume::bounded::(128); + let (local_send, remote_recv) = flume::bounded::(128); + let (remote_send, local_recv) = flume::bounded::(128); let remote_chan = ( SendSink(remote_send.into_sink()), RecvStream(remote_recv.into_stream()), @@ -246,11 +249,12 @@ impl Connection for FlumeConnection { - sink: flume::Sender<(SendSink, RecvStream)>, +pub struct FlumeConnection { + #[allow(clippy::type_complexity)] + sink: flume::Sender<(SendSink, RecvStream)>, } -impl Clone for FlumeConnection { +impl Clone for FlumeConnection { fn clone(&self) -> Self { Self { sink: self.sink.clone(), @@ -258,7 +262,7 @@ impl Clone for FlumeConnection { } } -impl fmt::Debug for FlumeConnection { +impl fmt::Debug for FlumeConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FlumeClientChannel") .field("sink", &self.sink) @@ -333,9 +337,7 @@ impl std::error::Error for CreateChannelError {} /// Create a flume server endpoint and a connected flume client channel. /// /// `buffer` the size of the buffer for each channel. Keep this at a low value to get backpressure -pub fn connection( - buffer: usize, -) -> (FlumeServerEndpoint, FlumeConnection) { +pub fn connection(buffer: usize) -> (FlumeServerEndpoint, FlumeConnection) { let (sink, stream) = flume::bounded(buffer); (FlumeServerEndpoint { stream }, FlumeConnection { sink }) } diff --git a/src/transport/hyper.rs b/src/transport/hyper.rs index 5b28024..e8b0e7c 100644 --- a/src/transport/hyper.rs +++ b/src/transport/hyper.rs @@ -2,15 +2,17 @@ //! //! [hyper]: https://crates.io/crates/hyper/ use std::{ - convert::Infallible, error, fmt, io, marker::PhantomData, net::SocketAddr, pin::Pin, result, - sync::Arc, task::Poll, + convert::Infallible, error, fmt, future::Future, io, marker::PhantomData, net::SocketAddr, + pin::Pin, result, sync::Arc, task::Poll, }; use crate::transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}; -use crate::RpcMessage; +use crate::{RpcMessage, Service}; use bytes::Bytes; use flume::{r#async::RecvFut, Receiver, Sender}; -use futures::{future::FusedFuture, Future, FutureExt, Sink, SinkExt, StreamExt}; +use futures_lite::{Stream, StreamExt}; +use futures_sink::Sink; +use futures_util::future::FusedFuture; use hyper::{ client::{connect::Connect, HttpConnector, ResponseFuture}, server::conn::{AddrIncoming, AddrStream}, @@ -31,9 +33,10 @@ struct HyperConnectionInner { } /// Hyper based connection to a server -pub struct HyperConnection { +#[derive(Clone)] +pub struct HyperConnection { inner: Arc, - _p: PhantomData<(In, Out)>, + _p: PhantomData, } /// Trait so we don't have to drag around the hyper internals @@ -47,7 +50,7 @@ impl Requester for Client { } } -impl HyperConnection { +impl HyperConnection { /// create a client given an uri and the default configuration pub fn new(uri: Uri) -> Self { Self::with_config(uri, ChannelConfig::default()) @@ -84,7 +87,7 @@ impl HyperConnection { } } -impl fmt::Debug for HyperConnection { +impl fmt::Debug for HyperConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ClientChannel") .field("uri", &self.inner.uri) @@ -93,15 +96,6 @@ impl fmt::Debug for HyperConnection { } } -impl Clone for HyperConnection { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - _p: PhantomData, - } - } -} - /// A pair of channels to send and receive messages on a single stream. /// /// A socket here is an abstraction of a single stream to a single peer which sends and @@ -179,9 +173,9 @@ impl Default for ChannelConfig { /// Creating this spawns a tokio task which runs the server, once dropped this task is shut /// down: no new connections will be accepted and existing channels will stop. #[derive(Debug)] -pub struct HyperServerEndpoint { +pub struct HyperServerEndpoint { /// The channel. - channel: Receiver>, + channel: Receiver>, /// The configuration. config: Arc, /// The sender to stop the server. @@ -194,11 +188,11 @@ pub struct HyperServerEndpoint { /// This is useful when the listen address uses a random port, `:0`, to find out which /// port was bound by the kernel. local_addr: [LocalAddr; 1], - /// Phantom data for in and out - _p: PhantomData<(In, Out)>, + /// Phantom data for service + _p: PhantomData, } -impl HyperServerEndpoint { +impl HyperServerEndpoint { /// Creates a server listening on the [`SocketAddr`], with the default configuration. pub fn serve(addr: &SocketAddr) -> hyper::Result { Self::serve_with_config(addr, Default::default()) @@ -258,9 +252,9 @@ impl HyperServerEndpoint { /// response and sends them to the [`ServerChannel`]. async fn handle_one_http2_request( req: Request, - accept_tx: Sender>, + accept_tx: Sender>, ) -> Result, String> { - let (req_tx, req_rx) = flume::bounded::>(32); + let (req_tx, req_rx) = flume::bounded::>(32); let (res_tx, res_rx) = flume::bounded::>(32); accept_tx .send_async((req_rx, res_tx)) @@ -371,7 +365,7 @@ fn spawn_recv_forwarder( // This does not want or need RpcMessage to be clone but still want to clone the // ServerChannel and it's containing channels itself. The derive macro can't cope with this // so this needs to be written by hand. -impl Clone for HyperServerEndpoint { +impl Clone for HyperServerEndpoint { fn clone(&self) -> Self { Self { channel: self.channel.clone(), @@ -411,14 +405,14 @@ impl Clone for RecvStream { } } -impl futures::Stream for RecvStream { +impl Stream for RecvStream { type Item = Result; fn poll_next( mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.recv.poll_next_unpin(cx) + Pin::new(&mut self.recv).poll_next(cx) } } @@ -466,8 +460,8 @@ impl Sink for SendSink { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.sink - .poll_ready_unpin(cx) + Pin::new(&mut self.sink) + .poll_ready(cx) .map_err(|_| SendError::ReceiverDropped) } @@ -481,8 +475,8 @@ impl Sink for SendSink { ), }; // attempt sending - self.sink - .start_send_unpin(send) + Pin::new(&mut self.sink) + .start_send(send) .map_err(|_| SendError::ReceiverDropped)?; res } @@ -491,8 +485,8 @@ impl Sink for SendSink { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.sink - .poll_flush_unpin(cx) + Pin::new(&mut self.sink) + .poll_flush(cx) .map_err(|_| SendError::ReceiverDropped) } @@ -500,8 +494,8 @@ impl Sink for SendSink { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.sink - .poll_close_unpin(cx) + Pin::new(&mut self.sink) + .poll_close(cx) .map_err(|_| SendError::ReceiverDropped) } } @@ -606,7 +600,7 @@ impl Future for OpenBiFuture { ) -> std::task::Poll { let this = self.project(); match this.chan { - Some(Ok((fut, _, _))) => match fut.poll_unpin(cx) { + Some(Ok((fut, _, _))) => match Pin::new(fut).poll(cx) { Poll::Ready(Ok(res)) => { event!(Level::TRACE, "OpenBiFuture got response"); let (_, out_tx, config) = this.chan.take().unwrap().unwrap(); @@ -697,12 +691,12 @@ impl Future for AcceptBiFuture { type Output = result::Result, AcceptBiError>; fn poll( - self: std::pin::Pin<&mut Self>, + self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll { let this = self.project(); match this.chan { - Some((fut, _)) => match fut.poll_unpin(cx) { + Some((fut, _)) => match Pin::new(fut).poll(cx) { Poll::Ready(Ok((recv, send))) => { let (_, config) = this.chan.take().unwrap(); Poll::Ready(Ok(( @@ -738,8 +732,8 @@ where } } -impl HyperConnection { - fn open_bi(&self) -> OpenBiFuture { +impl HyperConnection { + fn open_bi(&self) -> OpenBiFuture { event!(Level::TRACE, "open_bi {}", self.inner.uri); let (out_tx, out_rx) = flume::bounded::>(32); let req: Result, OpenBiError> = Request::post(&self.inner.uri) @@ -756,7 +750,7 @@ impl HyperConnection { } } -impl ConnectionErrors for HyperConnection { +impl ConnectionErrors for HyperConnection { type SendError = self::SendError; type RecvError = self::RecvError; @@ -764,21 +758,21 @@ impl ConnectionErrors for HyperConnection ConnectionCommon for HyperConnection { - type RecvStream = self::RecvStream; +impl ConnectionCommon for HyperConnection { + type RecvStream = self::RecvStream; - type SendSink = self::SendSink; + type SendSink = self::SendSink; } -impl Connection for HyperConnection { - type OpenBiFut = OpenBiFuture; +impl Connection for HyperConnection { + type OpenBiFut = OpenBiFuture; fn open_bi(&self) -> Self::OpenBiFut { self.open_bi() } } -impl ConnectionErrors for HyperServerEndpoint { +impl ConnectionErrors for HyperServerEndpoint { type SendError = self::SendError; type RecvError = self::RecvError; @@ -786,13 +780,13 @@ impl ConnectionErrors for HyperServerEndpoint ConnectionCommon for HyperServerEndpoint { - type RecvStream = self::RecvStream; - type SendSink = self::SendSink; +impl ConnectionCommon for HyperServerEndpoint { + type RecvStream = self::RecvStream; + type SendSink = self::SendSink; } -impl ServerEndpoint for HyperServerEndpoint { - type AcceptBiFut = AcceptBiFuture; +impl ServerEndpoint for HyperServerEndpoint { + type AcceptBiFut = AcceptBiFuture; fn local_addr(&self) -> &[LocalAddr] { &self.local_addr diff --git a/src/transport/interprocess.rs b/src/transport/interprocess.rs index 2047ac1..5673216 100644 --- a/src/transport/interprocess.rs +++ b/src/transport/interprocess.rs @@ -1,11 +1,12 @@ //! Custom quinn transport that uses the interprocess crate to provide //! local interprocess communication via either Unix domain sockets or //! Windows named pipes. -use std::{ffi::OsString, io, net::SocketAddr, path::Path}; +use std::{io, net::SocketAddr, path::Path}; use super::quinn_flume_socket::{make_endpoint, FlumeSocket, Packet}; use bytes::{Buf, Bytes, BytesMut}; use futures::StreamExt; +use interprocess::local_socket::{GenericFilePath, Name, ToFsName}; use quinn::{Endpoint, EndpointConfig}; use tokio::{ io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, @@ -31,20 +32,15 @@ impl<'a> Iterator for FrameIter<'a> { } /// Automatically chooses name type based on OS support and preference. -pub fn new_socket_name(root: impl AsRef, id: &str) -> OsString { - let namespaced = { - use interprocess::local_socket::NameTypeSupport; - let nts = NameTypeSupport::query(); - match nts { - NameTypeSupport::OnlyPaths | NameTypeSupport::Both => false, - NameTypeSupport::OnlyNamespaced => true, - } - }; - - if namespaced { - format!("@quic-rpc-socket-{}.sock", id).into() +pub fn new_socket_name(root: impl AsRef, id: &str) -> io::Result> { + if cfg!(windows) { + format!("@quic-rpc-socket-{}.sock", id).to_fs_name::() + } else if cfg!(unix) { + root.as_ref() + .join(format!("{id}.sock")) + .to_fs_name::() } else { - root.as_ref().join(format!("{id}.sock")).into() + panic!("unsupported OS"); } } diff --git a/src/transport/misc/mod.rs b/src/transport/misc/mod.rs index f48e531..a837ae8 100644 --- a/src/transport/misc/mod.rs +++ b/src/transport/misc/mod.rs @@ -1,10 +1,12 @@ //! Miscellaneous transport utilities +use futures_lite::{future, stream}; +use futures_sink::Sink; + use crate::{ transport::{ConnectionErrors, ServerEndpoint}, RpcMessage, }; -use futures::{future, stream, Sink}; use std::convert::Infallible; use super::ConnectionCommon; @@ -24,14 +26,14 @@ impl ConnectionErrors for DummyServerEndpoint { impl ConnectionCommon for DummyServerEndpoint { type RecvStream = stream::Pending>; - type SendSink = Box + Unpin + Send>; + type SendSink = Box + Unpin + Send + Sync>; } impl ServerEndpoint for DummyServerEndpoint { type AcceptBiFut = future::Pending>; fn accept_bi(&self) -> Self::AcceptBiFut { - futures::future::pending() + futures_lite::future::pending() } fn local_addr(&self) -> &[super::LocalAddr] { diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 3332c8c..f41637c 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -1,6 +1,8 @@ //! Transports for quic-rpc +use futures_lite::{Future, Stream}; +use futures_sink::Sink; + use crate::RpcError; -use futures::{Future, Sink, Stream}; use std::{ fmt::{self, Debug, Display}, net::SocketAddr, @@ -38,9 +40,9 @@ pub trait ConnectionErrors: Debug + Clone + Send + Sync + 'static { /// Having this as a separate trait is useful when writing generic code that works with both. pub trait ConnectionCommon: ConnectionErrors { /// Receive side of a bidirectional typed channel - type RecvStream: Stream> + Send + Unpin + 'static; + type RecvStream: Stream> + Send + Sync + Unpin + 'static; /// Send side of a bidirectional typed channel - type SendSink: Sink + Send + Unpin + 'static; + type SendSink: Sink + Send + Sync + Unpin + 'static; } /// A connection to a specific remote machine diff --git a/src/transport/quinn.rs b/src/transport/quinn.rs index 78d91b5..61ef40b 100644 --- a/src/transport/quinn.rs +++ b/src/transport/quinn.rs @@ -1,10 +1,11 @@ //! QUIC transport implementation based on [quinn](https://crates.io/crates/quinn) use crate::{ transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}, - RpcMessage, + RpcMessage, Service, }; -use futures::channel::oneshot; -use futures::{Future, FutureExt, Sink, SinkExt, Stream, StreamExt}; +use futures_lite::{Future, Stream, StreamExt}; +use futures_sink::Sink; +use futures_util::FutureExt; use pin_project::pin_project; use serde::de::DeserializeOwned; use serde::Serialize; @@ -12,6 +13,7 @@ use std::net::SocketAddr; use std::sync::Arc; use std::task::{Context, Poll}; use std::{fmt, io, marker::PhantomData, pin::Pin, result}; +use tokio::sync::oneshot; use tracing::{debug_span, Instrument}; use super::{ @@ -53,12 +55,12 @@ impl Drop for ServerEndpointInner { /// A server endpoint using a quinn connection #[derive(Debug)] -pub struct QuinnServerEndpoint { +pub struct QuinnServerEndpoint { inner: Arc, - _phantom: PhantomData<(In, Out)>, + _phantom: PhantomData, } -impl QuinnServerEndpoint { +impl QuinnServerEndpoint { /// handles RPC requests from a connection /// /// to cleanly shutdown the handler, drop the receiver side of the sender. @@ -175,7 +177,7 @@ impl QuinnServerEndpoint { } } -impl Clone for QuinnServerEndpoint { +impl Clone for QuinnServerEndpoint { fn clone(&self) -> Self { Self { inner: self.inner.clone(), @@ -184,7 +186,7 @@ impl Clone for QuinnServerEndpoint { } } -impl ConnectionErrors for QuinnServerEndpoint { +impl ConnectionErrors for QuinnServerEndpoint { type SendError = io::Error; type RecvError = io::Error; @@ -192,13 +194,13 @@ impl ConnectionErrors for QuinnServerEndpoint ConnectionCommon for QuinnServerEndpoint { - type RecvStream = self::RecvStream; - type SendSink = self::SendSink; +impl ConnectionCommon for QuinnServerEndpoint { + type SendSink = self::SendSink; + type RecvStream = self::RecvStream; } -impl ServerEndpoint for QuinnServerEndpoint { - type AcceptBiFut = AcceptBiFuture; +impl ServerEndpoint for QuinnServerEndpoint { + type AcceptBiFut = AcceptBiFuture; fn accept_bi(&self) -> Self::AcceptBiFut { AcceptBiFuture(self.inner.receiver.clone().into_recv_async(), PhantomData) @@ -245,12 +247,12 @@ impl Drop for ClientConnectionInner { } /// A connection using a quinn connection -pub struct QuinnConnection { +pub struct QuinnConnection { inner: Arc, - _phantom: PhantomData<(In, Out)>, + _phantom: PhantomData, } -impl QuinnConnection { +impl QuinnConnection { async fn single_connection_handler_inner( connection: quinn::Connection, requests: flume::Receiver>>, @@ -301,47 +303,111 @@ impl QuinnConnection { name: String, requests: flume::Receiver>>, ) { - 'outer: loop { - tracing::debug!("Connecting to {} as {}", addr, name); - let connecting = match endpoint.connect(addr, &name) { - Ok(connecting) => connecting, - Err(e) => { - tracing::warn!("error calling connect: {}", e); - // try again. Maybe delay? - continue; + let reconnect = ReconnectHandler { + endpoint, + state: ConnectionState::NotConnected, + addr, + name, + }; + tokio::pin!(reconnect); + + let mut receiver = Receiver::new(&requests); + + let mut pending_request: Option< + oneshot::Sender>, + > = None; + let mut connection = None; + + enum Racer { + Reconnect(Result), + Channel(Option>>), + } + + loop { + let mut conn_result = None; + let mut chann_result = None; + if !reconnect.connected() && pending_request.is_none() { + match futures_lite::future::race( + reconnect.as_mut().map(Racer::Reconnect), + receiver.next().map(Racer::Channel), + ) + .await + { + Racer::Reconnect(connection_result) => conn_result = Some(connection_result), + Racer::Channel(channel_result) => { + chann_result = Some(channel_result); + } } - }; - let connection = match connecting.await { - Ok(connection) => connection, - Err(e) => { - tracing::warn!("error awaiting connect: {}", e); - // try again. Maybe delay? - continue; + } else if !reconnect.connected() { + // only need a new connection + conn_result = Some(reconnect.as_mut().await); + } else if pending_request.is_none() { + // there is a connection, just need a request + chann_result = Some(receiver.next().await); + } + + if let Some(conn_result) = conn_result { + tracing::trace!("tick: connection result"); + match conn_result { + Ok(new_connection) => { + connection = Some(new_connection); + } + Err(e) => { + let connection_err = match e { + ReconnectErr::Connect(e) => { + // TODO(@divma): the type for now accepts only a + // ConnectionError, not a ConnectError. I'm mapping this now to + // some ConnectionError since before it was not even reported. + // Maybe adjust the type? + tracing::warn!(%e, "error calling connect"); + quinn::ConnectionError::Reset + } + ReconnectErr::Connection(e) => { + tracing::warn!(%e, "failed to connect"); + e + } + }; + if let Some(request) = pending_request.take() { + if request.send(Err(connection_err)).is_err() { + tracing::debug!("requester dropped"); + } + } + } } - }; - loop { - tracing::debug!("Awaiting request for new bidi substream..."); - let request = match requests.recv_async().await { - Ok(request) => request, - Err(_) => { + } + + if let Some(req) = chann_result { + tracing::trace!("tick: bidi request"); + match req { + Some(request) => pending_request = Some(request), + None => { tracing::debug!("client dropped"); - connection.close(0u32.into(), b"requester dropped"); + if let Some(connection) = connection { + connection.close(0u32.into(), b"requester dropped"); + } break; } - }; - tracing::debug!("Got request for new bidi substream"); - match connection.open_bi().await { - Ok(pair) => { - tracing::debug!("Bidi substream opened"); - if request.send(Ok(pair)).is_err() { - tracing::debug!("requester dropped"); + } + } + + if let Some(connection) = connection.as_mut() { + if let Some(request) = pending_request.take() { + match connection.open_bi().await { + Ok(pair) => { + tracing::debug!("Bidi substream opened"); + if request.send(Ok(pair)).is_err() { + tracing::debug!("requester dropped"); + } + } + Err(e) => { + tracing::warn!("error opening bidi substream: {}", e); + tracing::warn!("recreating connection"); + // NOTE: the connection might be stale, so we recreate the + // connection and set the request as pending instead of + // sending the error as a response + reconnect.set_not_connected(); + pending_request = Some(request); } - } - Err(e) => { - tracing::warn!("error opening bidi substream: {}", e); - tracing::warn!("recreating connection"); - // try again. Maybe delay? - continue 'outer; } } } @@ -392,7 +458,143 @@ impl QuinnConnection { } } -impl fmt::Debug for QuinnConnection { +struct ReconnectHandler { + endpoint: quinn::Endpoint, + state: ConnectionState, + addr: SocketAddr, + name: String, +} + +impl ReconnectHandler { + pub fn set_not_connected(&mut self) { + self.state.set_not_connected() + } + + pub fn connected(&self) -> bool { + matches!(self.state, ConnectionState::Connected(_)) + } +} + +enum ConnectionState { + /// There is no active connection. An attempt to connect will be made. + NotConnected, + /// Connecting to the remote. + Connecting(quinn::Connecting), + /// A connection is already established. In this state, no more connection attempts are made. + Connected(quinn::Connection), + /// Intermediate state while processing. + Poisoned, +} + +impl ConnectionState { + pub fn poison(&mut self) -> ConnectionState { + std::mem::replace(self, ConnectionState::Poisoned) + } + + pub fn set_not_connected(&mut self) { + *self = ConnectionState::NotConnected + } +} + +enum ReconnectErr { + Connect(quinn::ConnectError), + Connection(quinn::ConnectionError), +} + +impl Future for ReconnectHandler { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.state.poison() { + ConnectionState::NotConnected => match self.endpoint.connect(self.addr, &self.name) { + Ok(connecting) => { + self.state = ConnectionState::Connecting(connecting); + self.poll(cx) + } + Err(e) => { + self.state = ConnectionState::NotConnected; + Poll::Ready(Err(ReconnectErr::Connect(e))) + } + }, + ConnectionState::Connecting(mut connecting) => match Pin::new(&mut connecting).poll(cx) + { + Poll::Ready(res) => match res { + Ok(connection) => { + self.state = ConnectionState::Connected(connection.clone()); + Poll::Ready(Ok(connection)) + } + Err(e) => { + self.state = ConnectionState::NotConnected; + Poll::Ready(Err(ReconnectErr::Connection(e))) + } + }, + Poll::Pending => { + self.state = ConnectionState::Connecting(connecting); + Poll::Pending + } + }, + ConnectionState::Connected(connection) => { + self.state = ConnectionState::Connected(connection.clone()); + Poll::Ready(Ok(connection)) + } + ConnectionState::Poisoned => unreachable!("poisoned connection state"), + } + } +} + +/// Wrapper over [`flume::Receiver`] that can be used with [`tokio::select`]. +/// +/// NOTE: from https://github.com/zesterer/flume/issues/104: +/// > If RecvFut is dropped without being polled, the item is never received. +enum Receiver<'a, T> +where + Self: 'a, +{ + PreReceive(&'a flume::Receiver), + Receiving(&'a flume::Receiver, flume::r#async::RecvFut<'a, T>), + Poisoned, +} + +impl<'a, T> Receiver<'a, T> { + fn new(recv: &'a flume::Receiver) -> Self { + Receiver::PreReceive(recv) + } + + fn poison(&mut self) -> Self { + std::mem::replace(self, Self::Poisoned) + } +} + +impl<'a, T> Stream for Receiver<'a, T> { + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.poison() { + Receiver::PreReceive(recv) => { + let fut = recv.recv_async(); + *self = Receiver::Receiving(recv, fut); + self.poll_next(cx) + } + Receiver::Receiving(recv, mut fut) => match Pin::new(&mut fut).poll(cx) { + Poll::Ready(Ok(t)) => { + *self = Receiver::PreReceive(recv); + Poll::Ready(Some(t)) + } + Poll::Ready(Err(flume::RecvError::Disconnected)) => { + *self = Receiver::PreReceive(recv); + Poll::Ready(None) + } + Poll::Pending => { + *self = Receiver::Receiving(recv, fut); + Poll::Pending + } + }, + Receiver::Poisoned => unreachable!("poisoned receiver state"), + } + } +} + +impl fmt::Debug for QuinnConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ClientChannel") .field("inner", &self.inner) @@ -400,7 +602,7 @@ impl fmt::Debug for QuinnConnection { } } -impl Clone for QuinnConnection { +impl Clone for QuinnConnection { fn clone(&self) -> Self { Self { inner: self.inner.clone(), @@ -409,7 +611,7 @@ impl Clone for QuinnConnection { } } -impl ConnectionErrors for QuinnConnection { +impl ConnectionErrors for QuinnConnection { type SendError = io::Error; type RecvError = io::Error; @@ -417,13 +619,13 @@ impl ConnectionErrors for QuinnConnection ConnectionCommon for QuinnConnection { - type SendSink = self::SendSink; - type RecvStream = self::RecvStream; +impl ConnectionCommon for QuinnConnection { + type SendSink = self::SendSink; + type RecvStream = self::RecvStream; } -impl Connection for QuinnConnection { - type OpenBiFut = OpenBiFuture; +impl Connection for QuinnConnection { + type OpenBiFut = OpenBiFuture; fn open_bi(&self) -> Self::OpenBiFut { let (sender, receiver) = oneshot::channel(); @@ -469,25 +671,25 @@ impl Sink for SendSink { self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - self.project().0.poll_ready_unpin(cx) + Pin::new(&mut self.project().0).poll_ready(cx) } fn start_send(self: Pin<&mut Self>, item: Out) -> Result<(), Self::Error> { - self.project().0.start_send_unpin(item) + Pin::new(&mut self.project().0).start_send(item) } fn poll_flush( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - self.project().0.poll_flush_unpin(cx) + Pin::new(&mut self.project().0).poll_flush(cx) } fn poll_close( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - self.project().0.poll_close_unpin(cx) + Pin::new(&mut self.project().0).poll_close(cx) } } @@ -526,7 +728,7 @@ impl Stream for RecvStream { self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - self.project().0.poll_next_unpin(cx) + Pin::new(&mut self.project().0).poll_next(cx) } } @@ -574,7 +776,7 @@ impl Future for OpenBiFuture { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.0.take() { - OpenBiFutureState::Sending(mut fut, recever) => match fut.poll_unpin(cx) { + OpenBiFutureState::Sending(mut fut, recever) => match Pin::new(&mut fut).poll(cx) { Poll::Ready(Ok(_)) => { self.0 = OpenBiFutureState::Receiving(recever); self.poll(cx) @@ -585,7 +787,7 @@ impl Future for OpenBiFuture { } Poll::Ready(Err(_)) => Poll::Ready(Err(quinn::ConnectionError::LocallyClosed)), }, - OpenBiFutureState::Receiving(mut fut) => match fut.poll_unpin(cx) { + OpenBiFutureState::Receiving(mut fut) => match Pin::new(&mut fut).poll(cx) { Poll::Ready(Ok(Ok((send, recv)))) => { let send = SendSink::new(send); let recv = RecvStream::new(recv); @@ -623,7 +825,7 @@ impl Future for AcceptBiFuture { self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll { - self.project().0.poll_unpin(cx).map(|conn| { + Pin::new(&mut self.project().0).poll(cx).map(|conn| { let (send, recv) = conn.map_err(|e| { tracing::warn!("accept_bi: error receiving connection: {}", e); quinn::ConnectionError::LocallyClosed diff --git a/src/transport/util.rs b/src/transport/util.rs index e477830..3c4073b 100644 --- a/src/transport/util.rs +++ b/src/transport/util.rs @@ -4,7 +4,8 @@ use std::{ }; use bincode::Options; -use futures::{Sink, SinkExt, Stream, StreamExt}; +use futures_lite::Stream; +use futures_sink::Sink; use pin_project::pin_project; use serde::{de::DeserializeOwned, Serialize}; use tokio::io::{AsyncRead, AsyncWrite}; @@ -57,7 +58,7 @@ impl Stream for FramedBincodeRead { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - self.project().0.poll_next_unpin(cx) + Pin::new(&mut self.project().0).poll_next(cx) } } @@ -109,25 +110,25 @@ impl Sink for FramedBincodeWrite { self: Pin<&mut Self>, cx: &mut task::Context<'_>, ) -> Poll> { - self.project().0.poll_ready_unpin(cx) + Pin::new(&mut self.project().0).poll_ready(cx) } fn start_send(self: Pin<&mut Self>, item: Out) -> Result<(), Self::Error> { - self.project().0.start_send_unpin(item) + Pin::new(&mut self.project().0).start_send(item) } fn poll_flush( self: Pin<&mut Self>, cx: &mut task::Context<'_>, ) -> Poll> { - self.project().0.poll_flush_unpin(cx) + Pin::new(&mut self.project().0).poll_flush(cx) } fn poll_close( self: Pin<&mut Self>, cx: &mut task::Context<'_>, ) -> Poll> { - self.project().0.poll_close_unpin(cx) + Pin::new(&mut self.project().0).poll_close(cx) } } diff --git a/tests/flume.rs b/tests/flume.rs index 13a633b..219a229 100644 --- a/tests/flume.rs +++ b/tests/flume.rs @@ -1,12 +1,17 @@ #![cfg(feature = "flume-transport")] +#![allow(non_local_definitions)] mod math; use math::*; -use quic_rpc::{server::RpcServerError, transport::flume, RpcClient, RpcServer}; +use quic_rpc::{ + server::{RpcChannel, RpcServerError}, + transport::flume, + RpcClient, RpcServer, Service, +}; #[tokio::test] async fn flume_channel_bench() -> anyhow::Result<()> { tracing_subscriber::fmt::try_init().ok(); - let (server, client) = flume::connection::(1); + let (server, client) = flume::connection::(1); let server = RpcServer::::new(server); let server_handle = tokio::task::spawn(ComputeService::server(server)); @@ -20,11 +25,80 @@ async fn flume_channel_bench() -> anyhow::Result<()> { Ok(()) } +#[tokio::test] +async fn flume_channel_mapped_bench() -> anyhow::Result<()> { + use derive_more::{From, TryInto}; + use serde::{Deserialize, Serialize}; + + tracing_subscriber::fmt::try_init().ok(); + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + enum OuterRequest { + Inner(InnerRequest), + } + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + enum InnerRequest { + Compute(ComputeRequest), + } + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + enum OuterResponse { + Inner(InnerResponse), + } + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + enum InnerResponse { + Compute(ComputeResponse), + } + #[derive(Debug, Clone)] + struct OuterService; + impl Service for OuterService { + type Req = OuterRequest; + type Res = OuterResponse; + } + #[derive(Debug, Clone)] + struct InnerService; + impl Service for InnerService { + type Req = InnerRequest; + type Res = InnerResponse; + } + let (server, client) = flume::connection::(1); + + let server = RpcServer::new(server); + let server_handle: tokio::task::JoinHandle>> = + tokio::task::spawn(async move { + let service = ComputeService; + loop { + let (req, chan) = server.accept().await?; + let service = service.clone(); + tokio::spawn(async move { + let req: OuterRequest = req; + match req { + OuterRequest::Inner(InnerRequest::Compute(req)) => { + let chan: RpcChannel = chan.map(); + let chan: RpcChannel = chan.map(); + ComputeService::handle_rpc_request(service, req, chan).await + } + } + }); + } + }); + + let client = RpcClient::::new(client); + let client: RpcClient = client.map(); + let client: RpcClient = client.map(); + bench(client, 1000000).await?; + // dropping the client will cause the server to terminate + match server_handle.await? { + Err(RpcServerError::Accept(_)) => {} + e => panic!("unexpected termination result {e:?}"), + } + Ok(()) +} + /// simple happy path test for all 4 patterns #[tokio::test] async fn flume_channel_smoke() -> anyhow::Result<()> { tracing_subscriber::fmt::try_init().ok(); - let (server, client) = flume::connection::(1); + let (server, client) = flume::connection::(1); let server = RpcServer::::new(server); let server_handle = tokio::task::spawn(ComputeService::server(server)); diff --git a/tests/hyper.rs b/tests/hyper.rs index 94a72c7..3ccd00d 100644 --- a/tests/hyper.rs +++ b/tests/hyper.rs @@ -1,13 +1,10 @@ #![cfg(feature = "hyper-transport")] -#![allow(clippy::redundant_pattern_matching)] - -use std::{net::SocketAddr, result}; +use std::{assert, net::SocketAddr, result}; use ::hyper::Uri; use derive_more::{From, TryInto}; use flume::Receiver; use quic_rpc::{ - client::RpcClientError, declare_rpc, server::RpcServerError, transport::hyper::{self, HyperConnection, HyperServerEndpoint, RecvError}, @@ -21,8 +18,8 @@ use math::*; mod util; fn run_server(addr: &SocketAddr) -> JoinHandle> { - let channel = HyperServerEndpoint::::serve(addr).unwrap(); - let server = RpcServer::::new(channel); + let channel = HyperServerEndpoint::::serve(addr).unwrap(); + let server = RpcServer::new(channel); tokio::spawn(async move { loop { let server = server.clone(); @@ -33,13 +30,112 @@ fn run_server(addr: &SocketAddr) -> JoinHandle> { }) } +#[derive(Debug, Serialize, Deserialize, From, TryInto)] +enum TestResponse { + Unit(()), + Big(Vec), + NoSer(NoSer), + NoDeser(NoDeser), +} + +type SC = HyperServerEndpoint; + +/// request that can be too big +#[derive(Debug, Serialize, Deserialize)] +pub struct BigRequest(Vec); + +/// request that looks serializable but isn't +#[derive(Debug, Serialize, Deserialize)] +pub struct NoSerRequest(NoSer); + +/// request that looks deserializable but isn't +#[derive(Debug, Serialize, Deserialize)] +pub struct NoDeserRequest(NoDeser); + +/// request where the response is not serializable +#[derive(Debug, Serialize, Deserialize)] +pub struct NoSerResponseRequest; + +/// request where the response is not deserializable +#[derive(Debug, Serialize, Deserialize)] +pub struct NoDeserResponseRequest; + +/// request that can produce a response that is too big +#[derive(Debug, Serialize, Deserialize)] +pub struct BigResponseRequest(usize); + +/// helper struct that implements serde::Serialize but errors on serialization +#[derive(Debug, Deserialize)] +pub struct NoSer; + +impl serde::Serialize for NoSer { + fn serialize(&self, _serializer: S) -> Result + where + S: serde::Serializer, + { + Err(serde::ser::Error::custom("nope")) + } +} + +/// helper struct that implements serde::Deserialize but errors on deserialization +#[derive(Debug, Serialize)] +pub struct NoDeser; + +impl<'de> serde::Deserialize<'de> for NoDeser { + fn deserialize(_deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Err(serde::de::Error::custom("nope")) + } +} + +#[allow(clippy::enum_variant_names)] +#[derive(Debug, Serialize, Deserialize, From, TryInto)] +enum TestRequest { + BigRequest(BigRequest), + NoSerRequest(NoSerRequest), + NoDeserRequest(NoDeserRequest), + NoSerResponseRequest(NoSerResponseRequest), + NoDeserResponseRequest(NoDeserResponseRequest), + BigResponseRequest(BigResponseRequest), +} + +#[derive(Debug, Clone)] +struct TestService; + +impl Service for TestService { + type Req = TestRequest; + type Res = TestResponse; +} + +impl TestService { + async fn big(self, _req: BigRequest) {} + + async fn noser(self, _req: NoSerRequest) {} + + async fn nodeser(self, _req: NoDeserRequest) {} + + async fn noserresponse(self, _req: NoSerResponseRequest) -> NoSer { + NoSer + } + + async fn nodeserresponse(self, _req: NoDeserResponseRequest) -> NoDeser { + NoDeser + } + + async fn bigresponse(self, req: BigResponseRequest) -> Vec { + vec![0; req.0] + } +} + #[tokio::test] async fn hyper_channel_bench() -> anyhow::Result<()> { let addr: SocketAddr = "127.0.0.1:3000".parse()?; let uri: Uri = "http://127.0.0.1:3000".parse()?; let server_handle = run_server(&addr); - let client = HyperConnection::new(uri); - let client = RpcClient::::new(client); + let client = HyperConnection::::new(uri); + let client = RpcClient::new(client); bench(client, 50000).await?; println!("terminating server"); server_handle.abort(); @@ -52,121 +148,22 @@ async fn hyper_channel_smoke() -> anyhow::Result<()> { let addr: SocketAddr = "127.0.0.1:3001".parse()?; let uri: Uri = "http://127.0.0.1:3001".parse()?; let server_handle = run_server(&addr); - let client = HyperConnection::new(uri); + let client = HyperConnection::::new(uri); smoke_test(client).await?; server_handle.abort(); let _ = server_handle.await; Ok(()) } +declare_rpc!(TestService, BigRequest, ()); +declare_rpc!(TestService, NoSerRequest, ()); +declare_rpc!(TestService, NoDeserRequest, ()); +declare_rpc!(TestService, NoSerResponseRequest, NoSer); +declare_rpc!(TestService, NoDeserResponseRequest, NoDeser); +declare_rpc!(TestService, BigResponseRequest, Vec); + #[tokio::test] async fn hyper_channel_errors() -> anyhow::Result<()> { - type SC = HyperServerEndpoint; - - /// request that can be too big - #[derive(Debug, Serialize, Deserialize)] - pub struct BigRequest(Vec); - - /// request that looks serializable but isn't - #[derive(Debug, Serialize, Deserialize)] - pub struct NoSerRequest(NoSer); - - /// request that looks deserializable but isn't - #[derive(Debug, Serialize, Deserialize)] - pub struct NoDeserRequest(NoDeser); - - /// request where the response is not serializable - #[derive(Debug, Serialize, Deserialize)] - pub struct NoSerResponseRequest; - - /// request where the response is not deserializable - #[derive(Debug, Serialize, Deserialize)] - pub struct NoDeserResponseRequest; - - /// request that can produce a response that is too big - #[derive(Debug, Serialize, Deserialize)] - pub struct BigResponseRequest(usize); - - /// helper struct that implements serde::Serialize but errors on serialization - #[derive(Debug, Deserialize)] - pub struct NoSer; - - impl serde::Serialize for NoSer { - fn serialize(&self, _serializer: S) -> Result - where - S: serde::Serializer, - { - Err(serde::ser::Error::custom("nope")) - } - } - - /// helper struct that implements serde::Deserialize but errors on deserialization - #[derive(Debug, Serialize)] - pub struct NoDeser; - - impl<'de> serde::Deserialize<'de> for NoDeser { - fn deserialize(_deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Err(serde::de::Error::custom("nope")) - } - } - - #[allow(clippy::enum_variant_names)] - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - enum TestRequest { - BigRequest(BigRequest), - NoSerRequest(NoSerRequest), - NoDeserRequest(NoDeserRequest), - NoSerResponseRequest(NoSerResponseRequest), - NoDeserResponseRequest(NoDeserResponseRequest), - BigResponseRequest(BigResponseRequest), - } - - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - enum TestResponse { - Unit(()), - Big(Vec), - NoSer(NoSer), - NoDeser(NoDeser), - } - - #[derive(Debug, Clone)] - struct TestService; - - impl Service for TestService { - type Req = TestRequest; - type Res = TestResponse; - } - - impl TestService { - async fn big(self, _req: BigRequest) {} - - async fn noser(self, _req: NoSerRequest) {} - - async fn nodeser(self, _req: NoDeserRequest) {} - - async fn noserresponse(self, _req: NoSerResponseRequest) -> NoSer { - NoSer - } - - async fn nodeserresponse(self, _req: NoDeserResponseRequest) -> NoDeser { - NoDeser - } - - async fn bigresponse(self, req: BigResponseRequest) -> Vec { - vec![0; req.0] - } - } - - declare_rpc!(TestService, BigRequest, ()); - declare_rpc!(TestService, NoSerRequest, ()); - declare_rpc!(TestService, NoDeserRequest, ()); - declare_rpc!(TestService, NoSerResponseRequest, NoSer); - declare_rpc!(TestService, NoDeserResponseRequest, NoDeser); - declare_rpc!(TestService, BigResponseRequest, Vec); - #[allow(clippy::type_complexity)] fn run_test_server( addr: &SocketAddr, @@ -174,8 +171,8 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { JoinHandle>, Receiver>>, ) { - let channel = HyperServerEndpoint::serve(addr).unwrap(); - let server = RpcServer::::new(channel); + let channel = HyperServerEndpoint::::serve(addr).unwrap(); + let server = RpcServer::new(channel); let (res_tx, res_rx) = flume::unbounded(); let handle = tokio::spawn(async move { loop { @@ -215,8 +212,8 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { let addr: SocketAddr = "127.0.0.1:3002".parse()?; let uri: Uri = "http://127.0.0.1:3002".parse()?; let (server_handle, server_results) = run_test_server(&addr); - let client = HyperConnection::new(uri); - let client = RpcClient::::new(client); + let client = HyperConnection::::new(uri); + let client = RpcClient::new(client); macro_rules! assert_matches { ($e:expr, $p:pat) => { @@ -249,7 +246,9 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { let res = client.rpc(BigRequest(vec![0; 20_000_000])).await; assert_matches!( res, - Err(RpcClientError::Send(hyper::SendError::SizeError(_))) + Err(quic_rpc::pattern::rpc::Error::Send( + hyper::SendError::SizeError(_) + )) ); assert_server_result!(Err(RpcServerError::EarlyClose)); @@ -257,20 +256,22 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { let res = client.rpc(NoSerRequest(NoSer)).await; assert_matches!( res, - Err(RpcClientError::Send(hyper::SendError::SerializeError(_))) + Err(quic_rpc::pattern::rpc::Error::Send( + hyper::SendError::SerializeError(_) + )) ); assert_server_result!(Err(RpcServerError::EarlyClose)); // not deserializable - should fail on the server side let res = client.rpc(NoDeserRequest(NoDeser)).await; - assert_matches!(res, Err(RpcClientError::EarlyClose)); + assert_matches!(res, Err(quic_rpc::pattern::rpc::Error::EarlyClose)); assert_server_result!(Err(RpcServerError::RecvError( hyper::RecvError::DeserializeError(_) ))); // response not serializable - should fail on the server side let res = client.rpc(NoSerResponseRequest).await; - assert_matches!(res, Err(RpcClientError::EarlyClose)); + assert_matches!(res, Err(quic_rpc::pattern::rpc::Error::EarlyClose)); assert_server_result!(Err(RpcServerError::SendError( hyper::SendError::SerializeError(_) ))); @@ -279,18 +280,20 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { let res = client.rpc(NoDeserResponseRequest).await; assert_matches!( res, - Err(RpcClientError::RecvError(RecvError::DeserializeError(_))) + Err(quic_rpc::pattern::rpc::Error::RecvError( + RecvError::DeserializeError(_) + )) ); assert_server_result!(Ok(())); // response small - should succeed let res = client.rpc(BigResponseRequest(10_000_000)).await; - assert_matches!(res, Ok(_)); + assert!(res.is_ok()); assert_server_result!(Ok(())); // response big - should fail let res = client.rpc(BigResponseRequest(20_000_000)).await; - assert_matches!(res, Err(RpcClientError::EarlyClose)); + assert_matches!(res, Err(quic_rpc::pattern::rpc::Error::EarlyClose)); assert_server_result!(Err(RpcServerError::SendError(hyper::SendError::SizeError( _ )))); diff --git a/tests/interprocess.rs b/tests/interprocess.rs index 23deeb1..6cb953c 100644 --- a/tests/interprocess.rs +++ b/tests/interprocess.rs @@ -4,14 +4,14 @@ use std::{ sync::Arc, }; -use futures::{io::BufReader, AsyncBufReadExt, AsyncWriteExt as _}; +use interprocess::local_socket::ListenerOptions; use quic_rpc::{ transport::interprocess::{new_socket_name, tokio_io_endpoint}, RpcClient, RpcServer, }; use quinn::{ClientConfig, Endpoint, ServerConfig}; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::task::JoinHandle; -use tokio_util::compat::{FuturesAsyncReadCompatExt, FuturesAsyncWriteCompatExt}; mod math; use math::*; @@ -104,7 +104,8 @@ pub fn make_endpoints() -> anyhow::Result { fn run_server(server: quinn::Endpoint) -> JoinHandle> { tokio::task::spawn(async move { - let connection = quic_rpc::transport::quinn::QuinnServerEndpoint::new(server)?; + let connection = + quic_rpc::transport::quinn::QuinnServerEndpoint::::new(server)?; let server = RpcServer::::new(connection); ComputeService::server(server).await?; anyhow::Ok(()) @@ -165,8 +166,11 @@ async fn quinn_flume_channel_bench() -> anyhow::Result<()> { tracing::debug!("Starting server"); let server_handle = run_server(server); tracing::debug!("Starting client"); - let client = - quic_rpc::transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); + let client = quic_rpc::transport::quinn::QuinnConnection::::new( + client, + server_addr, + "localhost".into(), + ); let client = RpcClient::::new(client); tracing::debug!("Starting benchmark"); bench(client, 50000).await?; @@ -183,8 +187,11 @@ async fn quinn_flume_channel_smoke() -> anyhow::Result<()> { server_addr, } = make_endpoints()?; let server_handle = run_server(server); - let client_connection = - quic_rpc::transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); + let client_connection = quic_rpc::transport::quinn::QuinnConnection::::new( + client, + server_addr, + "localhost".into(), + ); smoke_test(client_connection).await?; server_handle.abort(); Ok(()) @@ -195,10 +202,12 @@ async fn quinn_flume_channel_smoke() -> anyhow::Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn interprocess_accept_connect_raw() -> anyhow::Result<()> { tracing_subscriber::fmt::try_init().ok(); - use interprocess::local_socket::tokio::*; + use interprocess::local_socket::tokio::prelude::*; let dir = tempfile::tempdir()?; - let socket_name = new_socket_name(dir.path(), "interprocess-accept-connect-raw"); - let socket = LocalSocketListener::bind(socket_name.clone())?; + let socket_name = new_socket_name(dir.path(), "interprocess-accept-connect-raw")?; + let socket = ListenerOptions::new() + .name(socket_name.clone()) + .create_tokio()?; let socket_name_2 = socket_name.clone(); let server = tokio::spawn(async move { tracing::info!("server: spawn"); @@ -249,20 +258,20 @@ async fn interprocess_accept_connect_raw() -> anyhow::Result<()> { #[tokio::test] async fn interprocess_quinn_accept_connect_raw() -> anyhow::Result<()> { tracing_subscriber::fmt::try_init().ok(); - use interprocess::local_socket::tokio::*; + use interprocess::local_socket::tokio::prelude::*; let (server_config, server_certs) = configure_server()?; let client_config = configure_client(&[&server_certs])?; let dir = tempfile::tempdir()?; - let socket_name = new_socket_name(dir.path(), "interprocess-quinn-accet-connect-raw"); - let socket = LocalSocketListener::bind(socket_name.clone())?; + let socket_name = new_socket_name(dir.path(), "interprocess-quinn-accet-connect-raw")?; + let socket = ListenerOptions::new() + .name(socket_name.clone()) + .create_tokio()?; let local = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1).into(); let remote = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 2).into(); let server = tokio::spawn(async move { let stream = socket.accept().await?; let (r, w) = stream.split(); - let r = r.compat(); - let w = w.compat_write(); let (endpoint, _s, _r) = tokio_io_endpoint(r, w, remote, local, Some(server_config))?; tracing::debug!("server accepting connection"); let connection = endpoint.accept().await.unwrap().await?; @@ -278,8 +287,6 @@ async fn interprocess_quinn_accept_connect_raw() -> anyhow::Result<()> { let client = tokio::spawn(async move { let stream = LocalSocketStream::connect(socket_name).await?; let (r, w) = stream.split(); - let r = r.compat(); - let w = w.compat_write(); let (mut endpoint, _s, _r) = tokio_io_endpoint(r, w, local, remote, None)?; endpoint.set_default_client_config(client_config); tracing::debug!("client connecting to server at {} using localhost", remote); @@ -313,20 +320,20 @@ async fn interprocess_quinn_accept_connect_raw() -> anyhow::Result<()> { #[tokio::test] async fn interprocess_quinn_smoke() -> anyhow::Result<()> { tracing_subscriber::fmt::try_init().ok(); - use interprocess::local_socket::tokio::*; + use interprocess::local_socket::tokio::prelude::*; let (server_config, server_certs) = configure_server()?; let client_config = configure_client(&[&server_certs])?; let dir = tempfile::tempdir()?; - let socket_name = new_socket_name(dir.path(), "interprocess-quinn-smoke"); - let socket = LocalSocketListener::bind(socket_name.clone())?; + let socket_name = new_socket_name(dir.path(), "interprocess-quinn-smoke")?; + let socket = ListenerOptions::new() + .name(socket_name.clone()) + .create_tokio()?; let local = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1).into(); let remote = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 2).into(); let server = tokio::spawn(async move { let stream = socket.accept().await?; let (r, w) = stream.split(); - let r = r.compat(); - let w = w.compat_write(); let (endpoint, _s, _r) = tokio_io_endpoint(r, w, remote, local, Some(server_config))?; // run rpc server on endpoint tracing::debug!("creating test rpc server"); @@ -336,15 +343,13 @@ async fn interprocess_quinn_smoke() -> anyhow::Result<()> { let client = tokio::spawn(async move { let stream = LocalSocketStream::connect(socket_name).await?; let (r, w) = stream.split(); - let r = r.compat(); - let w = w.compat_write(); let (mut endpoint, _s, _r) = tokio_io_endpoint(r, w, local, remote, None)?; endpoint.set_default_client_config(client_config); tracing::debug!( "creating test rpc client, connecting to server at {} using localhost", remote ); - let client: quic_rpc::transport::quinn::QuinnConnection = + let client: quic_rpc::transport::quinn::QuinnConnection = quic_rpc::transport::quinn::QuinnConnection::new(endpoint, remote, "localhost".into()); smoke_test(client).await?; anyhow::Ok(()) @@ -358,20 +363,20 @@ async fn interprocess_quinn_smoke() -> anyhow::Result<()> { #[tokio::test] async fn interprocess_quinn_bench() -> anyhow::Result<()> { tracing_subscriber::fmt::try_init().ok(); - use interprocess::local_socket::tokio::*; + use interprocess::local_socket::tokio::prelude::*; let (server_config, server_certs) = configure_server()?; let client_config = configure_client(&[&server_certs])?; let dir = tempfile::tempdir()?; - let socket_name = new_socket_name(dir.path(), "interprocess-quinn-bench"); - let socket = LocalSocketListener::bind(socket_name.clone())?; + let socket_name = new_socket_name(dir.path(), "interprocess-quinn-bench")?; + let socket = ListenerOptions::new() + .name(socket_name.clone()) + .create_tokio()?; let local = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1).into(); let remote = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 2).into(); let server = tokio::spawn(async move { let stream = socket.accept().await?; let (r, w) = stream.split(); - let r = r.compat(); - let w = w.compat_write(); let (endpoint, _s, _r) = tokio_io_endpoint(r, w, remote, local, Some(server_config))?; // run rpc server on endpoint tracing::debug!("creating test rpc server"); @@ -381,15 +386,13 @@ async fn interprocess_quinn_bench() -> anyhow::Result<()> { let client = tokio::spawn(async move { let stream = LocalSocketStream::connect(socket_name).await?; let (r, w) = stream.split(); - let r = r.compat(); - let w = w.compat_write(); let (mut endpoint, _s, _r) = tokio_io_endpoint(r, w, local, remote, None)?; endpoint.set_default_client_config(client_config); tracing::debug!( "creating test rpc client, connecting to server at {} using localhost", remote ); - let client: quic_rpc::transport::quinn::QuinnConnection = + let client: quic_rpc::transport::quinn::QuinnConnection = quic_rpc::transport::quinn::QuinnConnection::new(endpoint, remote, "localhost".into()); let client = RpcClient::new(client); bench(client, 50000).await?; diff --git a/tests/math.rs b/tests/math.rs index 13b854b..aa92e08 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -7,13 +7,15 @@ #![allow(dead_code)] use async_stream::stream; use derive_more::{From, TryInto}; -use futures::{SinkExt, Stream, StreamExt, TryStreamExt}; +use futures_buffered::BufferedStreamExt; +use futures_lite::{Stream, StreamExt}; +use futures_util::SinkExt; use quic_rpc::{ message::{ BidiStreaming, BidiStreamingMsg, ClientStreaming, ClientStreamingMsg, Msg, RpcMsg, ServerStreaming, ServerStreamingMsg, }, - server::RpcServerError, + server::{RpcChannel, RpcServerError}, RpcClient, RpcServer, Service, ServiceConnection, ServiceEndpoint, }; use serde::{Deserialize, Serialize}; @@ -165,10 +167,50 @@ impl ComputeService { let s = server; let service = ComputeService; loop { + let (req, chan) = s.accept().await?; + let service = service.clone(); + tokio::spawn(async move { Self::handle_rpc_request(service, req, chan).await }); + } + } + + pub async fn handle_rpc_request( + service: ComputeService, + req: ComputeRequest, + chan: RpcChannel, + ) -> Result<(), RpcServerError> + where + S: Service, + E: ServiceEndpoint, + { + use ComputeRequest::*; + #[rustfmt::skip] + match req { + Sqr(msg) => chan.rpc(msg, service, ComputeService::sqr).await, + Sum(msg) => chan.client_streaming(msg, service, ComputeService::sum).await, + Fibonacci(msg) => chan.server_streaming(msg, service, ComputeService::fibonacci).await, + Multiply(msg) => chan.bidi_streaming(msg, service, ComputeService::multiply).await, + MultiplyUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, + SumUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, + }?; + Ok(()) + } + + /// Runs the service until `count` requests have been received. + pub async fn server_bounded>( + server: RpcServer, + count: usize, + ) -> result::Result, RpcServerError> { + tracing::info!(%count, "server running"); + let s = server; + let mut received = 0; + let service = ComputeService; + while received < count { + received += 1; let (req, chan) = s.accept().await?; let service = service.clone(); tokio::spawn(async move { use ComputeRequest::*; + tracing::info!(?req, "got request"); #[rustfmt::skip] match req { Sqr(msg) => chan.rpc(msg, service, ComputeService::sqr).await, @@ -181,6 +223,8 @@ impl ComputeService { Ok::<_, RpcServerError>(()) }); } + tracing::info!(%count, "server finished"); + Ok(s) } pub async fn server_par>( @@ -213,8 +257,8 @@ impl ComputeService { } }); process_stream - .buffer_unordered(parallelism) - .for_each(|x| async move { + .buffered_unordered(parallelism) + .for_each(|x| { if let Err(e) = x { eprintln!("error: {e:?}"); } @@ -248,7 +292,7 @@ pub async fn smoke_test>(client: C) -> anyh // server streaming call tracing::debug!("calling server_streaming Fibonacci(10)"); let s = client.server_streaming(Fibonacci(10)).await?; - let res = s.map_ok(|x| x.0).try_collect::>().await?; + let res: Vec<_> = s.map(|x| x.map(|x| x.0)).try_collect().await?; tracing::debug!("got response {:?}", res); assert_eq!(res, vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]); @@ -261,7 +305,7 @@ pub async fn smoke_test>(client: C) -> anyh } Ok::<_, C::SendError>(()) }); - let res = recv.map_ok(|x| x.0).try_collect::>().await?; + let res: Vec<_> = recv.map(|x| x.map(|x| x.0)).try_collect().await?; tracing::debug!("got response {:?}", res); assert_eq!(res, vec![2, 4, 6]); @@ -273,12 +317,11 @@ fn clear_line() { print!("\r{}\r", " ".repeat(80)); } -pub async fn bench>( - client: RpcClient, - n: u64, -) -> anyhow::Result<()> +pub async fn bench(client: RpcClient, n: u64) -> anyhow::Result<()> where C::SendError: std::error::Error, + S: Service, + C: ServiceConnection, { // individual RPCs { @@ -299,8 +342,8 @@ where // parallel RPCs { let t0 = std::time::Instant::now(); - let reqs = futures::stream::iter((0..n).map(Sqr)); - let resp = reqs + let reqs = futures_lite::stream::iter((0..n).map(Sqr)); + let resp: Vec<_> = reqs .map(|x| { let client = client.clone(); async move { @@ -308,8 +351,8 @@ where anyhow::Ok(res) } }) - .buffer_unordered(32) - .try_collect::>() + .buffered_unordered(32) + .try_collect() .await?; let sum = resp.into_iter().sum::(); let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round(); @@ -322,8 +365,8 @@ where let t0 = std::time::Instant::now(); let (send, recv) = client.bidi(Multiply(2)).await?; let handle = tokio::task::spawn(async move { - let requests = futures::stream::iter((0..n).map(MultiplyUpdate)); - requests.map(Ok).forward(send).await?; + let requests = futures_lite::stream::iter((0..n).map(MultiplyUpdate)); + futures_util::StreamExt::forward(requests.map(Ok), send).await?; anyhow::Result::<()>::Ok(()) }); let mut sum = 0; diff --git a/tests/quinn.rs b/tests/quinn.rs index a5a0126..99eeaba 100644 --- a/tests/quinn.rs +++ b/tests/quinn.rs @@ -4,7 +4,7 @@ use std::{ sync::Arc, }; -use quic_rpc::{RpcClient, RpcServer}; +use quic_rpc::{transport, RpcClient, RpcServer}; use quinn::{ClientConfig, Endpoint, ServerConfig}; use tokio::task::JoinHandle; @@ -91,8 +91,8 @@ pub fn make_endpoints(port: u16) -> anyhow::Result { fn run_server(server: quinn::Endpoint) -> JoinHandle> { tokio::task::spawn(async move { - let connection = quic_rpc::transport::quinn::QuinnServerEndpoint::new(server)?; - let server = RpcServer::::new(connection); + let connection = transport::quinn::QuinnServerEndpoint::::new(server)?; + let server = RpcServer::new(connection); ComputeService::server(server).await?; anyhow::Ok(()) }) @@ -110,9 +110,12 @@ async fn quinn_channel_bench() -> anyhow::Result<()> { tracing::debug!("Starting server"); let server_handle = run_server(server); tracing::debug!("Starting client"); - let client = - quic_rpc::transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); - let client = RpcClient::::new(client); + let client = transport::quinn::QuinnConnection::::new( + client, + server_addr, + "localhost".into(), + ); + let client = RpcClient::new(client); tracing::debug!("Starting benchmark"); bench(client, 50000).await?; server_handle.abort(); @@ -128,9 +131,65 @@ async fn quinn_channel_smoke() -> anyhow::Result<()> { server_addr, } = make_endpoints(12346)?; let server_handle = run_server(server); - let client_connection = - quic_rpc::transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); + let client_connection = transport::quinn::QuinnConnection::::new( + client, + server_addr, + "localhost".into(), + ); smoke_test(client_connection).await?; server_handle.abort(); Ok(()) } + +/// Test that using the client after the server goes away and comes back behaves as if the server +/// had never gone away in the first place. +/// +/// This is a regression test. +#[tokio::test] +async fn server_away_and_back() -> anyhow::Result<()> { + tracing_subscriber::fmt::try_init().ok(); + tracing::info!("Creating endpoints"); + + let server_addr: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 12347)); + let (server_config, server_cert) = configure_server()?; + + // create the RPC client + let client = make_client_endpoint("0.0.0.0:0".parse()?, &[&server_cert])?; + let client_connection = transport::quinn::QuinnConnection::::new( + client, + server_addr, + "localhost".into(), + ); + let client = RpcClient::new(client_connection); + + // send a request. No server available so it should fail + client.rpc(Sqr(4)).await.unwrap_err(); + + // create the RPC Server + let server = Endpoint::server(server_config.clone(), server_addr)?; + let connection = transport::quinn::QuinnServerEndpoint::::new(server)?; + let server = RpcServer::new(connection); + let server_handle = tokio::task::spawn(ComputeService::server_bounded(server, 1)); + + // send the first request and wait for the response to ensure everything works as expected + let SqrResponse(response) = client.rpc(Sqr(4)).await.unwrap(); + assert_eq!(response, 16); + + let server = server_handle.await.unwrap().unwrap(); + drop(server); + // wait for drop to free the socket + tokio::time::sleep(tokio::time::Duration::from_millis(300)).await; + + // make the server run again + let server = Endpoint::server(server_config, server_addr)?; + let connection = transport::quinn::QuinnServerEndpoint::::new(server)?; + let server = RpcServer::new(connection); + let server_handle = tokio::task::spawn(ComputeService::server_bounded(server, 5)); + + // server is running, this should work + let SqrResponse(response) = client.rpc(Sqr(3)).await.unwrap(); + assert_eq!(response, 9); + + server_handle.abort(); + Ok(()) +} diff --git a/tests/slow_math.rs b/tests/slow_math.rs index e68ec10..cd4da46 100644 --- a/tests/slow_math.rs +++ b/tests/slow_math.rs @@ -7,7 +7,7 @@ mod math; use std::result; use async_stream::stream; -use futures::{Stream, StreamExt}; +use futures_lite::{Stream, StreamExt}; use math::*; use quic_rpc::{ message::{ diff --git a/tests/try.rs b/tests/try.rs new file mode 100644 index 0000000..3c1e59f --- /dev/null +++ b/tests/try.rs @@ -0,0 +1,103 @@ +#![cfg(feature = "flume-transport")] +use derive_more::{From, TryInto}; +use futures_lite::{Stream, StreamExt}; +use quic_rpc::{ + message::Msg, + pattern::try_server_streaming::{StreamCreated, TryServerStreaming, TryServerStreamingMsg}, + server::RpcServerError, + transport::flume, + RpcClient, RpcServer, Service, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone)] +struct TryService; + +impl Service for TryService { + type Req = TryRequest; + type Res = TryResponse; +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct StreamN { + n: u64, +} + +impl Msg for StreamN { + type Pattern = TryServerStreaming; +} + +impl TryServerStreamingMsg for StreamN { + type Item = u64; + type ItemError = String; + type CreateError = String; +} + +/// request enum +#[derive(Debug, Serialize, Deserialize, From, TryInto)] +pub enum TryRequest { + StreamN(StreamN), +} + +#[derive(Debug, Serialize, Deserialize, From, TryInto, Clone)] +pub enum TryResponse { + StreamN(std::result::Result), + StreamNError(std::result::Result), +} + +#[derive(Clone)] +struct Handler; + +impl Handler { + async fn try_stream_n( + self, + req: StreamN, + ) -> std::result::Result>, String> { + if req.n % 2 != 0 { + return Err("odd n not allowed".to_string()); + } + let stream = async_stream::stream! { + for i in 0..req.n { + if i > 5 { + yield Err("n too large".to_string()); + return; + } + yield Ok(i); + } + }; + Ok(stream) + } +} + +#[tokio::test] +async fn try_server_streaming() -> anyhow::Result<()> { + tracing_subscriber::fmt::try_init().ok(); + let (server, client) = flume::connection::(1); + + let server = RpcServer::::new(server); + let server_handle = tokio::task::spawn(async move { + loop { + let (req, chan) = server.accept().await?; + let handler = Handler; + match req { + TryRequest::StreamN(req) => { + chan.try_server_streaming(req, handler, Handler::try_stream_n) + .await?; + } + } + } + #[allow(unreachable_code)] + Ok(()) + }); + let client = RpcClient::::new(client); + let stream_n = client.try_server_streaming(StreamN { n: 10 }).await?; + let items: Vec<_> = stream_n.collect().await; + println!("{:?}", items); + drop(client); + // dropping the client will cause the server to terminate + match server_handle.await? { + Err(RpcServerError::Accept(_)) => {} + e => panic!("unexpected termination result {e:?}"), + } + Ok(()) +}