diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e3662b..ea9e87d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,6 +73,8 @@ jobs: run: cargo check --locked --no-default-features --features spans --bins --tests --examples - name: cargo check (irpc, feature rpc) run: cargo check --locked --no-default-features --features rpc --bins --tests --examples + - name: cargo check (irpc, feature tracing-opentelemetry) + run: cargo check --locked --no-default-features --features tracing-opentelemetry --bins --tests --examples test-release: runs-on: ${{ matrix.target.os }} diff --git a/Cargo.lock b/Cargo.lock index ca14168..7bea929 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,7 +141,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.18", + "thiserror", "time", ] @@ -248,22 +248,22 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "blake3" -version = "1.8.4" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "cpufeatures 0.3.0", + "cpufeatures 0.2.17", ] [[package]] @@ -304,20 +304,14 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.60" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -365,9 +359,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -387,9 +381,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -415,7 +409,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.18", + "thiserror", ] [[package]] @@ -642,15 +636,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "data-encoding-macro" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" +checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -658,9 +652,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" +checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", "syn", @@ -668,9 +662,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.0" +version = "0.8.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +checksum = "02c1d73e9668ea6b6a28172aa55f3ebec38507131ce179051c8033b5c6037653" dependencies = [ "const-oid", "pem-rfc7468", @@ -762,9 +756,9 @@ checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer", "const-oid", @@ -879,9 +873,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fiat-crypto" @@ -1180,12 +1174,6 @@ dependencies = [ "foldhash 0.2.0", ] -[[package]] -name = "hashbrown" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" - [[package]] name = "heapless" version = "0.7.17" @@ -1230,10 +1218,10 @@ dependencies = [ "http", "idna", "ipnet", - "jni 0.22.4", + "jni", "rand 0.10.1", "rustls", - "thiserror 2.0.18", + "thiserror", "tinyvec", "tokio", "tokio-rustls", @@ -1250,12 +1238,12 @@ dependencies = [ "data-encoding", "idna", "ipnet", - "jni 0.22.4", + "jni", "once_cell", "prefix-trie", "rand 0.10.1", "ring", - "thiserror 2.0.18", + "thiserror", "tinyvec", "tracing", "url", @@ -1273,7 +1261,7 @@ dependencies = [ "hickory-proto", "ipconfig", "ipnet", - "jni 0.22.4", + "jni", "moka", "ndk-context", "once_cell", @@ -1283,7 +1271,7 @@ dependencies = [ "rustls", "smallvec", "system-configuration", - "thiserror 2.0.18", + "thiserror", "tokio", "tokio-rustls", "tracing", @@ -1336,18 +1324,18 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.10" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" dependencies = [ "typenum", ] [[package]] name = "hyper" -version = "1.9.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -1360,6 +1348,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -1367,14 +1356,15 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.9" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http", "hyper", "hyper-util", "rustls", + "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -1397,7 +1387,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.3", "tokio", "tower-service", "tracing", @@ -1429,13 +1419,12 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", - "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1443,9 +1432,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -1456,9 +1445,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1470,15 +1459,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -1490,15 +1479,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", @@ -1570,12 +1559,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -1591,15 +1580,14 @@ dependencies = [ [[package]] name = "ipconfig" -version = "0.3.4" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2", + "socket2 0.5.10", "widestring", - "windows-registry", - "windows-result", - "windows-sys 0.61.2", + "windows-sys 0.48.0", + "winreg", ] [[package]] @@ -1613,9 +1601,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.7.12" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -1623,9 +1611,9 @@ dependencies = [ [[package]] name = "iroh" -version = "0.98.0" +version = "0.98.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936882068af6037912c205ad73ae83e1ab9b81f5bd01200210c90f000e24f4d3" +checksum = "9881b221c7c645d90594cbd331012f7cccb914894288a6cf5538a9115f6d0f3e" dependencies = [ "backon", "blake3", @@ -1633,6 +1621,7 @@ dependencies = [ "cfg_aliases", "ctutils", "data-encoding", + "der", "derive_more", "ed25519-dalek", "futures-util", @@ -1794,9 +1783,11 @@ dependencies = [ "futures-buffered", "futures-util", "irpc-derive", + "lazy_static", "n0-error", "n0-future", "noq", + "opentelemetry", "postcard", "rcgen", "rustls", @@ -1807,6 +1798,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "tracing-opentelemetry", "tracing-subscriber", "trybuild", ] @@ -1835,11 +1827,15 @@ dependencies = [ "irpc-derive", "n0-error", "n0-future", + "n0-tracing-test", + "opentelemetry", + "opentelemetry_sdk", "postcard", - "rand 0.9.4", + "rand 0.9.2", "serde", "tokio", "tracing", + "tracing-opentelemetry", "tracing-subscriber", ] @@ -1851,25 +1847,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.18" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jni" @@ -1880,10 +1860,10 @@ dependencies = [ "cfg-if", "combine", "jni-macros", - "jni-sys 0.4.1", + "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.18", + "thiserror", "walkdir", "windows-link", ] @@ -1901,15 +1881,6 @@ dependencies = [ "syn", ] -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - [[package]] name = "jni-sys" version = "0.4.1" @@ -1931,12 +1902,10 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ - "cfg-if", - "futures-util", "once_cell", "wasm-bindgen", ] @@ -1955,15 +1924,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "lock_api" @@ -1995,9 +1964,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.16.4" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ "hashbrown 0.16.1", ] @@ -2037,9 +2006,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.2.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", @@ -2048,9 +2017,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.15" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -2105,6 +2074,27 @@ dependencies = [ "web-time", ] +[[package]] +name = "n0-tracing-test" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "274dc19cfda091561b364e4f61b39aa959ade203232f9794884f1911022e8e59" +dependencies = [ + "n0-tracing-test-macro", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "n0-tracing-test-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab691281e87f2453a860e76dde99157f4464df18cb7cb3eb9c3847161ccc4ce1" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "n0-watcher" version = "0.6.1" @@ -2188,7 +2178,7 @@ dependencies = [ "log", "netlink-packet-core", "netlink-sys", - "thiserror 2.0.18", + "thiserror", ] [[package]] @@ -2229,7 +2219,7 @@ dependencies = [ "objc2-system-configuration", "pin-project-lite", "serde", - "socket2", + "socket2 0.6.3", "time", "tokio", "tokio-util", @@ -2264,8 +2254,8 @@ dependencies = [ "pin-project-lite", "rustc-hash", "rustls", - "socket2", - "thiserror 2.0.18", + "socket2 0.6.3", + "thiserror", "tokio", "tokio-stream", "tracing", @@ -2292,7 +2282,7 @@ dependencies = [ "rustls-pki-types", "slab", "sorted-index-buffer", - "thiserror 2.0.18", + "thiserror", "tinyvec", "tracing", "web-time", @@ -2306,7 +2296,7 @@ checksum = "ee91b05f4f3353290936ba1f3233518868fb4e2da99cb4c90d1f8cebb064e527" dependencies = [ "cfg_aliases", "libc", - "socket2", + "socket2 0.6.3", "tracing", "windows-sys 0.61.2", ] @@ -2332,9 +2322,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -2475,11 +2465,42 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror", + "tracing", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.2", + "thiserror", + "tokio", + "tokio-stream", +] + [[package]] name = "papaya" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "997ee03cd38c01469a7046643714f0ad28880bcb9e6679ff0666e24817ca19b7" +checksum = "f92dd0b07c53a0a0c764db2ace8c541dc47320dad97c2200c2a637ab9dd2328f" dependencies = [ "equivalent", "seize", @@ -2581,11 +2602,17 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkcs8" -version = "0.11.0-rc.11" +version = "0.11.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12922b6296c06eb741b02d7b5161e3aaa22864af38dfa025a1a3ba3f68c84577" +checksum = "b226d2cc389763951db8869584fd800cbbe2962bf454e2edeb5172b31ee99774" dependencies = [ "der", "spki", @@ -2646,7 +2673,7 @@ dependencies = [ "rand 0.10.1", "serde", "smallvec", - "socket2", + "socket2 0.6.3", "time", "tokio", "tokio-util", @@ -2682,9 +2709,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -2706,9 +2733,9 @@ dependencies = [ [[package]] name = "prefix-trie" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23370be78b7e5bcbb0cab4a02047eb040279a693c78daad04c2c5f1c24a83503" +checksum = "90f561214012d3fc240a1f9c817cc4d57f5310910d066069c1b093f766bb5966" dependencies = [ "either", "ipnet", @@ -2775,9 +2802,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core 0.9.5", @@ -2861,9 +2888,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64", "bytes", @@ -2918,9 +2945,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -2942,9 +2969,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "log", "once_cell", @@ -2979,13 +3006,13 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", - "jni 0.21.1", + "jni", "log", "once_cell", "rustls", @@ -3006,9 +3033,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.12" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", @@ -3092,9 +3119,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.28" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "send_wrapper" @@ -3157,9 +3184,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -3249,6 +3276,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.3" @@ -3293,9 +3330,9 @@ checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" [[package]] name = "spki" -version = "0.8.0" +version = "0.8.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" dependencies = [ "base64ct", "der", @@ -3419,33 +3456,13 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "614b328ff036a4ef882c61570f72918f7e9c5bee1da33f8e7f91e01daee7e56c" -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -3510,9 +3527,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -3535,9 +3552,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -3545,16 +3562,16 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.3", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -3622,9 +3639,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.2+spec-1.1.0" +version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96" dependencies = [ "indexmap", "serde_core", @@ -3637,18 +3654,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +version = "1.0.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" dependencies = [ "indexmap", "toml_datetime", @@ -3658,18 +3675,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.1.1+spec-1.1.0" +version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" [[package]] name = "tower" @@ -3760,6 +3777,22 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +dependencies = [ + "js-sys", + "opentelemetry", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + [[package]] name = "tracing-subscriber" version = "0.3.23" @@ -3813,9 +3846,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-xid" @@ -3866,9 +3899,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -3980,9 +4013,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -3993,19 +4026,23 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ + "cfg-if", + "futures-util", "js-sys", + "once_cell", "wasm-bindgen", + "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4013,9 +4050,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -4026,9 +4063,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -4082,9 +4119,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -4111,9 +4148,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -4238,17 +4275,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - [[package]] name = "windows-result" version = "0.4.1" @@ -4269,11 +4295,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.48.5", ] [[package]] @@ -4296,17 +4322,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "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]] @@ -4336,9 +4362,9 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" @@ -4348,9 +4374,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" @@ -4360,9 +4386,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" @@ -4378,9 +4404,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" @@ -4390,9 +4416,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" @@ -4402,9 +4428,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" @@ -4414,9 +4440,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" @@ -4426,13 +4452,23 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -4523,24 +4559,24 @@ dependencies = [ [[package]] name = "wmi" -version = "0.18.4" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c81b85c57a57500e56669586496bf2abd5cf082b9d32995251185d105208b64" +checksum = "003e65f4934cf9449b9ce913ad822cd054a5af669d24f93db101fdb02856bb23" dependencies = [ "chrono", "futures", "log", "serde", - "thiserror 2.0.18", + "thiserror", "windows", "windows-core", ] [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "ws_stream_wasm" @@ -4555,7 +4591,7 @@ dependencies = [ "pharos", "rustc_version", "send_wrapper", - "thiserror 2.0.18", + "thiserror", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -4575,7 +4611,7 @@ dependencies = [ "oid-registry", "ring", "rusticata-macros", - "thiserror 2.0.18", + "thiserror", "time", ] @@ -4605,9 +4641,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4616,9 +4652,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -4628,18 +4664,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "5c5030500cb2d66bdfbb4ebc9563be6ce7005a4b5d0f26be0c523870fe372ca6" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "a5f86989a046a79640b9d8867c823349a139367bda96549794fcc3313ce91f4e" dependencies = [ "proc-macro2", "quote", @@ -4648,18 +4684,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", @@ -4689,9 +4725,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -4700,9 +4736,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -4711,9 +4747,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0ad3729..134284e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,12 +44,17 @@ n0-future = { workspace = true } futures-util = { workspace = true, optional = true } # for the derive reexport/feature irpc-derive = { version = "0.11.0", path = "./irpc-derive", optional = true } +# for remote span propagation when tracing-opentelemetry feature is enabled +opentelemetry = { version = "0.31", optional = true } +tracing-opentelemetry = { version = "0.32", optional = true } [target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies] noq = { workspace = true, optional = true, features = ["runtime-tokio"] } [dev-dependencies] tracing-subscriber = { workspace = true, features = ["fmt"] } +# pin lazy_static minimum for -Z minimal-versions compatibility (sharded-slab needs >= 1.4) +lazy_static = { version = "1.4" } # just convenient for the enum definitions, in the manual example derive_more = { version = "2", features = ["from"] } # we need full for example main etc. @@ -72,6 +77,8 @@ spans = ["dep:tracing"] stream = ["dep:futures-util"] derive = ["dep:irpc-derive"] varint-util = ["dep:postcard", "dep:smallvec", "tokio/io-util"] +# enable OpenTelemetry span context propagation across remote connections +tracing-opentelemetry = ["dep:opentelemetry", "dep:tracing-opentelemetry", "rpc"] default = ["rpc", "noq_endpoint_setup", "spans", "stream", "derive"] [[example]] @@ -91,6 +98,7 @@ name = "storage" required-features = ["rpc", "noq_endpoint_setup"] [workspace] +resolver = "2" members = ["irpc-derive", "irpc-iroh"] [package.metadata.docs.rs] @@ -108,7 +116,7 @@ serde = { version = "1", default-features = false, features = ["derive"] } tracing = { version = "0.1.41", default-features = false } n0-future = { version = "0.3", default-features = false } n0-error = { version = "0.1" } -tracing-subscriber = { version = "0.3.20" } +tracing-subscriber = { version = "0.3.23" } iroh = { version = "0.98" } iroh-base = { version = "0.98" } noq = { version = "0.18.0", default-features = false } diff --git a/irpc-derive/src/lib.rs b/irpc-derive/src/lib.rs index 9947027..974b333 100644 --- a/irpc-derive/src/lib.rs +++ b/irpc-derive/src/lib.rs @@ -159,15 +159,21 @@ pub fn rpc_requests(attr: TokenStream, item: TokenStream) -> TokenStream { let message_from_impls = generate_message_enum_from_impls(message_enum_name, &variants_with_attr, enum_name); + let span_propagation = args.span_propagation; let service_impl = quote! { impl ::irpc::Service for #enum_name { type Message = #message_enum_name; + const SPAN_PROPAGATION: bool = #span_propagation; } }; let remote_service_impl = if !args.no_rpc { - let block = - generate_remote_service_impl(message_enum_name, enum_name, &variants_with_attr); + let block = generate_remote_service_impl( + message_enum_name, + enum_name, + &variants_with_attr, + args.span_propagation, + ); quote! { #cfg_feature_rpc #block @@ -287,17 +293,37 @@ fn generate_message_enum_from_impls( } /// Generate `RemoteService` impl for message enums. +/// +/// When `span_propagation` is true, the generated code will create spans for each +/// request and set their parent from the propagated remote context. fn generate_remote_service_impl( message_enum_name: &Ident, proto_enum_name: &Ident, variants_with_attr: &[(Ident, Type)], + span_propagation: bool, ) -> TokenStream2 { let variants = variants_with_attr .iter() .map(|(variant_name, _inner_type)| { - quote! { - #proto_enum_name::#variant_name(msg) => { - #message_enum_name::from(::irpc::WithChannels::from((msg, tx, rx))) + let span_name = variant_name.to_string(); + + if span_propagation { + // When span_propagation is enabled, create spans and set parent from remote context + quote! { + #proto_enum_name::#variant_name(msg) => { + // Create a span for this specific RPC operation + let span = ::tracing::info_span!(#span_name); + // Set its parent to the propagated remote context if available + ::irpc::span_propagation::set_span_parent_from_remote(&span); + let _guard = span.enter(); + #message_enum_name::from(::irpc::WithChannels::from((msg, tx, rx))) + } + } + } else { + quote! { + #proto_enum_name::#variant_name(msg) => { + #message_enum_name::from(::irpc::WithChannels::from((msg, tx, rx))) + } } } }); @@ -346,6 +372,8 @@ struct MacroArgs { rpc_feature: Option, no_rpc: bool, no_spans: bool, + /// When true, includes span context in the wire format and enables span propagation. + span_propagation: bool, } impl Parse for MacroArgs { @@ -381,6 +409,9 @@ impl Parse for MacroArgs { "no_spans" => { this.no_spans = true; } + "span_propagation" => { + this.span_propagation = true; + } _ => { return syn_err(arg.span(), format!("Unknown parameter: {arg}")); } diff --git a/irpc-iroh/Cargo.toml b/irpc-iroh/Cargo.toml index fd961e2..f31696f 100644 --- a/irpc-iroh/Cargo.toml +++ b/irpc-iroh/Cargo.toml @@ -36,3 +36,11 @@ hex = "0.4.3" rand = "0.9.2" anyhow = { workspace = true } tokio = { workspace = true, features = ["full"] } +n0-tracing-test = "0.3.0" +opentelemetry = "0.31.0" +opentelemetry_sdk = { version = "0.31.0", features = ["testing"] } +tracing-opentelemetry = "0.32.1" + +[features] +default = [] +tracing-opentelemetry = ["irpc/tracing-opentelemetry"] diff --git a/irpc-iroh/examples/span_propagation.rs b/irpc-iroh/examples/span_propagation.rs new file mode 100644 index 0000000..88cc879 --- /dev/null +++ b/irpc-iroh/examples/span_propagation.rs @@ -0,0 +1,111 @@ +//! Demonstrates how to enable OpenTelemetry span context propagation across +//! irpc-iroh remote calls. +//! +//! Run with: +//! +//! ```sh +//! cargo run --example span_propagation -p irpc-iroh \ +//! --features tracing-opentelemetry +//! ``` +//! +//! What this shows: +//! +//! 1. Setting up `tracing-opentelemetry` with a `TextMapPropagator` so that +//! trace context can be injected into / extracted from RPC payloads. +//! 2. Declaring a service with `#[rpc_requests(..., span_propagation)]`. Only +//! services that opt in pay the wire-format cost; everything else is +//! unchanged. +//! 3. Entering the per-request span that `WithChannels` carries. The macro +//! creates a span named after the protocol variant (here `"Get"`) and +//! attaches the propagated remote context as its parent. Entering it makes +//! any work the handler does — including child spans — part of the same +//! distributed trace. + +use std::sync::Arc; + +use anyhow::Result; +use iroh::{Endpoint, endpoint::presets, protocol::Router}; +use irpc::{WithChannels, channel::oneshot, rpc::RemoteService, rpc_requests}; +use irpc_iroh::IrohProtocol; +use serde::{Deserialize, Serialize}; +use tracing::{Instrument, info, info_span}; +use tracing_subscriber::{EnvFilter, Layer, Registry, layer::SubscriberExt}; + +#[rpc_requests(message = Message, span_propagation)] +#[derive(Debug, Serialize, Deserialize)] +enum Proto { + #[rpc(tx = oneshot::Sender)] + #[wrap(GetRequest)] + Get(String), +} + +const ALPN: &[u8] = b"irpc-iroh/span_propagation/1"; + +#[tokio::main] +async fn main() -> Result<()> { + // (1) Install a W3C trace-context propagator. Without this, the carrier + // written by the client and read by the server has no fields to + // inject into / extract from. + opentelemetry::global::set_text_map_propagator( + opentelemetry_sdk::propagation::TraceContextPropagator::new(), + ); + + // A no-op global tracer is enough for this example. In a real service + // you'd wire this up to OTLP / Jaeger / etc. + let tracer = opentelemetry::global::tracer("irpc-iroh-example"); + let env = EnvFilter::try_from_default_env() + .unwrap_or_else(|_| EnvFilter::new("span_propagation=info")); + let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); + let subscriber = Registry::default().with(telemetry).with( + tracing_subscriber::fmt::layer() + .with_target(false) + .with_filter(env), + ); + tracing::subscriber::set_global_default(subscriber)?; + + // (2) Server: a handler that opts into span propagation via the + // `span_propagation` flag on `rpc_requests` (above). + let endpoint = Endpoint::bind(presets::N0).await?; + let protocol = IrohProtocol::::new(Arc::new(|req, rx, tx| { + Box::pin(async move { + let msg: Message = ::with_remote_channels(req, rx, tx); + match msg { + Message::Get(msg) => { + // (3) `WithChannels` carries a `span` field. The derive macro + // created it as `info_span!("Get")` and set its parent + // from the propagated remote context. Entering it makes + // the rest of the handler — and any child spans it + // creates — part of the same distributed trace. + let WithChannels { + inner, tx, span, .. + } = msg; + let _guard = span.enter(); + info!(?inner, "handling request"); + tx.send(inner.0.to_uppercase()).await.ok(); + } + } + Ok(()) + }) + })); + let server = Router::builder(endpoint).accept(ALPN, protocol).spawn(); + + // Client: every request lives inside a `req` span. + let client_ep = Endpoint::bind(presets::N0).await?; + let server_addr = server.endpoint().addr(); + for req_id in 0..3 { + let client = irpc_iroh::client::(client_ep.clone(), server_addr.clone(), ALPN); + let payload = format!("hello-{req_id}"); + async { + info!(%payload, "sending request"); + let res = client.rpc(GetRequest(payload)).await?; + info!(%res, "got response"); + anyhow::Ok(()) + } + .instrument(info_span!("req", req_id)) + .await?; + } + + server.shutdown().await?; + client_ep.close().await; + Ok(()) +} diff --git a/irpc-iroh/src/lib.rs b/irpc-iroh/src/lib.rs index b0146b1..7307ab2 100644 --- a/irpc-iroh/src/lib.rs +++ b/irpc-iroh/src/lib.rs @@ -15,7 +15,7 @@ use iroh::{ protocol::{AcceptError, ProtocolHandler}, }; use irpc::{ - LocalSender, RequestError, + LocalSender, RequestError, Service, channel::oneshot, rpc::{ ERROR_CODE_MAX_MESSAGE_SIZE_EXCEEDED, Handler, MAX_MESSAGE_SIZE, RemoteConnection, @@ -25,7 +25,6 @@ use irpc::{ }; use n0_error::{Result, e}; use n0_future::{TryFutureExt, future::Boxed as BoxFuture}; -use serde::de::DeserializeOwned; use tracing::{Instrument, debug, error_span, trace, trace_span, warn}; /// Returns a client that connects to a irpc service using an [`iroh::Endpoint`]. @@ -188,8 +187,8 @@ async fn connect_and_open_bi( /// A [`ProtocolHandler`] for an irpc protocol. /// /// Can be added to an [`iroh::protocol::Router`] to handle incoming connections for an ALPN string. -pub struct IrohProtocol { - handler: Handler, +pub struct IrohProtocol { + handler: Handler, request_id: AtomicU64, } @@ -199,17 +198,17 @@ impl fmt::Debug for IrohProtocol { } } -impl IrohProtocol { - pub fn with_sender(local_sender: impl Into>) -> Self +impl IrohProtocol { + pub fn with_sender(local_sender: impl Into>) -> Self where - R: RemoteService, + S: RemoteService, { - let handler = R::remote_handler(local_sender.into()); + let handler = S::remote_handler(local_sender.into()); Self::new(handler) } /// Creates a new [`IrohProtocol`] for the `handler`. - pub fn new(handler: Handler) -> Self { + pub fn new(handler: Handler) -> Self { Self { handler, request_id: Default::default(), @@ -217,13 +216,13 @@ impl IrohProtocol { } } -impl ProtocolHandler for IrohProtocol { +impl ProtocolHandler for IrohProtocol { async fn accept(&self, connection: Connection) -> Result<(), AcceptError> { let handler = self.handler.clone(); let request_id = self .request_id .fetch_add(1, std::sync::atomic::Ordering::AcqRel); - let fut = handle_connection(&connection, handler).map_err(AcceptError::from_err); + let fut = handle_connection::(&connection, handler).map_err(AcceptError::from_err); let span = trace_span!("rpc", id = request_id); fut.instrument(span).await } @@ -234,8 +233,9 @@ impl ProtocolHandler for IrohProtocol { /// Can be added to an [`iroh::protocol::Router`] to handle incoming connections for an ALPN string. /// /// For details about when it is safe to use 0rtt, see -pub struct Iroh0RttProtocol { - handler: Handler, +/// For details about when it is safe to use 0rtt, see +pub struct Iroh0RttProtocol { + handler: Handler, request_id: AtomicU64, } @@ -245,17 +245,17 @@ impl fmt::Debug for Iroh0RttProtocol { } } -impl Iroh0RttProtocol { - pub fn with_sender(local_sender: impl Into>) -> Self +impl Iroh0RttProtocol { + pub fn with_sender(local_sender: impl Into>) -> Self where - R: RemoteService, + S: RemoteService, { - let handler = R::remote_handler(local_sender.into()); + let handler = S::remote_handler(local_sender.into()); Self::new(handler) } /// Creates a new [`Iroh0RttProtocol`] for the `handler`. - pub fn new(handler: Handler) -> Self { + pub fn new(handler: Handler) -> Self { Self { handler, request_id: Default::default(), @@ -263,14 +263,14 @@ impl Iroh0RttProtocol { } } -impl ProtocolHandler for Iroh0RttProtocol { +impl ProtocolHandler for Iroh0RttProtocol { async fn on_accepting(&self, accepting: Accepting) -> Result { let zrtt_conn = accepting.into_0rtt(); let handler = self.handler.clone(); let request_id = self .request_id .fetch_add(1, std::sync::atomic::Ordering::AcqRel); - handle_connection(&zrtt_conn, handler) + handle_connection::(&zrtt_conn, handler) .map_err(AcceptError::from_err) .instrument(trace_span!("rpc", id = request_id)) .await?; @@ -288,29 +288,39 @@ impl ProtocolHandler for Iroh0RttProtocol< } /// Handles a single iroh connection with the provided `handler`. -pub async fn handle_connection( +/// +/// The wire format used depends on `S::SPAN_PROPAGATION` - if true, span context is expected. +pub async fn handle_connection( connection: &impl IncomingRemoteConnection, - handler: Handler, + handler: Handler, ) -> io::Result<()> { if let Ok(remote) = connection.remote_id() { tracing::Span::current().record("remote", tracing::field::display(remote.fmt_short())); } debug!("connection accepted"); loop { - let Some((msg, rx, tx)) = read_request_raw(connection).await? else { + let Some((msg, carrier, rx, tx)) = read_request_inner::(connection).await? else { return Ok(()); }; - handler(msg, rx, tx).await?; + irpc::span_propagation::scope_remote(carrier, handler(msg, rx, tx)).await?; } } -/// Reads a single request from a connection, and a message with channels. +/// Reads a request from a connection and converts it to a message enum. +/// +/// This combines `read_request_raw` with `RemoteService::with_remote_channels`. pub async fn read_request( connection: &impl IncomingRemoteConnection, ) -> std::io::Result> { - Ok(read_request_raw::(connection) - .await? - .map(|(msg, rx, tx)| S::with_remote_channels(msg, rx, tx))) + let Some((msg, carrier, rx, tx)) = read_request_inner::(connection).await? else { + return Ok(None); + }; + Ok(Some( + irpc::span_propagation::scope_remote(carrier, async move { + S::with_remote_channels(msg, rx, tx) + }) + .await, + )) } /// Abstracts over [`Connection`] and [`IncomingZeroRttConnection`]. @@ -361,12 +371,34 @@ impl IncomingRemoteConnection for Connection { /// /// This accepts a bi-directional stream from the connection and reads and parses the request. /// +/// When `S::SPAN_PROPAGATION` is true, any propagated span context on the wire is +/// silently dropped. Use [`handle_connection`] (or [`read_request`]) if you need +/// the propagated context to reach the generated handler spans. +/// /// Returns the parsed request and the stream pair if reading and parsing the request succeeded. /// Returns None if the remote closed the connection with error code `0`. /// Returns an error for all other failure cases. -pub async fn read_request_raw( +pub async fn read_request_raw( connection: &impl IncomingRemoteConnection, -) -> std::io::Result> { +) -> std::io::Result> { + Ok(read_request_inner::(connection) + .await? + .map(|(msg, _carrier, rx, tx)| (msg, rx, tx))) +} + +/// Internal: read a request and also return the propagated span context carrier. +/// +/// The carrier is `Some` iff `S::SPAN_PROPAGATION` is true and the remote sent one. +async fn read_request_inner( + connection: &impl IncomingRemoteConnection, +) -> std::io::Result< + Option<( + S, + Option, + RecvStream, + SendStream, + )>, +> { let (send, mut recv) = match connection.accept_bi().await { Ok((s, r)) => (s, r), Err(ConnectionError::ApplicationClosed(cause)) if cause.error_code.into_inner() == 0 => { @@ -393,15 +425,23 @@ pub async fn read_request_raw( recv.read_exact(&mut buf) .await .map_err(|e| io::Error::new(io::ErrorKind::UnexpectedEof, e))?; - let msg: R = - postcard::from_bytes(&buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - let rx = recv; - let tx = send; - Ok(Some((msg, rx, tx))) + + let (carrier, msg): (Option, S) = + if S::SPAN_PROPAGATION { + postcard::from_bytes(&buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? + } else { + let msg = postcard::from_bytes(&buf) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + (None, msg) + }; + + Ok(Some((msg, carrier, recv, send))) } -/// Utility function to listen for incoming connections and handle them with the provided handler -pub async fn listen(endpoint: iroh::Endpoint, handler: Handler) { +/// Utility function to listen for incoming connections and handle them with the provided handler. +/// +/// The wire format used depends on `S::SPAN_PROPAGATION` - if true, span context is expected. +pub async fn listen(endpoint: iroh::Endpoint, handler: Handler) { let mut request_id = 0u64; let mut tasks = n0_future::task::JoinSet::new(); loop { @@ -420,7 +460,7 @@ pub async fn listen(endpoint: iroh::Endpoint, han let handler = handler.clone(); let fut = async move { match incoming.await { - Ok(connection) => match handle_connection(&connection, handler).await { + Ok(connection) => match handle_connection::(&connection, handler).await { Err(err) => warn!("connection closed with error: {err:?}"), Ok(()) => debug!("connection closed"), }, @@ -434,3 +474,6 @@ pub async fn listen(endpoint: iroh::Endpoint, han request_id += 1; } } + +#[cfg(test)] +mod tests; diff --git a/irpc-iroh/src/tests.rs b/irpc-iroh/src/tests.rs new file mode 100644 index 0000000..b154e6b --- /dev/null +++ b/irpc-iroh/src/tests.rs @@ -0,0 +1,171 @@ +#[cfg(feature = "tracing-opentelemetry")] +mod span_propagation { + use std::sync::Arc; + + use iroh::{Endpoint, endpoint::presets, protocol::Router}; + use irpc::{Service, WithChannels, channel::oneshot, rpc::RemoteService, rpc_requests}; + use n0_error::{Result, StdResultExt}; + use opentelemetry::trace::TraceId; + use opentelemetry_sdk::trace::{InMemorySpanExporter, SdkTracerProvider, SpanData}; + use serde::{Deserialize, Serialize}; + use tracing::{Instrument, info, info_span}; + use tracing_subscriber::{EnvFilter, Layer, Registry, layer::SubscriberExt}; + + use crate::IrohProtocol; + + /// Find the trace ID of the first span named `name` that has a `req_id` attribute equal to `val`. + fn trace_id_for_req(spans: &[SpanData], name: &str, val: i64) -> Option { + spans + .iter() + .find(|s| { + s.name == name + && s.attributes + .iter() + .any(|kv| kv.key.as_str() == "req_id" && kv.value == val.into()) + }) + .map(|s| s.span_context.trace_id()) + } + + /// Find a server-side "Get" span that shares the given trace ID. + fn server_span_for_trace(spans: &[SpanData], trace_id: TraceId) -> Option<&SpanData> { + spans + .iter() + .find(|s| s.name == "Get" && s.span_context.trace_id() == trace_id) + } + + #[tokio::test] + async fn span_propagation() -> n0_error::Result<()> { + // Register W3C TraceContext propagator so inject/extract actually work. + opentelemetry::global::set_text_map_propagator( + opentelemetry_sdk::propagation::TraceContextPropagator::new(), + ); + + // Use an in-memory exporter so we can inspect exported spans after the test. + let exporter = InMemorySpanExporter::default(); + let provider = SdkTracerProvider::builder() + .with_simple_exporter(exporter.clone()) + .build(); + opentelemetry::global::set_tracer_provider(provider.clone()); + let tracer = opentelemetry::global::tracer("test"); + + // Create a tracing layer with the configured tracer + let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); + // Use the tracing subscriber `Registry`, or any other subscriber + // that impls `LookupSpan` + let subscriber = Registry::default() + .with(telemetry) + .with(tracing_subscriber::fmt::layer().with_filter(EnvFilter::from_default_env())); + tracing::subscriber::set_global_default(subscriber).expect("global already set"); + + #[rpc_requests(message = Message, span_propagation)] + #[derive(Debug, Serialize, Deserialize)] + enum Proto { + #[rpc(tx = oneshot::Sender)] + #[wrap(GetRequest)] + Get(String), + } + + const ALPN: &[u8] = b"test"; + + async fn listen() -> Result { + let endpoint = Endpoint::bind(presets::N0).await?; + let protocol = IrohProtocol::::new(Arc::new(|req, rx, tx| { + Box::pin( + async move { + info!("handle request {req:?}"); + let msg: Message = + ::with_remote_channels(req, rx, tx); + info!("handle request {msg:?}"); + match msg { + Message::Get(msg) => { + let WithChannels { + inner, tx, span, .. + } = msg; + info!("handle request {inner:?} with span {span:?}"); + let _guard = span.enter(); + info!("handle request {inner:?}, entered span"); + tx.send(inner.0.to_uppercase()).await.ok(); + } + } + Ok(()) + } + .instrument(info_span!("server-handler")), + ) + })); + let router = Router::builder(endpoint).accept(ALPN, protocol).spawn(); + info!("endpoint id: {}", router.endpoint().id()); + Ok(router) + } + + info!( + "span propagation enabled: {}", + ::SPAN_PROPAGATION + ); + + let server = listen().instrument(info_span!("server")).await?; + let client_ep = Endpoint::bind(presets::N0).await?; + let client = crate::client::(client_ep.clone(), server.endpoint().addr(), ALPN); + + async { + async { + info!("send request: hello"); + let res = client.rpc(GetRequest("hello".to_string())).await?; + info!("got response: {res:?}"); + assert_eq!(res, "HELLO"); + n0_error::Ok(()) + } + .instrument(info_span!("req", req_id = 23)) + .await?; + + async { + let client = + crate::client::(client_ep.clone(), server.endpoint().addr(), ALPN); + info!("send request: bye"); + let res = client.rpc(GetRequest("bye".to_string())).await?; + info!("got response: {res:?}"); + assert_eq!(res, "BYE"); + n0_error::Ok(()) + } + .instrument(info_span!("req", req_id = 24)) + .await?; + + n0_error::Ok(()) + } + .instrument(info_span!("client")) + .await?; + + server.shutdown().await.anyerr()?; + client_ep.close().await; + + // Flush to ensure all spans are exported, then inspect them. + let _ = provider.force_flush(); + let spans = exporter.get_finished_spans().unwrap(); + + // For each request, verify the server-side "Get" span shares the same + // trace_id as the client-side "req" span — proving context was propagated + // across the remote connection. + for req_id in [23, 24] { + let client_trace_id = trace_id_for_req(&spans, "req", req_id) + .unwrap_or_else(|| panic!("no client span found with req_id={req_id}")); + + let server_span = server_span_for_trace(&spans, client_trace_id).unwrap_or_else(|| { + panic!("no server 'Get' span with trace_id={client_trace_id} for req_id={req_id}") + }); + + assert!( + server_span.parent_span_is_remote, + "server span for req_id={req_id} should have a remote parent" + ); + + info!( + "req_id={}: trace_id={}, server parent_span_id={} (remote={})", + req_id, + client_trace_id, + server_span.parent_span_id, + server_span.parent_span_is_remote, + ); + } + + Ok(()) + } +} diff --git a/irpc-iroh/tests/span_propagation_concurrent.rs b/irpc-iroh/tests/span_propagation_concurrent.rs new file mode 100644 index 0000000..34b2250 --- /dev/null +++ b/irpc-iroh/tests/span_propagation_concurrent.rs @@ -0,0 +1,134 @@ +//! Stress test for span context propagation under concurrent load on a +//! multi-threaded runtime. +//! +//! Fires many in-flight requests so that handler futures are very likely to +//! yield and migrate worker threads between dispatch and span creation. With +//! the previous `thread_local!` storage this was racy — a yielding task could +//! lose its slot to another request landing on the same OS thread. The +//! `task_local!` scope is per-task and survives migration, so every server +//! "Get" span must share its client's trace id. +//! +//! Lives in its own integration-test binary because it installs a global +//! tracing subscriber and tracer provider, which would conflict with the +//! sibling unit test `tests::span_propagation::span_propagation`. + +#![cfg(feature = "tracing-opentelemetry")] + +use std::sync::Arc; + +use iroh::{Endpoint, endpoint::presets, protocol::Router}; +use irpc::{WithChannels, channel::oneshot, rpc::RemoteService, rpc_requests}; +use irpc_iroh::IrohProtocol; +use n0_error::StdResultExt; +use opentelemetry::trace::TraceId; +use opentelemetry_sdk::trace::{InMemorySpanExporter, SdkTracerProvider, SpanData}; +use serde::{Deserialize, Serialize}; +use tracing::{Instrument, info_span}; +use tracing_subscriber::{EnvFilter, Layer, Registry, layer::SubscriberExt}; + +fn trace_id_for_req(spans: &[SpanData], name: &str, val: i64) -> Option { + spans + .iter() + .find(|s| { + s.name == name + && s.attributes + .iter() + .any(|kv| kv.key.as_str() == "req_id" && kv.value == val.into()) + }) + .map(|s| s.span_context.trace_id()) +} + +fn server_span_for_trace(spans: &[SpanData], trace_id: TraceId) -> Option<&SpanData> { + spans + .iter() + .find(|s| s.name == "Get" && s.span_context.trace_id() == trace_id) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn span_propagation_concurrent() -> n0_error::Result<()> { + opentelemetry::global::set_text_map_propagator( + opentelemetry_sdk::propagation::TraceContextPropagator::new(), + ); + let exporter = InMemorySpanExporter::default(); + let provider = SdkTracerProvider::builder() + .with_simple_exporter(exporter.clone()) + .build(); + opentelemetry::global::set_tracer_provider(provider.clone()); + let tracer = opentelemetry::global::tracer("test-concurrent"); + let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); + let subscriber = Registry::default() + .with(telemetry) + .with(tracing_subscriber::fmt::layer().with_filter(EnvFilter::from_default_env())); + tracing::subscriber::set_global_default(subscriber).expect("global already set"); + + #[rpc_requests(message = Message, span_propagation)] + #[derive(Debug, Serialize, Deserialize)] + enum Proto { + #[rpc(tx = oneshot::Sender)] + #[wrap(GetRequest)] + Get(String), + } + + const ALPN: &[u8] = b"test-concurrent"; + + let endpoint = Endpoint::bind(presets::N0).await?; + let protocol = IrohProtocol::::new(Arc::new(|req, rx, tx| { + Box::pin(async move { + // Yield before constructing the WithChannels so the handler is + // very likely to resume on a different worker thread. + tokio::task::yield_now().await; + let msg: Message = ::with_remote_channels(req, rx, tx); + match msg { + Message::Get(msg) => { + let WithChannels { inner, tx, .. } = msg; + tokio::task::yield_now().await; + tx.send(inner.0.to_uppercase()).await.ok(); + } + } + Ok(()) + }) + })); + let server = Router::builder(endpoint).accept(ALPN, protocol).spawn(); + + let client_ep = Endpoint::bind(presets::N0).await?; + let server_addr = server.endpoint().addr(); + + const N: i64 = 32; + let mut handles = Vec::with_capacity(N as usize); + for req_id in 0..N { + let client = irpc_iroh::client::(client_ep.clone(), server_addr.clone(), ALPN); + let payload = format!("req-{req_id}"); + let expected = payload.to_uppercase(); + let h = tokio::spawn( + async move { + let res = client.rpc(GetRequest(payload)).await.unwrap(); + assert_eq!(res, expected); + } + .instrument(info_span!("req", req_id)), + ); + handles.push(h); + } + for h in handles { + h.await.unwrap(); + } + + server.shutdown().await.anyerr()?; + client_ep.close().await; + + let _ = provider.force_flush(); + let spans = exporter.get_finished_spans().unwrap(); + + for req_id in 0..N { + let client_trace_id = trace_id_for_req(&spans, "req", req_id) + .unwrap_or_else(|| panic!("no client span found with req_id={req_id}")); + let server_span = server_span_for_trace(&spans, client_trace_id).unwrap_or_else(|| { + panic!("no server 'Get' span with trace_id={client_trace_id} for req_id={req_id}") + }); + assert!( + server_span.parent_span_is_remote, + "server span for req_id={req_id} should have a remote parent" + ); + } + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 3dd3105..8bb27ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -215,6 +215,12 @@ use std::{fmt::Debug, future::Future, io, marker::PhantomData, ops::Deref}; /// * `no_rpc` *(optional, no value)*: If set, no implementation of `RemoteService` will be generated and the generated /// code works without the `rpc` feature of `irpc`. /// * `no_spans` *(optional, no value)*: If set, the generated code works without the `spans` feature of `irpc`. +/// * `span_propagation` *(optional, no value)*: If set, enables OpenTelemetry span context propagation +/// across remote connections. When enabled, span context is included in the wire format as +/// `(Option, Message)`, and the generated `RemoteService` implementation +/// will set the parent span from the propagated remote context. Requires the `tracing-opentelemetry` +/// feature to be enabled for actual OpenTelemetry integration; without it, the context is +/// still serialized but has no effect. /// /// ## Variant attributes /// @@ -320,6 +326,122 @@ mod sealed { pub trait Sealed {} } +/// Span context propagation for remote RPC calls +/// +/// This module provides the `SpanContextCarrier` type for propagating trace context +/// across remote boundaries. The type is always available when `rpc` feature is enabled, +/// but actual OpenTelemetry integration requires the `tracing-opentelemetry` feature. +/// +/// The propagated context is scoped to a single request handler via a tokio task-local, +/// installed by the dispatch loop in `handle_connection`. This isolates concurrent +/// requests from each other and is robust to thread migration across `.await` points. +#[cfg(feature = "rpc")] +#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "rpc")))] +pub mod span_propagation { + use std::{collections::HashMap, future::Future}; + + use serde::{Deserialize, Serialize}; + + #[cfg(feature = "tracing-opentelemetry")] + tokio::task_local! { + static SPAN_CONTEXT: opentelemetry::Context; + } + + /// Carrier for propagating span context across RPC boundaries using W3C Trace Context format. + /// + /// This type is always available for serialization purposes. When the + /// `tracing-opentelemetry` feature is enabled, it can extract/inject actual + /// OpenTelemetry trace context. Without that feature, it simply serializes as an + /// empty map. + #[derive(Debug, Clone, Serialize, Deserialize, Default)] + pub struct SpanContextCarrier { + headers: HashMap, + } + + #[cfg(feature = "tracing-opentelemetry")] + impl opentelemetry::propagation::Injector for SpanContextCarrier { + fn set(&mut self, key: &str, value: String) { + self.headers.insert(key.to_string(), value); + } + } + + #[cfg(feature = "tracing-opentelemetry")] + impl opentelemetry::propagation::Extractor for SpanContextCarrier { + fn get(&self, key: &str) -> Option<&str> { + self.headers.get(key).map(|v| v.as_str()) + } + + fn keys(&self) -> Vec<&str> { + self.headers.keys().map(|k| k.as_str()).collect() + } + } + + impl SpanContextCarrier { + /// Create a carrier from the current OpenTelemetry context. + /// + /// When `tracing-opentelemetry` feature is enabled, this extracts the current + /// trace context. Without the feature, this returns an empty carrier. + #[cfg(feature = "tracing-opentelemetry")] + pub fn from_current() -> Self { + use opentelemetry::global; + use tracing_opentelemetry::OpenTelemetrySpanExt; + let mut carrier = Self::default(); + // Get the OTel context from the current tracing span, not from + // opentelemetry::Context::current(). The tracing-opentelemetry layer + // stores OTel spans inside tracing spans, so the thread-local OTel + // context won't have the right span. + let ctx = tracing::Span::current().context(); + global::get_text_map_propagator(|prop| { + prop.inject_context(&ctx, &mut carrier); + }); + carrier + } + + #[cfg(not(feature = "tracing-opentelemetry"))] + pub fn from_current() -> Self { + Self::default() + } + + /// Extract an OpenTelemetry context from this carrier. + #[cfg(feature = "tracing-opentelemetry")] + pub fn to_context(&self) -> opentelemetry::Context { + use opentelemetry::global; + global::get_text_map_propagator(|prop| { + prop.extract_with_context(&opentelemetry::Context::current(), self) + }) + } + } + + /// Run `fut` with `carrier`'s context installed as the per-task scope read by + /// [`set_span_parent_from_remote`]. + /// + /// Used by transport implementations (`irpc::rpc`, `irpc-iroh`) to wrap a single + /// request handler. Most users will not call this directly. + pub async fn scope_remote(carrier: Option, fut: F) -> F::Output { + #[cfg(feature = "tracing-opentelemetry")] + if let Some(carrier) = carrier { + return SPAN_CONTEXT.scope(carrier.to_context(), fut).await; + } + let _ = carrier; + fut.await + } + + /// Set the parent of a span from the propagated remote context, if one is in scope. + /// + /// Called by the code generated by `rpc_requests(span_propagation)`. Looks up the + /// task-local installed by the dispatch loop; no-op outside that scope. + pub fn set_span_parent_from_remote(span: &tracing::Span) { + #[cfg(feature = "tracing-opentelemetry")] + { + let _ = SPAN_CONTEXT.try_with(|ctx| { + use tracing_opentelemetry::OpenTelemetrySpanExt; + let _ = span.set_parent(ctx.clone()); + }); + } + let _ = span; + } +} + /// Requirements for a RPC message /// /// Even when just using the mem transport, we require messages to be Serializable and Deserializable. @@ -348,6 +470,14 @@ pub trait Service: Serialize + DeserializeOwned + Send + Sync + Debug + 'static /// protocol enum, but its single unit field is the [`WithChannels`] struct /// that contains the inner request plus the `tx` and `rx` channels. type Message: Send + Unpin + 'static; + + /// Whether this protocol includes span context in the wire format. + /// + /// When `true`, messages are serialized as `(Option, Message)`. + /// When `false` (default), messages are serialized directly without span context wrapper. + /// + /// This is controlled by the `span_propagation` attribute on the `rpc_requests` macro. + const SPAN_PROPAGATION: bool = false; } /// Sealed marker trait for a sender @@ -1988,15 +2118,33 @@ pub mod rpc { std::marker::PhantomData, ); + /// Serialize a message for sending over the wire. + /// + /// When `S::SPAN_PROPAGATION` is true, the message is wrapped in a tuple with + /// span context: `(Option, msg)`. + /// When false, the message is serialized directly. pub(crate) fn prepare_write( msg: impl Into, ) -> Result, WriteError> { let msg = msg.into(); - if postcard::experimental::serialized_size(&msg)? as u64 > MAX_MESSAGE_SIZE { - return Err(e!(WriteError::MaxMessageSizeExceeded)); - } let mut buf = SmallVec::<[u8; 128]>::new(); - buf.write_length_prefixed(&msg)?; + + if S::SPAN_PROPAGATION { + // Include span context in wire format + let span_ctx = Some(crate::span_propagation::SpanContextCarrier::from_current()); + let payload = (span_ctx, msg); + if postcard::experimental::serialized_size(&payload)? as u64 > MAX_MESSAGE_SIZE { + return Err(e!(WriteError::MaxMessageSizeExceeded)); + } + buf.write_length_prefixed(&payload)?; + } else { + // Original wire format without span context + if postcard::experimental::serialized_size(&msg)? as u64 > MAX_MESSAGE_SIZE { + return Err(e!(WriteError::MaxMessageSizeExceeded)); + } + buf.write_length_prefixed(&msg)?; + } + Ok(buf) } @@ -2320,17 +2468,22 @@ pub mod rpc { /// Creates a [`Handler`] that forwards all messages to a [`LocalSender`]. fn remote_handler(local_sender: LocalSender) -> Handler { Arc::new(move |msg, rx, tx| { - let msg = Self::with_remote_channels(msg, rx, tx); - Box::pin(local_sender.send_raw(msg)) + // `with_remote_channels` reads the task-local span context installed by + // the dispatch loop, so it must run inside the future (which is polled + // within that scope) rather than eagerly here. + let local_sender = local_sender.clone(); + Box::pin(async move { + let msg = Self::with_remote_channels(msg, rx, tx); + local_sender.send_raw(msg).await + }) }) } } - /// Utility function to listen for incoming connections and handle them with the provided handler - pub async fn listen( - endpoint: noq::Endpoint, - handler: Handler, - ) { + /// Utility function to listen for incoming connections and handle them with the provided handler. + /// + /// The wire format used depends on `S::SPAN_PROPAGATION` - if true, span context is expected. + pub async fn listen(endpoint: noq::Endpoint, handler: Handler) { let mut request_id = 0u64; let mut tasks = JoinSet::new(); loop { @@ -2365,9 +2518,12 @@ pub mod rpc { } /// Handles a quic connection with the provided `handler`. - pub async fn handle_connection( + /// + /// This function handles requests for a service `S`. The wire format used depends on + /// `S::SPAN_PROPAGATION` - if true, span context is expected in the wire format. + pub async fn handle_connection( connection: noq::Connection, - handler: Handler, + handler: Handler, ) -> io::Result<()> { tracing::Span::current().record( "remote", @@ -2375,31 +2531,62 @@ pub mod rpc { ); debug!("connection accepted"); loop { - let Some((msg, rx, tx)) = read_request_raw(&connection).await? else { + let Some((msg, carrier, rx, tx)) = read_request_inner::(&connection).await? else { return Ok(()); }; - handler(msg, rx, tx).await?; + crate::span_propagation::scope_remote(carrier, handler(msg, rx, tx)).await?; } } + /// Reads a request from a connection and converts it to a message enum. + /// + /// This combines `read_request_raw` with `RemoteService::with_remote_channels`. pub async fn read_request( connection: &noq::Connection, ) -> std::io::Result> { - Ok(read_request_raw::(connection) - .await? - .map(|(msg, rx, tx)| S::with_remote_channels(msg, rx, tx))) + let Some((msg, carrier, rx, tx)) = read_request_inner::(connection).await? else { + return Ok(None); + }; + Ok(Some( + crate::span_propagation::scope_remote(carrier, async move { + S::with_remote_channels(msg, rx, tx) + }) + .await, + )) } /// Reads a single request from the connection. /// /// This accepts a bi-directional stream from the connection and reads and parses the request. /// + /// When `S::SPAN_PROPAGATION` is true, any propagated span context on the wire is + /// silently dropped. Use [`handle_connection`] (or [`read_request`]) if you need + /// the propagated context to reach the generated handler spans. + /// /// Returns the parsed request and the stream pair if reading and parsing the request succeeded. /// Returns None if the remote closed the connection with error code `0`. /// Returns an error for all other failure cases. - pub async fn read_request_raw( + pub async fn read_request_raw( connection: &noq::Connection, - ) -> std::io::Result> { + ) -> std::io::Result> { + Ok(read_request_inner::(connection) + .await? + .map(|(msg, _carrier, rx, tx)| (msg, rx, tx))) + } + + /// Internal: read a request and also return the propagated span context carrier. + /// + /// The carrier is `Some` iff `S::SPAN_PROPAGATION` is true and the remote sent one. + async fn read_request_inner( + connection: &noq::Connection, + ) -> std::io::Result< + Option<( + S, + Option, + noq::RecvStream, + noq::SendStream, + )>, + > { let (send, mut recv) = match connection.accept_bi().await { Ok((s, r)) => (s, r), Err(ConnectionError::ApplicationClosed(cause)) @@ -2428,11 +2615,18 @@ pub mod rpc { recv.read_exact(&mut buf) .await .map_err(|e| io::Error::new(io::ErrorKind::UnexpectedEof, e))?; - let msg: R = postcard::from_bytes(&buf) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - let rx = recv; - let tx = send; - Ok(Some((msg, rx, tx))) + + let (carrier, msg): (Option, S) = + if S::SPAN_PROPAGATION { + postcard::from_bytes(&buf) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? + } else { + let msg = postcard::from_bytes(&buf) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + (None, msg) + }; + + Ok(Some((msg, carrier, recv, send))) } }